summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp/tests')
-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
61 files changed, 35310 insertions, 0 deletions
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);
+}