From 749714731ee9a59ae39be77e7db3915ce3ad0bd8 Mon Sep 17 00:00:00 2001 From: Javier Garcia Date: Fri, 22 Jan 2021 10:38:12 +0100 Subject: [PATCH] pceplib: Integrate pcelib into frr Signed-off-by: Brady Johnson Co-authored-by: Javier Garcia Signed-off-by: Javier Garcia --- .gitignore | 1 + Makefile.am | 2 + configure.ac | 22 +- debian/frr.install | 1 + doc/developer/images/PCEPlib_design.jpg | Bin 0 -> 42003 bytes .../images/PCEPlib_internal_deps.jpg | Bin 0 -> 31742 bytes doc/developer/images/PCEPlib_socket_comm.jpg | Bin 0 -> 36823 bytes .../images/PCEPlib_threading_model.jpg | Bin 0 -> 69181 bytes .../PCEPlib_threading_model_frr_infra.jpg | Bin 0 -> 83409 bytes doc/developer/images/PCEPlib_timers.jpg | Bin 0 -> 37363 bytes doc/developer/index.rst | 1 + doc/developer/pceplib.rst | 781 ++++++++ pathd/path_pcep.c | 25 +- pathd/path_pcep.h | 4 +- pathd/path_pcep_cli.c | 22 +- pathd/path_pcep_config.c | 25 +- pathd/path_pcep_config.h | 9 +- pathd/path_pcep_controller.c | 227 +-- pathd/path_pcep_controller.h | 22 +- pathd/path_pcep_debug.c | 4 +- pathd/path_pcep_debug.h | 4 +- pathd/path_pcep_lib.c | 14 +- pathd/path_pcep_lib.h | 2 +- pathd/path_pcep_pcc.c | 73 +- pathd/path_pcep_pcc.h | 9 +- pathd/subdir.am | 13 +- pceplib/.gitignore | 14 + pceplib/pcep.h | 48 + pceplib/pcep_msg_encoding.h | 140 ++ pceplib/pcep_msg_messages.c | 308 +++ pceplib/pcep_msg_messages.h | 132 ++ pceplib/pcep_msg_messages_encoding.c | 351 ++++ pceplib/pcep_msg_object_error_types.c | 389 ++++ pceplib/pcep_msg_object_error_types.h | 284 +++ pceplib/pcep_msg_objects.c | 854 ++++++++ pceplib/pcep_msg_objects.h | 741 +++++++ pceplib/pcep_msg_objects_encoding.c | 1720 +++++++++++++++++ pceplib/pcep_msg_tlvs.c | 464 +++++ pceplib/pcep_msg_tlvs.h | 380 ++++ pceplib/pcep_msg_tlvs_encoding.c | 1282 ++++++++++++ pceplib/pcep_msg_tools.c | 465 +++++ pceplib/pcep_msg_tools.h | 71 + pceplib/pcep_pcc.c | 517 +++++ pceplib/pcep_pcc_api.c | 392 ++++ pceplib/pcep_pcc_api.h | 103 + pceplib/pcep_session_logic.c | 683 +++++++ pceplib/pcep_session_logic.h | 290 +++ pceplib/pcep_session_logic_counters.c | 450 +++++ pceplib/pcep_session_logic_internals.h | 109 ++ pceplib/pcep_session_logic_loop.c | 360 ++++ pceplib/pcep_session_logic_states.c | 1133 +++++++++++ pceplib/pcep_socket_comm.c | 781 ++++++++ pceplib/pcep_socket_comm.h | 198 ++ pceplib/pcep_socket_comm_internals.h | 69 + pceplib/pcep_socket_comm_loop.c | 486 +++++ pceplib/pcep_socket_comm_loop.h | 32 + pceplib/pcep_socket_comm_mock.c | 363 ++++ pceplib/pcep_socket_comm_mock.h | 67 + pceplib/pcep_timer_internals.h | 76 + pceplib/pcep_timers.c | 482 +++++ pceplib/pcep_timers.h | 92 + pceplib/pcep_timers_event_loop.c | 106 + pceplib/pcep_timers_event_loop.h | 34 + pceplib/pcep_utils_counters.c | 475 +++++ pceplib/pcep_utils_counters.h | 232 +++ pceplib/pcep_utils_double_linked_list.c | 262 +++ pceplib/pcep_utils_double_linked_list.h | 72 + pceplib/pcep_utils_logging.c | 82 + pceplib/pcep_utils_logging.h | 66 + pceplib/pcep_utils_memory.c | 220 +++ pceplib/pcep_utils_memory.h | 89 + pceplib/pcep_utils_ordered_list.c | 322 +++ pceplib/pcep_utils_ordered_list.h | 109 ++ pceplib/pcep_utils_queue.c | 150 ++ pceplib/pcep_utils_queue.h | 49 + pceplib/subdir.am | 62 + pceplib/test/pcep_msg_messages_test.c | 498 +++++ pceplib/test/pcep_msg_messages_test.h | 48 + pceplib/test/pcep_msg_messages_tests.c | 256 +++ .../test/pcep_msg_object_error_types_test.c | 84 + .../test/pcep_msg_object_error_types_test.h | 37 + pceplib/test/pcep_msg_objects_test.c | 1289 ++++++++++++ pceplib/test/pcep_msg_objects_test.h | 64 + pceplib/test/pcep_msg_tests_valgrind.sh | 2 + pceplib/test/pcep_msg_tlvs_test.c | 671 +++++++ pceplib/test/pcep_msg_tlvs_test.h | 51 + pceplib/test/pcep_msg_tools_test.c | 1258 ++++++++++++ pceplib/test/pcep_msg_tools_test.h | 48 + pceplib/test/pcep_pcc_api_test.c | 285 +++ pceplib/test/pcep_pcc_api_test.h | 43 + pceplib/test/pcep_pcc_api_tests.c | 88 + pceplib/test/pcep_pcc_api_tests_valgrind.sh | 2 + pceplib/test/pcep_session_logic_loop_test.c | 219 +++ pceplib/test/pcep_session_logic_loop_test.h | 40 + pceplib/test/pcep_session_logic_states_test.c | 919 +++++++++ pceplib/test/pcep_session_logic_states_test.h | 52 + pceplib/test/pcep_session_logic_test.c | 360 ++++ pceplib/test/pcep_session_logic_test.h | 43 + pceplib/test/pcep_session_logic_tests.c | 201 ++ .../test/pcep_session_logic_tests_valgrind.sh | 2 + pceplib/test/pcep_socket_comm_loop_test.c | 194 ++ pceplib/test/pcep_socket_comm_loop_test.h | 38 + pceplib/test/pcep_socket_comm_test.c | 308 +++ pceplib/test/pcep_socket_comm_test.h | 42 + pceplib/test/pcep_socket_comm_tests.c | 128 ++ .../test/pcep_socket_comm_tests_valgrind.sh | 2 + pceplib/test/pcep_tests_valgrind.sh | 15 + pceplib/test/pcep_timers_event_loop_test.c | 160 ++ pceplib/test/pcep_timers_event_loop_test.h | 38 + pceplib/test/pcep_timers_test.c | 109 ++ pceplib/test/pcep_timers_test.h | 40 + pceplib/test/pcep_timers_tests.c | 113 ++ pceplib/test/pcep_timers_tests_valgrind.sh | 2 + pceplib/test/pcep_utils_counters_test.c | 254 +++ pceplib/test/pcep_utils_counters_test.h | 43 + .../test/pcep_utils_double_linked_list_test.c | 297 +++ .../test/pcep_utils_double_linked_list_test.h | 38 + pceplib/test/pcep_utils_memory_test.c | 241 +++ pceplib/test/pcep_utils_memory_test.h | 33 + pceplib/test/pcep_utils_ordered_list_test.c | 248 +++ pceplib/test/pcep_utils_ordered_list_test.h | 39 + pceplib/test/pcep_utils_queue_test.c | 157 ++ pceplib/test/pcep_utils_queue_test.h | 36 + pceplib/test/pcep_utils_tests.c | 136 ++ pceplib/test/pcep_utils_tests_valgrind.sh | 2 + pceplib/test/subdir.am | 122 ++ redhat/frr.spec.in | 1 + 127 files changed, 27102 insertions(+), 211 deletions(-) create mode 100644 doc/developer/images/PCEPlib_design.jpg create mode 100644 doc/developer/images/PCEPlib_internal_deps.jpg create mode 100644 doc/developer/images/PCEPlib_socket_comm.jpg create mode 100644 doc/developer/images/PCEPlib_threading_model.jpg create mode 100644 doc/developer/images/PCEPlib_threading_model_frr_infra.jpg create mode 100644 doc/developer/images/PCEPlib_timers.jpg create mode 100644 doc/developer/pceplib.rst create mode 100644 pceplib/.gitignore create mode 100644 pceplib/pcep.h create mode 100644 pceplib/pcep_msg_encoding.h create mode 100644 pceplib/pcep_msg_messages.c create mode 100644 pceplib/pcep_msg_messages.h create mode 100644 pceplib/pcep_msg_messages_encoding.c create mode 100644 pceplib/pcep_msg_object_error_types.c create mode 100644 pceplib/pcep_msg_object_error_types.h create mode 100644 pceplib/pcep_msg_objects.c create mode 100644 pceplib/pcep_msg_objects.h create mode 100644 pceplib/pcep_msg_objects_encoding.c create mode 100644 pceplib/pcep_msg_tlvs.c create mode 100644 pceplib/pcep_msg_tlvs.h create mode 100644 pceplib/pcep_msg_tlvs_encoding.c create mode 100644 pceplib/pcep_msg_tools.c create mode 100644 pceplib/pcep_msg_tools.h create mode 100644 pceplib/pcep_pcc.c create mode 100644 pceplib/pcep_pcc_api.c create mode 100644 pceplib/pcep_pcc_api.h create mode 100644 pceplib/pcep_session_logic.c create mode 100644 pceplib/pcep_session_logic.h create mode 100644 pceplib/pcep_session_logic_counters.c create mode 100644 pceplib/pcep_session_logic_internals.h create mode 100644 pceplib/pcep_session_logic_loop.c create mode 100644 pceplib/pcep_session_logic_states.c create mode 100644 pceplib/pcep_socket_comm.c create mode 100644 pceplib/pcep_socket_comm.h create mode 100644 pceplib/pcep_socket_comm_internals.h create mode 100644 pceplib/pcep_socket_comm_loop.c create mode 100644 pceplib/pcep_socket_comm_loop.h create mode 100644 pceplib/pcep_socket_comm_mock.c create mode 100644 pceplib/pcep_socket_comm_mock.h create mode 100644 pceplib/pcep_timer_internals.h create mode 100644 pceplib/pcep_timers.c create mode 100644 pceplib/pcep_timers.h create mode 100644 pceplib/pcep_timers_event_loop.c create mode 100644 pceplib/pcep_timers_event_loop.h create mode 100644 pceplib/pcep_utils_counters.c create mode 100644 pceplib/pcep_utils_counters.h create mode 100644 pceplib/pcep_utils_double_linked_list.c create mode 100644 pceplib/pcep_utils_double_linked_list.h create mode 100644 pceplib/pcep_utils_logging.c create mode 100644 pceplib/pcep_utils_logging.h create mode 100644 pceplib/pcep_utils_memory.c create mode 100644 pceplib/pcep_utils_memory.h create mode 100644 pceplib/pcep_utils_ordered_list.c create mode 100644 pceplib/pcep_utils_ordered_list.h create mode 100644 pceplib/pcep_utils_queue.c create mode 100644 pceplib/pcep_utils_queue.h create mode 100644 pceplib/subdir.am create mode 100644 pceplib/test/pcep_msg_messages_test.c create mode 100644 pceplib/test/pcep_msg_messages_test.h create mode 100644 pceplib/test/pcep_msg_messages_tests.c create mode 100644 pceplib/test/pcep_msg_object_error_types_test.c create mode 100644 pceplib/test/pcep_msg_object_error_types_test.h create mode 100644 pceplib/test/pcep_msg_objects_test.c create mode 100644 pceplib/test/pcep_msg_objects_test.h create mode 100755 pceplib/test/pcep_msg_tests_valgrind.sh create mode 100644 pceplib/test/pcep_msg_tlvs_test.c create mode 100644 pceplib/test/pcep_msg_tlvs_test.h create mode 100644 pceplib/test/pcep_msg_tools_test.c create mode 100644 pceplib/test/pcep_msg_tools_test.h create mode 100644 pceplib/test/pcep_pcc_api_test.c create mode 100644 pceplib/test/pcep_pcc_api_test.h create mode 100644 pceplib/test/pcep_pcc_api_tests.c create mode 100755 pceplib/test/pcep_pcc_api_tests_valgrind.sh create mode 100644 pceplib/test/pcep_session_logic_loop_test.c create mode 100644 pceplib/test/pcep_session_logic_loop_test.h create mode 100644 pceplib/test/pcep_session_logic_states_test.c create mode 100644 pceplib/test/pcep_session_logic_states_test.h create mode 100644 pceplib/test/pcep_session_logic_test.c create mode 100644 pceplib/test/pcep_session_logic_test.h create mode 100644 pceplib/test/pcep_session_logic_tests.c create mode 100755 pceplib/test/pcep_session_logic_tests_valgrind.sh create mode 100644 pceplib/test/pcep_socket_comm_loop_test.c create mode 100644 pceplib/test/pcep_socket_comm_loop_test.h create mode 100644 pceplib/test/pcep_socket_comm_test.c create mode 100644 pceplib/test/pcep_socket_comm_test.h create mode 100644 pceplib/test/pcep_socket_comm_tests.c create mode 100755 pceplib/test/pcep_socket_comm_tests_valgrind.sh create mode 100755 pceplib/test/pcep_tests_valgrind.sh create mode 100644 pceplib/test/pcep_timers_event_loop_test.c create mode 100644 pceplib/test/pcep_timers_event_loop_test.h create mode 100644 pceplib/test/pcep_timers_test.c create mode 100644 pceplib/test/pcep_timers_test.h create mode 100644 pceplib/test/pcep_timers_tests.c create mode 100755 pceplib/test/pcep_timers_tests_valgrind.sh create mode 100644 pceplib/test/pcep_utils_counters_test.c create mode 100644 pceplib/test/pcep_utils_counters_test.h create mode 100644 pceplib/test/pcep_utils_double_linked_list_test.c create mode 100644 pceplib/test/pcep_utils_double_linked_list_test.h create mode 100644 pceplib/test/pcep_utils_memory_test.c create mode 100644 pceplib/test/pcep_utils_memory_test.h create mode 100644 pceplib/test/pcep_utils_ordered_list_test.c create mode 100644 pceplib/test/pcep_utils_ordered_list_test.h create mode 100644 pceplib/test/pcep_utils_queue_test.c create mode 100644 pceplib/test/pcep_utils_queue_test.h create mode 100644 pceplib/test/pcep_utils_tests.c create mode 100755 pceplib/test/pcep_utils_tests_valgrind.sh create mode 100644 pceplib/test/subdir.am diff --git a/.gitignore b/.gitignore index fbbb04b60c..ba17c29d39 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ /libtool /libtool.orig /changelog-auto +/test-driver /Makefile /Makefile.in diff --git a/Makefile.am b/Makefile.am index 90c8407010..59abdbddd2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -158,6 +158,8 @@ include bfdd/subdir.am include yang/subdir.am include yang/libyang_plugins/subdir.am include vrrpd/subdir.am +include pceplib/subdir.am +include pceplib/test/subdir.am include pathd/subdir.am include vtysh/subdir.am diff --git a/configure.ac b/configure.ac index f3d1f38986..ff290c32b3 100755 --- a/configure.ac +++ b/configure.ac @@ -1709,12 +1709,10 @@ fi AS_IF([test "$enable_pathd" != "no"], [ AC_DEFINE([HAVE_PATHD], [1], [pathd]) -]) - -AS_IF([test "$enable_pcep" != "no"], [ AC_DEFINE([HAVE_PATHD_PCEP], [1], [pathd-pcep]) ]) + if test "$ac_cv_lib_json_c_json_object_get" = "no" -a "$BFDD" = "bfdd"; then AC_MSG_ERROR(["you must use json-c library to use bfdd"]) fi @@ -2536,15 +2534,14 @@ AM_CONDITIONAL([HAVE_PROTOBUF], [test "$enable_protobuf" = "yes"]) AM_CONDITIONAL([HAVE_PROTOBUF3], [$PROTO3]) dnl PCEP plugin -AM_CONDITIONAL([HAVE_PATHD_PCEP], [test "$enable_pcep" = "yes"]) -AS_IF([test "$enable_pcep" = "yes"], [ - AC_CHECK_LIB([pcep_pcc], [initialize_pcc], [ - PATHD_PCEP_LIBS="-lpcep_pcc" - ],[ - AC_MSG_ERROR([PCEP library libpcep_pcc not found]) - ]) - AC_SUBST([PATHD_PCEP_LIBS]) -]) +AS_IF([test "$enable_pathd" != "no"], [ + AC_SUBST([PATHD_PCEP_LIBS], ["pceplib/libpcep_pcc.la"]) + AC_SUBST([PATHD_PCEP_INCL], ["-I./pceplib "]) + ]) +AC_CHECK_LIB([cunit], [CU_initialize_registry], [pcep_cunit=yes],[pcep_cunit=no]) +AM_CONDITIONAL([PATHD_PCEP_TEST], [test "x${pcep_cunit}" = xyes]) +AC_CHECK_PROG(VALGRIND_CHECK, valgrind, yes) +AM_CONDITIONAL([HAVE_VALGRIND_PCEP], [test "$VALGRIND_CHECK" = "yes"]) dnl daemons AM_CONDITIONAL([VTYSH], [test "$VTYSH" = "vtysh"]) @@ -2569,6 +2566,7 @@ AM_CONDITIONAL([STATICD], [test "$enable_staticd" != "no"]) AM_CONDITIONAL([FABRICD], [test "$enable_fabricd" != "no"]) AM_CONDITIONAL([VRRPD], [test "$enable_vrrpd" != "no"]) AM_CONDITIONAL([PATHD], [test "$enable_pathd" != "no"]) +AM_CONDITIONAL([PATHD_PCEP], [test "$enable_pathd" != "no"]) AC_CONFIG_FILES([Makefile],[ test "$enable_dev_build" = "yes" && makefile_devbuild="--dev-build" diff --git a/debian/frr.install b/debian/frr.install index cefc3135b2..9972b579f0 100644 --- a/debian/frr.install +++ b/debian/frr.install @@ -11,6 +11,7 @@ usr/lib/*/frr/modules/dplane_fpm_nl.so usr/lib/*/frr/modules/zebra_cumulus_mlag.so usr/lib/*/frr/modules/zebra_fpm.so usr/lib/*/frr/modules/zebra_irdp.so +usr/lib/*/frr/modules/pathd_pcep.so usr/lib/frr/*.sh usr/lib/frr/*d usr/lib/frr/watchfrr diff --git a/doc/developer/images/PCEPlib_design.jpg b/doc/developer/images/PCEPlib_design.jpg new file mode 100644 index 0000000000000000000000000000000000000000..41aada377419cec2d09a9e6475c525f79876e5b0 GIT binary patch literal 42003 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

9vK$2X+VtulWBjhh{`2xkzL1R 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 sGsFd7%KT%RUJ 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 DRarVq literal 0 HcmV?d00001 diff --git a/doc/developer/images/PCEPlib_threading_model_frr_infra.jpg b/doc/developer/images/PCEPlib_threading_model_frr_infra.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5648a9d788347bfe596d255b68fc4330711c83b1 GIT binary patch literal 83409 zcmeFY1yo$iwguXFAP@q<-GWPShd_b{cPF@{aVMk+A-Dtx5S-xJ1Zmve-QC??o8LL- z-S?97?mhSYKfa9d$7{xh-GjZW)>^f9)mn4TdYF1x0z8$Kl#v9$!2Vx}e`q32~_e!CA0xj%q82Y{##I?UqTw`l^hHI(!zUmldQMA6&%nsV&BM#bFYxNM zgrt+zi>sTvhvz51&;9{{LBUbcUt?n9zQre`XJlq& z=j7()mzI@7Dk`h0Yg$^{+B-VCx_d@O$Hpfnr>19?S60{7H#WDncaBd^&(1F{udZ)? z`UM9-_|q-e?>`;;i(gnUzu*xO5fD*+`UMB?20IY25RoX^k+H>;QQkT{qvH7Z2uD2f zdr31YHK)oEuCe1V8XgVT^7G@LuKnTJ|I9Jp|4W|zr(^%+*9-t10S-2J2v`77z~v2n zx)0j_9{(GI|1X~bCgL_SijdbI#dDI~Sl{P5g(quAnBg`V>=5~9w-*;b|3ZFkMvN2c z1w={hq^))!=yB$Xy$??y(20SBcNQueusYvzI07l?#yT)8b~&wTXj3n$oyWd;gb+xl ztF)Cg?FO%g+Yk!Umu0P&2G>RjuLNJu-a0AxOMiX<1lSfDN46Pz>__*ayAv`hFnxy( zmOcPd1wprGjmzrH{jc2FrPv&2q%VS*TEYG?R>FnYGfgx#Upk*Y{S*a9S4o1PJjD~A zE9XU`fyGds)O4F}papOguFks8BQ}hTxaD7%>$=ooYJUKf?KN%=EJE+uuGE<{Mp@5^ zZ)tu6%9wVQ;sx2e(Z7vo8qqG)p9vYVV~M(zhTYP9uA59JqxHIif$Owl3b^FM6@@j4 zFk3C8t}863kf;>iaw_31vSIXwY6WMORp7H3jj5-4VK;YQ+TN=s?dfiEF=Yd``&6?B zfQah@;Bp((60vv7c70m;0BDqh;-+5IiN?g;$BS-Al`9*NeyGt%w4CJS`N1p}<4B3P z9;hVX8L0vKFQZkV6Q7HFT5mZtD>;PIgO5SKHRd1^2h8IRnUXZsm8xZ?(gUi%ksDaT z!Vbivi|w|f@oL9io_X)-nvtVx&~Ad(>44qyJzdX7(xX6h`oi#Z)9wNAA?}Xeqx@M} z2vIN1_*mpVj%LUMz+xISnA~`OV|WDq05tCAW~yZZVa&}#3j89Md^xHq$ktrBmHKg@ z1fuoNn5#QI$X_cP3hP-{@Fcv$ce?U)U+{_qlGcimf9sPJ+YNQteIqcDYDXjf0BEh` zVV7ND7n(T!)=GxFlDU#+jz5EW`1)SzcJY%)V0wdB-8eJw+@pL-NR)f+sH2qMm*5D& zc}GG%dvV+I^I0rgCM0>yA?ST#qO(eOlC28Iqd`(Dp`PBv$onlDI5%5UuXEg`nZ()c z4ob3*EC+X+dqb4dp)TE|PB-cp)Y_#JI(VO2-DK>TaPct_y^v8+_}-l(DwWZ!_I=OE zNAa=N1MA#yZUr;7l*f*8hcPZH`L@smyUgVuvn~(tcaDI6jZLmcq4C~IulBoyny9~y z4G!qPVwiPbjJf{yA7lRe!;!~n6;#-nmH4P`o5HHqL$1=&i2Zycq2Kr-1m%wrEW%xd zZ|QSnd8@jUi)1;qlMox*Tv$vqCA#j{XBryuhD;L*FQLY%qv9H68NUv}uG6t7P}bJL zt3|FhAEEk|6S|*(%uoNJzIhJ>mav}8PKc@zx=3(+D}kf>R9ONC@c+i4u6r*zC|ALY zUx|vS-Nfvf`cfczk8#{1wT)2!P7aZ|trIt)hRvw&0QFA1cwKuum4+lRsoHpY>;cf? zw!CfZ+W3`{SSaP%{bL4ewu+c^BE4;&?Qa$NwcYqo8CQ7 z&>h_eQrLVl-FWoG4oVeqrEn+s01zz$Es-qVNq7VXizl|d(ZsBSWvgZ?=?2Ly+Vm75(O(>OPvR~3iCpr zH9mO&4DKynq3}Kcu%v#ssjjA+diWr9&tLva){qHtmwgpk)qsFfNwn8%JSs&~Pt4yh z|0gbF`Pr4DZ(4N-Te3@opLR4yyp*DigGInlJI-XZaar)_@D2(Kl8+PU7zgxc91|Y^ zfDKq2nfl)6@qRs7bUfG3p#slZJ)5I>Gw|hl^gbCe9RQ+Ypbe)6%>jrEqe9$A3P?rwDiG&|6=$W|G6yYni z10|N}!2tTd94Fr5=MZ6yo$K(|;TocrV_z~(Hh1L3)XSmHv#PsjAW99JHTGvt1q>pa zV@kncd|6XW@yC<#4}eE^A2b#@_rJVb92M-~+}Gz;Zyeo+(BnPMWFhJ*1rS8UD@ZAN zCa5YUL~jX1I(`b?F*4W|p`4`9CDj0O5aUJaytSsWurDOGWNBJ^Vx4tozmqVKmS>T> z6ZZH~w{9dNPa7Mh!gBP0Ofz_-WaDTeRe0?Zajx^?Q~XNs@(8~Id#sYZ``zut*zT;i zpzclMikSfKtf6XTMW79l+b1wvigs3%X4qoCkrvW>$48qV2X$&~GIXtZ$!JrS4|t{A zp-M9G@Grs?mf%RvcDMB(UzxlC^2{`w#PR|4y5YjjHA0g%(VuuF)FLO^*s=+gQ|~(x z(q0G6wE;dP!Rg3hL)L+y*15pt#hJ>pLWdjY(kp);a<6iVMkVmYymSGjp~%agMs@-^ zge1>|(WCZsZ5t?6WwjctlDb^+rlgH8Bor*2xrbi3O&%=`QGfN(`hjY%gWoPx^Uc3a1CAv}ntg zXX|ZGcGkJ!M@xIkO79a*UZ^WQ0l#%MjA#t)0GV18bTeJ^(PFZ*Wn(VL3+t?45VK`H)Ihov~V1@G%3H_O*p_FUGH({IVt6|tk1EtVx^@= zrN^_q{PVeuD4_r8v?j51sZFG4G^D+xoLC0%T3Ybs_LS1`)5)94FN_?}n(HJkj(Ppg z<#`sMMJ+`CIV;fu@kh*Wb8LMwgb1U;+m~T^GwybJl_J0-hkk3IGC0=i?L5_iiWtq4 zC9;m1?K}P)c`FOMQmuxz?=0)V8Zy)b9S8_1zJ5mw-x8cBBpJx~*R;*uBIDQW#gM)w zrMxV-=@;hOy;-bpg#S=1?6Q5MTe+h>%2n%4JDpa+iEFXYp{*mX^)WiiyCANla)S1l zH&7wb8~Dz5LwLm!-7l5J^L;Ds%(yO4WM*V%X9b6eWi;SxEFBFxW)6JycXhD&x!vYC z@j%r3Jj=LilEq+?Mtx54aJfLT+gp{V&riDK7kQq{ZITL(%WJ!6L*z&~+caHwv-M?b z$wuJQwQ<%Y*T3x#@O2tm?;bke0$=St04&89Es`xF&?QMd$tR*!MuFTmbmes+eV+sQ z5Pcuxcf62fRK&Qx zOrdJ1nxI3`E|1uQiqm$DP`M#1$QXGmDtrTnV{ptYD#c#oHjFacJga5rSKoe}pr4sg zE)X9YQkB7uqCw5*g*E545n#Z=cm4cB<55#?`QfJHx>E|`10Ygh@cz2-%xO|e-FaVR zR<1-&@HGQG9%EL}ZJ*YOcXuI9RQBpdqv0vH!}}cr#WSIUQF%F0|B9J0)wwWnTy5$v z3QZKJ?B%Qum86pV{mx7|v<0g`HGM_n>P87DjwL`k^-?MyuNr|V?y+-SLJevoOU$N_(IJmfmK;N_wcsb@ hEjcwC=so&v<1kOu2nW45t9ya@vn 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 Ge-i*4*HG^O literal 0 HcmV?d00001 diff --git a/doc/developer/images/PCEPlib_timers.jpg b/doc/developer/images/PCEPlib_timers.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a178ee9c6f919f250218a01ea1a16b5d963f96da GIT binary patch literal 37363 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�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 K*cSdh_x}N}^`0L9 literal 0 HcmV?d00001 diff --git a/doc/developer/index.rst b/doc/developer/index.rst index 8e7913419f..46fd8f612e 100644 --- a/doc/developer/index.rst +++ b/doc/developer/index.rst @@ -19,4 +19,5 @@ FRRouting Developer's Guide zebra vtysh path + pceplib link-state diff --git a/doc/developer/pceplib.rst b/doc/developer/pceplib.rst new file mode 100644 index 0000000000..774617dfe5 --- /dev/null +++ b/doc/developer/pceplib.rst @@ -0,0 +1,781 @@ +.. _pceplib: + +******* +PCEPlib +******* + +Overview +======== + +The PCEPlib is a PCEP implementation library that can be used by either a PCE +or PCC. + +Currently, only the FRR pathd has been implemented as a PCC with the PCEPlib. +The PCEPlib is able to simultaneously connect to multiple PCEP peers and can +maintain persistent PCEP connections. + + +PCEPlib compliance +================== + +The PCEPlib implements version 1 of the PCEP protocol, according to `RFC 5440 `_. + +Additionally, the PCEPlib implements the following PCEP extensions: + +- `RFC 8281 `_ PCE initiated for PCE-Initiated LSP Setup +- `RFC 8231 `_ Extensions for Stateful PCE +- `RFC 8232 `_ Optimizations of Label Switched Path State Synchronization Procedures for a Stateful PCE +- `RFC 8282 `_ Extensions to PCEP for Inter-Layer MPLS and GMPLS Traffic Engineering +- `RFC 8408 `_ Conveying Path Setup Type in PCE Communication Protocol (PCEP) Messages +- `draft-ietf-pce-segment-routing-07 `_, + `draft-ietf-pce-segment-routing-16 `_, + `RFC 8664 `_ Segment routing protocol extensions +- `RFC 7470 `_ Conveying Vendor-Specific Constraints +- `Draft-ietf-pce-association-group-10 `_ + Establishing Relationships Between Sets of Label Switched Paths +- `Draft-barth-pce-segment-routing-policy-cp-04 `_ + Segment Routing Policy Candidate Paths + + +PCEPlib Architecture +==================== + +The PCEPlib is comprised of the following modules, each of which will be +detailed in the following sections. + +- **pcep_messages** + - PCEP messages, objects, and TLVs implementations + +- **pcep_pcc** + - PCEPlib public PCC API with a sample PCC binary + +- **pcep_session_logic** + - PCEP Session handling + +- **pcep_socket_comm** + - Socket communications + +- **pcep_timers** + - PCEP timers + +- **pcep_utils** + - Internal utilities used by the PCEPlib modules. + +The interaction of these modules can be seen in the following diagram. + +PCEPlib Architecture: + +.. image:: images/PCEPlib_design.jpg + + +PCEP Session Logic library +-------------------------- + +The PCEP Session Logic library orchestrates calls to the rest of the PCC libraries. + +PCEP Session Logic library responsibilities: + +- Handle messages received from "PCEP Socket Comm" +- Create and manage "PCEP Session" objects +- Set timers and react to timer expirations +- Manage counters + +The PCEP Session Logic library will have 2 main triggers controlled by a +pthread condition variable: + +- Timer expirations - ``on_timer_expire()`` callback +- Messages received from PCEP SocketComm - ``message_received()`` callback + +The counters are created and managed using the ``pcep_utils/pcep_utils_counters.h`` +counters library. The following are the different counter groups managed: + +- **COUNTER_SUBGROUP_ID_RX_MSG** +- **COUNTER_SUBGROUP_ID_TX_MSG** +- **COUNTER_SUBGROUP_ID_RX_OBJ** +- **COUNTER_SUBGROUP_ID_TX_OBJ** +- **COUNTER_SUBGROUP_ID_RX_SUBOBJ** +- **COUNTER_SUBGROUP_ID_TX_SUBOBJ** +- **COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ** +- **COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ** +- **COUNTER_SUBGROUP_ID_RX_TLV** +- **COUNTER_SUBGROUP_ID_TX_TLV** +- **COUNTER_SUBGROUP_ID_EVENT** + +The counters can be obtained and reset as explained later in the PCEPlib PCC API. + +PCEP Socket Comm library +------------------------ + +PCEP communication can be configured to be handled internally in this simple +library. When this library is instantiated by the PCEP Session Logic, callbacks +are provided to handle received messages and error conditions. + +The following diagram illustrates how the library works. + +PCEPlib Socket Comm: + +.. image:: images/PCEPlib_socket_comm.jpg + + +PCEP Timers library +------------------- + +Timers can be configured to be handled internally by this library. When this +library is instantiated by the PCEP Session Logic, callbacks are provided to +ha:0 +ndle timer expirations. The following timers are implemented and handled, +according to `RFC 5440 `_. + +- Open KeepWait (fixed at 60 seconds) + - Set once the PCC sends an Open, and if it expires before receiving a KeepAlive or PCErr, then the PCC should send a PCErr and close the TCP connection + +- Keepalive timer + - How often the PCC should send Keepalive messages to the PCE (and vice-versa) + - The timer will be reset after any message is sent: any message serves as a Keepalive + +- DeadTimer + - If no messages are received before expiration, the session is declared as down + - Reset everytime any message is received + +- PCReq request timer + - How long the PCC waits for the PCE to reply to PCReq messages. + +PCEPlib Timers: + +.. image:: images/PCEPlib_timers.jpg + + +PCEP Messages library +--------------------- + +The PCEP Messages library has all of the implemented PCEP messages, objects, +TLVs, and related functionality. + +The following header files can be used for creating and handling received PCEP +entities. + +- pcep-messages.h +- pcep-objects.h +- pcep-tlvs.h + + +PCEP Messages ++++++++++++++ + +The following PCEP messages can be created and received: + +- ``struct pcep_message* pcep_msg_create_open(...);`` +- ``struct pcep_message* pcep_msg_create_open_with_tlvs(...);`` +- ``struct pcep_message* pcep_msg_create_request(...);`` +- ``struct pcep_message* pcep_msg_create_request_ipv6(...);`` +- ``struct pcep_message* pcep_msg_create_reply(...);`` +- ``struct pcep_message* pcep_msg_create_close(...);`` +- ``struct pcep_message* pcep_msg_create_error(...);`` +- ``struct pcep_message* pcep_msg_create_error_with_objects(...);`` +- ``struct pcep_message* pcep_msg_create_keepalive(...);`` +- ``struct pcep_message* pcep_msg_create_report(...);`` +- ``struct pcep_message* pcep_msg_create_update(...);`` +- ``struct pcep_message* pcep_msg_create_initiate(...);`` + +Refer to ``pcep_messages/include/pcep-messages.h`` and the API section +below for more details. + + +PCEP Objects +++++++++++++ + +The following PCEP objects can be created and received: + +- ``struct pcep_object_open* pcep_obj_create_open(...);`` +- ``struct pcep_object_rp* pcep_obj_create_rp(...);`` +- ``struct pcep_object_notify* pcep_obj_create_notify(...);`` +- ``struct pcep_object_nopath* pcep_obj_create_nopath(...);`` +- ``struct pcep_object_association_ipv4* pcep_obj_create_association_ipv4(...);`` +- ``struct pcep_object_association_ipv6* pcep_obj_create_association_ipv6(...);`` +- ``struct pcep_object_endpoints_ipv4* pcep_obj_create_endpoint_ipv4(...);`` +- ``struct pcep_object_endpoints_ipv6* pcep_obj_create_endpoint_ipv6(...);`` +- ``struct pcep_object_bandwidth* pcep_obj_create_bandwidth(...);`` +- ``struct pcep_object_metric* pcep_obj_create_metric(...);`` +- ``struct pcep_object_lspa* pcep_obj_create_lspa(...);`` +- ``struct pcep_object_svec* pcep_obj_create_svec(...);`` +- ``struct pcep_object_error* pcep_obj_create_error(...);`` +- ``struct pcep_object_close* pcep_obj_create_close(...);`` +- ``struct pcep_object_srp* pcep_obj_create_srp(...);`` +- ``struct pcep_object_lsp* pcep_obj_create_lsp(...);`` +- ``struct pcep_object_vendor_info* pcep_obj_create_vendor_info(...);`` +- ``struct pcep_object_ro* pcep_obj_create_ero(...);`` +- ``struct pcep_object_ro* pcep_obj_create_rro(...);`` +- ``struct pcep_object_ro* pcep_obj_create_iro(...);`` +- ``struct pcep_ro_subobj_ipv4* pcep_obj_create_ro_subobj_ipv4(...);`` +- ``struct pcep_ro_subobj_ipv6* pcep_obj_create_ro_subobj_ipv6(...);`` +- ``struct pcep_ro_subobj_unnum* pcep_obj_create_ro_subobj_unnum(...);`` +- ``struct pcep_ro_subobj_32label* pcep_obj_create_ro_subobj_32label(...);`` +- ``struct pcep_ro_subobj_asn* pcep_obj_create_ro_subobj_asn(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_nonai(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv4_node(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv6_node(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv4_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv6_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj(...);`` + +Refer to ``pcep_messages/include/pcep-objects.h`` and the API section +below for more details. + + +PCEP TLVs ++++++++++ + +The following PCEP TLVs (Tag, Length, Value) can be created and received: + +- Open Object TLVs + - ``struct pcep_object_tlv_stateful_pce_capability* pcep_tlv_create_stateful_pce_capability(...);`` + - ``struct pcep_object_tlv_lsp_db_version* pcep_tlv_create_lsp_db_version(...);`` + - ``struct pcep_object_tlv_speaker_entity_identifier* pcep_tlv_create_speaker_entity_id(...);`` + - ``struct pcep_object_tlv_path_setup_type* pcep_tlv_create_path_setup_type(...);`` + - ``struct pcep_object_tlv_path_setup_type_capability* pcep_tlv_create_path_setup_type_capability(...);`` + - ``struct pcep_object_tlv_sr_pce_capability* pcep_tlv_create_sr_pce_capability(...);`` + +- LSP Object TLVs + - ``struct pcep_object_tlv_ipv4_lsp_identifier* pcep_tlv_create_ipv4_lsp_identifiers(...);`` + - ``struct pcep_object_tlv_ipv6_lsp_identifier* pcep_tlv_create_ipv6_lsp_identifiers(...);`` + - ``struct pcep_object_tlv_symbolic_path_name* pcep_tlv_create_symbolic_path_name(...);`` + - ``struct pcep_object_tlv_lsp_error_code* pcep_tlv_create_lsp_error_code(...);`` + - ``struct pcep_object_tlv_rsvp_error_spec* pcep_tlv_create_rsvp_ipv4_error_spec(...);`` + - ``struct pcep_object_tlv_rsvp_error_spec* pcep_tlv_create_rsvp_ipv6_error_spec(...);`` + - ``struct pcep_object_tlv_nopath_vector* pcep_tlv_create_nopath_vector(...);`` + - ``struct pcep_object_tlv_vendor_info* pcep_tlv_create_vendor_info(...);`` + - ``struct pcep_object_tlv_arbitrary* pcep_tlv_create_tlv_arbitrary(...);`` + +- SRPAG (SR Association Group) TLVs + - ``struct pcep_object_tlv_srpag_pol_id *pcep_tlv_create_srpag_pol_id_ipv4(...);`` + - ``struct pcep_object_tlv_srpag_pol_id *pcep_tlv_create_srpag_pol_id_ipv6(...);`` + - ``struct pcep_object_tlv_srpag_pol_name *pcep_tlv_create_srpag_pol_name(...);`` + - ``struct pcep_object_tlv_srpag_cp_id *pcep_tlv_create_srpag_cp_id(...);`` + - ``struct pcep_object_tlv_srpag_cp_pref *pcep_tlv_create_srpag_cp_pref(...);`` + +Refer to ``pcep_messages/include/pcep-tlvs.h`` and the API section +below for more details. + + +PCEP PCC +-------- + +This module has a Public PCC API library (explained in detail later) and a +sample PCC binary. The APIs in this library encapsulate other PCEPlib libraries +for simplicity. With this API, the PCEPlib PCC can be started and stopped, and +the PCEPlib event queue can be accessed. The PCEP Messages library is not +encapsulated, and should be used directly. + + +Internal Dependencies +--------------------- + +The following diagram illustrates the internal PCEPlib library dependencies. + +PCEPlib internal dependencies: + +.. image:: images/PCEPlib_internal_deps.jpg + + +External Dependencies +--------------------- + +Originally the PCEPlib was based on the open source `libpcep project `_, +but that dependency has been reduced to just one source file (pcep-tools.[ch]). + + +PCEPlib Threading model +----------------------- + +The PCEPlib can be run in stand-alone mode whereby a thread is launched for +timers and socket comm, as is illustrated in the following diagram. + +PCEPlib Threading model: + +.. image:: images/PCEPlib_threading_model.jpg + +The PCEPlib can also be configured to use an external timers and socket +infrastructure like the FRR threads and tasks. In this case, no internal +threads are launched for timers and socket comm, as is illustrated in the +following diagram. + +PCEPlib Threading model with external infra: + +.. image:: images/PCEPlib_threading_model_frr_infra.jpg + + +Building +-------- + +The autotools build system is used and integrated with the frr build system. + +Testing +------- + +The Unit Tests for an individual library are executed with the ``make check`` +command. The Unit Test binary will be written to the project ``build`` directory. +All Unit Tests are executed with Valgrind, and any memory issues reported by +Valgrind will cause the Unit Test to fail. + + +PCEPlib PCC API +=============== + +The following sections describe the PCEPlib PCC API. + + +PCEPlib PCC Initialization and Destruction +------------------------------------------ + +The PCEPlib can be initialized to handle memory, timers, and socket comm +internally in what is called stand-alone mode, or with an external +infrastructure, like FRR. + +PCEPlib PCC Initialization and Destruction in stand-alone mode +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +PCEPlib PCC initialization and destruction functions: + +- ``bool initialize_pcc();`` +- ``bool initialize_pcc_wait_for_completion();`` +- ``bool destroy_pcc();`` + +The PCC can be initialized with either ``initialize_pcc()`` or +``initialize_pcc_wait_for_completion()``. + +- ``initialize_pcc_wait_for_completion()`` blocks until ``destroy_pcc()`` + is called from a separate pthread. +- ``initialize_pcc()`` is non-blocking and will be stopped when + ``destroy_pcc()`` is called. + +Both initialize functions will launch 3 pthreads: + +- 1 Timer pthread +- 1 SocketComm pthread +- 1 SessionLogic pthread + +When ``destroy_pcc()`` is called, all pthreads will be stopped and all +resources will be released. + +All 3 functions return true upon success, and false otherwise. + +PCEPlib PCC Initialization and Destruction with FRR infrastructure +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +PCEPlib PCC initialization and destruction functions: + +- ``bool initialize_pcc_infra(struct pceplib_infra_config *infra_config);`` +- ``bool destroy_pcc();`` + +The ``pceplib_infra_config`` struct has the following fields: + +- **void *pceplib_infra_mt** + - FRR Memory type pointer for infra related memory management + +- **void *pceplib_messages_mt** + - FRR Memory type pointer for PCEP messages related memory management + +- **pceplib_malloc_func mfunc** + - FRR malloc function pointer + +- **pceplib_calloc_func cfunc** + - FRR calloc function pointer + +- **pceplib_realloc_func rfunc** + - FRR realloc function pointer + +- **pceplib_strdup_func sfunc** + - FRR strdup function pointer + +- **pceplib_free_func ffunc** + - FRR free function pointer + +- **void *external_infra_data** + - FRR data used by FRR timers and sockets infrastructure + +- **ext_timer_create timer_create_func** + - FRR timer create function pointer + +- **ext_timer_cancel timer_cancel_func** + - FRR timer cancel function pointer + +- **ext_socket_write socket_write_func** + - FRR socket write function pointer, indicating fd is ready to be written to + +- **ext_socket_read socket_read_func** + - FRR socket write function pointer, indicating fd is ready to be read from + + +PCEPlib PCC configuration +------------------------- + +PCEPlib PCC configuratoin functions: + +- ``pcep_configuration *create_default_pcep_configuration();`` +- ``void destroy_pcep_configuration(pcep_configuration *config);`` + +A ``pcep_configuration`` object with default values is created with +``create_default_pcep_configuration()``. These values can be tailored to +specific use cases. + +Created ``pcep_configuration`` objects are destroyed with +``destroy_pcep_configuration()``. + + +PCEPlib PCC configuration paramaters +++++++++++++++++++++++++++++++++++++ + +The ``pcep_configuration`` object is defined in ``pcep_session_logic/include/pcep_session_logic.h`` +The attributes in the ``pcep_configuration`` object are detailed as follows. + +PCEP Connection parameters: + +- **dst_pcep_port** + - Defaults to 0, in which case the default PCEP TCP destination port + 4189 will be used. + - Set to use a specific PCEP TCP destination port. + +- **src_pcep_port** + - Defaults to 0, in which case the default PCEP TCP source port + 4189 will be used. + - Set to use a specific PCEP TCP source port. + +- **Source IP** + - Defaults to IPv4 INADDR_ANY + - Set **src_ip.src_ipv4** and **is_src_ipv6=false** to set the source IPv4. + - Set **src_ip.src_ipv6** and **is_src_ipv6=true** to set the source IPv6. + +- **socket_connect_timeout_millis** + - Maximum amount of time to wait to connect to the PCE TCP socket + before failing, in milliseconds. + +PCEP Versioning: + +- **pcep_msg_versioning->draft_ietf_pce_segment_routing_07** + - Defaults to false, in which case draft 16 versioning will be used. + - Set to true to use draft 07 versioning. + +PCEP Open Message Parameters: + +- **keep_alive_seconds** + - Sent to PCE in PCEP Open Msg + - Recommended value = 30, Minimum value = 1 + - Disabled by setting value = 0 + +- **dead_timer_seconds** + - Sent to PCE in PCEP Open Msg + - Recommended value = 4 * keepalive timer value + +- Supported value ranges for PCEP Open Message received from the PCE + - **min_keep_alive_seconds**, **max_keep_alive_seconds** + - **min_dead_timer_seconds**, **max_dead_timer_seconds** + +- **request_time_seconds** + - When a PCC sends a PcReq to a PCE, the amount of time a PCC will + wait for a PcRep reply from the PCE. + +- **max_unknown_requests** + - If a PCC/PCE receives PCRep/PCReq messages with unknown requests + at a rate equal or greater than MAX-UNKNOWN-REQUESTS per minute, + the PCC/PCE MUST send a PCEP CLOSE message. + - Recommended value = 5 + +- **max_unknown_messages** + - If a PCC/PCE receives unrecognized messages at a rate equal or + greater than MAX-UNKNOWN-MESSAGES per minute, the PCC/PCE MUST + send a PCEP CLOSE message + - Recommended value = 5 + +Stateful PCE Capability TLV configuration parameters (RFC 8231, 8232, 8281, and +draft-ietf-pce-segment-routing-16): + +- **support_stateful_pce_lsp_update** + - If this flag is true, then a Stateful PCE Capability TLV will + be added to the PCEP Open object, with the LSP Update Capability + U-flag set true. + - The rest of these parameters are used to configure the Stateful + PCE Capability TLV + +- **support_pce_lsp_instantiation** + - Sets the I-flag true, indicating the PCC allows instantiation + of an LSP by a PCE. + +- **support_include_db_version** + - Sets the S-bit true, indicating the PCC will include the + LSP-DB-VERSION TLV in each LSP object. See lsp_db_version below. + +- **support_lsp_triggered_resync** + - Sets the T-bit true, indicating the PCE can trigger resynchronization + of LSPs at any point in the life of the session. + +- **support_lsp_delta_sync** + - Sets the D-bit true, indicating the PCEP speaker allows incremental + (delta) State Synchronization. + +- **support_pce_triggered_initial_sync** + - Sets the F-bit true, indicating the PCE SHOULD trigger initial (first) + State Synchronization + +LSP DB Version TLV configuration parameters: + +- **lsp_db_version** + - If this parameter has a value other than 0, and the above + support_include_db_version flag is true, then an LSP DB + Version TLV will be added to the PCEP Open object. + - This parameter should only be set if LSP-DB survived a restart + and is available. + - This value will be copied over to the pcep_session upon initialization. + +SR PCE Capability sub-TLV configuration parameters (draft-ietf-pce-segment-routing-16): + +- **support_sr_te_pst** + - If this flag is true, then an SR PCE Capability sub-TLV will be + added to a Path Setup type Capability TLV, which will be added + to the PCEP Open object. + - The PST used in the Path Setup type Capability will be 1, + indicating the Path is setup using Segment Routing Traffic Engineering. + +Only set the following fields if the **support_sr_te_pst** flag is true. + +- **pcc_can_resolve_nai_to_sid** + - Sets the N-flag true, indicating that the PCC is capable of resolving + a Node or Adjacency Identifier to a SID + +- **max_sid_depth** + - If set other than 0, then the PCC imposes a limit on the Maximum + SID depth. + - If this parameter is other than 0, then the X bit will be true, + and the parameter value will be set in the MSD field. + + +PCEPlib PCC connections +----------------------- + +PCEPlib PCC connect and disconnect functions: + +- ``pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip);`` +- ``pcep_session *connect_pce_ipv6(pcep_configuration *config, struct in6_addr *pce_ip);`` +- ``void disconnect_pce(pcep_session *session);`` + +When connecting to a PCE, a ``pcep_session`` will be returned on success, NULL +otherwise. + +Refer to the above PCC configuration parameters section for setting the source +and destination PCEP TCP ports, and the source IP address and version. + + +PCEP Messages, Objects, and TLVs +-------------------------------- + +The PCEP messages, objects, and TLVs created in the PCEPlib are high-level API +structures, meaning they need to be encoded before being sent on-the-wire, and +the raw data received needs to be decoded into these structures. This makes +using these objects much easier for the library consumer, since they do not +need to know the detailed raw format of the PCEP entities. + + +PCEP Messages ++++++++++++++ + +Received messages (in the ``pcep_event`` explained below) are of type +``pcep_message``, which have the following fields: + +- ``struct pcep_message_header *msg_header;`` + - Defines the PCEP version and message type + +- ``double_linked_list *obj_list;`` + - A double linked list of the message objects + - Each entry is a pointer to a ``struct pcep_object_header``, and + using the ``object_class`` and ``object_type`` fields, the pointer + can be cast to the appropriate object structure to access the + rest of the object fields + +- ``uint8_t *encoded_message;`` + - This field is only populated for received messages or once the + ``pcep_encode_message()`` function has been called on the message. + - This field is a pointer to the raw PCEP data for the entire + message, including all objects and TLVs. + +- ``uint16_t encoded_message_length;`` + - This field is only populated for received messages or once the + ``pcep_encode_message()`` function has been called on the message. + - This field is the length of the entire raw message, including + all objects and TLVs. + - This field is in host byte order. + + +PCEP Objects +++++++++++++ + +A PCEP message has a double linked list of pointers to ``struct pcep_object_header`` +structures, which have the following fields: + +- ``enum pcep_object_classes object_class;`` +- ``enum pcep_object_types object_type;`` +- ``bool flag_p;`` + - PCC Processing rule bit: When set, the object MUST be taken into + account, when cleared the object is optional + +- ``bool flag_i;`` + - PCE Ignore bit: indicates to a PCC whether or not an optional + object was processed + +- ``double_linked_list *tlv_list;`` + - A double linked list of the object TLVs + - Each entry is a pointer to a ``struct pcep_object_tlv_header``, and + using the TLV type field, the pointer can be cast to the + appropriate TLV structure to access the rest of the TLV fields + +- ``uint8_t *encoded_object;`` + - This field is only populated for received objects or once the + ``pcep_encode_object()`` (called by ``pcep_encode_message()``) + function has been called on the object. + - Pointer into the encoded_message field (from the pcep_message) + where the raw object PCEP data starts. + +- ``uint16_t encoded_object_length;`` + - This field is only populated for received objects or once the + ``pcep_encode_object()`` (called by ``pcep_encode_message()``) + function has been called on the object. + - This field is the length of the entire raw TLV + - This field is in host byte order. + +The object class and type can be used to cast the ``struct pcep_object_header`` +pointer to the appropriate object structure so the specific object fields can +be accessed. + + +PCEP TLVs ++++++++++ + +A PCEP object has a double linked list of pointers to ``struct pcep_object_tlv_header`` +structures, which have the following fields: + +- ``enum pcep_object_tlv_types type;`` +- ``uint8_t *encoded_tlv;`` + - This field is only populated for received TLVs or once the + ``pcep_encode_tlv()`` (called by ``pcep_encode_message()``) + function has been called on the TLV. + - Pointer into the encoded_message field (from the pcep_message) + where the raw TLV PCEP data starts. + +- ``uint16_t encoded_tlv_length;`` + - This field is only populated for received TLVs or once the + ``pcep_encode_tlv()`` (called by ``pcep_encode_message()``) + function has been called on the TLV. + - This field is the length of the entire raw TLV + - This field is in host byte order. + + +Memory management ++++++++++++++++++ + +Any of the PCEPlib Message Library functions that receive a pointer to a +``double_linked_list``, ``pcep_object_header``, or ``pcep_object_tlv_header``, +transfer the ownership of the entity to the PCEPlib. The memory will be freed +internally when the encapsulating structure is freed. If the memory for any of +these is freed by the caller, then there will be a double memory free error +when the memory is freed internally in the PCEPlib. + +Any of the PCEPlib Message Library functions that receive either a pointer to a +``struct in_addr`` or ``struct in6_addr`` will allocate memory for the IP +address internally and copy the IP address. It is the responsibility of the +caller to manage the memory for the IP address passed into the PCEPlib Message +Library functions. + +For messages received via the event queue (explained below), the message will +be freed when the event is freed by calling ``destroy_pcep_event()``. + +When sending messages, the message will be freed internally in the PCEPlib when +the ``send_message()`` ``pcep_pcc`` API function when the ``free_after_send`` flag +is set true. + +To manually delete a message, call the ``pcep_msg_free_message()`` function. +Internally, this will call ``pcep_obj_free_object()`` and ``pcep_obj_free_tlv()`` +appropriately. + + +Sending a PCEP Report message +----------------------------- + +This section shows how to send a PCEP Report messages from the PCC to the PCE, +and serves as an example of how to send other messages. Refer to the sample +PCC binary located in ``pcep_pcc/src/pcep_pcc.c`` for code examples os sending +a PCEP Report message. + +The Report message must have at least an SRP, LSP, and ERO object. + +The PCEP Report message objects are created with the following APIs: + +- ``struct pcep_object_srp *pcep_obj_create_srp(...);`` +- ``struct pcep_object_lsp *pcep_obj_create_lsp(...);`` +- ``struct pcep_object_ro *pcep_obj_create_ero(...);`` + - Create ero subobjects with the ``pcep_obj_create_ro_subobj_*(...);`` functions + +PCEP Report message is created with the following API: + +- ``struct pcep_header *pcep_msg_create_report(double_linked_list *report_object_list);`` + +A PCEP report messages is sent with the following API: + +- ``void send_message(pcep_session *session, pcep_message *message, bool free_after_send);`` + + +PCEPlib Received event queue +---------------------------- + +PCEP events and messages of interest to the PCEPlib consumer will be stored +internally in a message queue for retrieval. + +The following are the event types: + +- **MESSAGE_RECEIVED** +- **PCE_CLOSED_SOCKET** +- **PCE_SENT_PCEP_CLOSE** +- **PCE_DEAD_TIMER_EXPIRED** +- **PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED** +- **PCC_CONNECTED_TO_PCE** +- **PCC_CONNECTION_FAILURE** +- **PCC_PCEP_SESSION_CLOSED** +- **PCC_RCVD_INVALID_OPEN** +- **PCC_SENT_INVALID_OPEN** +- **PCC_RCVD_MAX_INVALID_MSGS** +- **PCC_RCVD_MAX_UNKOWN_MSGS** + +The following PCEP messages will not be posted on the message queue, as they +are handled internally in the library: + +- **Open** +- **Keep Alive** +- **Close** + +Received event queue API: + +- ``bool event_queue_is_empty();`` + - Returns true if the queue is empty, false otherwise + +- ``uint32_t event_queue_num_events_available();`` + - Return the number of events on the queue, 0 if empty + +- ``struct pcep_event *event_queue_get_event();`` + - Return the next event on the queue, NULL if empty + - The ``message`` pointer will only be non-NULL if ``event_type`` + is ``MESSAGE_RECEIVED`` + +- ``void destroy_pcep_event(struct pcep_event *event);`` + - Free the PCEP Event resources, including the PCEP message if present + + +PCEPlib Counters +---------------- + +The PCEPlib counters are managed in the ``pcep_session_logic`` library, and can +be accessed in the ``pcep_session_counters`` field of the ``pcep_session`` structure. +There are 2 API functions to manage the counters: + +- ``void dump_pcep_session_counters(pcep_session *session);`` + - Dump all of the counters to the logs + +- ``void reset_pcep_session_counters(pcep_session *session);`` + diff --git a/pathd/path_pcep.c b/pathd/path_pcep.c index 2f9ff4f0f0..8b5ca8aff3 100644 --- a/pathd/path_pcep.c +++ b/pathd/path_pcep.c @@ -17,7 +17,7 @@ */ #include -#include +#include "pceplib/pcep_utils_counters.h" #include "log.h" #include "command.h" @@ -215,29 +215,10 @@ int pcep_main_event_update_candidate(struct path *path) ret = path_pcep_config_update_path(path); if (ret != PATH_NB_ERR && path->srp_id != 0) { - /* ODL and Cisco requires the first reported - * LSP to have a DOWN status, the later status changes - * will be comunicated through hook calls. - */ - enum pcep_lsp_operational_status real_status; if ((resp = path_pcep_config_get_path(&path->nbkey))) { resp->srp_id = path->srp_id; - real_status = resp->status; - resp->status = PCEP_LSP_OPERATIONAL_DOWN; - pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, resp); - /* If the update did not have any effect and the real - * status is not DOWN, we need to send a second report - * so the PCE is aware of the real status. This is due - * to the fact that NO notification will be received - * if the update did not apply any changes */ - if ((ret == PATH_NB_NO_CHANGE) - && (real_status != PCEP_LSP_OPERATIONAL_DOWN)) { - resp->status = real_status; - resp->srp_id = 0; - pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, - resp); - } - pcep_free_path(resp); + pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, resp, + ret == PATH_NB_NO_CHANGE); } } return ret; diff --git a/pathd/path_pcep.h b/pathd/path_pcep.h index 1896c265c1..b131b31445 100644 --- a/pathd/path_pcep.h +++ b/pathd/path_pcep.h @@ -22,8 +22,8 @@ #include #include #include -#include -#include +#include "pceplib/pcep_utils_logging.h" +#include "pceplib/pcep_pcc_api.h" #include "mpls.h" #include "pathd/pathd.h" #include "pathd/path_pcep_memory.h" diff --git a/pathd/path_pcep_cli.c b/pathd/path_pcep_cli.c index add3391f22..7bbed9464d 100644 --- a/pathd/path_pcep_cli.c +++ b/pathd/path_pcep_cli.c @@ -18,8 +18,8 @@ */ #include -#include -#include +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_session_logic.h" #include "log.h" #include "command.h" @@ -321,8 +321,9 @@ pcep_cli_merge_pcep_pce_config_options(struct pce_opts_cli *pce_opts_cli) default_pcep_config_group_opts_g.tcp_md5_auth; } } - strncpy(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth, - tcp_md5_auth_str, TCP_MD5SIG_MAXKEYLEN); + strlcpy(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth, + tcp_md5_auth_str, + sizeof(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth)); struct ipaddr *source_ip = &pce_opts_cli->pce_config_group_opts.source_ip; @@ -525,8 +526,9 @@ static int path_pcep_cli_show_srte_pcep_counters(struct vty *vty) tm_info = localtime(&group->start_time); strftime(tm_buffer, sizeof(tm_buffer), "%Y-%m-%d %H:%M:%S", tm_info); - vty_out(vty, "PCEP counters since %s (%luh %lum %lus):\n", tm_buffer, - diff_time / 3600, (diff_time / 60) % 60, diff_time % 60); + vty_out(vty, "PCEP counters since %s (%uh %um %us):\n", tm_buffer, + (uint32_t)(diff_time / 3600), (uint32_t)((diff_time / 60) % 60), + (uint32_t)(diff_time % 60)); /* Prepare table. */ tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); @@ -815,7 +817,8 @@ static int path_pcep_cli_peer_tcp_md5_auth(struct vty *vty, return CMD_ERR_NO_MATCH; } - strncpy(pce_config->tcp_md5_auth, tcp_md5_auth, TCP_MD5SIG_MAXKEYLEN); + strlcpy(pce_config->tcp_md5_auth, tcp_md5_auth, + sizeof(pce_config->tcp_md5_auth)); return CMD_SUCCESS; } @@ -1220,8 +1223,9 @@ static void print_pcep_session(struct vty *vty, struct pce_opts *pce_opts, localtime_r(¤t_time, <); gmtime_r(&session->time_connected, <); vty_out(vty, - " Connected for %ld seconds, since %d-%02d-%02d %02d:%02d:%02d UTC\n", - (current_time - session->time_connected), + " Connected for %u seconds, since %d-%02d-%02d %02d:%02d:%02d UTC\n", + (uint32_t)(current_time + - session->time_connected), lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec); } diff --git a/pathd/path_pcep_config.c b/pathd/path_pcep_config.c index 989223ebc3..107475bec9 100644 --- a/pathd/path_pcep_config.c +++ b/pathd/path_pcep_config.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include "pceplib/pcep_msg_objects.h" #include "pathd/pathd.h" #include "pathd/path_pcep.h" #include "pathd/path_pcep_config.h" @@ -45,14 +45,13 @@ status_int_to_ext(enum srte_policy_status status); static enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type); static enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type); -static int path_pcep_config_lookup_cb(struct thread *t) +void path_pcep_refine_path(struct path *path) { - struct path *path = THREAD_ARG(t); struct srte_candidate *candidate = lookup_candidate(&path->nbkey); struct srte_lsp *lsp; if (candidate == NULL) - return 0; + return; lsp = candidate->lsp; @@ -65,16 +64,6 @@ static int path_pcep_config_lookup_cb(struct thread *t) if ((path->update_origin == SRTE_ORIGIN_UNDEFINED) && (lsp->segment_list != NULL)) path->update_origin = lsp->segment_list->protocol_origin; - - return 0; -} - -void path_pcep_config_lookup(struct path *path) -{ - /* - * Configuration access is strictly done via the main thread - */ - thread_execute(master, path_pcep_config_lookup_cb, path, 0); } struct path *path_pcep_config_get_path(struct lsp_nb_key *key) @@ -346,9 +335,11 @@ int path_pcep_config_update_path(struct path *path) SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); for (metric = path->first_metric; metric != NULL; metric = metric->next) - srte_lsp_set_metric(candidate->lsp, metric->type, metric->value, - metric->enforce, metric->is_bound, - metric->is_computed); + srte_lsp_set_metric( + candidate->lsp, + (enum srte_candidate_metric_type)metric->type, + metric->value, metric->enforce, metric->is_bound, + metric->is_computed); if (path->has_bandwidth) srte_lsp_set_bandwidth(candidate->lsp, path->bandwidth, diff --git a/pathd/path_pcep_config.h b/pathd/path_pcep_config.h index de29ab29c1..223dd10c82 100644 --- a/pathd/path_pcep_config.h +++ b/pathd/path_pcep_config.h @@ -31,10 +31,11 @@ typedef int (*path_list_cb_t)(struct path *path, void *arg); /* Lookup the candidate path and fill up the missing path attributes like name - and type. Used for path generated from PCEP message received from the PCE - so they contains more information about the candidate path. If no matching - policy or candidate path is found, nothing is changed */ -void path_pcep_config_lookup(struct path *path); + * and type. Used for path generated from PCEP message received from the PCE + * so they contains more information about the candidate path. If no matching + * policy or candidate path is found, nothing is changed. + * MUST BE CALLED FROM THE MAIN THREAD */ +void path_pcep_refine_path(struct path *path); struct path *path_pcep_config_get_path(struct lsp_nb_key *key); void path_pcep_config_list_path(path_list_cb_t cb, void *arg); int path_pcep_config_update_path(struct path *path); diff --git a/pathd/path_pcep_controller.c b/pathd/path_pcep_controller.c index f4871a4d8d..db7d2b55a5 100644 --- a/pathd/path_pcep_controller.c +++ b/pathd/path_pcep_controller.c @@ -55,7 +55,9 @@ enum pcep_ctrl_event_type { EV_SYNC_PATH, EV_SYNC_DONE, EV_PCEPLIB_EVENT, - EV_RESET_PCC_SESSION + EV_RESET_PCC_SESSION, + EV_SEND_REPORT, + EV_PATH_REFINED }; struct pcep_ctrl_event_data { @@ -73,18 +75,20 @@ struct pcep_main_event_data { void *payload; }; -/* Synchronous call arguments */ - -struct get_counters_args { +struct pcep_refine_path_event_data { struct ctrl_state *ctrl_state; int pcc_id; - struct counters_group *counters; + pcep_refine_callback_t continue_lsp_update_handler; + struct path *path; + void *payload; }; -struct send_report_args { +/* Synchronous call arguments */ + +struct get_counters_args { struct ctrl_state *ctrl_state; int pcc_id; - struct path *path; + struct counters_group *counters; }; struct get_pcep_session_args { @@ -95,13 +99,10 @@ struct get_pcep_session_args { /* Internal Functions Called From Main Thread */ static int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res); +static int pcep_refine_path_event_cb(struct thread *thread); /* Internal Functions Called From Controller Thread */ static int pcep_thread_finish_event_handler(struct thread *thread); -static int pcep_thread_get_counters_callback(struct thread *t); -static int pcep_thread_send_report_callback(struct thread *t); -static int pcep_thread_get_pcep_session_callback(struct thread *t); -static int pcep_thread_get_pcc_info_callback(struct thread *t); /* Controller Thread Timer Handler */ static int schedule_thread_timer(struct ctrl_state *ctrl_state, int pcc_id, @@ -148,6 +149,9 @@ static int pcep_thread_event_sync_done(struct ctrl_state *ctrl_state, static int pcep_thread_event_pathd_event(struct ctrl_state *ctrl_state, enum pcep_pathd_event_type type, struct path *path); +static void +pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data); /* Main Thread Event Handler */ static int send_to_main(struct ctrl_state *ctrl_state, int pcc_id, @@ -280,48 +284,50 @@ struct counters_group *pcep_ctrl_get_counters(struct frr_pthread *fpt, int pcc_id) { struct ctrl_state *ctrl_state = get_ctrl_state(fpt); - struct get_counters_args args = { - .ctrl_state = ctrl_state, .pcc_id = pcc_id, .counters = NULL}; - thread_execute(ctrl_state->self, pcep_thread_get_counters_callback, - &args, 0); - return args.counters; + struct counters_group *counters = NULL; + struct pcc_state *pcc_state; + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + counters = pcep_lib_copy_counters(pcc_state->sess); + } + return counters; } pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id) { struct ctrl_state *ctrl_state = get_ctrl_state(fpt); - struct get_pcep_session_args args = {.ctrl_state = ctrl_state, - .pcc_id = pcc_id, - .pcep_session = NULL}; - thread_execute(ctrl_state->self, pcep_thread_get_pcep_session_callback, - &args, 0); - return args.pcep_session; + struct pcc_state *pcc_state; + pcep_session *session = NULL; + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + session = pcep_lib_copy_pcep_session(pcc_state->sess); + } + return session; } struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, const char *pce_name) { struct ctrl_state *ctrl_state = get_ctrl_state(fpt); - struct pcep_pcc_info *args = XCALLOC(MTYPE_PCEP, sizeof(*args)); - args->ctrl_state = ctrl_state; - strncpy(args->pce_name, pce_name, sizeof(args->pce_name)); - thread_execute(ctrl_state->self, pcep_thread_get_pcc_info_callback, - args, 0); + struct pcep_pcc_info *pcc_info = XCALLOC(MTYPE_PCEP, sizeof(*pcc_info)); + if( pcc_info && ctrl_state){ + strlcpy(pcc_info->pce_name, pce_name, sizeof(pcc_info->pce_name)); + pcep_pcc_copy_pcc_info(ctrl_state->pcc, pcc_info); + } - return args; + return pcc_info; } -void pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, - struct path *path) +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable) { - /* Sends a report stynchronously */ struct ctrl_state *ctrl_state = get_ctrl_state(fpt); - struct send_report_args args = { - .ctrl_state = ctrl_state, .pcc_id = pcc_id, .path = path}; - thread_execute(ctrl_state->self, pcep_thread_send_report_callback, - &args, 0); + return send_to_thread(ctrl_state, pcc_id, EV_SEND_REPORT, is_stable, + path); } + /* ------------ Internal Functions Called from Main Thread ------------ */ int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res) @@ -333,6 +339,20 @@ int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res) return 0; } +int pcep_refine_path_event_cb(struct thread *thread) +{ + struct pcep_refine_path_event_data *data = THREAD_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + struct path *path = data->path; + assert(path != NULL); + int pcc_id = data->pcc_id; + + + path_pcep_refine_path(path); + return send_to_thread(ctrl_state, pcc_id, EV_PATH_REFINED, 0, data); +} + /* ------------ API Functions Called From Controller Thread ------------ */ @@ -442,6 +462,41 @@ int pcep_thread_pcc_count(struct ctrl_state *ctrl_state) return ctrl_state->pcc_count; } +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload) +{ + struct pcep_refine_path_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->path = path; + data->pcc_id = pcc_id; + data->continue_lsp_update_handler = cb; + data->payload = payload; + + thread_add_event(ctrl_state->main, pcep_refine_path_event_cb, + (void *)data, 0, NULL); + return 0; +} + +void pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data) +{ + assert(data != NULL); + int pcc_id = data->pcc_id; + pcep_refine_callback_t continue_lsp_update_handler = data->continue_lsp_update_handler; + assert(continue_lsp_update_handler != NULL); + struct path *path = data->path; + void *payload = data->payload; + struct pcc_state *pcc_state = NULL; + XFREE(MTYPE_PCEP, data); + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + continue_lsp_update_handler(ctrl_state, pcc_state, path, payload); +} + + /* ------------ Internal Functions Called From Controller Thread ------------ */ int pcep_thread_finish_event_handler(struct thread *thread) @@ -467,78 +522,6 @@ int pcep_thread_finish_event_handler(struct thread *thread) return 0; } -int pcep_thread_get_counters_callback(struct thread *t) -{ - struct get_counters_args *args = THREAD_ARG(t); - assert(args != NULL); - struct ctrl_state *ctrl_state = args->ctrl_state; - assert(ctrl_state != NULL); - struct pcc_state *pcc_state; - - pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); - if (pcc_state) { - args->counters = pcep_lib_copy_counters(pcc_state->sess); - } else { - args->counters = NULL; - } - - return 0; -} - -int pcep_thread_send_report_callback(struct thread *t) -{ - struct send_report_args *args = THREAD_ARG(t); - assert(args != NULL); - struct ctrl_state *ctrl_state = args->ctrl_state; - assert(ctrl_state != NULL); - struct pcc_state *pcc_state; - - if (args->pcc_id == 0) { - for (int i = 0; i < MAX_PCC; i++) { - if (ctrl_state->pcc[i]) { - pcep_pcc_send_report(ctrl_state, - ctrl_state->pcc[i], - args->path); - } - } - } else { - pcc_state = - pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); - pcep_pcc_send_report(ctrl_state, pcc_state, args->path); - } - - return 0; -} - -int pcep_thread_get_pcep_session_callback(struct thread *t) -{ - struct get_pcep_session_args *args = THREAD_ARG(t); - assert(args != NULL); - struct ctrl_state *ctrl_state = args->ctrl_state; - assert(ctrl_state != NULL); - struct pcc_state *pcc_state; - - pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, args->pcc_id); - if (pcc_state) { - args->pcep_session = - pcep_lib_copy_pcep_session(pcc_state->sess); - } - - return 0; -} - -int pcep_thread_get_pcc_info_callback(struct thread *t) -{ - struct pcep_pcc_info *args = THREAD_ARG(t); - assert(args != NULL); - struct ctrl_state *ctrl_state = args->ctrl_state; - assert(ctrl_state != NULL); - - pcep_pcc_copy_pcc_info(ctrl_state->pcc, args); - - return 0; -} - /* ------------ Controller Thread Timer Handler ------------ */ int schedule_thread_timer_with_cb(struct ctrl_state *ctrl_state, int pcc_id, @@ -752,11 +735,14 @@ int pcep_thread_event_handler(struct thread *thread) /* Possible sub-type values */ enum pcep_pathd_event_type path_event_type = PCEP_PATH_UNDEFINED; - /* Possible payload values */ + /* Possible payload values, maybe an union would be better... */ struct path *path = NULL; struct pcc_opts *pcc_opts = NULL; struct pce_opts *pce_opts = NULL; struct pcc_state *pcc_state = NULL; + struct pcep_refine_path_event_data *refine_data = NULL; + + struct path *path_copy = NULL; switch (type) { case EV_UPDATE_PCC_OPTS: @@ -808,6 +794,30 @@ int pcep_thread_event_handler(struct thread *thread) (const char *)payload); } break; + case EV_SEND_REPORT: + assert(payload != NULL); + path = (struct path *)payload; + if (pcc_id == 0) { + for (int i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + path_copy = pcep_copy_path(path); + pcep_pcc_send_report( + ctrl_state, ctrl_state->pcc[i], + path_copy, (bool)sub_type); + } + } + } else { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_send_report(ctrl_state, pcc_state, path, + (bool)sub_type); + } + break; + case EV_PATH_REFINED: + assert(payload != NULL); + refine_data = (struct pcep_refine_path_event_data *)payload; + pcep_thread_path_refined_event(ctrl_state, refine_data); + break; default: flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, "Unexpected event received in controller thread: %u", @@ -984,6 +994,7 @@ int pcep_main_event_handler(struct thread *thread) /* ------------ Helper functions ------------ */ + void set_ctrl_state(struct frr_pthread *fpt, struct ctrl_state *ctrl_state) { assert(fpt != NULL); diff --git a/pathd/path_pcep_controller.h b/pathd/path_pcep_controller.h index f6eaa0ca2a..1b7c3a4c72 100644 --- a/pathd/path_pcep_controller.h +++ b/pathd/path_pcep_controller.h @@ -21,16 +21,21 @@ #include "pathd/path_pcep.h" +struct ctrl_state; +struct pcc_state; enum pcep_main_event_type { PCEP_MAIN_EVENT_UNDEFINED = 0, PCEP_MAIN_EVENT_START_SYNC, PCEP_MAIN_EVENT_UPDATE_CANDIDATE, - PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP + PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP, }; typedef int (*pcep_main_event_handler_t)(enum pcep_main_event_type type, int pcc_id, void *payload); +typedef void (*pcep_refine_callback_t)(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct path *path, void *payload); enum pcep_pathd_event_type { PCEP_PATH_UNDEFINED = 0, @@ -124,10 +129,13 @@ pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id); struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, const char *pce_name); -/* Synchronously send a report, the caller is responsible to free the path, - * If `pcc_id` is `0` the report is sent by all PCCs */ -void pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, - struct path *path); +/* Asynchronously send a report. The caller is giving away the path structure, + * it shouldn't be allocated on the stack. If `pcc_id` is `0` the report is + * sent by all PCCs. The parameter is_stable is used to hint wether the status + * will soon change, this is used to ensure all report updates are sent even + * when missing status update events */ +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable); /* Functions called from the controller thread */ void pcep_thread_start_sync(struct ctrl_state *ctrl_state, int pcc_id); @@ -162,5 +170,9 @@ int pcep_thread_send_ctrl_event(void *fpt, void *payload, pcep_ctrl_thread_callback cb); int pcep_thread_pcep_event(struct thread *thread); int pcep_thread_pcc_count(struct ctrl_state *ctrl_state); +/* Called by the PCC to refine a path in the main thread */ +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload); #endif // _PATH_PCEP_CONTROLLER_H_ diff --git a/pathd/path_pcep_debug.c b/pathd/path_pcep_debug.c index bcaadfe4d8..2dfe2130bf 100644 --- a/pathd/path_pcep_debug.c +++ b/pathd/path_pcep_debug.c @@ -636,8 +636,8 @@ const char *pcep_message_type_name(enum pcep_message_types pcep_message_type) return "UPDATE"; case PCEP_TYPE_INITIATE: return "INITIATE"; - case PCEP_TYPE_UNKOWN_MSG: - return "UNKOWN_MSG"; + case PCEP_TYPE_START_TLS: + return "START_TLS"; default: return "UNKNOWN"; } diff --git a/pathd/path_pcep_debug.h b/pathd/path_pcep_debug.h index 68b29ab657..5a504e4e1a 100644 --- a/pathd/path_pcep_debug.h +++ b/pathd/path_pcep_debug.h @@ -20,8 +20,8 @@ #define _PATH_PCEP_DEBUG_H_ #include "pathd/path_debug.h" -#include -#include +#include "pceplib/pcep_pcc_api.h" +#include "pceplib/pcep_msg_objects.h" #include "pathd/path_pcep.h" #include "pathd/path_pcep_controller.h" #include "pathd/path_pcep_pcc.h" diff --git a/pathd/path_pcep_lib.c b/pathd/path_pcep_lib.c index bb6bfb1336..1d2f25889e 100644 --- a/pathd/path_pcep_lib.c +++ b/pathd/path_pcep_lib.c @@ -16,9 +16,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include + #include -#include -#include +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_timers.h" #include "pathd/path_errors.h" #include "pathd/path_memory.h" #include "pathd/path_pcep.h" @@ -176,11 +178,11 @@ pcep_lib_connect(struct ipaddr *src_addr, int src_port, struct ipaddr *dst_addr, /* TODO when available in the pceplib, set it here pcep_options->state_timeout_inteval_seconds;*/ - if (pcep_options->tcp_md5_auth != NULL - && pcep_options->tcp_md5_auth[0] != '\0') { + if (pcep_options->tcp_md5_auth[0] != '\0') { config->is_tcp_auth_md5 = true; - strncpy(config->tcp_authentication_str, - pcep_options->tcp_md5_auth, TCP_MD5SIG_MAXKEYLEN); + strlcpy(config->tcp_authentication_str, + pcep_options->tcp_md5_auth, + sizeof(config->tcp_authentication_str)); } else { config->is_tcp_auth_md5 = false; } diff --git a/pathd/path_pcep_lib.h b/pathd/path_pcep_lib.h index 3bea28432d..3f34edcb3f 100644 --- a/pathd/path_pcep_lib.h +++ b/pathd/path_pcep_lib.h @@ -20,7 +20,7 @@ #define _PATH_PCEP_LIB_H_ #include -#include +#include "pceplib/pcep_pcc_api.h" #include "frr_pthread.h" #include "pathd/path_pcep.h" diff --git a/pathd/path_pcep_pcc.c b/pathd/path_pcep_pcc.c index c1f60edd22..9de3ba7811 100644 --- a/pathd/path_pcep_pcc.c +++ b/pathd/path_pcep_pcc.c @@ -55,6 +55,7 @@ #define MAX_ERROR_MSG_SIZE 256 #define MAX_COMPREQ_TRIES 3 +pthread_mutex_t g_pcc_info_mtx = PTHREAD_MUTEX_INITIALIZER; /* PCEP Event Handler */ static void handle_pcep_open(struct ctrl_state *ctrl_state, @@ -63,12 +64,15 @@ static void handle_pcep_open(struct ctrl_state *ctrl_state, static void handle_pcep_message(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, struct pcep_message *msg); +static void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); static void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, struct pcep_message *msg); -static void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, +static void continue_pcep_lsp_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, - struct pcep_message *msg); + struct path *path, void *payload); static void handle_pcep_comp_reply(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, struct pcep_message *msg); @@ -543,23 +547,43 @@ void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, } void pcep_pcc_send_report(struct ctrl_state *ctrl_state, - struct pcc_state *pcc_state, struct path *path) + struct pcc_state *pcc_state, struct path *path, + bool is_stable) { - if (pcc_state->status != PCEP_PCC_OPERATING) + if ((pcc_state->status != PCEP_PCC_OPERATING) + || (!pcc_state->caps.is_stateful)) { + pcep_free_path(path); return; + } - if (pcc_state->caps.is_stateful) { - PCEP_DEBUG("%s Send report for candidate path %s", - pcc_state->tag, path->name); + PCEP_DEBUG("%s Send report for candidate path %s", pcc_state->tag, + path->name); + + /* ODL and Cisco requires the first reported + * LSP to have a DOWN status, the later status changes + * will be comunicated through hook calls. + */ + enum pcep_lsp_operational_status real_status = path->status; + path->status = PCEP_LSP_OPERATIONAL_DOWN; + send_report(pcc_state, path); + + /* If no update is expected and the real status wasn't down, we need to + * send a second report with the real status */ + if (is_stable && (real_status != PCEP_LSP_OPERATIONAL_DOWN)) { + path->srp_id = 0; + path->status = real_status; send_report(pcc_state, path); } + + pcep_free_path(path); } + /* ------------ Timeout handler ------------ */ void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, - enum pcep_ctrl_timer_type type, void *param) + enum pcep_ctrl_timeout_type type, void *param) { struct req_entry *req; @@ -926,6 +950,7 @@ int pcep_pcc_calculate_best_pce(struct pcc_state **pcc) // Changed of state so ... if (step_0_best != best_pce) { + pthread_mutex_lock(&g_pcc_info_mtx); // Calculate previous previous_best_pce = step_0_best; // Clean state @@ -970,6 +995,7 @@ int pcep_pcc_calculate_best_pce(struct pcc_state **pcc) } } } + pthread_mutex_unlock(&g_pcc_info_mtx); } return ((best_pce == -1) ? 0 : pcc[best_pce]->id); @@ -1094,18 +1120,24 @@ void pcep_pcc_copy_pcc_info(struct pcc_state **pcc, } pcc_info->ctrl_state = NULL; - pcc_info->msd = pcc_state->pcc_opts->msd; - pcc_info->pcc_port = pcc_state->pcc_opts->port; + if(pcc_state->pcc_opts){ + pcc_info->msd = pcc_state->pcc_opts->msd; + pcc_info->pcc_port = pcc_state->pcc_opts->port; + } pcc_info->next_plspid = pcc_state->next_plspid; pcc_info->next_reqid = pcc_state->next_reqid; pcc_info->status = pcc_state->status; pcc_info->pcc_id = pcc_state->id; + pthread_mutex_lock(&g_pcc_info_mtx); pcc_info->is_best_multi_pce = pcc_state->is_best; pcc_info->previous_best = pcc_state->previous_best; + pthread_mutex_unlock(&g_pcc_info_mtx); pcc_info->precedence = pcc_state->pce_opts ? pcc_state->pce_opts->precedence : 0; - memcpy(&pcc_info->pcc_addr, &pcc_state->pcc_addr_tr, - sizeof(struct ipaddr)); + if(pcc_state->pcc_addr_tr.ipa_type != IPADDR_NONE){ + memcpy(&pcc_info->pcc_addr, &pcc_state->pcc_addr_tr, + sizeof(struct ipaddr)); + } } @@ -1154,12 +1186,19 @@ void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, struct pcep_message *msg) { - char err[MAX_ERROR_MSG_SIZE] = ""; struct path *path; path = pcep_lib_parse_path(msg); lookup_nbkey(pcc_state, path); - /* TODO: Investigate if this is safe to do in the controller thread */ - path_pcep_config_lookup(path); + pcep_thread_refine_path(ctrl_state, pcc_state->id, + &continue_pcep_lsp_update, path, NULL); +} + +void continue_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + void *payload) +{ + char err[MAX_ERROR_MSG_SIZE] = {0}; + specialize_incoming_path(pcc_state, path); PCEP_DEBUG("%s Received LSP update", pcc_state->tag); PCEP_DEBUG_PATH("%s", format_path(path)); @@ -1309,13 +1348,13 @@ void select_transport_address(struct pcc_state *pcc_state) * address */ if (IS_IPADDR_V4(&pcc_state->pce_opts->addr)) { if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { - taddr->ipa_type = IPADDR_V4; taddr->ipaddr_v4 = pcc_state->pcc_addr_v4; + taddr->ipa_type = IPADDR_V4; } } else { if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { - taddr->ipa_type = IPADDR_V6; taddr->ipaddr_v6 = pcc_state->pcc_addr_v6; + taddr->ipa_type = IPADDR_V6; } } } diff --git a/pathd/path_pcep_pcc.h b/pathd/path_pcep_pcc.h index a466d92d50..c07a6ae541 100644 --- a/pathd/path_pcep_pcc.h +++ b/pathd/path_pcep_pcc.h @@ -113,13 +113,18 @@ void pcep_pcc_pathd_event_handler(struct ctrl_state *ctrl_state, struct path *path); void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, - enum pcep_ctrl_timer_type type, void *param); + enum pcep_ctrl_timeout_type type, void *param); void pcep_pcc_sync_path(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, struct path *path); void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state); +/* Send a report explicitly. When doing so the PCC may send multiple reports + * due to expectations from vendors for the first report to be with a DOWN + * status. The parameter is_stable is used for that purpose as a hint wheter + * to expect an update for the report */ void pcep_pcc_send_report(struct ctrl_state *ctrl_state, - struct pcc_state *pcc_state, struct path *path); + struct pcc_state *pcc_state, struct path *path, + bool is_stable); int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id, struct pcc_state **pcc_state_list); int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state, diff --git a/pathd/subdir.am b/pathd/subdir.am index 520a8c696a..452d824669 100644 --- a/pathd/subdir.am +++ b/pathd/subdir.am @@ -11,7 +11,7 @@ vtysh_daemons += pathd # TODO add man page #man8 += $(MANBUILD)/pathd.8 -if HAVE_PATHD_PCEP +if PATHD_PCEP vtysh_scan += $(top_srcdir)/pathd/path_pcep_cli.c module_LTLIBRARIES += pathd/pathd_pcep.la endif @@ -69,6 +69,15 @@ pathd_pathd_pcep_la_SOURCES = \ pathd/path_pcep_config.c \ pathd/path_pcep_pcc.c \ # end + +if PATHD_PCEP +pathd_pathd_pcep_la_CPPFLAGS = -I./pceplib $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = pceplib/libpcep_pcc.la +else +pathd_pathd_pcep_la_CPPFLAGS = $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = +endif + + pathd_pathd_pcep_la_CFLAGS = $(WERROR) pathd_pathd_pcep_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -pathd_pathd_pcep_la_LIBADD = @PATHD_PCEP_LIBS@ diff --git a/pceplib/.gitignore b/pceplib/.gitignore new file mode 100644 index 0000000000..5861f25a41 --- /dev/null +++ b/pceplib/.gitignore @@ -0,0 +1,14 @@ +pcep_pcc +test/pcep_msg_tests +test/pcep_pcc_api_tests +test/pcep_session_logic_tests +test/pcep_socket_comm_tests +test/pcep_timers_tests +test/pcep_utils_tests +test/valgrind.pcep_msg_tests.log +test/valgrind.pcep_pcc_api_tests.log +test/valgrind.pcep_session_logic_tests.log +test/valgrind.pcep_socket_comm_tests.log +test/valgrind.pcep_timers_tests.log +test/valgrind.pcep_utils_tests.log +../test-driver diff --git a/pceplib/pcep.h b/pceplib/pcep.h new file mode 100644 index 0000000000..278ab9d5dc --- /dev/null +++ b/pceplib/pcep.h @@ -0,0 +1,48 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + + +#ifndef PCEP_H_ +#define PCEP_H_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(linux) || defined(GNU_LINUX) +//#include +#define ipv6_u __in6_u +#else +// bsd family +#define TCP_MD5SIG_MAXKEYLEN 80 +//#include +#define ipv6_u __u6_addr +#ifdef __FreeBSD__ +#include +#else +#include +#endif /* __FreeBSD__ */ +#endif + +#include +#include +#include +#endif diff --git a/pceplib/pcep_msg_encoding.h b/pceplib/pcep_msg_encoding.h new file mode 100644 index 0000000000..d835b87f94 --- /dev/null +++ b/pceplib/pcep_msg_encoding.h @@ -0,0 +1,140 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Definitions for encoding and decoding PCEP messages, objects, and TLVs. + */ + +#ifndef PCEP_ENCODING_H +#define PCEP_ENCODING_H + +#include + +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct pcep_versioning { + bool draft_ietf_pce_segment_routing_07; /* If false, use draft16 */ + /* As more draft versions are incorporated, add appropriate attributes + */ +}; + +#define MESSAGE_HEADER_LENGTH 4 +#define PCEP_MESSAGE_LENGTH 65535 +#define OBJECT_HEADER_LENGTH 4 +#define OBJECT_RO_SUBOBJ_HEADER_LENGTH 2 +#define TLV_HEADER_LENGTH 4 +#define LENGTH_1WORD sizeof(uint32_t) +#define LENGTH_2WORDS sizeof(uint32_t) * 2 +#define LENGTH_3WORDS sizeof(uint32_t) * 3 +#define LENGTH_4WORDS sizeof(uint32_t) * 4 +#define LENGTH_5WORDS sizeof(uint32_t) * 5 +#define LENGTH_6WORDS sizeof(uint32_t) * 6 +#define LENGTH_7WORDS sizeof(uint32_t) * 7 +#define LENGTH_8WORDS sizeof(uint32_t) * 8 +#define LENGTH_9WORDS sizeof(uint32_t) * 9 +#define LENGTH_10WORDS sizeof(uint32_t) * 10 +#define LENGTH_11WORDS sizeof(uint32_t) * 11 +#define LENGTH_12WORDS sizeof(uint32_t) * 12 +#define LENGTH_13WORDS sizeof(uint32_t) * 13 + +/* When iterating sub-objects or TLVs, limit to 10 in case corrupt data is + * received */ +#define MAX_ITERATIONS 10 + +struct pcep_versioning *create_default_pcep_versioning(void); +void destroy_pcep_versioning(struct pcep_versioning *versioning); + +/* + * Message encoding / decoding functions + */ + +/* Called before sending messages to encode the message to a byte buffer in + * Network byte order. This function will also encode all the objects and their + * TLVs in the message. The result will be stored in the encoded_message field + * in the pcep_message. Implemented in pcep-messages-encoding.c */ +void pcep_encode_message(struct pcep_message *message, + struct pcep_versioning *versioning); + +/* Decode the message header and return the message length. + * Returns < 0 for invalid message headers. */ +int32_t pcep_decode_validate_msg_header(const uint8_t *msg_buf); + +/* Decode the entire message */ +struct pcep_message *pcep_decode_message(const uint8_t *message_buffer); + + +/* + * Object encoding / decoding functions + */ + +/* Implemented in pcep-objects-encoding.c + * Encode the object in struct pcep_object_header* into the uint8_t *buf, + * and return the encoded object_length. */ +uint16_t pcep_encode_object(struct pcep_object_header *object_hdr, + struct pcep_versioning *versioning, uint8_t *buf); + +/* Implemented in pcep-objects-encoding.c + * Decode the object, including the TLVs (if any) and return the object. + * Returns object on success, NULL otherwise. */ +struct pcep_object_header *pcep_decode_object(const uint8_t *msg_buf); + +/* Internal util functions implemented in pcep-objects-encoding.c */ +void encode_ipv6(struct in6_addr *src_ipv6, uint32_t *dst); +void decode_ipv6(const uint32_t *src, struct in6_addr *dst_ipv6); +uint16_t normalize_pcep_tlv_length(uint16_t length); +bool pcep_object_has_tlvs(struct pcep_object_header *object_hdr); +uint16_t pcep_object_get_length_by_hdr(struct pcep_object_header *object_hdr); +uint16_t pcep_object_get_length(enum pcep_object_classes object_class, + enum pcep_object_types object_type); + + +/* + * TLV encoding / decoding functions + */ + +/* Implemented in pcep-tlv-encoding.c + * Encode the tlv in struct pcep_tlv_header* into the uint8_t *buf, + * and return the encoded tlv_length. */ +uint16_t pcep_encode_tlv(struct pcep_object_tlv_header *tlv_hdr, + struct pcep_versioning *versioning, uint8_t *buf); + +/* Decode the TLV in tlv_buf and return a pointer to the object */ +struct pcep_object_tlv_header *pcep_decode_tlv(const uint8_t *tlv_buf); + + +/* + * utils mainly for testing purposes + */ +bool validate_message_objects(struct pcep_message *msg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_messages.c b/pceplib/pcep_msg_messages.c new file mode 100644 index 0000000000..ec2a237f30 --- /dev/null +++ b/pceplib/pcep_msg_messages.c @@ -0,0 +1,308 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message API. + */ + +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +static struct pcep_message * +pcep_msg_create_common_with_obj_list(enum pcep_message_types msg_type, + double_linked_list *obj_list) +{ + struct pcep_message *message = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + memset(message, 0, sizeof(struct pcep_message)); + message->msg_header = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_message_header)); + memset(message->msg_header, 0, sizeof(struct pcep_message_header)); + message->msg_header->type = msg_type; + message->msg_header->pcep_version = PCEP_MESSAGE_HEADER_VERSION; + message->obj_list = ((obj_list == NULL) ? dll_initialize() : obj_list); + + return message; +} + +static struct pcep_message * +pcep_msg_create_common(enum pcep_message_types msg_type) +{ + return pcep_msg_create_common_with_obj_list(msg_type, NULL); +} + +struct pcep_message *pcep_msg_create_open(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_OPEN); + dll_append(message->obj_list, + pcep_obj_create_open(keepalive, deadtimer, sid, NULL)); + + return message; +} + +struct pcep_message * +pcep_msg_create_open_with_tlvs(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid, double_linked_list *tlv_list) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_OPEN); + dll_append(message->obj_list, + pcep_obj_create_open(keepalive, deadtimer, sid, tlv_list)); + + return message; +} + + +struct pcep_message * +pcep_msg_create_request(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv4 *endpoints, + double_linked_list *object_list) +{ + if ((rp == NULL) || (endpoints == NULL)) { + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREQ, object_list); + dll_prepend(message->obj_list, endpoints); + dll_prepend(message->obj_list, rp); + + return message; +} + +struct pcep_message * +pcep_msg_create_request_ipv6(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv6 *endpoints, + double_linked_list *object_list) +{ + if ((rp == NULL) || (endpoints == NULL)) { + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREQ, object_list); + dll_prepend(message->obj_list, endpoints); + dll_prepend(message->obj_list, rp); + + return message; +} + +struct pcep_message *pcep_msg_create_reply(struct pcep_object_rp *rp, + double_linked_list *object_list) +{ + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREP, object_list); + + if (rp != NULL) { + dll_prepend(message->obj_list, rp); + } + + return message; +} + +struct pcep_message *pcep_msg_create_close(uint8_t reason) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_CLOSE); + dll_append(message->obj_list, pcep_obj_create_close(reason)); + + return message; +} + +struct pcep_message *pcep_msg_create_error(uint8_t error_type, + uint8_t error_value) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_ERROR); + dll_append(message->obj_list, + pcep_obj_create_error(error_type, error_value)); + + return message; +} + +struct pcep_message * +pcep_msg_create_error_with_objects(uint8_t error_type, uint8_t error_value, + double_linked_list *object_list) +{ + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_ERROR, object_list); + dll_prepend(message->obj_list, + pcep_obj_create_error(error_type, error_value)); + + return message; +} + +struct pcep_message *pcep_msg_create_keepalive() +{ + return (pcep_msg_create_common(PCEP_TYPE_KEEPALIVE)); +} + +struct pcep_message * +pcep_msg_create_report(double_linked_list *state_report_object_list) +{ + return (state_report_object_list == NULL + ? NULL + : pcep_msg_create_common_with_obj_list( + PCEP_TYPE_REPORT, state_report_object_list)); +} + +struct pcep_message * +pcep_msg_create_update(double_linked_list *update_request_object_list) +{ + if (update_request_object_list == NULL) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update NULL update_request_object_list", + __func__); + return NULL; + } + + /* There must be at least 3 objects: + * These 3 are mandatory: SRP, LSP, and ERO. The ERO may be empty */ + if (update_request_object_list->num_entries < 3) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update there must be at least 3 update objects", + __func__); + return NULL; + } + + double_linked_list_node *node = update_request_object_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + + /* Check for the mandatory first SRP object */ + if (obj_hdr->object_class != PCEP_OBJ_CLASS_SRP) { + /* If the SRP object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=10 (SRP object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory first SRP object", + __func__); + return NULL; + } + + /* Check for the mandatory 2nd LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_LSP) { + /* If the LSP object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=8 (LSP object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory second LSP object", + __func__); + return NULL; + } + + /* Check for the mandatory 3rd ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_ERO) { + /* If the ERO object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=9 (ERO object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory third ERO object", + __func__); + return NULL; + } + + return (pcep_msg_create_common_with_obj_list( + PCEP_TYPE_UPDATE, update_request_object_list)); +} + +struct pcep_message * +pcep_msg_create_initiate(double_linked_list *lsp_object_list) +{ + if (lsp_object_list == NULL) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate NULL update_request_object_list", + __func__); + return NULL; + } + + /* There must be at least 2 objects: SRP and LSP. */ + if (lsp_object_list->num_entries < 2) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate there must be at least 2 objects", + __func__); + return NULL; + } + + double_linked_list_node *node = lsp_object_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + + /* Check for the mandatory first SRP object */ + if (obj_hdr->object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate missing mandatory first SRP object", + __func__); + return NULL; + } + + /* Check for the mandatory 2nd LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate missing mandatory second LSP object", + __func__); + return NULL; + } + + return (pcep_msg_create_common_with_obj_list(PCEP_TYPE_INITIATE, + lsp_object_list)); +} + +struct pcep_message *pcep_msg_create_notify(struct pcep_object_notify *notify, + double_linked_list *object_list) +{ + if (notify == NULL) { + pcep_log(LOG_INFO, + "%s: pcep_msg_create_notify NULL notify object", + __func__); + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCNOTF, object_list); + dll_prepend(message->obj_list, notify); + + return message; +} diff --git a/pceplib/pcep_msg_messages.h b/pceplib/pcep_msg_messages.h new file mode 100644 index 0000000000..8542ea10e7 --- /dev/null +++ b/pceplib/pcep_msg_messages.h @@ -0,0 +1,132 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message API. + */ + +#ifndef PCEP_MESSAGES_H +#define PCEP_MESSAGES_H + +#include +#include /* struct in_addr */ + +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum pcep_message_types { + PCEP_TYPE_OPEN = 1, + PCEP_TYPE_KEEPALIVE = 2, + PCEP_TYPE_PCREQ = 3, + PCEP_TYPE_PCREP = 4, + PCEP_TYPE_PCNOTF = 5, + PCEP_TYPE_ERROR = 6, + PCEP_TYPE_CLOSE = 7, + PCEP_TYPE_REPORT = 10, + PCEP_TYPE_UPDATE = 11, + PCEP_TYPE_INITIATE = 12, + PCEP_TYPE_START_TLS = 13, + PCEP_TYPE_MAX, +}; + +#define PCEP_MESSAGE_HEADER_VERSION 1 + +struct pcep_message_header { + uint8_t pcep_version; /* Current version is 1. */ + enum pcep_message_types + type; /* Defines message type: + OPEN/KEEPALIVE/PCREQ/PCREP/PCNOTF/ERROR/CLOSE */ +}; + +/* The obj_list is a double_linked_list of struct pcep_object_header pointers. + */ +struct pcep_message { + struct pcep_message_header *msg_header; + double_linked_list *obj_list; + uint8_t *encoded_message; + uint16_t encoded_message_length; +}; + + +/* + * Regarding memory usage: + * When creating messages, any objects and tlvs passed into these APIs will be + * free'd when the pcep_message is free'd. That includes the + * double_linked_list's. So, just create the objects and TLVs, put them in their + * double_linked_list's, and everything will be managed internally. The message + * will be deleted by pcep_msg_free_message() or pcep_msg_free_message_list() + * which, in turn will call one of: pcep_obj_free_object() and + * pcep_obj_free_tlv(). For received messages, call pcep_msg_free_message() to + * free them. + */ + +struct pcep_message *pcep_msg_create_open(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid); +struct pcep_message * +pcep_msg_create_open_with_tlvs(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid, double_linked_list *tlv_list); +struct pcep_message * +pcep_msg_create_request(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv4 *endpoints, + double_linked_list *object_list); +struct pcep_message * +pcep_msg_create_request_ipv6(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv6 *endpoints, + double_linked_list *object_list); +struct pcep_message *pcep_msg_create_reply(struct pcep_object_rp *rp, + double_linked_list *object_list); +struct pcep_message *pcep_msg_create_close(uint8_t reason); +struct pcep_message *pcep_msg_create_error(uint8_t error_type, + uint8_t error_value); +struct pcep_message *pcep_msg_create_error_with_objects( + uint8_t error_type, uint8_t error_value, + double_linked_list *object_list); /* include the offending objects */ +struct pcep_message *pcep_msg_create_keepalive(void); +struct pcep_message *pcep_msg_create_notify(struct pcep_object_notify *notify, + double_linked_list *object_list); + +/* Message defined in RFC 8231 section 6.1. Expecting double_linked_list of + * struct pcep_object_header* objects of type SRP, LSP, or path (ERO, Bandwidth, + * metrics, and RRO objects). */ +struct pcep_message * +pcep_msg_create_report(double_linked_list *state_report_object_list); +/* Message defined in RFC 8231. Expecting double_linked_list of at least 3 + * struct pcep_object_header* objects of type SRP, LSP, and path (ERO and + * intended-attribute-list). The ERO must be present, but may be empty if + * the PCE cannot find a valid path for a delegated LSP. */ +struct pcep_message * +pcep_msg_create_update(double_linked_list *update_request_object_list); +/* Message defined in RFC 8281. Expecting double_linked_list of at least 2 + * struct pcep_object_header* objects of type SRP and LSP for LSP deletion, and + * may also contain Endpoints, ERO and an attribute list for LSP creation. */ +struct pcep_message * +pcep_msg_create_initiate(double_linked_list *lsp_object_list); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_messages_encoding.c b/pceplib/pcep_msg_messages_encoding.c new file mode 100644 index 0000000000..23ccef480c --- /dev/null +++ b/pceplib/pcep_msg_messages_encoding.c @@ -0,0 +1,351 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP messages. + */ + +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +#define ANY_OBJECT 0 +#define NO_OBJECT -1 +#define NUM_CHECKED_OBJECTS 4 +/* It wont compile with this definition: + static const int + MANDATORY_MESSAGE_OBJECT_CLASSES[PCEP_TYPE_INITIATE+1][NUM_CHECKED_OBJECTS] + */ +static const enum pcep_object_classes MANDATORY_MESSAGE_OBJECT_CLASSES[13][4] = + { + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 0 */ + {PCEP_OBJ_CLASS_OPEN, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_OPEN = 1 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_KEEPALIVE = 2 */ + {PCEP_OBJ_CLASS_RP, PCEP_OBJ_CLASS_ENDPOINTS, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCREQ = 3 */ + {PCEP_OBJ_CLASS_RP, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCREP = 4 */ + {PCEP_OBJ_CLASS_NOTF, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCNOTF = 5 */ + {PCEP_OBJ_CLASS_ERROR, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_ERROR = 6 */ + {PCEP_OBJ_CLASS_CLOSE, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_CLOSE = 7 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 8 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 9 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_REPORT = 10 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_UPDATE = 11 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_INITIATE = 12 */ +}; + +/* PCEP Message Common Header, According to RFC 5440 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Ver | Flags | Message-Type | Message-Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Ver (Version - 3 bits): PCEP version number. Current version is version 1. + * + * Flags (5 bits): No flags are currently defined. Unassigned bits are + * considered as reserved. They MUST be set to zero on transmission + * and MUST be ignored on receipt. + */ +void pcep_encode_message(struct pcep_message *message, + struct pcep_versioning *versioning) +{ + if (message == NULL) { + return; + } + + if (message->msg_header == NULL) { + return; + } + + /* Internal buffer used for the entire message. Later, once the entire + * length is known, memory will be allocated and this buffer will be + * copied. */ + uint8_t message_buffer[PCEP_MESSAGE_LENGTH] = {0}; + + /* Write the message header. The message header length will be + * written when the entire length is known. */ + uint32_t message_length = MESSAGE_HEADER_LENGTH; + uint16_t net_order_length = 0; + message_buffer[0] = (message->msg_header->pcep_version << 5) & 0xf0; + message_buffer[1] = message->msg_header->type; + + if (message->obj_list == NULL) { + net_order_length = htons(message_length); + memcpy(message_buffer + 2, &net_order_length, + sizeof(net_order_length)); + message->encoded_message = + pceplib_malloc(PCEPLIB_MESSAGES, message_length); + memcpy(message->encoded_message, message_buffer, + message_length); + message->encoded_message_length = message_length; + + return; + } + + /* Encode each of the objects */ + double_linked_list_node *node = message->obj_list->head; + for (; node != NULL; node = node->next_node) { + message_length += + pcep_encode_object(node->data, versioning, + message_buffer + message_length); + if (message_length > PCEP_MESSAGE_LENGTH) { + message->encoded_message = NULL; + message->encoded_message_length = 0; + return; + } + } + + net_order_length = htons(message_length); + memcpy(message_buffer + 2, &net_order_length, sizeof(net_order_length)); + message->encoded_message = + pceplib_malloc(PCEPLIB_MESSAGES, message_length); + memcpy(message->encoded_message, message_buffer, message_length); + message->encoded_message_length = message_length; +} + +/* + * Decoding functions + */ + +/* Expecting Host byte ordered header */ +static bool validate_msg_header(uint8_t msg_version, uint8_t msg_flags, + uint8_t msg_type, uint16_t msg_length) +{ + /* Invalid message if the length is less than the header + * size or if its not a multiple of 4 */ + if (msg_length < MESSAGE_HEADER_LENGTH || (msg_length % 4) != 0) { + pcep_log(LOG_INFO, + "%s: Invalid PCEP message header length [%d]", + __func__, msg_length); + return false; + } + + if (msg_version != PCEP_MESSAGE_HEADER_VERSION) { + pcep_log( + LOG_INFO, + "%s: Invalid PCEP message header version [0x%x] expected version [0x%x]", + __func__, msg_version, PCEP_MESSAGE_HEADER_VERSION); + return false; + } + + if (msg_flags != 0) { + pcep_log(LOG_INFO, + "%s: Invalid PCEP message header flags [0x%x]", + __func__, msg_flags); + return false; + } + + switch (msg_type) { + /* Supported message types */ + case PCEP_TYPE_OPEN: + case PCEP_TYPE_KEEPALIVE: + case PCEP_TYPE_PCREQ: + case PCEP_TYPE_PCREP: + case PCEP_TYPE_PCNOTF: + case PCEP_TYPE_ERROR: + case PCEP_TYPE_CLOSE: + case PCEP_TYPE_REPORT: + case PCEP_TYPE_UPDATE: + case PCEP_TYPE_INITIATE: + break; + default: + pcep_log(LOG_INFO, "%s: Invalid PCEP message header type [%d]", + __func__, msg_type); + return false; + break; + } + + return true; +} + +/* Internal util function */ +static uint16_t pcep_decode_msg_header(const uint8_t *msg_buf, + uint8_t *msg_version, uint8_t *msg_flags, + uint8_t *msg_type) +{ + // Check RFC 5440 for version and flags position. + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + //| Ver | Flags | Message-Type | Message-Length | + //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + *msg_version = (msg_buf[0] >> 5) & 0x07; + *msg_flags = (msg_buf[0] & 0x1f); + *msg_type = msg_buf[1]; + uint16_t host_order_length; + memcpy(&host_order_length, msg_buf + 2, sizeof(host_order_length)); + return ntohs(host_order_length); +} + +/* Decode the message header and return the message length */ +int32_t pcep_decode_validate_msg_header(const uint8_t *msg_buf) +{ + uint8_t msg_version; + uint8_t msg_flags; + uint8_t msg_type; + uint32_t msg_length; + + msg_length = pcep_decode_msg_header(msg_buf, &msg_version, &msg_flags, + &msg_type); + + return ((validate_msg_header(msg_version, msg_flags, msg_type, + msg_length) + == false) + ? -1 + : (int32_t)msg_length); +} + +bool validate_message_objects(struct pcep_message *msg) +{ + if (msg->msg_header->type >= PCEP_TYPE_START_TLS) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unknown message type [%d]", + __func__, msg->msg_header->type); + return false; + } + + const enum pcep_object_classes *object_classes = + MANDATORY_MESSAGE_OBJECT_CLASSES[msg->msg_header->type]; + double_linked_list_node *node; + int index; + for (node = (msg->obj_list == NULL ? NULL : msg->obj_list->head), + index = 0; + index < NUM_CHECKED_OBJECTS; + index++, (node = (node == NULL ? NULL : node->next_node))) { + struct pcep_object_header *obj = + ((node == NULL) + ? NULL + : (struct pcep_object_header *)node->data); + + if ((int)object_classes[index] == NO_OBJECT) { + if (node != NULL) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unexpected object [%d] present", + __func__, obj->object_class); + return false; + } + } else if (object_classes[index] != ANY_OBJECT) { + if (node == NULL) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Expecting object in position [%d], but none received", + __func__, index); + return false; + } else if (object_classes[index] != obj->object_class) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unexpected Object Class received [%d]", + __func__, object_classes[index]); + return false; + } + } + } + + return true; +} + +struct pcep_message *pcep_decode_message(const uint8_t *msg_buf) +{ + uint8_t msg_version; + uint8_t msg_flags; + uint8_t msg_type; + uint16_t msg_length; + + msg_length = pcep_decode_msg_header(msg_buf, &msg_version, &msg_flags, + &msg_type); + + struct pcep_message *msg = + pceplib_calloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + + msg->msg_header = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_message_header)); + msg->msg_header->pcep_version = msg_version; + msg->msg_header->type = msg_type; + + msg->obj_list = dll_initialize(); + msg->encoded_message = pceplib_malloc(PCEPLIB_MESSAGES, msg_length); + memcpy(msg->encoded_message, msg_buf, msg_length); + msg->encoded_message_length = msg_length; + + uint16_t bytes_read = MESSAGE_HEADER_LENGTH; + while ((msg_length - bytes_read) >= OBJECT_HEADER_LENGTH) { + struct pcep_object_header *obj_hdr = + pcep_decode_object(msg_buf + bytes_read); + + if (obj_hdr == NULL) { + pcep_log(LOG_INFO, "%s: Discarding invalid message", + __func__); + pcep_msg_free_message(msg); + + return NULL; + } + + dll_append(msg->obj_list, obj_hdr); + bytes_read += obj_hdr->encoded_object_length; + } + + if (validate_message_objects(msg) == false) { + pcep_log(LOG_INFO, "%s: Discarding invalid message", __func__); + pcep_msg_free_message(msg); + + return NULL; + } + + return msg; +} + +struct pcep_versioning *create_default_pcep_versioning() +{ + struct pcep_versioning *versioning = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memset(versioning, 0, sizeof(struct pcep_versioning)); + + return versioning; +} + +void destroy_pcep_versioning(struct pcep_versioning *versioning) +{ + pceplib_free(PCEPLIB_INFRA, versioning); +} diff --git a/pceplib/pcep_msg_object_error_types.c b/pceplib/pcep_msg_object_error_types.c new file mode 100644 index 0000000000..a4fd8151cd --- /dev/null +++ b/pceplib/pcep_msg_object_error_types.c @@ -0,0 +1,389 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + +#include + +#include "pcep_msg_object_error_types.h" +#include "pcep_utils_logging.h" + +/* All of these values were copied from: + * https://www.iana.org/assignments/pcep/pcep.xhtml#pcep-error-object + * Which was last updated 2020-06-02 */ + +static const char *error_type_strings[] = { + "Reserved", + "PCEP session establishment failure", + "Capability not supported", + "Unknown Object", + "Not supported object", + "Policy violation", + "Mandatory Object missing", + "Synchronized path computation request missing", + "Unknown request reference", + "Attempt to establish a second PCEP session", + + "Reception of an invalid object", /* 10 */ + "Unrecognized EXRS subobject", + "Diffserv-aware TE error", + "BRPC procedure completion failure", + "Unassigned 14", + "Global Concurrent Optimization Error", + "P2MP Capability Error", + "P2MP END-POINTS Error", + "P2MP Fragmentation Error", + "Invalid Operation", + + "LSP State Synchronization Error", /* 20 */ + "Invalid traffic engineering path setup type", + "Unassigned 22", + "Bad parameter value", + "LSP instantiation error", + "PCEP StartTLS failure", + "Association Error", + "WSON RWA Error", + "H-PCE Error", + "Path computation failure", + "Unassigned 30"}; + +static const char *error_value_strings[MAX_ERROR_TYPE][MAX_ERROR_VALUE] = { + + /* 0 Reserved */ + {"Unassigned"}, + + /* 1 PCEP session establishment failure */ + { + "Unassigned", + "reception of an invalid Open message or a non Open message.", + "no Open message received before the expiration of the OpenWait timer", + "unacceptable and non negotiable session characteristics", + "unacceptable but negotiable session characteristics", + "reception of a second Open message with still unacceptable session characteristics", + "reception of a PCErr message proposing unacceptable session characteristics", + "No Keepalive or PCErr message received before the expiration of the KeepWait timer", + "PCEP version not supported", + }, + + /* 2 Capability not supported */ + {"Unassigned"}, + + /* 3 Unknown Object */ + { + "Unassigned", + "Unrecognized object class", + "Unrecognized object Type", + }, + + /* 4 Not supported object */ + { + "Unassigned", + "Not supported object class", + "Not supported object Type", + "Unassigned", + "Unsupported parameter", + "Unsupported network performance constraint", + "Bandwidth Object type 3 or 4 not supported", + "Unsupported endpoint type in END-POINTS Generalized Endpoint object type", + "Unsupported TLV present in END-POINTS Generalized Endpoint object type", + "Unsupported granularity in the RP object flags", + }, + + /* 5 Policy violation */ + { + "Unassigned", + "C bit of the METRIC object set (request rejected)", + "O bit of the RP object cleared (request rejected)", + "objective function not allowed (request rejected)", + "OF bit of the RP object set (request rejected)", + "Global concurrent optimization not allowed", + "Monitoring message supported but rejected due to policy violation", + "P2MP Path computation is not allowed", + "Not allowed network performance constraint", + }, + + /* 6 Mandatory Object missing */ + { + "Unassigned", + "RP object missing", + "RRO missing for a reoptimization request (R bit of the RP object set)", + "END-POINTS object missing", + "MONITORING object missing", + "Unassigned", + "Unassigned", + "Unassigned", + "LSP object missing", + "ERO object missing", + "SRP object missing", + "LSP-IDENTIFIERS TLV missing", + "LSP-DB-VERSION TLV missing", + "S2LS object missing", + "P2MP-LSP-IDENTIFIERS TLV missing", + "DISJOINTNESS-CONFIGURATION TLV missing", + }, + + /* 7 Synchronized path computation request missing */ + {"Unassigned"}, + + /* 8 Unknown request reference */ + {"Unassigned"}, + + /* 9 Attempt to establish a second PCEP session */ + {"Unassigned"}, + + /* 10 Reception of an invalid object */ + { + "Unassigned", + "reception of an object with P flag not set although the P-flag must be set according to this specification.", + "Bad label value", + "Unsupported number of SR-ERO subobjects", + "Bad label format", + "ERO mixes SR-ERO subobjects with other subobject types", + "Both SID and NAI are absent in the SR-ERO subobject", + "Both SID and NAI are absent in the SR-RRO subobject", + "SYMBOLIC-PATH-NAME TLV missing", + "MSD exceeds the default for the PCEP session", + "RRO mixes SR-RRO subobjects with other subobject types", + "Malformed object", + "Missing PCE-SR-CAPABILITY sub-TLV", + "Unsupported NAI Type in the SR-ERO/SR-RRO subobject", + "Unknown SID", + "NAI cannot be resolved to a SID", + "Could not find SRGB", + "SID index exceeds SRGB size", + "Could not find SRLB", + "SID index exceeds SRLB size", + "Inconsistent SIDs in SR-ERO / SR-RRO subobjects", + "MSD must be nonzero", + "Mismatch of O field in S2LS and LSP object", + "Incompatible OF codes in H-PCE", + "Bad Bandwidth Object type 3 (Generalized bandwidth) or 4 (Generalized bandwidth of existing TE-LSP for which a reoptimization is requested)", + "Unsupported LSP Protection Flags in PROTECTION-ATTRIBUTE TLV", + "Unsupported Secondary LSP Protection Flags in PROTECTION-ATTRIBUTE TLV", + "Unsupported Link Protection Type in PROTECTION-ATTRIBUTE TLV", + "LABEL-SET TLV present with 0 bit set but without R bit set in RP", + "Wrong LABEL-SET TLV present with 0 and L bit set", + "Wrong LABEL-SET with O bit set and wrong format", + "Missing GMPLS-CAPABILITY TLV", + "Incompatible OF code", + }, + + /* 11 Unrecognized EXRS subobject */ + {"Unassigned"}, + + /* 12 Diffserv-aware TE error */ + { + "Unassigned", + "Unsupported class-type", + "Invalid class-type", + "Class-Type and setup priority do not form a configured TE-class", + }, + + /* 13 BRPC procedure completion failure */ + { + "Unassigned", + "BRPC procedure not supported by one or more PCEs along the domain path", + }, + + /* 14 Unassigned */ + {"Unassigned"}, + + /* 15 Global Concurrent Optimization Error */ + { + "Unassigned", + "Insufficient memory", + "Global concurrent optimization not supported", + }, + + /* 16 P2MP Capability Error */ + { + "Unassigned", + "The PCE cannot satisfy the request due to insufficient memory", + "The PCE is not capable of P2MP computation", + }, + + /* 17 P2MP END-POINTS Error */ + { + "Unassigned", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 2", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 3", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 4", + "The PCE cannot satisfy the request due to inconsistent END-POINTS", + }, + + /* 18 P2MP Fragmentation Error */ + { + "Unassigned", + "Fragmented request failure", + "Fragmented Report failure", + "Fragmented Update failure", + "Fragmented Instantiation failure", + }, + + /* 19 Invalid Operation */ + { + "Unassigned", + "Attempted LSP Update Request for a non-delegated LSP. The PCEP-ERROR object is followed by the LSP object that identifies the LSP.", + "Attempted LSP Update Request if the stateful PCE capability was not advertised.", + "Attempted LSP Update Request for an LSP identified by an unknown PLSP-ID.", + "Unassigned", + "Attempted LSP State Report if active stateful PCE capability was not advertised.", + "PCE-initiated LSP limit reached", + "Delegation for PCE-initiated LSP cannot be revoked", + "Non-zero PLSP-ID in LSP Initiate Request", + "LSP is not PCE initiated", + "PCE-initiated operation-frequency limit reached", + "Attempted LSP State Report for P2MP if stateful PCE capability for P2MP was not advertised", + "Attempted LSP Update Request for P2MP if active stateful PCE capability for P2MP was not advertised", + "Attempted LSP Instantiation Request for P2MP if stateful PCE instantiation capability for P2MP was not advertised", + "Auto-Bandwidth capability was not advertised", + }, + + /* 20 LSP State Synchronization Error */ + { + "Unassigned", + "A PCE indicates to a PCC that it cannot process (an otherwise valid) LSP State Report. The PCEP- ERROR object is followed by the LSP object that identifies the LSP.", + "LSP-DB version mismatch.", + "Attempt to trigger synchronization before PCE trigger.", + "Attempt to trigger a synchronization when the PCE triggered synchronization capability has not been advertised.", + "A PCC indicates to a PCE that it cannot complete the State Synchronization.", + "Received an invalid LSP-DB Version Number.", + "Received an invalid Speaker Entity Identifier.", + }, + + /* 21 Invalid traffic engineering path setup type */ + { + "Unassigned", + "Unsupported path setup type", + "Mismatched path setup type", + }, + + /* 22 Unassigned */ + {"Unassigned"}, + + /* 23 Bad parameter value */ + { + "Unassigned", + "SYMBOLIC-PATH-NAME in use", + "Speaker identity included for an LSP that is not PCE initiated", + }, + + /* 24 LSP instantiation error */ + { + "Unassigned", + "Unacceptable instantiation parameters", + "Internal error", + "Signaling error", + }, + + /* 25 PCEP StartTLS failure */ + { + "Unassigned", + "Reception of StartTLS after any PCEP exchange", + "Reception of any other message apart from StartTLS, Open, or PCErr", + "Failure, connection without TLS is not possible", + "Failure, connection without TLS is possible", + "No StartTLS message (nor PCErr/Open) before StartTLSWait timer expiry", + }, + + /* 26 Association Error */ + { + "Unassigned", + "Association Type is not supported", + "Too many LSPs in the association group", + "Too many association groups", + "Association unknown", + "Operator-configured association information mismatch", + "Association information mismatch", + "Cannot join the association group", + "Association ID not in range", + "Tunnel ID or End points mismatch for Path Protection Association", + "Attempt to add another working/protection LSP for Path Protection Association", + "Protection type is not supported", + }, + + /* 27 WSON RWA Error */ + { + "Unassigned", + "Insufficient Memory", + "RWA computation Not supported", + "Syntactical Encoding error", + }, + + /* 28 H-PCE Error */ + { + "Unassigned", + "H-PCE Capability not advertised", + "Parent PCE Capability cannot be provided", + }, + + /* 29 Path computation failure */ + { + "Unassigned", + "Unacceptable request message", + "Generalized bandwidth value not supported", + "Label Set constraint could not be met", + "Label constraint could not be met", + } + + /* 30-255 Unassigned */ +}; + + +const char *get_error_type_str(enum pcep_error_type error_type) +{ + if (error_type < 0 || error_type >= MAX_ERROR_TYPE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_type_str: error_type [%d] out of range [0..%d]", + __func__, error_type, MAX_ERROR_TYPE); + + return NULL; + } + + return error_type_strings[error_type]; +} + +const char *get_error_value_str(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + if (error_type < 0 || error_type >= MAX_ERROR_TYPE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_value_str: error_type [%d] out of range [0..%d]", + __func__, error_type, MAX_ERROR_TYPE); + + return NULL; + } + + if (error_value < 0 || error_value >= MAX_ERROR_VALUE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_value_str: error_value [%d] out of range [0..%d]", + __func__, error_value, MAX_ERROR_VALUE); + + return NULL; + } + + if (error_value_strings[error_type][error_value] == NULL) { + return "Unassigned"; + } + + return error_value_strings[error_type][error_value]; +} diff --git a/pceplib/pcep_msg_object_error_types.h b/pceplib/pcep_msg_object_error_types.h new file mode 100644 index 0000000000..d62cc7e277 --- /dev/null +++ b/pceplib/pcep_msg_object_error_types.h @@ -0,0 +1,284 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + + +/* + * Error Object Type and Value definitions + */ + +#ifndef PCEP_OBJECT_ERROR_TYPES_H +#define PCEP_OBJECT_ERROR_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_ERROR_TYPE 30 +#define MAX_ERROR_VALUE 255 + +enum pcep_error_type { + PCEP_ERRT_SESSION_FAILURE = 1, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED = 2, + PCEP_ERRT_UNKNOW_OBJECT = 3, + PCEP_ERRT_NOT_SUPPORTED_OBJECT = 4, + PCEP_ERRT_POLICY_VIOLATION = 5, + PCEP_ERRT_MANDATORY_OBJECT_MISSING = 6, + PCEP_ERRT_SYNC_PC_REQ_MISSING = 7, + PCEP_ERRT_UNKNOWN_REQ_REF = 8, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION = 9, + PCEP_ERRT_RECEPTION_OF_INV_OBJECT = 10, + + PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ = 11, + PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR = 12, + PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR = 13, + PCEP_ERRT_UNASSIGNED14 = 14, + PCEP_ERRT_GLOBAL_CONCURRENT_ERROR = 15, + PCEP_ERRT_P2PMP_CAP_ERROR = 16, + PCEP_ERRT_P2P_ENDPOINTS_ERROR = 17, + PCEP_ERRT_P2P_FRAGMENTATION_ERROR = 18, + PCEP_ERRT_INVALID_OPERATION = 19, + PCEP_ERRT_LSP_STATE_SYNC_ERROR = 20, + + PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE = 21, + PCEP_ERRT_UNASSIGNED22 = 22, + PCEP_ERRT_BAD_PARAMETER_VALUE = 23, + PCEP_ERRT_LSP_INSTANTIATE_ERROR = 24, + PCEP_ERRT_START_TLS_FAILURE = 25, + PCEP_ERRT_ASSOCIATION_ERROR = 26, + PCEP_ERRT_WSON_RWA_ERROR = 27, + PCEP_ERRT_H_PCE_ERROR = 28, + PCEP_ERRT_PATH_COMP_FAILURE = 29, + PCEP_ERRT_UNASSIGNED30 = 30 /* 30 - 255 Unassigned */ +}; + +enum pcep_error_value { + /* Error Value for Error Types that do not use an Error Value: + * PCEP_ERRT_CAPABILITY_NOT_SUPPORTED=2 + * PCEP_ERRT_SYNC_PC_REQ_MISSING=7 + * PCEP_ERRT_UNKNOWN_REQ_REF=8 + * PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION=9 + * PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ=11 */ + PCEP_ERRV_UNASSIGNED = 0, + + /* Error Values for PCEP_ERRT_SESSION_FAILURE=1 */ + PCEP_ERRV_RECVD_INVALID_OPEN_MSG = 1, + PCEP_ERRV_OPENWAIT_TIMED_OUT = 2, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NO_NEG = 3, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG = 4, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE = 5, + PCEP_ERRV_RECVD_PCERR = 6, + PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT = 7, + PCEP_ERRV_PCEP_VERSION_NOT_SUPPORTED = 8, + + /* Error Values for PCEP_ERRT_UNKNOW_OBJECT=3 */ + PCEP_ERRV_UNREC_OBJECT_CLASS = 1, + PCEP_ERRV_UNREC_OBJECT_TYPE = 2, + + /* Error Values for PCEP_ERRT_NOT_SUPPORTED_OBJECT=4 */ + PCEP_ERRV_NOT_SUPPORTED_OBJECT_CLASS = 1, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_TYPE = 2, + /* 3: Unassigned */ + PCEP_ERRV_UNSUPPORTED_PARAM = 4, + PCEP_ERRV_UNSUPPORTED_NW_PERF_CONSTRAINT = 5, + PCEP_ERRV_NOT_SUPPORTED_BW_OBJECT_3_4 = 6, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TYPE = 7, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TLV = 8, + PCEP_ERRV_UNSUPPORTED_RP_FLAG_GRANULARITY = 9, + + /* Error Values for PCEP_ERRT_POLICY_VIOLATION=5 */ + PCEP_ERRV_C_BIT_SET_IN_METRIC_OBJECT = 1, + PCEP_ERRV_O_BIT_CLEARD_IN_RP_OBJECT = 2, + PCEP_ERRV_OBJECTIVE_FUNC_NOT_ALLOWED = 3, + PCEP_ERRV_RP_OF_BIT_SET = 4, + PCEP_ERRV_GLOBAL_CONCURRENCY_NOT_ALLOWED = 5, + PCEP_ERRV_MONITORING_MSG_REJECTED = 6, + PCEP_ERRV_P2MP_PATH_COMP_NOT_ALLOWED = 7, + PCEP_ERRV_UNALLOWED_NW_PERF_CONSTRAINT = 8, + + /* Error Values for PCEP_ERRT_MANDATORY_OBJECT_MISSING=6 */ + PCEP_ERRV_RP_OBJECT_MISSING = 1, + PCEP_ERRV_RRO_OBJECT_MISSING_FOR_REOP = 2, + PCEP_ERRV_EP_OBJECT_MISSING = 3, + PCEP_ERRV_MONITOR_OBJECT_MISSING = 4, + /* 5 - 7 Unassigned */ + PCEP_ERRV_LSP_OBJECT_MISSING = 8, + PCEP_ERRV_ERO_OBJECT_MISSING = 9, + PCEP_ERRV_SRP_OBJECT_MISSING = 10, + PCEP_ERRV_LSP_ID_TLV_MISSING = 11, + PCEP_ERRV_LSP_DB_TLV_MISSING = 12, + PCEP_ERRV_S2LS_OBJECT_MISSING = 13, + PCEP_ERRV_P2MP_LSP_ID_TLV_MISSING = 14, + PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING = 15, + + /* Error Values for PCEP_ERRT_RECEPTION_OF_INV_OBJECT=10 */ + PCEP_ERRV_P_FLAG_NOT_CORRECT_IN_OBJECT = 1, + PCEP_ERRV_BAD_LABEL_VALUE = 2, + PCEP_ERRV_UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS = 3, + PCEP_ERRV_BAD_LABEL_FORMAT = 4, + PCEP_ERRV_ERO_SR_ERO_MIX = 5, + PCEP_ERRV_SR_ERO_SID_NAI_ABSENT = 6, + PCEP_ERRV_SR_RRO_SID_NAI_ABSENT = 7, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING = 8, + PCEP_ERRV_MSD_EXCEEDS_PCEP_SESSION_MAX = 9, + + PCEP_ERRV_RRO_SR_RRO_MIX = 10, + PCEP_ERRV_MALFORMED_OBJECT = 11, + PCEP_ERRV_MISSING_PCE_SR_CAP_TLV = 12, + PCEP_ERRV_UNSUPPORTED_NAI = 13, + PCEP_ERRV_UNKNOWN_SID = 14, + PCEP_ERRV_CANNOT_RESOLVE_NAI_TO_SID = 15, + PCEP_ERRV_COULD_NOT_FIND_SRGB = 16, + PCEP_ERRV_SID_EXCEEDS_SRGB = 17, + PCEP_ERRV_COULD_NOT_FIND_SRLB = 18, + PCEP_ERRV_SID_EXCEEDS_SRLB = 19, + + PCEP_ERRV_INCONSISTENT_SID = 20, + PCEP_ERRV_MSD_MUST_BE_NONZERO = 21, + PCEP_ERRV_MISMATCH_O_S2LS_LSP = 22, + PCEP_ERRV_INCOMPATIBLE_H_PCE_OF = 23, + PCEP_ERRV_BAD_BANDWIDTH_TYPE_3_4 = 24, + PCEP_ERRV_UNSUPPORTED_LSP_PROT_FLAGS = 25, + PCEP_ERRV_UNSUPPORTED_2ND_LSP_PROT_FLAGS = 26, + PCEP_ERRV_UNSUPPORTED_LINK_PROT_TYPE = 27, + PCEP_ERRV_LABEL_SET_TLV_NO_RP_R = 28, + PCEP_ERRV_WRONG_LABEL_SET_TLV_O_L_SET = 29, + + PCEP_ERRV_WRONG_LABEL_SET_O_SET = 30, + PCEP_ERRV_MISSING_GMPLS_CAP_TLV = 31, + PCEP_ERRV_INCOMPATIBLE_OF_CODE = 32, + + /* PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR = 12 */ + PCEP_ERRV_UNSUPPORTED_CLASS_TYPE = 1, + PCEP_ERRV_INVALID_CLASS_TYPE = 2, + PCEP_ERRV_CLASS_SETUP_TYPE_NOT_TE_CLASS = 3, + + /* PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR = 13 */ + PCEP_ERRV_BRPC_PROC_NOT_SUPPORTED = 1, + + /* PCEP_ERRT_UNASSIGNED14 = 14 */ + + /* PCEP_ERRT_GLOBAL_CONCURRENT_ERROR = 15 */ + PCEP_ERRV_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED = 2, + + /* PCEP_ERRT_P2PMP_CAP_ERROR = 16 */ + PCEP_ERRV_PCE_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_PCE_NOT_CAPABLE_P2MP_COMP = 2, + + /* PCEP_ERRT_P2P_ENDPOINTS_ERROR = 17 */ + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE2 = 1, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE3 = 2, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE4 = 3, + PCEP_ERRV_INCONSITENT_EP = 4, + + /* PCEP_ERRT_P2P_FRAGMENTATION_ERROR = 18 */ + PCEP_ERRV_FRAG_REQUEST_FAILURE = 1, + PCEP_ERRV_FRAG_REPORT_FAILURE = 2, + PCEP_ERRV_FRAG_UPDATE_FAILURE = 3, + PCEP_ERRV_FRAG_INSTANTIATION_FAILURE = 4, + + /* Error Values for PCEP_ERRT_INVALID_OPERATION=19 */ + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP = 1, + PCEP_ERRV_LSP_UPDATE_NON_ADVERTISED_PCE = 2, + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID = 3, + /* 4: unassigned */ + PCEP_ERRV_LSP_REPORT_NON_ADVERTISED_PCE = 5, + PCEP_ERRV_PCE_INIT_LSP_LIMIT_REACHED = 6, + PCEP_ERRV_PCE_INIT_LSP_DELEGATION_CANT_REVOKE = 7, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID = 8, + PCEP_ERRV_LSP_NOT_PCE_INITIATED = 9, + PCEP_ERRV_PCE_INIT_OP_FREQ_LIMIT_REACHED = 10, + PCEP_ERRV_LSP_REPORT_P2MP_NOT_ADVERTISED = 11, + PCEP_ERRV_LSP_UPDATE_P2MP_NOT_ADVERTISED = 12, + PCEP_ERRV_LSP_INSTANTIATION_P2MP_NOT_ADVERTISED = 13, + PCEP_ERRV_AUTO_BW_CAP_NOT_ADVERTISED = 14, + + /* Error Values for PCEP_ERRT_LSP_STATE_SYNC_ERROR=20 */ + PCEP_ERRV_PCE_CANT_PROCESS_LSP_REPORT = 1, + PCEP_ERRV_LSP_DB_VERSION_MISMATCH = 2, + PCEP_ERRV_TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER = 3, + PCEP_ERRV_TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP = 4, + PCEP_ERRV_PCC_CANT_COMPLETE_STATE_SYNC = 5, + PCEP_ERRV_INVALID_LSP_DB_VERSION_NUMBER = 6, + PCEP_ERRV_INVALID_SPEAKER_ENTITY_ID = 7, + + /* PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE = 21 */ + PCEP_ERRV_UNSUPPORTED_PATH_SETUP_TYPE = 1, + PCEP_ERRV_MISMATCHED_PATH_SETUP_TYPE = 2, + + /* PCEP_ERRT_UNASSIGNED22 = 22 */ + + /* Error Values for PCEP_ERRT_BAD_PARAMETER_VALUE=23 */ + PCEP_ERRV_SYMBOLIC_PATH_NAME_IN_USE = 1, + PCEP_ERRV_LSP_SPEAKER_ID_NOT_PCE_INITIATED = 2, + + /* Error Values for PCEP_ERRT_LSP_INSTANTIATE_ERROR=24 */ + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR = 1, + PCEP_ERRV_INTERNAL_ERROR = 2, + PCEP_ERRV_SIGNALLING_ERROR = 3, + + /* PCEP_ERRT_START_TLS_FAILURE = 25 */ + PCEP_ERRV_START_TLS_AFTER_PCEP_EXCHANGE = 1, + PCEP_ERRV_MSG_NOT_START_TLS_OPEN_ERROR = 2, + PCEP_ERRV_CONNECTION_WO_TLS_NOT_POSSIBLE = 3, + PCEP_ERRV_CONNECTION_WO_TLS_IS_POSSIBLE = 4, + PCEP_ERRV_NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER = 5, + + /* PCEP_ERRT_ASSOCIATION_ERROR = 26 */ + PCEP_ERRV_ASSOC_TYPE_NOT_SUPPORTED = 1, + PCEP_ERRV_TOO_MANY_LSPS_IN_ASSOC_GRP = 2, + PCEP_ERRV_TOO_MANY_ASSOC_GROUPS = 3, + PCEP_ERRV_ASSOCIATION_UNKNOWN = 4, + PCEP_ERRV_OP_CONF_ASSOC_INFO_MISMATCH = 5, + PCEP_ERRV_ASSOC_INFO_MISMATCH = 6, + PCEP_ERRV_CANNOT_JOIN_ASSOC_GROUP = 7, + PCEP_ERRV_ASSOC_ID_NOT_IN_RANGE = 8, + PCEP_ERRV_TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC = 9, + PCEP_ERRV_ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC = 10, + PCEP_ERRV_PROTECTION_TYPE_NOT_SUPPORTED = 11, + + /* PCEP_ERRT_WSON_RWA_ERROR = 27 */ + PCEP_ERRV_RWA_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_RWA_COMP_NOT_SUPPORTED = 2, + PCEP_ERRV_SYNTAX_ENC_ERROR = 3, + + /* PCEP_ERRT_H_PCE_ERROR = 28 */ + PCEP_ERRV_H_PCE_CAP_NOT_ADVERTISED = 1, + PCEP_ERRV_PARENT_PCE_CAP_CANT_BE_PROVIDED = 2, + + /* PCEP_ERRT_PATH_COMP_FAILURE = 29 */ + PCEP_ERRV_UNACCEPTABLE_REQUEST_MSG = 1, + PCEP_ERRV_GENERALIZED_BW_VAL_NOT_SUPPORTED = 2, + PCEP_ERRV_LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET = 3, + PCEP_ERRV_LABEL_CONSTRAINT_COULD_NOT_BE_MET = 4, + +}; + +const char *get_error_type_str(enum pcep_error_type error_type); +const char *get_error_value_str(enum pcep_error_type error_type, + enum pcep_error_value error_value); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_objects.c b/pceplib/pcep_msg_objects.c new file mode 100644 index 0000000000..6c943ddc2a --- /dev/null +++ b/pceplib/pcep_msg_objects.c @@ -0,0 +1,854 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message object API. + */ + +#include +#include +#include +#include + +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* Internal common function used to create a pcep_object and populate the header + */ +static struct pcep_object_header *pcep_obj_create_common_with_tlvs( + uint8_t obj_length, enum pcep_object_classes object_class, + enum pcep_object_types object_type, double_linked_list *tlv_list) +{ + uint8_t *buffer = pceplib_malloc(PCEPLIB_MESSAGES, obj_length); + memset(buffer, 0, obj_length); + + /* The flag_p and flag_i flags will be set externally */ + struct pcep_object_header *hdr = (struct pcep_object_header *)buffer; + hdr->object_class = object_class; + hdr->object_type = object_type; + hdr->tlv_list = tlv_list; + + return hdr; +} + +static struct pcep_object_header * +pcep_obj_create_common(uint8_t obj_length, + enum pcep_object_classes object_class, + enum pcep_object_types object_type) +{ + return pcep_obj_create_common_with_tlvs(obj_length, object_class, + object_type, NULL); +} + +struct pcep_object_open *pcep_obj_create_open(uint8_t keepalive, + uint8_t deadtimer, uint8_t sid, + double_linked_list *tlv_list) +{ + struct pcep_object_open *open = + (struct pcep_object_open *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_open), PCEP_OBJ_CLASS_OPEN, + PCEP_OBJ_TYPE_OPEN, tlv_list); + + open->open_version = + PCEP_OBJECT_OPEN_VERSION; /* PCEP version. Current version is 1 + /No flags are currently defined. */ + open->open_keepalive = + keepalive; /* Maximum period of time between two consecutive + PCEP messages sent by the sender. */ + open->open_deadtimer = deadtimer; /* Specifies the amount of time before + closing the session down. */ + open->open_sid = sid; /* PCEP session number that identifies the current + session. */ + + return open; +} + +struct pcep_object_rp *pcep_obj_create_rp(uint8_t priority, bool flag_r, + bool flag_b, bool flag_s, + bool flag_of, uint32_t reqid, + double_linked_list *tlv_list) +{ + if (priority > OBJECT_RP_MAX_PRIORITY) { + pcep_log( + LOG_INFO, + "%s: Error creating RP object, invalid priority [%d], max priority [%d].", + __func__, priority, OBJECT_RP_MAX_PRIORITY); + return NULL; + } + + struct pcep_object_rp *obj = + (struct pcep_object_rp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_rp), PCEP_OBJ_CLASS_RP, + PCEP_OBJ_TYPE_RP, tlv_list); + + obj->priority = priority; + obj->flag_reoptimization = flag_r; + obj->flag_bidirectional = flag_b; + obj->flag_strict = flag_s; + obj->flag_of = flag_of; + obj->request_id = reqid; + + return obj; +} + +struct pcep_object_notify * +pcep_obj_create_notify(enum pcep_notification_types notification_type, + enum pcep_notification_values notification_value) +{ + struct pcep_object_notify *obj = + (struct pcep_object_notify *)pcep_obj_create_common( + sizeof(struct pcep_object_notify), PCEP_OBJ_CLASS_NOTF, + PCEP_OBJ_TYPE_NOTF); + + obj->notification_type = notification_type; + obj->notification_value = notification_value; + + return obj; +} + +struct pcep_object_nopath * +pcep_obj_create_nopath(uint8_t ni, bool flag_c, + enum pcep_nopath_tlv_err_codes error_code) +{ + struct pcep_object_tlv_nopath_vector *tlv = + pcep_tlv_create_nopath_vector(error_code); + double_linked_list *tlv_list = dll_initialize(); + dll_append(tlv_list, tlv); + + struct pcep_object_nopath *obj = + (struct pcep_object_nopath *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_nopath), + PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH, tlv_list); + + obj->ni = ni; + obj->flag_c = flag_c; + obj->err_code = error_code; + + return obj; +} + +struct pcep_object_association_ipv4 * +pcep_obj_create_association_ipv4(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in_addr src) +{ + struct pcep_object_association_ipv4 *obj = + (struct pcep_object_association_ipv4 *)pcep_obj_create_common( + sizeof(struct pcep_object_association_ipv4), + PCEP_OBJ_CLASS_ASSOCIATION, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4); + + obj->R_flag = r_flag; + obj->association_type = association_type; + obj->association_id = association_id; + obj->src = src; + + return obj; +} +struct pcep_object_association_ipv6 * +pcep_obj_create_association_ipv6(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in6_addr src) +{ + struct pcep_object_association_ipv6 *obj = + (struct pcep_object_association_ipv6 *)pcep_obj_create_common( + sizeof(struct pcep_object_association_ipv6), + PCEP_OBJ_CLASS_ASSOCIATION, + PCEP_OBJ_TYPE_ASSOCIATION_IPV6); + + obj->R_flag = r_flag; + obj->association_type = association_type; + obj->association_id = association_id; + obj->src = src; + + return obj; +} +struct pcep_object_endpoints_ipv4 * +pcep_obj_create_endpoint_ipv4(const struct in_addr *src_ipv4, + const struct in_addr *dst_ipv4) +{ + if (src_ipv4 == NULL || dst_ipv4 == NULL) { + return NULL; + } + + struct pcep_object_endpoints_ipv4 *obj = + (struct pcep_object_endpoints_ipv4 *)pcep_obj_create_common( + sizeof(struct pcep_object_endpoints_ipv4), + PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + + obj->src_ipv4.s_addr = src_ipv4->s_addr; + obj->dst_ipv4.s_addr = dst_ipv4->s_addr; + + return obj; +} + +struct pcep_object_endpoints_ipv6 * +pcep_obj_create_endpoint_ipv6(const struct in6_addr *src_ipv6, + const struct in6_addr *dst_ipv6) +{ + if (src_ipv6 == NULL || dst_ipv6 == NULL) { + return NULL; + } + + struct pcep_object_endpoints_ipv6 *obj = + (struct pcep_object_endpoints_ipv6 *)pcep_obj_create_common( + sizeof(struct pcep_object_endpoints_ipv6), + PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV6); + + memcpy(&obj->src_ipv6, src_ipv6, sizeof(struct in6_addr)); + memcpy(&obj->dst_ipv6, dst_ipv6, sizeof(struct in6_addr)); + + return obj; +} + +struct pcep_object_bandwidth *pcep_obj_create_bandwidth(float bandwidth) +{ + struct pcep_object_bandwidth *obj = + (struct pcep_object_bandwidth *)pcep_obj_create_common( + sizeof(struct pcep_object_bandwidth), + PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ); + + obj->bandwidth = bandwidth; + + return obj; +} + +struct pcep_object_metric *pcep_obj_create_metric(enum pcep_metric_types type, + bool flag_b, bool flag_c, + float value) +{ + struct pcep_object_metric *obj = + (struct pcep_object_metric *)pcep_obj_create_common( + sizeof(struct pcep_object_metric), + PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC); + + obj->flag_b = flag_b; + obj->flag_c = flag_c; + obj->type = type; + obj->value = value; + + return obj; +} + +struct pcep_object_lspa * +pcep_obj_create_lspa(uint32_t exclude_any, uint32_t include_any, + uint32_t include_all, uint8_t setup_priority, + uint8_t holding_priority, bool flag_local_protection) +{ + struct pcep_object_lspa *obj = + (struct pcep_object_lspa *)pcep_obj_create_common( + sizeof(struct pcep_object_lspa), PCEP_OBJ_CLASS_LSPA, + PCEP_OBJ_TYPE_LSPA); + + obj->lspa_exclude_any = exclude_any; + obj->lspa_include_any = include_any; + obj->lspa_include_all = include_all; + obj->setup_priority = setup_priority; + obj->holding_priority = holding_priority; + obj->flag_local_protection = flag_local_protection; + + return obj; +} + +struct pcep_object_svec * +pcep_obj_create_svec(bool srlg, bool node, bool link, + double_linked_list *request_id_list) +{ + if (request_id_list == NULL) { + return NULL; + } + + struct pcep_object_svec *obj = + (struct pcep_object_svec *)pcep_obj_create_common( + sizeof(struct pcep_object_svec), PCEP_OBJ_CLASS_SVEC, + PCEP_OBJ_TYPE_SVEC); + + obj->flag_srlg_diverse = srlg; + obj->flag_node_diverse = node; + obj->flag_link_diverse = link; + obj->request_id_list = request_id_list; + + return obj; +} + +struct pcep_object_error * +pcep_obj_create_error(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + struct pcep_object_error *obj = + (struct pcep_object_error *)pcep_obj_create_common( + sizeof(struct pcep_object_error), PCEP_OBJ_CLASS_ERROR, + PCEP_OBJ_TYPE_ERROR); + + obj->error_type = error_type; + obj->error_value = error_value; + + return obj; +} + +struct pcep_object_close *pcep_obj_create_close(enum pcep_close_reason reason) +{ + struct pcep_object_close *obj = + (struct pcep_object_close *)pcep_obj_create_common( + sizeof(struct pcep_object_close), PCEP_OBJ_CLASS_CLOSE, + PCEP_OBJ_TYPE_CLOSE); + + obj->reason = reason; + + return obj; +} + +struct pcep_object_srp *pcep_obj_create_srp(bool lsp_remove, + uint32_t srp_id_number, + double_linked_list *tlv_list) +{ + struct pcep_object_srp *obj = + (struct pcep_object_srp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_srp), PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_TYPE_SRP, tlv_list); + + obj->flag_lsp_remove = lsp_remove; + obj->srp_id_number = srp_id_number; + + return obj; +} + +struct pcep_object_lsp * +pcep_obj_create_lsp(uint32_t plsp_id, enum pcep_lsp_operational_status status, + bool c_flag, bool a_flag, bool r_flag, bool s_flag, + bool d_flag, double_linked_list *tlv_list) +{ + /* The plsp_id is only 20 bits */ + if (plsp_id > MAX_PLSP_ID) { + pcep_log( + LOG_INFO, + "%s: pcep_obj_create_lsp invalid plsp_id [%d] max value [%d]", + __func__, plsp_id, MAX_PLSP_ID); + return NULL; + } + + /* The status is only 3 bits */ + if (status > MAX_LSP_STATUS) { + pcep_log( + LOG_INFO, + "%s: pcep_obj_create_lsp invalid status [%d] max value [%d]", + __func__, plsp_id, MAX_PLSP_ID); + return NULL; + } + + struct pcep_object_lsp *obj = + (struct pcep_object_lsp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_lsp), PCEP_OBJ_CLASS_LSP, + PCEP_OBJ_TYPE_LSP, tlv_list); + + obj->plsp_id = plsp_id; + obj->operational_status = status; + obj->flag_c = c_flag; + obj->flag_a = a_flag; + obj->flag_r = r_flag; + obj->flag_s = s_flag; + obj->flag_d = d_flag; + + return obj; +} + +struct pcep_object_vendor_info * +pcep_obj_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_spec_info) +{ + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)pcep_obj_create_common( + sizeof(struct pcep_object_vendor_info), + PCEP_OBJ_CLASS_VENDOR_INFO, PCEP_OBJ_TYPE_VENDOR_INFO); + + obj->enterprise_number = enterprise_number; + obj->enterprise_specific_info = enterprise_spec_info; + + return obj; +} + +struct pcep_object_inter_layer * +pcep_obj_create_inter_layer(bool flag_i, bool flag_m, bool flag_t) +{ + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)pcep_obj_create_common( + sizeof(struct pcep_object_inter_layer), + PCEP_OBJ_CLASS_INTER_LAYER, PCEP_OBJ_TYPE_INTER_LAYER); + + obj->flag_i = flag_i; + obj->flag_m = flag_m; + obj->flag_t = flag_t; + + return obj; +} + +struct pcep_object_switch_layer * +pcep_obj_create_switch_layer(double_linked_list *switch_layer_rows) +{ + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)pcep_obj_create_common( + sizeof(struct pcep_object_switch_layer), + PCEP_OBJ_CLASS_SWITCH_LAYER, + PCEP_OBJ_TYPE_SWITCH_LAYER); + + obj->switch_layer_rows = switch_layer_rows; + + return obj; +} + +struct pcep_object_req_adap_cap * +pcep_obj_create_req_adap_cap(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding) +{ + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)pcep_obj_create_common( + sizeof(struct pcep_object_req_adap_cap), + PCEP_OBJ_CLASS_REQ_ADAP_CAP, + PCEP_OBJ_TYPE_REQ_ADAP_CAP); + + obj->switching_capability = sw_cap; + obj->encoding = encoding; + + return obj; +} + +struct pcep_object_server_indication * +pcep_obj_create_server_indication(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding, + double_linked_list *tlv_list) +{ + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *) + pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_server_indication), + PCEP_OBJ_CLASS_SERVER_IND, + PCEP_OBJ_TYPE_SERVER_IND, tlv_list); + + obj->switching_capability = sw_cap; + obj->encoding = encoding; + + return obj; +} + +struct pcep_object_objective_function * +pcep_obj_create_objective_function(uint16_t of_code, + double_linked_list *tlv_list) +{ + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *) + pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_objective_function), + PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF, tlv_list); + + obj->of_code = of_code; + + return obj; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_ero(double_linked_list *ero_list) +{ + struct pcep_object_ro *ero = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_ERO, + PCEP_OBJ_TYPE_ERO); + ero->sub_objects = ero_list; + + return ero; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_iro(double_linked_list *iro_list) +{ + struct pcep_object_ro *iro = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_IRO, + PCEP_OBJ_TYPE_IRO); + iro->sub_objects = iro_list; + + return iro; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_rro(double_linked_list *rro_list) +{ + struct pcep_object_ro *rro = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_RRO, + PCEP_OBJ_TYPE_RRO); + rro->sub_objects = rro_list; + + return rro; +} + +/* + * Route Object Sub-object creation functions + */ + +static struct pcep_object_ro_subobj * +pcep_obj_create_ro_subobj_common(uint8_t subobj_size, + enum pcep_ro_subobj_types ro_subobj_type, + bool flag_subobj_loose_hop) +{ + struct pcep_object_ro_subobj *ro_subobj = + pceplib_malloc(PCEPLIB_MESSAGES, subobj_size); + memset(ro_subobj, 0, subobj_size); + ro_subobj->flag_subobj_loose_hop = flag_subobj_loose_hop; + ro_subobj->ro_subobj_type = ro_subobj_type; + + return ro_subobj; +} + +struct pcep_ro_subobj_ipv4 * +pcep_obj_create_ro_subobj_ipv4(bool loose_hop, const struct in_addr *rro_ipv4, + uint8_t prefix_length, bool flag_local_prot) +{ + if (rro_ipv4 == NULL) { + return NULL; + } + + struct pcep_ro_subobj_ipv4 *obj = + (struct pcep_ro_subobj_ipv4 *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_ipv4), RO_SUBOBJ_TYPE_IPV4, + loose_hop); + obj->ip_addr.s_addr = rro_ipv4->s_addr; + obj->prefix_length = prefix_length; + obj->flag_local_protection = flag_local_prot; + + return obj; +} + +struct pcep_ro_subobj_ipv6 * +pcep_obj_create_ro_subobj_ipv6(bool loose_hop, const struct in6_addr *rro_ipv6, + uint8_t prefix_length, bool flag_local_prot) +{ + if (rro_ipv6 == NULL) { + return NULL; + } + + struct pcep_ro_subobj_ipv6 *obj = + (struct pcep_ro_subobj_ipv6 *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_ipv6), RO_SUBOBJ_TYPE_IPV6, + loose_hop); + obj->prefix_length = prefix_length; + obj->flag_local_protection = flag_local_prot; + memcpy(&obj->ip_addr, rro_ipv6, sizeof(struct in6_addr)); + + return obj; +} + +struct pcep_ro_subobj_unnum * +pcep_obj_create_ro_subobj_unnum(struct in_addr *router_id, uint32_t if_id) +{ + if (router_id == NULL) { + return NULL; + } + + struct pcep_ro_subobj_unnum *obj = + (struct pcep_ro_subobj_unnum *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_unnum), + RO_SUBOBJ_TYPE_UNNUM, false); + obj->interface_id = if_id; + obj->router_id.s_addr = router_id->s_addr; + + return obj; +} + +struct pcep_ro_subobj_32label * +pcep_obj_create_ro_subobj_32label(bool flag_global_label, uint8_t class_type, + uint32_t label) +{ + struct pcep_ro_subobj_32label *obj = (struct pcep_ro_subobj_32label *) + pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_32label), + RO_SUBOBJ_TYPE_LABEL, false); + obj->class_type = class_type; + obj->flag_global_label = flag_global_label; + obj->label = label; + + return obj; +} + +struct pcep_ro_subobj_asn *pcep_obj_create_ro_subobj_asn(uint16_t asn) +{ + struct pcep_ro_subobj_asn *obj = + (struct pcep_ro_subobj_asn *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_asn), RO_SUBOBJ_TYPE_ASN, + false); + obj->asn = asn; + + return obj; +} + +/* Internal util function to create pcep_ro_subobj_sr sub-objects */ +static struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_common(enum pcep_sr_subobj_nai nai_type, + bool loose_hop, bool f_flag, bool s_flag, + bool c_flag_in, bool m_flag_in) +{ + struct pcep_ro_subobj_sr *obj = + (struct pcep_ro_subobj_sr *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_sr), RO_SUBOBJ_TYPE_SR, + loose_hop); + + /* Flag logic according to draft-ietf-pce-segment-routing-16 */ + bool c_flag = c_flag_in; + bool m_flag = m_flag_in; + if (s_flag) { + c_flag = false; + m_flag = false; + } + + if (m_flag == false) { + c_flag = false; + } + + obj->nai_type = nai_type; + obj->flag_f = f_flag; + obj->flag_s = s_flag; + obj->flag_c = c_flag; + obj->flag_m = m_flag; + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_nonai(bool loose_hop, + uint32_t sid, + bool c_flag, + bool m_flag) +{ + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=0, the F bit MUST be 1, the S bit MUST be zero and the + * Length MUST be 8. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_ABSENT, loose_hop, true, false, c_flag, + m_flag); + obj->sid = sid; + + return obj; +} + +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv4_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *ipv4_node_id) +{ + if (ipv4_node_id == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=1, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 8, otherwise the Length MUST be 12 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + /* Since the IP has to be stored in the list, copy it so the caller + * doesnt have any restrictions about the type of memory used externally + * for the IP. This memory will be freed with the object is freed. */ + struct in_addr *ipv4_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + ipv4_node_id_copy->s_addr = ipv4_node_id->s_addr; + dll_append(obj->nai_list, ipv4_node_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv6_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *ipv6_node_id) +{ + if (ipv6_node_id == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=2, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 20, otherwise the Length MUST be 24. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *ipv6_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(ipv6_node_id_copy, ipv6_node_id, sizeof(struct in6_addr)); + dll_append(obj->nai_list, ipv6_node_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *local_ipv4, struct in_addr *remote_ipv4) +{ + if (local_ipv4 == NULL || remote_ipv4 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=3, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 12, otherwise the Length MUST be 16 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in_addr *local_ipv4_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + struct in_addr *remote_ipv4_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + local_ipv4_copy->s_addr = local_ipv4->s_addr; + remote_ipv4_copy->s_addr = remote_ipv4->s_addr; + dll_append(obj->nai_list, local_ipv4_copy); + dll_append(obj->nai_list, remote_ipv4_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, struct in6_addr *remote_ipv6) +{ + if (local_ipv6 == NULL || remote_ipv6 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=4, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 36, otherwise the Length MUST be 40 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *local_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + struct in6_addr *remote_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(local_ipv6_copy, local_ipv6, sizeof(struct in6_addr)); + memcpy(remote_ipv6_copy, remote_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, local_ipv6_copy); + dll_append(obj->nai_list, remote_ipv6_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + uint32_t local_node_id, uint32_t local_if_id, uint32_t remote_node_id, + uint32_t remote_if_id) +{ + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=5, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 20, otherwise the Length MUST be 24. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, loose_hop, false, + sid_absent, c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + + obj->nai_list = dll_initialize(); + uint32_t *local_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_node_id_copy = local_node_id; + dll_append(obj->nai_list, local_node_id_copy); + + uint32_t *local_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_if_id_copy = local_if_id; + dll_append(obj->nai_list, local_if_id_copy); + + uint32_t *remote_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_node_id_copy = remote_node_id; + dll_append(obj->nai_list, remote_node_id_copy); + + uint32_t *remote_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_if_id_copy = remote_if_id; + dll_append(obj->nai_list, remote_if_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, uint32_t local_if_id, + struct in6_addr *remote_ipv6, uint32_t remote_if_id) +{ + if (local_ipv6 == NULL || remote_ipv6 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=6, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 44, otherwise the Length MUST be 48 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, loose_hop, false, + sid_absent, c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *local_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(local_ipv6_copy, local_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, local_ipv6_copy); + + uint32_t *local_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_if_id_copy = local_if_id; + dll_append(obj->nai_list, local_if_id_copy); + + struct in6_addr *remote_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(remote_ipv6_copy, remote_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, remote_ipv6_copy); + + uint32_t *remote_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_if_id_copy = remote_if_id; + dll_append(obj->nai_list, remote_if_id_copy); + + return obj; +} diff --git a/pceplib/pcep_msg_objects.h b/pceplib/pcep_msg_objects.h new file mode 100644 index 0000000000..959a6f8cf6 --- /dev/null +++ b/pceplib/pcep_msg_objects.h @@ -0,0 +1,741 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message object API. + */ + +#ifndef PCEP_OBJECTS_H +#define PCEP_OBJECTS_H + +#include +#include + +#include "pcep.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_tlvs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Regarding memory usage: + * When creating objects, any objects passed into these APIs will be free'd when + * the enclosing pcep_message is free'd. That includes the double_linked_list's. + * So, just create the objects and TLVs, put them in their double_linked_list's, + * and everything will be managed internally. The enclosing message will be + * deleted by pcep_msg_free_message() or pcep_msg_free_message_list() which, + * in turn will call one of: pcep_obj_free_object() and pcep_obj_free_tlv(). + * For received messages with objects, call pcep_msg_free_message() to free + * them. + */ + +enum pcep_object_classes { + PCEP_OBJ_CLASS_OPEN = 1, + PCEP_OBJ_CLASS_RP = 2, + PCEP_OBJ_CLASS_NOPATH = 3, + PCEP_OBJ_CLASS_ENDPOINTS = 4, + PCEP_OBJ_CLASS_BANDWIDTH = 5, + PCEP_OBJ_CLASS_METRIC = 6, + PCEP_OBJ_CLASS_ERO = 7, + PCEP_OBJ_CLASS_RRO = 8, + PCEP_OBJ_CLASS_LSPA = 9, + PCEP_OBJ_CLASS_IRO = 10, + PCEP_OBJ_CLASS_SVEC = 11, + PCEP_OBJ_CLASS_NOTF = 12, + PCEP_OBJ_CLASS_ERROR = 13, + PCEP_OBJ_CLASS_CLOSE = 15, + PCEP_OBJ_CLASS_OF = 21, + PCEP_OBJ_CLASS_LSP = 32, + PCEP_OBJ_CLASS_SRP = 33, + PCEP_OBJ_CLASS_VENDOR_INFO = 34, + PCEP_OBJ_CLASS_INTER_LAYER = 36, /* RFC 8282 */ + PCEP_OBJ_CLASS_SWITCH_LAYER = 37, /* RFC 8282 */ + PCEP_OBJ_CLASS_REQ_ADAP_CAP = 38, /* RFC 8282 */ + PCEP_OBJ_CLASS_SERVER_IND = 39, /* RFC 8282 */ + PCEP_OBJ_CLASS_ASSOCIATION = 40, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_CLASS_MAX, +}; + +enum pcep_object_types { + PCEP_OBJ_TYPE_OPEN = 1, + PCEP_OBJ_TYPE_RP = 1, + PCEP_OBJ_TYPE_NOPATH = 1, + PCEP_OBJ_TYPE_ENDPOINT_IPV4 = 1, + PCEP_OBJ_TYPE_ENDPOINT_IPV6 = 2, + PCEP_OBJ_TYPE_BANDWIDTH_REQ = 1, + PCEP_OBJ_TYPE_BANDWIDTH_TELSP = 2, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO = + 5, /* IANA unassigned, but rcvd from Cisco PCE */ + PCEP_OBJ_TYPE_SRP = 1, + PCEP_OBJ_TYPE_VENDOR_INFO = 1, + PCEP_OBJ_TYPE_LSP = 1, + PCEP_OBJ_TYPE_METRIC = 1, + PCEP_OBJ_TYPE_ERO = 1, + PCEP_OBJ_TYPE_RRO = 1, + PCEP_OBJ_TYPE_LSPA = 1, + PCEP_OBJ_TYPE_IRO = 1, + PCEP_OBJ_TYPE_SVEC = 1, + PCEP_OBJ_TYPE_NOTF = 1, + PCEP_OBJ_TYPE_ERROR = 1, + PCEP_OBJ_TYPE_CLOSE = 1, + PCEP_OBJ_TYPE_INTER_LAYER = 1, + PCEP_OBJ_TYPE_SWITCH_LAYER = 1, + PCEP_OBJ_TYPE_REQ_ADAP_CAP = 1, + PCEP_OBJ_TYPE_SERVER_IND = 1, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4 = + 1, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_TYPE_ASSOCIATION_IPV6 = + 2, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_TYPE_OF = 1, + PCEP_OBJ_TYPE_MAX = 2, +}; + +#define OBJECT_HEADER_FLAG_I 0x01 +#define OBJECT_HEADER_FLAG_P 0x02 + +/* The flag_p and flag_i arent set via the APIs, if they need to be set, just + * set them on the returned object once it has been created. */ +struct pcep_object_header { + enum pcep_object_classes object_class; + enum pcep_object_types object_type; + bool flag_p; /* PCC Processing rule bit: When set, the object MUST be + taken into account, when cleared the object is optional. + */ + bool flag_i; /* PCE Ignore bit: indicates to a PCC whether or not an + optional object was processed */ + double_linked_list *tlv_list; + /* Pointer into encoded_message field from the pcep_message */ + const uint8_t *encoded_object; + uint16_t encoded_object_length; +}; + +#define PCEP_OBJECT_OPEN_VERSION 1 + +struct pcep_object_open { + struct pcep_object_header header; + uint8_t open_version; /* PCEP version. Current version is 1 */ + uint8_t open_keepalive; /* Maximum period of time between two + consecutive PCEP messages sent by the sender. + */ + uint8_t open_deadtimer; /* Specifies the amount of time before closing + the session down. */ + uint8_t open_sid; /* PCEP session number that identifies the current + session. */ +}; + +#define OBJECT_RP_FLAG_R 0x08 +#define OBJECT_RP_FLAG_B 0x10 +#define OBJECT_RP_FLAG_O 0x20 +#define OBJECT_RP_FLAG_OF 0x80 +#define OBJECT_RP_MAX_PRIORITY 0x07 + +struct pcep_object_rp { + struct pcep_object_header header; + uint8_t priority; /* 3 bit priority, max priority is 7 */ + bool flag_reoptimization; + bool flag_bidirectional; + bool flag_strict; /* when set, a loose path is acceptable */ + bool flag_of; /* Supply Objective Function on Response */ + uint32_t request_id; /* The Request-id-number value combined with the + source for PCC & PCE creates a uniquely number. + */ +}; + +enum pcep_notification_types { + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED = 1, + PCEP_NOTIFY_TYPE_PCE_OVERLOADED = 2 +}; + +enum pcep_notification_values { + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST = 1, + PCEP_NOTIFY_VALUE_PCE_CANCELLED_REQUEST = 2, + PCEP_NOTIFY_VALUE_PCE_CURRENTLY_OVERLOADED = 1, + PCEP_NOTIFY_VALUE_PCE_NO_LONGER_OVERLOADED = 2 +}; + +struct pcep_object_notify { + struct pcep_object_header header; + enum pcep_notification_types notification_type; + enum pcep_notification_values notification_value; +}; + +enum pcep_association_type { + PCEP_ASSOCIATION_TYPE_PATH_PROTECTION_ASSOCIATION = + 1, // iana unique value define as 2020-01-08! + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE = + 65535 // TBD1 draft-barth-pce-segment-routing-policy-cp-04 +}; +#define OBJECT_ASSOCIATION_FLAG_R 0x01 +struct pcep_object_association_ipv4 { // draft-ietf-pce-association-group-10 + struct pcep_object_header header; + bool R_flag; + uint16_t association_type; + uint16_t association_id; + struct in_addr src; +}; + +struct pcep_object_association_ipv6 { // draft-ietf-pce-association-group-10 + struct pcep_object_header header; + bool R_flag; + uint16_t association_type; + uint16_t association_id; + struct in6_addr src; +}; + + +enum pcep_nopath_nature_of_issue { + PCEP_NOPATH_NI_NO_PATH_FOUND = 0, + PCEP_NOPATH_NI_PCE_CHAIN_BROKEN = 1, +}; + +enum pcep_nopath_tlv_err_codes { + PCEP_NOPATH_TLV_ERR_NO_TLV = 0, + PCEP_NOPATH_TLV_ERR_PCE_UNAVAILABLE = 1, + PCEP_NOPATH_TLV_ERR_UNKNOWN_DST = 2, + PCEP_NOPATH_TLV_ERR_UNKNOWN_SRC = 3 +}; + +#define OBJECT_NOPATH_FLAG_C 0x80 + +struct pcep_object_nopath { + struct pcep_object_header header; + uint8_t ni; /* Nature of Issue, reports the nature of the issue that led + to a negative reply */ + bool flag_c; /* when set, indicates the unsatisfied constraints by + including relevant PCEP objects. */ + enum pcep_nopath_tlv_err_codes + err_code; /* When set other than 0, an appropriate TLV will be + included */ +}; + +struct pcep_object_endpoints_ipv4 { + struct pcep_object_header header; + struct in_addr src_ipv4; + struct in_addr dst_ipv4; +}; + +struct pcep_object_endpoints_ipv6 { + struct pcep_object_header header; + struct in6_addr src_ipv6; + struct in6_addr dst_ipv6; +}; + +/* PCEP floats are encoded according to: + * https://en.wikipedia.org/wiki/IEEE_754-1985 + * Luckily, this is the same encoding used by C */ +struct pcep_object_bandwidth { + struct pcep_object_header header; + float bandwidth; +}; + +enum pcep_metric_types { + /* RFC 5440 */ + PCEP_METRIC_IGP = 1, + PCEP_METRIC_TE = 2, + PCEP_METRIC_HOP_COUNT = 3, + /* RFC 5541 */ + PCEP_METRIC_AGGREGATE_BW = 4, + PCEP_METRIC_MOST_LOADED_LINK = 5, + PCEP_METRIC_CUMULATIVE_IGP = 6, + PCEP_METRIC_CUMULATIVE_TE = 7, + /* RFC 8306 */ + PCEP_METRIC_P2MP_IGP = 8, + PCEP_METRIC_P2MP_TE = 9, + PCEP_METRIC_P2MP_HOP_COUNT = 10, + /* RFC 8864 */ + PCEP_METRIC_SEGMENT_ID_DEPTH = 11, + /* RFC 8233 */ + PCEP_METRIC_PATH_DELAY = 12, + PCEP_METRIC_PATH_DELAY_VARIATION = 13, + PCEP_METRIC_PATH_LOSS = 14, + PCEP_METRIC_P2MP_PATH_DELAY = 15, + PCEP_METRIC_P2MP_PATH_DELAY_VARIATION = 16, + PCEP_METRIC_P2MP_PATH_LOSS = 17, + /* RFC 8282 */ + PCEP_METRIC_NUM_PATH_ADAPTATIONS = 18, + PCEP_METRIC_NUM_PATH_LAYERS = 19, + /* RFC 8685 */ + PCEP_METRIC_DOMAIN_COUNT = 20, + PCEP_METRIC_BORDER_NODE_COUNT = 21, +}; + +#define OBJECT_METRIC_FLAC_B 0x01 +#define OBJECT_METRIC_FLAC_C 0x02 + +/* PCEP floats are encoded according to: + * https://en.wikipedia.org/wiki/IEEE_754-1985 + * Luckily, this is the same encoding used by C */ +struct pcep_object_metric { + struct pcep_object_header header; + enum pcep_metric_types type; + bool flag_b; /* Bound flag */ + bool flag_c; /* Computed metric */ + float value; /* Metric value in 32 bits */ +}; + +#define OBJECT_LSPA_FLAG_L 0x01 + +struct pcep_object_lspa { + struct pcep_object_header header; + uint32_t lspa_exclude_any; + uint32_t lspa_include_any; + uint32_t lspa_include_all; + uint8_t setup_priority; + uint8_t holding_priority; + bool flag_local_protection; /* Local protection desired bit */ +}; + +/* The SVEC object with some custom extensions. */ +#define OBJECT_SVEC_FLAG_L 0x01 +#define OBJECT_SVEC_FLAG_N 0x02 +#define OBJECT_SVEC_FLAG_S 0x04 + +struct pcep_object_svec { + struct pcep_object_header header; + bool flag_link_diverse; + bool flag_node_diverse; + bool flag_srlg_diverse; + double_linked_list + *request_id_list; /* list of 32-bit request ID pointers */ +}; + +struct pcep_object_error { + struct pcep_object_header header; + enum pcep_error_type error_type; + enum pcep_error_value error_value; +}; + +struct pcep_object_load_balancing { + struct pcep_object_header header; + uint8_t load_maxlsp; /* Maximum number of TE LSPs in the set */ + uint32_t load_minband; /* Specifies the minimum bandwidth of each + element */ +}; + +enum pcep_close_reason { + PCEP_CLOSE_REASON_NO = 1, + PCEP_CLOSE_REASON_DEADTIMER = 2, + PCEP_CLOSE_REASON_FORMAT = 3, + PCEP_CLOSE_REASON_UNKNOWN_REQ = 4, + PCEP_CLOSE_REASON_UNREC_MSG = 5 +}; + +struct pcep_object_close { + struct pcep_object_header header; + enum pcep_close_reason reason; +}; + +/* Stateful PCE Request Parameters RFC 8231, 8281 */ + +#define OBJECT_SRP_FLAG_R 0x01 + +struct pcep_object_srp { + struct pcep_object_header header; + bool flag_lsp_remove; /* RFC 8281 */ + uint32_t srp_id_number; +}; + +/* Label Switched Path Object RFC 8231 */ +enum pcep_lsp_operational_status { + PCEP_LSP_OPERATIONAL_DOWN = 0, + PCEP_LSP_OPERATIONAL_UP = 1, + PCEP_LSP_OPERATIONAL_ACTIVE = 2, + PCEP_LSP_OPERATIONAL_GOING_DOWN = 3, + PCEP_LSP_OPERATIONAL_GOING_UP = 4, +}; + +#define MAX_PLSP_ID 0x000fffff /* The plsp_id is only 20 bits */ +#define MAX_LSP_STATUS 0x0007 /* The status is only 3 bits */ +#define OBJECT_LSP_FLAG_D 0x01 +#define OBJECT_LSP_FLAG_S 0x02 +#define OBJECT_LSP_FLAG_R 0x04 +#define OBJECT_LSP_FLAG_A 0x08 +#define OBJECT_LSP_FLAG_C 0x80 + +struct pcep_object_lsp { + struct pcep_object_header header; + uint32_t plsp_id; /* plsp_id is 20 bits, must be <= MAX_PLSP_ID*/ + enum pcep_lsp_operational_status operational_status; /* max 3 bits */ + bool flag_d; + bool flag_s; + bool flag_r; + bool flag_a; + bool flag_c; +}; + +/* RFC 7470 */ +struct pcep_object_vendor_info { + struct pcep_object_header header; + uint32_t enterprise_number; + uint32_t enterprise_specific_info; +}; + +/* RFC 8282 */ +#define OBJECT_INTER_LAYER_FLAG_I 0x01 +#define OBJECT_INTER_LAYER_FLAG_M 0x02 +#define OBJECT_INTER_LAYER_FLAG_T 0x04 + +struct pcep_object_inter_layer { + struct pcep_object_header header; + bool flag_i; + bool flag_m; + bool flag_t; +}; + +/* RFC 8282 */ +#define OBJECT_SWITCH_LAYER_FLAG_I 0x01 +enum pcep_lsp_encoding_type { + /* Values taken from RFC 3471 as suggested by RFC 8282 */ + PCEP_LSP_ENC_PACKET = 1, + PCEP_LSP_ENC_ETHERNET = 2, + PCEP_LSP_ENC_PDH = 3, + PCEP_LSP_ENC_RESERVED4 = 4, + PCEP_LSP_ENC_SDH_SONET = 5, + PCEP_LSP_ENC_RESERVED6 = 6, + PCEP_LSP_ENC_DIG_WRAPPER = 7, + PCEP_LSP_ENC_LAMBDA = 8, + PCEP_LSP_ENC_FIBER = 9, + PCEP_LSP_ENC_RESERVED10 = 10, + PCEP_LSP_ENC_FIBER_CHAN = 11 +}; + +enum pcep_switching_capability { + /* Switching capability values taken from RFC 4203/3471 as suggested by + RFC 8282 */ + PCEP_SW_CAP_PSC1 = 1, /* Packet-Switch Capable-1 (PSC-1) */ + PCEP_SW_CAP_PSC2 = 2, + PCEP_SW_CAP_PSC3 = 3, + PCEP_SW_CAP_PSC4 = 4, + PCEP_SW_CAP_L2SC = 51, /* Layer-2 Switch Capable */ + PCEP_SW_CAP_TDM = 100, /* Time-Division-Multiplex Capable */ + PCEP_SW_CAP_LSC = 150, /* Lambda-Switch Capable */ + PCEP_SW_CAP_FSC = 200 /* Fiber-Switch Capable */ +}; + +struct pcep_object_switch_layer_row { + enum pcep_lsp_encoding_type lsp_encoding_type; + enum pcep_switching_capability switching_type; + bool flag_i; +}; + +struct pcep_object_switch_layer { + struct pcep_object_header header; + double_linked_list + *switch_layer_rows; /* list of struct + pcep_object_switch_layer_row */ +}; + +/* RFC 8282 + * Requested Adaptation capability */ + +struct pcep_object_req_adap_cap { + struct pcep_object_header header; + enum pcep_switching_capability switching_capability; + enum pcep_lsp_encoding_type encoding; +}; + +/* RFC 8282 */ + +struct pcep_object_server_indication { + struct pcep_object_header header; + enum pcep_switching_capability switching_capability; + enum pcep_lsp_encoding_type encoding; + /* This object is identical to req_adap_cap, except it allows TLVs */ +}; + +/* Objective Function Object: RFC 5541 */ + +struct pcep_object_objective_function { + struct pcep_object_header header; + uint16_t of_code; +}; + +/* + * Common Route Object sub-object definitions + * used by ERO, IRO, and RRO + */ + +/* Common Route Object sub-object types + * used by ERO, IRO, and RRO */ +enum pcep_ro_subobj_types { + RO_SUBOBJ_TYPE_IPV4 = 1, /* RFC 3209 */ + RO_SUBOBJ_TYPE_IPV6 = 2, /* RFC 3209 */ + RO_SUBOBJ_TYPE_LABEL = 3, /* RFC 3209 */ + RO_SUBOBJ_TYPE_UNNUM = 4, /* RFC 3477 */ + RO_SUBOBJ_TYPE_ASN = 32, /* RFC 3209, Section 4.3.3.4 */ + RO_SUBOBJ_TYPE_SR = 36, /* RFC 8408, draft-ietf-pce-segment-routing-16. + Type 5 for draft07 has been assigned to + something else. */ + RO_SUBOBJ_UNKNOWN +}; + +struct pcep_object_ro { + struct pcep_object_header header; + double_linked_list + *sub_objects; /* list of struct pcep_object_ro_subobj */ +}; + +struct pcep_object_ro_subobj { + bool flag_subobj_loose_hop; /* L subobj flag */ + enum pcep_ro_subobj_types ro_subobj_type; +}; + +#define OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT 0x01 + +struct pcep_ro_subobj_ipv4 { + struct pcep_object_ro_subobj ro_subobj; + struct in_addr ip_addr; + uint8_t prefix_length; + bool flag_local_protection; +}; + +struct pcep_ro_subobj_ipv6 { + struct pcep_object_ro_subobj ro_subobj; + struct in6_addr ip_addr; + uint8_t prefix_length; + bool flag_local_protection; +}; + +struct pcep_ro_subobj_unnum { + struct pcep_object_ro_subobj ro_subobj; + struct in_addr router_id; + uint32_t interface_id; +}; + +#define OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL 0x01 +struct pcep_ro_subobj_32label { + struct pcep_object_ro_subobj ro_subobj; + bool flag_global_label; + uint8_t class_type; /* label class-type (generalized label = 2) */ + uint32_t label; /* label supported */ +}; + +struct pcep_ro_subobj_asn { + struct pcep_object_ro_subobj ro_subobj; + uint16_t asn; /* Autonomous system number */ +}; + +/* The SR ERO and SR RRO subojbects are the same, except + * the SR-RRO does not have the L flag in the Type field. + * Defined in draft-ietf-pce-segment-routing-16 */ +enum pcep_sr_subobj_nai { + PCEP_SR_SUBOBJ_NAI_ABSENT = 0, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE = 1, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE = 2, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY = 3, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY = 4, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY = 5, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY = 6, + PCEP_SR_SUBOBJ_NAI_UNKNOWN +}; + +#define OBJECT_SUBOBJ_SR_FLAG_M 0x01 +#define OBJECT_SUBOBJ_SR_FLAG_C 0x02 +#define OBJECT_SUBOBJ_SR_FLAG_S 0x04 +#define OBJECT_SUBOBJ_SR_FLAG_F 0x08 + +struct pcep_ro_subobj_sr { + struct pcep_object_ro_subobj ro_subobj; + enum pcep_sr_subobj_nai nai_type; + bool flag_f; + bool flag_s; + bool flag_c; + bool flag_m; + + /* The SID and NAI are optional depending on the flags, + * and the NAI can be variable length */ + uint32_t sid; + double_linked_list + *nai_list; /* double linked list of in_addr or in6_addr */ +}; + +/* Macros to make a SID Label + * + * 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Label + | Label | TC |S| TTL | Stack + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Entry + */ +#define ENCODE_SR_ERO_SID(label_20bits, tc_3bits, stack_bottom_bit, ttl_8bits) \ + ((((label_20bits) << 12) & 0xfffff000) \ + | (((tc_3bits) << 9) & 0x00000e00) \ + | (((stack_bottom_bit) << 8) & 0x00000100) | ((ttl_8bits)&0xff)) +#define GET_SR_ERO_SID_LABEL(SID) ((SID & 0xfffff000) >> 12) +#define GET_SR_ERO_SID_TC(SID) ((SID & 0x00000e00) >> 9) +#define GET_SR_ERO_SID_S(SID) ((SID & 0x00000100) >> 8) +#define GET_SR_ERO_SID_TTL(SID) ((SID & 0x000000ff)) + +/* + * All created objects will be in Host byte order, except for IPs. + * All IP addresses are expected to be passed-in in Network byte order, + * and any objects received will have their IPs in Network byte order. + * The message containing the objects should be converted to Network byte order + * with pcep_encode_msg_header() before sending, which will also convert the + * Objects, TLVs, and sub-objects. + */ + +struct pcep_object_open *pcep_obj_create_open(uint8_t keepalive, + uint8_t deadtimer, uint8_t sid, + double_linked_list *tlv_list); +struct pcep_object_rp *pcep_obj_create_rp(uint8_t priority, bool flag_r, + bool flag_b, bool flag_s, + bool flag_of, uint32_t reqid, + double_linked_list *tlv_list); +struct pcep_object_notify * +pcep_obj_create_notify(enum pcep_notification_types notification_type, + enum pcep_notification_values notification_value); +struct pcep_object_nopath * +pcep_obj_create_nopath(uint8_t ni, bool flag_c, + enum pcep_nopath_tlv_err_codes error_code); +struct pcep_object_association_ipv4 * +pcep_obj_create_association_ipv4(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in_addr src); +struct pcep_object_association_ipv6 * +pcep_obj_create_association_ipv6(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in6_addr src); +struct pcep_object_endpoints_ipv4 * +pcep_obj_create_endpoint_ipv4(const struct in_addr *src_ipv4, + const struct in_addr *dst_ipv4); +struct pcep_object_endpoints_ipv6 * +pcep_obj_create_endpoint_ipv6(const struct in6_addr *src_ipv6, + const struct in6_addr *dst_ipv6); +struct pcep_object_bandwidth *pcep_obj_create_bandwidth(float bandwidth); +struct pcep_object_metric *pcep_obj_create_metric(enum pcep_metric_types type, + bool flag_b, bool flag_c, + float value); +struct pcep_object_lspa * +pcep_obj_create_lspa(uint32_t exclude_any, uint32_t include_any, + uint32_t include_all, uint8_t setup_priority, + uint8_t holding_priority, bool flag_local_protection); +struct pcep_object_svec * +pcep_obj_create_svec(bool srlg, bool node, bool link, + double_linked_list *request_id_list); +struct pcep_object_error * +pcep_obj_create_error(enum pcep_error_type error_type, + enum pcep_error_value error_value); +struct pcep_object_close *pcep_obj_create_close(enum pcep_close_reason reason); +struct pcep_object_srp *pcep_obj_create_srp(bool lsp_remove, + uint32_t srp_id_number, + double_linked_list *tlv_list); +struct pcep_object_lsp * +pcep_obj_create_lsp(uint32_t plsp_id, enum pcep_lsp_operational_status status, + bool c_flag, bool a_flag, bool r_flag, bool s_flag, + bool d_flag, double_linked_list *tlv_list); +struct pcep_object_vendor_info * +pcep_obj_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_spec_info); +struct pcep_object_inter_layer * +pcep_obj_create_inter_layer(bool flag_i, bool flag_m, bool flag_t); +struct pcep_object_switch_layer * +pcep_obj_create_switch_layer(double_linked_list *switch_layer_rows); +struct pcep_object_req_adap_cap * +pcep_obj_create_req_adap_cap(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding); +struct pcep_object_server_indication * +pcep_obj_create_server_indication(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding, + double_linked_list *tlv_list); +struct pcep_object_objective_function * +pcep_obj_create_objective_function(uint16_t of_code, + double_linked_list *tlv_list); + +/* Route Object (Explicit ero, Reported rro, and Include iro) functions + * First, the sub-objects should be created and appended to a + * double_linked_list, then call one of these Route Object creation functions + * with the subobj list */ +struct pcep_object_ro *pcep_obj_create_ero(double_linked_list *ero_list); +struct pcep_object_ro *pcep_obj_create_rro(double_linked_list *rro_list); +struct pcep_object_ro *pcep_obj_create_iro(double_linked_list *iro_list); +/* Route Object sub-object creation functions */ +struct pcep_ro_subobj_ipv4 * +pcep_obj_create_ro_subobj_ipv4(bool loose_hop, const struct in_addr *ro_ipv4, + uint8_t prefix_len, bool flag_local_prot); +struct pcep_ro_subobj_ipv6 * +pcep_obj_create_ro_subobj_ipv6(bool loose_hop, const struct in6_addr *ro_ipv6, + uint8_t prefix_len, bool flag_local_prot); +struct pcep_ro_subobj_unnum * +pcep_obj_create_ro_subobj_unnum(struct in_addr *router_id, uint32_t if_id); +struct pcep_ro_subobj_32label * +pcep_obj_create_ro_subobj_32label(bool flag_global_label, uint8_t class_type, + uint32_t label); +struct pcep_ro_subobj_asn *pcep_obj_create_ro_subobj_asn(uint16_t asn); + +/* SR ERO and SR RRO creation functions for different NAI (Node/Adj ID) types. + * - The loose_hop is only used for sr ero and must always be false for sr rro. + * - The NAI value will be set internally, depending on which function is used. + * m_flag: + * - If this flag is true, the SID value represents an MPLS label stack + * entry as specified in [RFC3032]. Otherwise, the SID value is an + * administratively configured value which represents an index into + * an MPLS label space (either SRGB or SRLB) per [RFC8402]. + * c_flag: + * - If the M flag and the C flag are both true, then the TC, S, and TTL + * fields in the MPLS label stack entry are specified by the PCE. However, + * a PCC MAY choose to override these values according to its local policy + * and MPLS forwarding rules. + * - If the M flag is true but the C flag is false, then the TC, S, and TTL + * fields MUST be ignored by the PCC. + * - The PCC MUST set these fields according to its local policy and MPLS + * forwarding rules. + * - If the M flag is false then the C bit MUST be false. */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_nonai(bool loose_hop, + uint32_t sid, + bool c_flag, + bool m_flag); + +/* The ipv4_node_id will be copied internally */ +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv4_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *ipv4_node_id); +/* The ipv6_node_id will be copied internally */ +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv6_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *ipv6_node_id); +/* The local_ipv4 and remote_ipv4 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *local_ipv4, struct in_addr *remote_ipv4); +/* The local_ipv6 and remote_ipv6 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, struct in6_addr *remote_ipv6); +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + uint32_t local_node_id, uint32_t local_if_id, uint32_t remote_node_id, + uint32_t remote_if_id); +/* The local_ipv6 and remote_ipv6 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, uint32_t local_if_id, + struct in6_addr *remote_ipv6, uint32_t remote_if_id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_objects_encoding.c b/pceplib/pcep_msg_objects_encoding.c new file mode 100644 index 0000000000..d40b840869 --- /dev/null +++ b/pceplib/pcep_msg_objects_encoding.c @@ -0,0 +1,1720 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP Objects. + */ + +#include +#include + +#include "pcep_msg_objects.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_object_header(struct pcep_object_header *object_hdr, + uint16_t object_length, uint8_t *buf); +void pcep_decode_object_hdr(const uint8_t *obj_buf, + struct pcep_object_header *obj_hdr); +void set_ro_subobj_fields(struct pcep_object_ro_subobj *subobj, bool flag_l, + uint8_t subobj_type); + +/* + * forward declarations for initialize_object_encoders() + */ +uint16_t pcep_encode_obj_open(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_rp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_nopath(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_endpoints(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_association(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_bandwidth(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_metric(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_ro(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_lspa(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_svec(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_notify(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_error(struct pcep_object_header *error, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_close(struct pcep_object_header *close, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_srp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_lsp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_vendor_info(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_inter_layer(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_switch_layer(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_req_adap_cap(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_server_ind(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_objective_function(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +typedef uint16_t (*object_encoder_funcptr)(struct pcep_object_header *, + struct pcep_versioning *versioning, + uint8_t *buf); + +#define MAX_OBJECT_ENCODER_INDEX 64 + +#define PCEP_ENCODERS_ARGS \ + struct pcep_object_header *, struct pcep_versioning *versioning, \ + uint8_t *buf +uint16_t (*const object_encoders[MAX_OBJECT_ENCODER_INDEX])( + PCEP_ENCODERS_ARGS) = { + [PCEP_OBJ_CLASS_OPEN] = pcep_encode_obj_open, + [PCEP_OBJ_CLASS_RP] = pcep_encode_obj_rp, + [PCEP_OBJ_CLASS_NOPATH] = pcep_encode_obj_nopath, + [PCEP_OBJ_CLASS_ENDPOINTS] = pcep_encode_obj_endpoints, + [PCEP_OBJ_CLASS_BANDWIDTH] = pcep_encode_obj_bandwidth, + [PCEP_OBJ_CLASS_METRIC] = pcep_encode_obj_metric, + [PCEP_OBJ_CLASS_ERO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_RRO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_LSPA] = pcep_encode_obj_lspa, + [PCEP_OBJ_CLASS_IRO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_SVEC] = pcep_encode_obj_svec, + [PCEP_OBJ_CLASS_NOTF] = pcep_encode_obj_notify, + [PCEP_OBJ_CLASS_ERROR] = pcep_encode_obj_error, + [PCEP_OBJ_CLASS_CLOSE] = pcep_encode_obj_close, + [PCEP_OBJ_CLASS_LSP] = pcep_encode_obj_lsp, + [PCEP_OBJ_CLASS_SRP] = pcep_encode_obj_srp, + [PCEP_OBJ_CLASS_ASSOCIATION] = pcep_encode_obj_association, + [PCEP_OBJ_CLASS_INTER_LAYER] = pcep_encode_obj_inter_layer, + [PCEP_OBJ_CLASS_SWITCH_LAYER] = pcep_encode_obj_switch_layer, + [PCEP_OBJ_CLASS_REQ_ADAP_CAP] = pcep_encode_obj_req_adap_cap, + [PCEP_OBJ_CLASS_SERVER_IND] = pcep_encode_obj_server_ind, + [PCEP_OBJ_CLASS_VENDOR_INFO] = pcep_encode_obj_vendor_info, + [PCEP_OBJ_CLASS_OF] = pcep_encode_obj_objective_function, +}; +/* + * forward declarations for initialize_object_decoders() + */ +struct pcep_object_header *pcep_decode_obj_open(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_rp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_nopath(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_endpoints(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_association(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_bandwidth(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_metric(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_ro(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_lspa(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_svec(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_notify(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_error(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_close(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_srp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_lsp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_vendor_info(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_inter_layer(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_switch_layer(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_req_adap_cap(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_server_ind(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_objective_function(struct pcep_object_header *hdr, + const uint8_t *buf); +typedef struct pcep_object_header *(*object_decoder_funcptr)( + struct pcep_object_header *, const uint8_t *buf); + +#define PCEP_DECODERS_ARGS struct pcep_object_header *, const uint8_t *buf + +struct pcep_object_header *(*const object_decoders[MAX_OBJECT_ENCODER_INDEX])( + PCEP_DECODERS_ARGS) = { + [PCEP_OBJ_CLASS_OPEN] = pcep_decode_obj_open, + [PCEP_OBJ_CLASS_RP] = pcep_decode_obj_rp, + [PCEP_OBJ_CLASS_NOPATH] = pcep_decode_obj_nopath, + [PCEP_OBJ_CLASS_ENDPOINTS] = pcep_decode_obj_endpoints, + [PCEP_OBJ_CLASS_BANDWIDTH] = pcep_decode_obj_bandwidth, + [PCEP_OBJ_CLASS_METRIC] = pcep_decode_obj_metric, + [PCEP_OBJ_CLASS_ERO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_RRO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_LSPA] = pcep_decode_obj_lspa, + [PCEP_OBJ_CLASS_IRO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_SVEC] = pcep_decode_obj_svec, + [PCEP_OBJ_CLASS_NOTF] = pcep_decode_obj_notify, + [PCEP_OBJ_CLASS_ERROR] = pcep_decode_obj_error, + [PCEP_OBJ_CLASS_CLOSE] = pcep_decode_obj_close, + [PCEP_OBJ_CLASS_LSP] = pcep_decode_obj_lsp, + [PCEP_OBJ_CLASS_SRP] = pcep_decode_obj_srp, + [PCEP_OBJ_CLASS_ASSOCIATION] = pcep_decode_obj_association, + [PCEP_OBJ_CLASS_INTER_LAYER] = pcep_decode_obj_inter_layer, + [PCEP_OBJ_CLASS_SWITCH_LAYER] = pcep_decode_obj_switch_layer, + [PCEP_OBJ_CLASS_REQ_ADAP_CAP] = pcep_decode_obj_req_adap_cap, + [PCEP_OBJ_CLASS_SERVER_IND] = pcep_decode_obj_server_ind, + [PCEP_OBJ_CLASS_VENDOR_INFO] = pcep_decode_obj_vendor_info, + [PCEP_OBJ_CLASS_OF] = pcep_decode_obj_objective_function, +}; + +/* Object lengths, including the Object Header. + * Used by pcep_object_get_length() and pcep_object_has_tlvs() */ +static uint8_t pcep_object_class_lengths[] = { + 0, /* Object class 0 unused */ + 8, /* PCEP_OBJ_CLASS_OPEN = 1 */ + 12, /* PCEP_OBJ_CLASS_RP = 2 */ + 16, /* PCEP_OBJ_CLASS_NOPATH = 3, includes 8 for mandatory TLV */ + 0, /* PCEP_OBJ_CLASS_ENDPOINTS = 4, could be ipv4 or ipv6, setting to 0 + */ + 8, /* PCEP_OBJ_CLASS_BANDWIDTH = 5 */ + 12, /* PCEP_OBJ_CLASS_METRIC = 6 */ + 0, /* PCEP_OBJ_CLASS_ERO = 7, setting 0, ROs cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_RRO = 8, setting 0, ROs cannot have TLVs */ + 20, /* PCEP_OBJ_CLASS_LSPA = 9 */ + 0, /* PCEP_OBJ_CLASS_IRO = 10, setting 0, ROs cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_SVEC = 11, SVECs cannot have TLVs */ + 8, /* PCEP_OBJ_CLASS_NOTF = 12 */ + 8, /* PCEP_OBJ_CLASS_ERROR = 13 */ + 0, /* Object class 14 unused */ + 8, /* PCEP_OBJ_CLASS_CLOSE = 15 */ + 0, 0, 0, 0, 0, /* Object classes 16 - 20 are not used */ + 8, /* PCEP_OBJ_CLASS_OF = 21 */ + 0, 0, 0, 0, 0, /* Object classes 22 - 26 are not used */ + 0, 0, 0, 0, 0, /* Object classes 27 - 31 are not used */ + 8, /* PCEP_OBJ_CLASS_LSP = 32 */ + 12, /* PCEP_OBJ_CLASS_SRP = 33 */ + 12, /* PCEP_OBJ_CLASS_VENDOR_INFO = 34 */ + 0, /* Object class 35 unused */ + 0, /* PCEP_OBJ_CLASS_INTER_LAYER = 36, cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_SWITCH_LAYER = 37, cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_REQ_ADAP_CAP = 38, cannot have TLVs*/ + 8, /* PCEP_OBJ_CLASS_SERVER_IND = 39 */ + 0, /* PCEP_OBJ_CLASS_ASSOCIATION = 40, cannot have TLVs */ +}; + +/* + * The TLVs can have strange length values, since they do not include padding in + * the TLV header length, but that extra padding must be taken into account by + * the enclosing object by rounding up to the next 4 byte boundary. + * Example returned lengths: + * normalize_length(4) = 4, normalize_length(5) = 8, normalize_length(6) + * = 8, normalize_length(7) = 8, normalize_length(8) = 8 + * normalize_length(9) = 12, normalize_length(10) = 12, normalize_length(11) = + * 12, normalize_length(12) = 12, normalize_length(13) = 13... + */ +uint16_t normalize_pcep_tlv_length(uint16_t length) +{ + return (length % 4 == 0) ? length : (length + (4 - (length % 4))); +} + +/* + * Encoding functions + */ +uint16_t pcep_encode_object(struct pcep_object_header *object_hdr, + struct pcep_versioning *versioning, uint8_t *buf) +{ + + if (object_hdr->object_class >= MAX_OBJECT_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot encode unknown Object class [%d]", + __func__, object_hdr->object_class); + return 0; + } + + object_encoder_funcptr obj_encoder = + object_encoders[object_hdr->object_class]; + if (obj_encoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object encoder found for Object class [%d]", + __func__, object_hdr->object_class); + return 0; + } + + uint16_t object_length = OBJECT_HEADER_LENGTH + + obj_encoder(object_hdr, versioning, + buf + OBJECT_HEADER_LENGTH); + double_linked_list_node *node = + (object_hdr->tlv_list == NULL ? NULL + : object_hdr->tlv_list->head); + for (; node != NULL; node = node->next_node) { + /* Returns the length of the TLV, including the TLV header */ + object_length += pcep_encode_tlv( + (struct pcep_object_tlv_header *)node->data, versioning, + buf + object_length); + } + object_length = normalize_pcep_tlv_length(object_length); + write_object_header(object_hdr, object_length, buf); + object_hdr->encoded_object = buf; + object_hdr->encoded_object_length = object_length; + + return object_length; +} + + +/* Object Header + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Object-Class | OT |Res|P|I| Object Length (bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * // (Object body) // + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +void write_object_header(struct pcep_object_header *object_hdr, + uint16_t object_length, uint8_t *buf) +{ + buf[0] = object_hdr->object_class; + buf[1] = ((object_hdr->object_type << 4) + | (object_hdr->flag_p ? OBJECT_HEADER_FLAG_P : 0x00) + | (object_hdr->flag_i ? OBJECT_HEADER_FLAG_I : 0x00)); + uint16_t net_order_length = htons(object_length); + memcpy(buf + 2, &net_order_length, sizeof(net_order_length)); +} + + +/* + * Functions to encode objects + * - they will be passed a pointer to a buffer to write the object body, + * which is past the object header. + * - they should return the object body length, not including the object header + * length. + */ + +uint16_t pcep_encode_obj_open(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_open *open = (struct pcep_object_open *)hdr; + obj_body_buf[0] = (open->open_version << 5) & 0xe0; + obj_body_buf[1] = open->open_keepalive; + obj_body_buf[2] = open->open_deadtimer; + obj_body_buf[3] = open->open_sid; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_rp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_rp *rp = (struct pcep_object_rp *)hdr; + obj_body_buf[3] = ((rp->flag_strict ? OBJECT_RP_FLAG_O : 0x00) + | (rp->flag_bidirectional ? OBJECT_RP_FLAG_B : 0x00) + | (rp->flag_reoptimization ? OBJECT_RP_FLAG_R : 0x00) + | (rp->flag_of ? OBJECT_RP_FLAG_OF : 0x00) + | (rp->priority & 0x07)); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + *uint32_ptr = htonl(rp->request_id); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_notify(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_notify *notify = (struct pcep_object_notify *)hdr; + obj_body_buf[2] = notify->notification_type; + obj_body_buf[3] = notify->notification_value; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_nopath(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_nopath *nopath = (struct pcep_object_nopath *)hdr; + obj_body_buf[0] = nopath->ni; + obj_body_buf[1] = ((nopath->flag_c) ? OBJECT_NOPATH_FLAG_C : 0x00); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_association(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + uint16_t *uint16_ptr = (uint16_t *)obj_body_buf; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + if (hdr->object_type == PCEP_OBJ_TYPE_ASSOCIATION_IPV4) { + struct pcep_object_association_ipv4 *ipv4 = + (struct pcep_object_association_ipv4 *)hdr; + obj_body_buf[3] = + (ipv4->R_flag ? OBJECT_ASSOCIATION_FLAG_R : 0x00); + uint16_ptr[2] = htons(ipv4->association_type); + uint16_ptr[3] = htons(ipv4->association_id); + uint32_ptr[2] = ipv4->src.s_addr; + + return LENGTH_3WORDS; + } else { + struct pcep_object_association_ipv6 *ipv6 = + (struct pcep_object_association_ipv6 *)hdr; + obj_body_buf[3] = + (ipv6->R_flag ? OBJECT_ASSOCIATION_FLAG_R : 0x00); + uint16_ptr[2] = htons(ipv6->association_type); + uint16_ptr[3] = htons(ipv6->association_id); + memcpy(uint32_ptr, &ipv6->src, sizeof(struct in6_addr)); + + return LENGTH_6WORDS; + } +} + +uint16_t pcep_encode_obj_endpoints(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + struct pcep_object_endpoints_ipv4 *ipv4 = + (struct pcep_object_endpoints_ipv4 *)hdr; + uint32_ptr[0] = ipv4->src_ipv4.s_addr; + uint32_ptr[1] = ipv4->dst_ipv4.s_addr; + + return LENGTH_2WORDS; + } else { + struct pcep_object_endpoints_ipv6 *ipv6 = + (struct pcep_object_endpoints_ipv6 *)hdr; + memcpy(uint32_ptr, &ipv6->src_ipv6, sizeof(struct in6_addr)); + memcpy(&uint32_ptr[4], &ipv6->dst_ipv6, + sizeof(struct in6_addr)); + + return LENGTH_8WORDS; + } +} + +uint16_t pcep_encode_obj_bandwidth(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_bandwidth *bandwidth = + (struct pcep_object_bandwidth *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + /* Seems like the compiler doesnt correctly copy the float, so memcpy() + * it */ + memcpy(uint32_ptr, &(bandwidth->bandwidth), sizeof(uint32_t)); + *uint32_ptr = htonl(*uint32_ptr); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_metric(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_metric *metric = (struct pcep_object_metric *)hdr; + obj_body_buf[2] = ((metric->flag_c ? OBJECT_METRIC_FLAC_C : 0x00) + | (metric->flag_b ? OBJECT_METRIC_FLAC_B : 0x00)); + obj_body_buf[3] = metric->type; + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + /* Seems like the compiler doesnt correctly copy the float, so memcpy() + * it */ + memcpy(uint32_ptr, &(metric->value), sizeof(uint32_t)); + *uint32_ptr = htonl(*uint32_ptr); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_lspa(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_lspa *lspa = (struct pcep_object_lspa *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl(lspa->lspa_exclude_any); + uint32_ptr[1] = htonl(lspa->lspa_include_any); + uint32_ptr[2] = htonl(lspa->lspa_include_all); + obj_body_buf[12] = lspa->setup_priority; + obj_body_buf[13] = lspa->holding_priority; + obj_body_buf[14] = + (lspa->flag_local_protection ? OBJECT_LSPA_FLAG_L : 0x00); + + return LENGTH_4WORDS; +} + +uint16_t pcep_encode_obj_svec(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_svec *svec = (struct pcep_object_svec *)hdr; + obj_body_buf[3] = + ((svec->flag_srlg_diverse ? OBJECT_SVEC_FLAG_S : 0x00) + | (svec->flag_node_diverse ? OBJECT_SVEC_FLAG_N : 0x00) + | (svec->flag_link_diverse ? OBJECT_SVEC_FLAG_L : 0x00)); + + if (svec->request_id_list == NULL) { + return LENGTH_1WORD; + } + + int index = 1; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + double_linked_list_node *node = svec->request_id_list->head; + for (; node != NULL; node = node->next_node) { + uint32_ptr[index++] = htonl(*((uint32_t *)(node->data))); + } + + return LENGTH_1WORD + + (svec->request_id_list->num_entries * sizeof(uint32_t)); +} + +uint16_t pcep_encode_obj_error(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_error *error = (struct pcep_object_error *)hdr; + obj_body_buf[2] = error->error_type; + obj_body_buf[3] = error->error_value; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_close(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_close *close = (struct pcep_object_close *)hdr; + obj_body_buf[3] = close->reason; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_srp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_srp *srp = (struct pcep_object_srp *)hdr; + obj_body_buf[3] = (srp->flag_lsp_remove ? OBJECT_SRP_FLAG_R : 0x00); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + *uint32_ptr = htonl(srp->srp_id_number); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_lsp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl((lsp->plsp_id << 12) & 0xfffff000); + obj_body_buf[3] = ((lsp->flag_c ? OBJECT_LSP_FLAG_C : 0x00) + | ((lsp->operational_status << 4) & 0x70) + | (lsp->flag_a ? OBJECT_LSP_FLAG_A : 0x00) + | (lsp->flag_r ? OBJECT_LSP_FLAG_R : 0x00) + | (lsp->flag_s ? OBJECT_LSP_FLAG_S : 0x00) + | (lsp->flag_d ? OBJECT_LSP_FLAG_D : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_vendor_info(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl(obj->enterprise_number); + uint32_ptr[1] = htonl(obj->enterprise_specific_info); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_inter_layer(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)hdr; + obj_body_buf[3] = ((obj->flag_i ? OBJECT_INTER_LAYER_FLAG_I : 0x00) + | (obj->flag_m ? OBJECT_INTER_LAYER_FLAG_M : 0x00) + | (obj->flag_t ? OBJECT_INTER_LAYER_FLAG_T : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_switch_layer(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)hdr; + uint8_t buf_index = 0; + + double_linked_list_node *node = obj->switch_layer_rows->head; + while (node != NULL) { + struct pcep_object_switch_layer_row *row = node->data; + if (row == NULL) { + break; + } + + obj_body_buf[buf_index] = row->lsp_encoding_type; + obj_body_buf[buf_index + 1] = row->switching_type; + obj_body_buf[buf_index + 3] = + (row->flag_i ? OBJECT_SWITCH_LAYER_FLAG_I : 0x00); + + buf_index += LENGTH_1WORD; + } + + return buf_index; +} + +uint16_t pcep_encode_obj_req_adap_cap(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)hdr; + + obj_body_buf[0] = obj->switching_capability; + obj_body_buf[1] = obj->encoding; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_server_ind(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *)hdr; + + obj_body_buf[0] = obj->switching_capability; + obj_body_buf[1] = obj->encoding; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_objective_function(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *)hdr; + + uint16_t *uint16_ptr = (uint16_t *)obj_body_buf; + *uint16_ptr = htons(obj->of_code); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_ro(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_ro *ro = (struct pcep_object_ro *)hdr; + if (ro == NULL || ro->sub_objects == NULL) { + return 0; + } + + /* RO Subobject format + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + * |L| Type | Length | (Subobject contents) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + */ + + uint16_t index = 0; + double_linked_list_node *node = ro->sub_objects->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = node->data; + obj_body_buf[index++] = + ((ro_subobj->flag_subobj_loose_hop ? 0x80 : 0x00) + | (ro_subobj->ro_subobj_type)); + /* The length will be written below, depending on the subobj + * type */ + uint8_t *length_ptr = &(obj_body_buf[index++]); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + index); + + /* - The index has already been incremented past the header, + * and now points to the ro_subobj body. Below it just needs + * to be incremented past the body. + * + * - Each section below needs to write the total length, + * including the 2 byte subobj header. */ + + switch (ro_subobj->ro_subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: { + struct pcep_ro_subobj_ipv4 *ipv4 = + (struct pcep_ro_subobj_ipv4 *)ro_subobj; + uint32_ptr[0] = ipv4->ip_addr.s_addr; + index += LENGTH_1WORD; + obj_body_buf[index++] = ipv4->prefix_length; + obj_body_buf[index++] = + (ipv4->flag_local_protection + ? OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT + : 0x00); + *length_ptr = LENGTH_2WORDS; + } break; + + case RO_SUBOBJ_TYPE_IPV6: { + struct pcep_ro_subobj_ipv6 *ipv6 = + (struct pcep_ro_subobj_ipv6 *)ro_subobj; + encode_ipv6(&ipv6->ip_addr, uint32_ptr); + index += LENGTH_4WORDS; + obj_body_buf[index++] = ipv6->prefix_length; + obj_body_buf[index++] = + (ipv6->flag_local_protection + ? OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT + : 0x00); + *length_ptr = LENGTH_5WORDS; + } break; + + case RO_SUBOBJ_TYPE_LABEL: { + struct pcep_ro_subobj_32label *label = + (struct pcep_ro_subobj_32label *)ro_subobj; + obj_body_buf[index++] = + (label->flag_global_label + ? OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL + : 0x00); + obj_body_buf[index++] = label->class_type; + uint32_ptr = (uint32_t *)(obj_body_buf + index); + *uint32_ptr = htonl(label->label); + *length_ptr = LENGTH_2WORDS; + index += LENGTH_1WORD; + } break; + + case RO_SUBOBJ_TYPE_UNNUM: { + struct pcep_ro_subobj_unnum *unum = + (struct pcep_ro_subobj_unnum *)ro_subobj; + index += 2; /* increment past 2 reserved bytes */ + uint32_ptr = (uint32_t *)(obj_body_buf + index); + uint32_ptr[0] = unum->router_id.s_addr; + uint32_ptr[1] = htonl(unum->interface_id); + *length_ptr = LENGTH_3WORDS; + index += LENGTH_2WORDS; + } break; + + case RO_SUBOBJ_TYPE_ASN: { + struct pcep_ro_subobj_asn *asn = + (struct pcep_ro_subobj_asn *)ro_subobj; + uint16_t *uint16_ptr = + (uint16_t *)(obj_body_buf + index); + *uint16_ptr = htons(asn->asn); + *length_ptr = LENGTH_1WORD; + index += 2; + } break; + + case RO_SUBOBJ_TYPE_SR: { + /* SR-ERO subobject format + * + * 0 1 2 3 0 1 2 3 4 + * 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |L| Type=36 | Length | NT | Flags + * |F|S|C|M| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SID (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * // NAI (variable, optional) // + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + struct pcep_ro_subobj_sr *sr_subobj = + (struct pcep_ro_subobj_sr *)ro_subobj; + obj_body_buf[index++] = + ((sr_subobj->nai_type << 4) & 0xf0); + obj_body_buf[index++] = + ((sr_subobj->flag_f ? OBJECT_SUBOBJ_SR_FLAG_F + : 0x00) + | (sr_subobj->flag_s ? OBJECT_SUBOBJ_SR_FLAG_S + : 0x00) + | (sr_subobj->flag_c ? OBJECT_SUBOBJ_SR_FLAG_C + : 0x00) + | (sr_subobj->flag_m ? OBJECT_SUBOBJ_SR_FLAG_M + : 0x00)); + uint32_ptr = (uint32_t *)(obj_body_buf + index); + /* Start with LENGTH_1WORD for the SubObj HDR + NT + + * Flags */ + uint8_t sr_base_length = LENGTH_1WORD; + /* If the sid_absent flag is true, then dont convert the + * sid */ + if (sr_subobj->flag_s == false) { + uint32_ptr[0] = htonl(sr_subobj->sid); + index += LENGTH_1WORD; + uint32_ptr = (uint32_t *)(obj_body_buf + index); + sr_base_length += LENGTH_1WORD; + } + + /* The lengths below need to include: + * - sr_base_length: set above to include SR SubObj Hdr + * and the SID if present + * - Number of bytes written to the NAI + * The index will only be incremented below by the + * number of bytes written to the NAI, since the RO SR + * subobj header and the SID have already been written. + */ + + double_linked_list_node *nai_node = + (sr_subobj->nai_list == NULL + ? NULL + : sr_subobj->nai_list->head); + if (nai_node == NULL) { + if (sr_subobj->nai_type + == PCEP_SR_SUBOBJ_NAI_ABSENT) { + *length_ptr = sr_base_length; + continue; + } else { + return 0; + } + } + switch (sr_subobj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_1WORD; + index += LENGTH_1WORD; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + *length_ptr = sr_base_length + LENGTH_4WORDS; + index += LENGTH_4WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[1] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[2] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[3] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_4WORDS; + index += LENGTH_4WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[1] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_2WORDS; + index += LENGTH_2WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + nai_node = nai_node->next_node; + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr + 4); + *length_ptr = sr_base_length + LENGTH_8WORDS; + index += LENGTH_8WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + nai_node = nai_node->next_node; + uint32_ptr[4] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr + 5); + nai_node = nai_node->next_node; + uint32_ptr[9] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_10WORDS; + index += LENGTH_10WORDS; + break; + + default: + break; + } + } break; + + default: + break; + } + } + + return index; +} + +void encode_ipv6(struct in6_addr *src_ipv6, uint32_t *dst) +{ + memcpy(dst, src_ipv6, sizeof(struct in6_addr)); +} + +/* + * Decoding functions. + */ + +void pcep_decode_object_hdr(const uint8_t *obj_buf, + struct pcep_object_header *obj_hdr) +{ + memset(obj_hdr, 0, sizeof(struct pcep_object_header)); + + obj_hdr->object_class = obj_buf[0]; + obj_hdr->object_type = (obj_buf[1] >> 4) & 0x0f; + obj_hdr->flag_p = (obj_buf[1] & OBJECT_HEADER_FLAG_P); + obj_hdr->flag_i = (obj_buf[1] & OBJECT_HEADER_FLAG_I); + uint16_t net_order_length; + memcpy(&net_order_length, obj_buf + 2, sizeof(net_order_length)); + obj_hdr->encoded_object_length = ntohs(net_order_length); + obj_hdr->encoded_object = obj_buf; +} + +uint16_t pcep_object_get_length(enum pcep_object_classes object_class, + enum pcep_object_types object_type) +{ + uint8_t object_length = pcep_object_class_lengths[object_class]; + if (object_length == 0) { + if (object_class == PCEP_OBJ_CLASS_ENDPOINTS) { + if (object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + return 12; + } else if (object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + return 36; + } + } + + return 0; + } + + return object_length; +} + +uint16_t pcep_object_get_length_by_hdr(struct pcep_object_header *object_hdr) +{ + return (pcep_object_get_length(object_hdr->object_class, + object_hdr->object_type)); +} + +bool pcep_object_has_tlvs(struct pcep_object_header *object_hdr) +{ + uint8_t object_length = pcep_object_get_length_by_hdr(object_hdr); + if (object_length == 0) { + return false; + } + + return (object_hdr->encoded_object_length - object_length) > 0; +} + +struct pcep_object_header *pcep_decode_object(const uint8_t *obj_buf) +{ + + struct pcep_object_header object_hdr; + /* Only initializes and decodes the Object Header: class, type, flags, + * and length */ + pcep_decode_object_hdr(obj_buf, &object_hdr); + + if (object_hdr.object_class >= MAX_OBJECT_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot decode unknown Object class [%d]", + __func__, object_hdr.object_class); + return NULL; + } + + object_decoder_funcptr obj_decoder = + object_decoders[object_hdr.object_class]; + if (obj_decoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object decoder found for Object class [%d]", + __func__, object_hdr.object_class); + return NULL; + } + + /* The object decoders will start decoding the object body, if + * anything from the header is needed, they have the object_hdr */ + struct pcep_object_header *object = + obj_decoder(&object_hdr, obj_buf + OBJECT_HEADER_LENGTH); + if (object == NULL) { + pcep_log(LOG_INFO, "%s: Unable to decode Object class [%d].", + __func__, object_hdr.object_class); + return NULL; + } + + if (pcep_object_has_tlvs(&object_hdr)) { + object->tlv_list = dll_initialize(); + int num_iterations = 0; + uint16_t tlv_index = pcep_object_get_length_by_hdr(&object_hdr); + while ((object->encoded_object_length - tlv_index) > 0 + && num_iterations++ < MAX_ITERATIONS) { + struct pcep_object_tlv_header *tlv = + pcep_decode_tlv(obj_buf + tlv_index); + if (tlv == NULL) { + /* TODO should we do anything else here ? */ + return object; + } + + /* The TLV length does not include the TLV header */ + tlv_index += normalize_pcep_tlv_length( + tlv->encoded_tlv_length + TLV_HEADER_LENGTH); + dll_append(object->tlv_list, tlv); + } + } + + return object; +} + +static struct pcep_object_header * +common_object_create(struct pcep_object_header *hdr, uint16_t new_obj_length) +{ + struct pcep_object_header *new_object = + pceplib_malloc(PCEPLIB_MESSAGES, new_obj_length); + memset(new_object, 0, new_obj_length); + memcpy(new_object, hdr, sizeof(struct pcep_object_header)); + + return new_object; +} + +/* + * Decoders + */ + +struct pcep_object_header *pcep_decode_obj_open(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_open *obj = + (struct pcep_object_open *)common_object_create( + hdr, sizeof(struct pcep_object_open)); + + obj->open_version = (obj_buf[0] >> 5) & 0x07; + obj->open_keepalive = obj_buf[1]; + obj->open_deadtimer = obj_buf[2]; + obj->open_sid = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_rp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_rp *obj = + (struct pcep_object_rp *)common_object_create( + hdr, sizeof(struct pcep_object_rp)); + + obj->flag_reoptimization = (obj_buf[3] & OBJECT_RP_FLAG_R); + obj->flag_bidirectional = (obj_buf[3] & OBJECT_RP_FLAG_B); + obj->flag_strict = (obj_buf[3] & OBJECT_RP_FLAG_O); + obj->flag_of = (obj_buf[3] & OBJECT_RP_FLAG_OF); + obj->priority = (obj_buf[3] & 0x07); + obj->request_id = ntohl(*((uint32_t *)(obj_buf + 4))); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_notify(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_notify *obj = + (struct pcep_object_notify *)common_object_create( + hdr, sizeof(struct pcep_object_notify)); + + obj->notification_type = obj_buf[2]; + obj->notification_value = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_nopath(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_nopath *obj = + (struct pcep_object_nopath *)common_object_create( + hdr, sizeof(struct pcep_object_nopath)); + + obj->ni = (obj_buf[0] >> 1); + obj->flag_c = (obj_buf[0] & OBJECT_NOPATH_FLAG_C); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_association(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + uint16_t *uint16_ptr = (uint16_t *)obj_buf; + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + if (hdr->object_type == PCEP_OBJ_TYPE_ASSOCIATION_IPV4) { + struct pcep_object_association_ipv4 *obj = + (struct pcep_object_association_ipv4 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_association_ipv4)); + obj->R_flag = (obj_buf[3] & OBJECT_ASSOCIATION_FLAG_R); + obj->association_type = ntohs(uint16_ptr[2]); + obj->association_id = ntohs(uint16_ptr[3]); + obj->src.s_addr = uint32_ptr[2]; + + return (struct pcep_object_header *)obj; + } else if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + struct pcep_object_association_ipv6 *obj = + (struct pcep_object_association_ipv6 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_association_ipv6)); + + obj->R_flag = (obj_buf[3] & OBJECT_ASSOCIATION_FLAG_R); + obj->association_type = ntohs(uint16_ptr[2]); + obj->association_id = ntohs(uint16_ptr[3]); + memcpy(&obj->src, &uint32_ptr[2], sizeof(struct in6_addr)); + + return (struct pcep_object_header *)obj; + } + + return NULL; +} +struct pcep_object_header * +pcep_decode_obj_endpoints(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + struct pcep_object_endpoints_ipv4 *obj = + (struct pcep_object_endpoints_ipv4 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_endpoints_ipv4)); + obj->src_ipv4.s_addr = uint32_ptr[0]; + obj->dst_ipv4.s_addr = uint32_ptr[1]; + + return (struct pcep_object_header *)obj; + } else if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + struct pcep_object_endpoints_ipv6 *obj = + (struct pcep_object_endpoints_ipv6 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_endpoints_ipv6)); + + memcpy(&obj->src_ipv6, &uint32_ptr[0], sizeof(struct in6_addr)); + memcpy(&obj->dst_ipv6, &uint32_ptr[4], sizeof(struct in6_addr)); + + return (struct pcep_object_header *)obj; + } + + return NULL; +} + +struct pcep_object_header * +pcep_decode_obj_bandwidth(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_bandwidth *obj = + (struct pcep_object_bandwidth *)common_object_create( + hdr, sizeof(struct pcep_object_bandwidth)); + + uint32_t value = ntohl(*((uint32_t *)obj_buf)); + /* Seems like the compiler doesnt correctly copy to the float, so + * memcpy() it */ + memcpy(&obj->bandwidth, &value, sizeof(uint32_t)); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_metric(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_metric *obj = + (struct pcep_object_metric *)common_object_create( + hdr, sizeof(struct pcep_object_metric)); + obj->flag_b = (obj_buf[2] & OBJECT_METRIC_FLAC_B); + obj->flag_c = (obj_buf[2] & OBJECT_METRIC_FLAC_C); + obj->type = obj_buf[3]; + uint32_t value = ntohl(*((uint32_t *)(obj_buf + 4))); + /* Seems like the compiler doesnt correctly copy to the float, so + * memcpy() it */ + memcpy(&obj->value, &value, sizeof(uint32_t)); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_lspa(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_lspa *obj = + (struct pcep_object_lspa *)common_object_create( + hdr, sizeof(struct pcep_object_lspa)); + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + obj->lspa_exclude_any = ntohl(uint32_ptr[0]); + obj->lspa_include_any = ntohl(uint32_ptr[1]); + obj->lspa_include_all = ntohl(uint32_ptr[2]); + obj->setup_priority = obj_buf[12]; + obj->holding_priority = obj_buf[13]; + obj->flag_local_protection = (obj_buf[14] & OBJECT_LSPA_FLAG_L); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_svec(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_svec *obj = + (struct pcep_object_svec *)common_object_create( + hdr, sizeof(struct pcep_object_svec)); + + obj->flag_link_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_L); + obj->flag_node_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_N); + obj->flag_srlg_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_S); + + if (hdr->encoded_object_length > LENGTH_2WORDS) { + obj->request_id_list = dll_initialize(); + uint16_t index = 1; + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + for (; + index < ((hdr->encoded_object_length - LENGTH_2WORDS) / 4); + index++) { + uint32_t *req_id_ptr = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(uint32_t)); + *req_id_ptr = uint32_ptr[index]; + dll_append(obj->request_id_list, req_id_ptr); + } + } + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_error(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_error *obj = + (struct pcep_object_error *)common_object_create( + hdr, sizeof(struct pcep_object_error)); + + obj->error_type = obj_buf[2]; + obj->error_value = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_close(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_close *obj = + (struct pcep_object_close *)common_object_create( + hdr, sizeof(struct pcep_object_close)); + + obj->reason = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_srp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_srp *obj = + (struct pcep_object_srp *)common_object_create( + hdr, sizeof(struct pcep_object_srp)); + + obj->flag_lsp_remove = (obj_buf[3] & OBJECT_SRP_FLAG_R); + obj->srp_id_number = ntohl(*((uint32_t *)(obj_buf + 4))); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_lsp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_lsp *obj = + (struct pcep_object_lsp *)common_object_create( + hdr, sizeof(struct pcep_object_lsp)); + + obj->flag_d = (obj_buf[3] & OBJECT_LSP_FLAG_D); + obj->flag_s = (obj_buf[3] & OBJECT_LSP_FLAG_S); + obj->flag_r = (obj_buf[3] & OBJECT_LSP_FLAG_R); + obj->flag_a = (obj_buf[3] & OBJECT_LSP_FLAG_A); + obj->flag_c = (obj_buf[3] & OBJECT_LSP_FLAG_C); + obj->operational_status = ((obj_buf[3] >> 4) & 0x07); + obj->plsp_id = ((ntohl(*((uint32_t *)obj_buf)) >> 12) & 0x000fffff); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_vendor_info(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)common_object_create( + hdr, sizeof(struct pcep_object_vendor_info)); + obj->enterprise_number = ntohl(*((uint32_t *)(obj_buf))); + obj->enterprise_specific_info = ntohl(*((uint32_t *)(obj_buf + 4))); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_inter_layer(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)common_object_create( + hdr, sizeof(struct pcep_object_inter_layer)); + obj->flag_t = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_T); + obj->flag_m = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_M); + obj->flag_i = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_I); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_switch_layer(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)common_object_create( + hdr, sizeof(struct pcep_object_switch_layer)); + obj->switch_layer_rows = dll_initialize(); + int num_rows = ((hdr->encoded_object_length - 4) / 4); + uint8_t buf_index = 0; + + int i = 0; + for (; i < num_rows; i++) { + struct pcep_object_switch_layer_row *row = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_object_switch_layer_row)); + row->lsp_encoding_type = obj_buf[buf_index]; + row->switching_type = obj_buf[buf_index + 1]; + row->flag_i = + (obj_buf[buf_index + 3] & OBJECT_SWITCH_LAYER_FLAG_I); + dll_append(obj->switch_layer_rows, row); + + buf_index += LENGTH_1WORD; + } + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_req_adap_cap(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)common_object_create( + hdr, sizeof(struct pcep_object_req_adap_cap)); + + obj->switching_capability = obj_buf[0]; + obj->encoding = obj_buf[1]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_server_ind(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *)common_object_create( + hdr, sizeof(struct pcep_object_server_indication)); + + obj->switching_capability = obj_buf[0]; + obj->encoding = obj_buf[1]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_objective_function(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *)common_object_create( + hdr, sizeof(struct pcep_object_objective_function)); + + uint16_t *uint16_ptr = (uint16_t *)obj_buf; + obj->of_code = ntohs(*uint16_ptr); + + return (struct pcep_object_header *)obj; +} + +void set_ro_subobj_fields(struct pcep_object_ro_subobj *subobj, bool flag_l, + uint8_t subobj_type) +{ + subobj->flag_subobj_loose_hop = flag_l; + subobj->ro_subobj_type = subobj_type; +} + +void decode_ipv6(const uint32_t *src, struct in6_addr *dst_ipv6) +{ + memcpy(dst_ipv6, src, sizeof(struct in6_addr)); +} +struct pcep_object_header *pcep_decode_obj_ro(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_ro *obj = + (struct pcep_object_ro *)common_object_create( + hdr, sizeof(struct pcep_object_ro)); + obj->sub_objects = dll_initialize(); + + /* RO Subobject format + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + * |L| Type | Length | (Subobject contents) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + */ + + uint16_t read_count = 0; + int num_sub_objects = 1; + uint32_t *uint32_ptr; + uint16_t obj_body_length = + hdr->encoded_object_length - OBJECT_HEADER_LENGTH; + + while ((obj_body_length - read_count) > OBJECT_RO_SUBOBJ_HEADER_LENGTH + && num_sub_objects < MAX_ITERATIONS) { + num_sub_objects++; + /* Read the Sub-Object Header */ + bool flag_l = (obj_buf[read_count] & 0x80); + uint8_t subobj_type = (obj_buf[read_count++] & 0x7f); + uint8_t subobj_length = obj_buf[read_count++]; + + if (subobj_length <= OBJECT_RO_SUBOBJ_HEADER_LENGTH) { + pcep_log(LOG_INFO, + "%s: Invalid ro subobj type [%d] length [%d]", + __func__, subobj_type, subobj_length); + pceplib_free(PCEPLIB_MESSAGES, obj); + return NULL; + } + + switch (subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: { + struct pcep_ro_subobj_ipv4 *ipv4 = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_ipv4)); + ipv4->ro_subobj.flag_subobj_loose_hop = flag_l; + ipv4->ro_subobj.ro_subobj_type = subobj_type; + uint32_ptr = (uint32_t *)(obj_buf + read_count); + ipv4->ip_addr.s_addr = *uint32_ptr; + read_count += LENGTH_1WORD; + ipv4->prefix_length = obj_buf[read_count++]; + ipv4->flag_local_protection = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + + dll_append(obj->sub_objects, ipv4); + } break; + + case RO_SUBOBJ_TYPE_IPV6: { + struct pcep_ro_subobj_ipv6 *ipv6 = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_ipv6)); + ipv6->ro_subobj.flag_subobj_loose_hop = flag_l; + ipv6->ro_subobj.ro_subobj_type = subobj_type; + decode_ipv6((uint32_t *)obj_buf, &ipv6->ip_addr); + read_count += LENGTH_4WORDS; + ipv6->prefix_length = obj_buf[read_count++]; + ipv6->flag_local_protection = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + + dll_append(obj->sub_objects, ipv6); + } break; + + case RO_SUBOBJ_TYPE_LABEL: { + struct pcep_ro_subobj_32label *label = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_32label)); + label->ro_subobj.flag_subobj_loose_hop = flag_l; + label->ro_subobj.ro_subobj_type = subobj_type; + label->flag_global_label = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL); + label->class_type = obj_buf[read_count++]; + label->label = ntohl(obj_buf[read_count]); + read_count += LENGTH_1WORD; + + dll_append(obj->sub_objects, label); + } break; + + case RO_SUBOBJ_TYPE_UNNUM: { + struct pcep_ro_subobj_unnum *unum = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_unnum)); + unum->ro_subobj.flag_subobj_loose_hop = flag_l; + unum->ro_subobj.ro_subobj_type = subobj_type; + set_ro_subobj_fields( + (struct pcep_object_ro_subobj *)unum, flag_l, + subobj_type); + uint32_ptr = (uint32_t *)(obj_buf + read_count); + unum->interface_id = ntohl(uint32_ptr[0]); + unum->router_id.s_addr = uint32_ptr[1]; + read_count += 2; + + dll_append(obj->sub_objects, unum); + } break; + + case RO_SUBOBJ_TYPE_ASN: { + struct pcep_ro_subobj_asn *asn = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_asn)); + asn->ro_subobj.flag_subobj_loose_hop = flag_l; + asn->ro_subobj.ro_subobj_type = subobj_type; + uint16_t *uint16_ptr = + (uint16_t *)(obj_buf + read_count); + asn->asn = ntohs(*uint16_ptr); + read_count += 2; + + dll_append(obj->sub_objects, asn); + } break; + + case RO_SUBOBJ_TYPE_SR: { + /* SR-ERO subobject format + * + * 0 1 2 3 0 1 2 3 4 + * 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |L| Type=36 | Length | NT | Flags + * |F|S|C|M| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SID (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * // NAI (variable, optional) // + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + struct pcep_ro_subobj_sr *sr_subobj = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_sr)); + sr_subobj->ro_subobj.flag_subobj_loose_hop = flag_l; + sr_subobj->ro_subobj.ro_subobj_type = subobj_type; + dll_append(obj->sub_objects, sr_subobj); + + sr_subobj->nai_list = dll_initialize(); + sr_subobj->nai_type = + ((obj_buf[read_count++] >> 4) & 0x0f); + sr_subobj->flag_f = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_F); + sr_subobj->flag_s = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_S); + sr_subobj->flag_c = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_C); + sr_subobj->flag_m = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_M); + read_count++; + + /* If the sid_absent flag is true, then dont decode the + * sid */ + uint32_ptr = (uint32_t *)(obj_buf + read_count); + if (sr_subobj->flag_s == false) { + sr_subobj->sid = ntohl(*uint32_ptr); + read_count += LENGTH_1WORD; + uint32_ptr += 1; + } + + switch (sr_subobj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = *uint32_ptr; + dll_append(sr_subobj->nai_list, ipv4); + read_count += LENGTH_1WORD; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + read_count += LENGTH_4WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[0]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[1]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[2]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[3]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_4WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[0]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[1]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_2WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + ipv6 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr + LENGTH_4WORDS, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + read_count += LENGTH_8WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[LENGTH_4WORDS]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv6 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr + LENGTH_5WORDS, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[LENGTH_9WORDS]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_10WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_ABSENT: + default: + break; + } + } break; + + default: + pcep_log( + LOG_INFO, + "%s: pcep_decode_obj_ro skipping unrecognized sub-object type [%d]", + __func__, subobj_type); + read_count += subobj_length; + break; + } + } + + return (struct pcep_object_header *)obj; +} diff --git a/pceplib/pcep_msg_tlvs.c b/pceplib/pcep_msg_tlvs.c new file mode 100644 index 0000000000..890da9517f --- /dev/null +++ b/pceplib/pcep_msg_tlvs.c @@ -0,0 +1,464 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message object TLV API. + */ + +#include +#include +#include +#include + +#include "pcep_msg_tlvs.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_memory.h" + +static struct pcep_object_tlv_header * +pcep_tlv_common_create(enum pcep_object_tlv_types type, uint16_t size) +{ + struct pcep_object_tlv_header *tlv = + pceplib_malloc(PCEPLIB_MESSAGES, size); + memset(tlv, 0, size); + tlv->type = type; + + return tlv; +} + +/* + * Open Object TLVs + */ + +struct pcep_object_tlv_stateful_pce_capability * +pcep_tlv_create_stateful_pce_capability( + bool flag_u_lsp_update_capability, bool flag_s_include_db_version, + bool flag_i_lsp_instantiation_capability, bool flag_t_triggered_resync, + bool flag_d_delta_lsp_sync, bool flag_f_triggered_initial_sync) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY, + sizeof(struct + pcep_object_tlv_stateful_pce_capability)); + tlv->flag_u_lsp_update_capability = flag_u_lsp_update_capability; + tlv->flag_s_include_db_version = flag_s_include_db_version; + tlv->flag_i_lsp_instantiation_capability = + flag_i_lsp_instantiation_capability; + tlv->flag_t_triggered_resync = flag_t_triggered_resync; + tlv->flag_d_delta_lsp_sync = flag_d_delta_lsp_sync; + tlv->flag_f_triggered_initial_sync = flag_f_triggered_initial_sync; + + return tlv; +} + +struct pcep_object_tlv_lsp_db_version * +pcep_tlv_create_lsp_db_version(uint64_t lsp_db_version) +{ + struct pcep_object_tlv_lsp_db_version *tlv = + (struct pcep_object_tlv_lsp_db_version *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION, + sizeof(struct pcep_object_tlv_lsp_db_version)); + tlv->lsp_db_version = lsp_db_version; + + return tlv; +} + +struct pcep_object_tlv_speaker_entity_identifier * +pcep_tlv_create_speaker_entity_id(double_linked_list *speaker_entity_id_list) +{ + if (speaker_entity_id_list == NULL) { + return NULL; + } + + if (speaker_entity_id_list->num_entries == 0) { + return NULL; + } + + struct pcep_object_tlv_speaker_entity_identifier *tlv = + (struct pcep_object_tlv_speaker_entity_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID, + sizeof(struct + pcep_object_tlv_speaker_entity_identifier)); + tlv->speaker_entity_id_list = speaker_entity_id_list; + + return tlv; +} + +struct pcep_object_tlv_path_setup_type * +pcep_tlv_create_path_setup_type(uint8_t pst) +{ + struct pcep_object_tlv_path_setup_type *tlv = + (struct pcep_object_tlv_path_setup_type *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE, + sizeof(struct pcep_object_tlv_path_setup_type)); + tlv->path_setup_type = pst; + + return tlv; +} + +struct pcep_object_tlv_path_setup_type_capability * +pcep_tlv_create_path_setup_type_capability(double_linked_list *pst_list, + double_linked_list *sub_tlv_list) +{ + if (pst_list == NULL) { + return NULL; + } + + if (pst_list->num_entries == 0) { + return NULL; + } + + struct pcep_object_tlv_path_setup_type_capability *tlv = + (struct pcep_object_tlv_path_setup_type_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY, + sizeof(struct + pcep_object_tlv_path_setup_type_capability)); + + tlv->pst_list = pst_list; + tlv->sub_tlv_list = sub_tlv_list; + + return tlv; +} + +struct pcep_object_tlv_sr_pce_capability * +pcep_tlv_create_sr_pce_capability(bool flag_n, bool flag_x, + uint8_t max_sid_depth) +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + (struct pcep_object_tlv_sr_pce_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY, + sizeof(struct + pcep_object_tlv_sr_pce_capability)); + tlv->flag_n = flag_n; + tlv->flag_x = flag_x; + tlv->max_sid_depth = max_sid_depth; + + return tlv; +} + +struct pcep_object_tlv_of_list * +pcep_tlv_create_of_list(double_linked_list *of_list) +{ + if (of_list == NULL) { + return NULL; + } + + struct pcep_object_tlv_of_list *tlv = + (struct pcep_object_tlv_of_list *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST, + sizeof(struct pcep_object_tlv_of_list)); + + tlv->of_list = of_list; + + return tlv; +} + +/* + * LSP Object TLVs + */ + +struct pcep_object_tlv_ipv4_lsp_identifier * +pcep_tlv_create_ipv4_lsp_identifiers(struct in_addr *ipv4_tunnel_sender, + struct in_addr *ipv4_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in_addr *extended_tunnel_id) +{ + if (ipv4_tunnel_sender == NULL || ipv4_tunnel_endpoint == NULL) { + return NULL; + } + + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv4_lsp_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS, + sizeof(struct + pcep_object_tlv_ipv4_lsp_identifier)); + tlv->ipv4_tunnel_sender.s_addr = ipv4_tunnel_sender->s_addr; + tlv->ipv4_tunnel_endpoint.s_addr = ipv4_tunnel_endpoint->s_addr; + tlv->lsp_id = lsp_id; + tlv->tunnel_id = tunnel_id; + tlv->extended_tunnel_id.s_addr = + (extended_tunnel_id == NULL ? INADDR_ANY + : extended_tunnel_id->s_addr); + + return tlv; +} + +struct pcep_object_tlv_ipv6_lsp_identifier * +pcep_tlv_create_ipv6_lsp_identifiers(struct in6_addr *ipv6_tunnel_sender, + struct in6_addr *ipv6_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in6_addr *extended_tunnel_id) +{ + if (ipv6_tunnel_sender == NULL || ipv6_tunnel_endpoint == NULL) { + return NULL; + } + + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv6_lsp_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS, + sizeof(struct + pcep_object_tlv_ipv6_lsp_identifier)); + + memcpy(&tlv->ipv6_tunnel_sender, ipv6_tunnel_sender, + sizeof(struct in6_addr)); + + tlv->tunnel_id = tunnel_id; + tlv->lsp_id = lsp_id; + + memcpy(&tlv->extended_tunnel_id, extended_tunnel_id, + sizeof(struct in6_addr)); + + memcpy(&tlv->ipv6_tunnel_endpoint, ipv6_tunnel_endpoint, + sizeof(struct in6_addr)); + + return tlv; +} + +struct pcep_object_tlv_symbolic_path_name * +pcep_tlv_create_symbolic_path_name(const char *symbolic_path_name, + uint16_t symbolic_path_name_length) +{ + /* symbolic_path_name_length should NOT include the null terminator and + * cannot be zero */ + if (symbolic_path_name == NULL || symbolic_path_name_length == 0) { + return NULL; + } + + struct pcep_object_tlv_symbolic_path_name *tlv = + (struct pcep_object_tlv_symbolic_path_name *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME, + sizeof(struct + pcep_object_tlv_symbolic_path_name)); + + uint16_t length = (symbolic_path_name_length > MAX_SYMBOLIC_PATH_NAME) + ? MAX_SYMBOLIC_PATH_NAME + : symbolic_path_name_length; + memcpy(tlv->symbolic_path_name, symbolic_path_name, length); + tlv->symbolic_path_name_length = length; + + return tlv; +} + +struct pcep_object_tlv_lsp_error_code * +pcep_tlv_create_lsp_error_code(enum pcep_tlv_lsp_error_codes lsp_error_code) +{ + struct pcep_object_tlv_lsp_error_code *tlv = + (struct pcep_object_tlv_lsp_error_code *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE, + sizeof(struct pcep_object_tlv_lsp_error_code)); + tlv->lsp_error_code = lsp_error_code; + + return tlv; +} + +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv4_error_spec(struct in_addr *error_node_ip, + uint8_t error_code, uint16_t error_value) +{ + if (error_node_ip == NULL) { + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->c_type = RSVP_ERROR_SPEC_IPV4_CTYPE; + tlv->class_num = RSVP_ERROR_SPEC_CLASS_NUM; + tlv->error_code = error_code; + tlv->error_value = error_value; + tlv->error_spec_ip.ipv4_error_node_address.s_addr = + error_node_ip->s_addr; + + return tlv; +} + +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv6_error_spec(struct in6_addr *error_node_ip, + uint8_t error_code, uint16_t error_value) +{ + if (error_node_ip == NULL) { + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->c_type = RSVP_ERROR_SPEC_IPV6_CTYPE; + tlv->class_num = RSVP_ERROR_SPEC_CLASS_NUM; + tlv->error_code = error_code; + tlv->error_value = error_value; + memcpy(&tlv->error_spec_ip, error_node_ip, sizeof(struct in6_addr)); + + return tlv; +} + +struct pcep_object_tlv_nopath_vector * +pcep_tlv_create_nopath_vector(uint32_t error_code) +{ + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR, + sizeof(struct pcep_object_tlv_nopath_vector)); + + tlv->error_code = error_code; + + return tlv; +} + +struct pcep_object_tlv_vendor_info * +pcep_tlv_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_specific_info) +{ + struct pcep_object_tlv_vendor_info *tlv = + (struct pcep_object_tlv_vendor_info *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_VENDOR_INFO, + sizeof(struct pcep_object_tlv_vendor_info)); + + tlv->enterprise_number = enterprise_number; + tlv->enterprise_specific_info = enterprise_specific_info; + + return tlv; +} + +/* + * SRPAG (SR Association Group) TLVs + */ + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv4(uint32_t color, struct in_addr *ipv4) +{ + struct pcep_object_tlv_srpag_pol_id *tlv = + (struct pcep_object_tlv_srpag_pol_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + sizeof(struct pcep_object_tlv_srpag_pol_id)); + tlv->color = color; + tlv->is_ipv4 = true; + memcpy(&tlv->end_point.ipv4.s_addr, ipv4, sizeof(struct in_addr)); + + return tlv; +} + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv6(uint32_t color, struct in6_addr *ipv6) +{ + struct pcep_object_tlv_srpag_pol_id *tlv = + (struct pcep_object_tlv_srpag_pol_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + sizeof(struct pcep_object_tlv_srpag_pol_id)); + tlv->color = color; + tlv->is_ipv4 = false; + memcpy(&tlv->end_point.ipv6, ipv6, sizeof(struct in6_addr)); + + return tlv; +} + + +struct pcep_object_tlv_srpag_pol_name * +pcep_tlv_create_srpag_pol_name(const char *pol_name, uint16_t pol_name_length) +{ + if (pol_name == NULL) { + return NULL; + } + struct pcep_object_tlv_srpag_pol_name *tlv = + (struct pcep_object_tlv_srpag_pol_name *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME, + sizeof(struct pcep_object_tlv_srpag_pol_name)); + uint16_t length = + (normalize_pcep_tlv_length(pol_name_length) > MAX_POLICY_NAME) + ? MAX_POLICY_NAME + : pol_name_length; + memcpy(tlv->name, pol_name, pol_name_length); + tlv->name_length = length; + + return tlv; +} +struct pcep_object_tlv_srpag_cp_id * +pcep_tlv_create_srpag_cp_id(uint8_t proto_origin, uint32_t asn, + struct in6_addr *in6_addr_with_mapped_ipv4, + uint32_t discriminator) +{ + if (!in6_addr_with_mapped_ipv4) { + return NULL; + } + + struct pcep_object_tlv_srpag_cp_id *tlv = + (struct pcep_object_tlv_srpag_cp_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID, + sizeof(struct pcep_object_tlv_srpag_cp_id)); + tlv->proto = proto_origin; + tlv->orig_asn = asn; + memcpy(&(tlv->orig_addres), in6_addr_with_mapped_ipv4, + sizeof(*in6_addr_with_mapped_ipv4)); + tlv->discriminator = discriminator; + + return tlv; +} +struct pcep_object_tlv_srpag_cp_pref * +pcep_tlv_create_srpag_cp_pref(uint32_t pref) +{ + + struct pcep_object_tlv_srpag_cp_pref *tlv = + (struct pcep_object_tlv_srpag_cp_pref *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE, + sizeof(struct pcep_object_tlv_srpag_cp_pref)); + tlv->preference = pref; + + return tlv; +} + +struct pcep_object_tlv_arbitrary * +pcep_tlv_create_tlv_arbitrary(const char *data, uint16_t data_length, + int tlv_id) +{ + if (data == NULL || data_length == 0) { + return NULL; + } + + struct pcep_object_tlv_arbitrary *tlv = + (struct pcep_object_tlv_arbitrary *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_ARBITRARY, + sizeof(struct pcep_object_tlv_arbitrary)); + + uint16_t length = (data_length > MAX_ARBITRARY_SIZE) + ? MAX_ARBITRARY_SIZE + : data_length; + memcpy(tlv->data, data, data_length); + tlv->data_length = length; + tlv->arbitraty_type = tlv_id; + + return tlv; +} diff --git a/pceplib/pcep_msg_tlvs.h b/pceplib/pcep_msg_tlvs.h new file mode 100644 index 0000000000..5197201e40 --- /dev/null +++ b/pceplib/pcep_msg_tlvs.h @@ -0,0 +1,380 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message object TLV API. + */ + +#ifndef PCEP_TLVS_H_ +#define PCEP_TLVS_H_ + +#include +#include + +#include "pcep.h" +#include "pcep_utils_double_linked_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Regarding memory usage: + * When creating TLVs, any TLVs passed into messages or objects with these APIs + * will be free'd when the the enclosing pcep_message is free'd. That includes + * the double_linked_list's. So, just create the objects and TLVs, put them in + * their double_linked_list's, and everything will be managed internally. The + * enclosing message will be deleted by pcep_msg_free_message() or + * pcep_msg_free_message_list() which, * in turn will call one of: + * pcep_obj_free_object() and pcep_obj_free_tlv(). + * For received messages, call pcep_msg_free_message() to free them. + */ + +/* These numbers can be found here: + * https://www.iana.org/assignments/pcep/pcep.xhtml */ +enum pcep_object_tlv_types { + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR = 1, + PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST = 4, /* RFC 5541 */ + PCEP_OBJ_TLV_TYPE_VENDOR_INFO = 7, /* RFC 7470 */ + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY = 16, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME = 17, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS = 18, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS = 19, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE = 20, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC = 21, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION = 23, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID = 24, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY = + 26, /* draft-ietf-pce-segment-routing-16 */ + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE = 28, /* RFC 8408 */ + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY = + 34, /* RFC 8408, draft-ietf-pce-segment-routing-16 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID = + 60, /*TDB2 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME = + 61, /*TDB3 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID = + 62, /*TDB4 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE = + 63, /*TDB5 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_UNKNOWN = 128, + PCEP_OBJ_TLV_TYPE_ARBITRARY = + 65533 /* Max IANA To write arbitrary data */ +}; + +struct pcep_object_tlv_header { + enum pcep_object_tlv_types type; + /* Pointer into encoded_message field from the pcep_message */ + const uint8_t *encoded_tlv; + uint16_t encoded_tlv_length; +}; + +/* STATEFUL-PCE-CAPABILITY TLV, Used in Open Object. RFCs: 8231, 8232, 8281 */ +#define TLV_STATEFUL_PCE_CAP_FLAG_U 0x01 +#define TLV_STATEFUL_PCE_CAP_FLAG_S 0x02 +#define TLV_STATEFUL_PCE_CAP_FLAG_I 0x04 +#define TLV_STATEFUL_PCE_CAP_FLAG_T 0x08 +#define TLV_STATEFUL_PCE_CAP_FLAG_D 0x10 +#define TLV_STATEFUL_PCE_CAP_FLAG_F 0x20 + +struct pcep_object_tlv_stateful_pce_capability { + struct pcep_object_tlv_header header; + bool flag_u_lsp_update_capability; /* RFC 8231 */ + bool flag_s_include_db_version; /* RFC 8232 */ + bool flag_i_lsp_instantiation_capability; /* RFC 8281 */ + bool flag_t_triggered_resync; /* RFC 8232 */ + bool flag_d_delta_lsp_sync; /* RFC 8232 */ + bool flag_f_triggered_initial_sync; /* RFC 8232 */ +}; + +/* NOPATH-VECTOR TLV, Used in the Reply NoPath Object. */ +struct pcep_object_tlv_nopath_vector { + struct pcep_object_tlv_header header; + uint32_t error_code; +}; + +/* STATEFUL-PCE-CAPABILITY TLV, Used in Open Object. RFCs: 8232 */ +struct pcep_object_tlv_lsp_db_version { + struct pcep_object_tlv_header header; + uint64_t lsp_db_version; +}; + +/* Speaker Entity Identifier TLV, Used in Open Object. RFCs: 8232 */ +struct pcep_object_tlv_speaker_entity_identifier { + struct pcep_object_tlv_header header; + double_linked_list *speaker_entity_id_list; /* list of uint32_t speaker + entity ids */ +}; + +/* Ipv4 LSP Identifier TLV, Used in LSP Object. RFCs: 8231 */ +struct pcep_object_tlv_ipv4_lsp_identifier { + struct pcep_object_tlv_header header; + struct in_addr ipv4_tunnel_sender; + uint16_t lsp_id; + uint16_t tunnel_id; + struct in_addr extended_tunnel_id; + struct in_addr ipv4_tunnel_endpoint; +}; + +/* Ipv6 LSP Identifier TLV, Used in LSP Object. RFCs: 8231 */ +struct pcep_object_tlv_ipv6_lsp_identifier { + struct pcep_object_tlv_header header; + struct in6_addr ipv6_tunnel_sender; + uint16_t lsp_id; + uint16_t tunnel_id; + struct in6_addr extended_tunnel_id; + struct in6_addr ipv6_tunnel_endpoint; +}; + +/* Symbolic Path Name TLV, Used in LSP Object. RFCs: 8231 */ +#define MAX_SYMBOLIC_PATH_NAME 256 + +struct pcep_object_tlv_symbolic_path_name { + struct pcep_object_tlv_header header; + uint16_t symbolic_path_name_length; + char symbolic_path_name[MAX_SYMBOLIC_PATH_NAME]; +}; + +/* LSP Error Code TLV, Used in LSP Object. RFCs: 8231 */ +enum pcep_tlv_lsp_error_codes { + PCEP_TLV_LSP_ERROR_CODE_UNKNOWN = 1, + PCEP_TLV_LSP_ERROR_CODE_LSP_LIMIT_REACHED = 2, + PCEP_TLV_LSP_ERROR_CODE_TOO_MANY_PENDING_LSP_UPDATES = 3, + PCEP_TLV_LSP_ERROR_CODE_UNACCEPTABLE_PARAMS = 4, + PCEP_TLV_LSP_ERROR_CODE_INTERNAL_ERROR = 5, + PCEP_TLV_LSP_ERROR_CODE_LSP_BROUGHT_DOWN = 6, + PCEP_TLV_LSP_ERROR_CODE_LSP_PREEMPTED = 7, + PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR = 8, +}; + +struct pcep_object_tlv_lsp_error_code { + struct pcep_object_tlv_header header; + enum pcep_tlv_lsp_error_codes lsp_error_code; +}; + +/* Path Setup Type TLV, Used in RP and SRP Object. RFCs: 8408, + * draft-ietf-pce-segment-routing-16 */ +#define SR_TE_PST 1 + +struct pcep_object_tlv_path_setup_type { + struct pcep_object_tlv_header header; + uint8_t path_setup_type; +}; + +/* Path Setup Type Capability TLV, Used in Open Object. RFCs: 8408, + * draft-ietf-pce-segment-routing-16 */ +struct pcep_object_tlv_path_setup_type_capability { + struct pcep_object_tlv_header header; + double_linked_list *pst_list; /* list of uint8_t PSTs */ + double_linked_list *sub_tlv_list; /* list of sub_tlvs */ +}; + +/* SR PCE Capability sub-TLV, Used in Open Object. RFCs: + * draft-ietf-pce-segment-routing-16 */ +#define TLV_SR_PCE_CAP_FLAG_X 0x01 +#define TLV_SR_PCE_CAP_FLAG_N 0x02 + +struct pcep_object_tlv_sr_pce_capability { + struct pcep_object_tlv_header header; + bool flag_n; + bool flag_x; + uint8_t max_sid_depth; +}; + + +/* RSVP Error Spec TLV, Used in LSP Object. RFCs: 8231, 2205 */ +#define RSVP_ERROR_SPEC_IPV4_CTYPE 1 +#define RSVP_ERROR_SPEC_IPV6_CTYPE 2 +#define RSVP_ERROR_SPEC_CLASS_NUM 6 + +struct pcep_object_tlv_rsvp_error_spec { + struct pcep_object_tlv_header header; + uint8_t class_num; + uint8_t c_type; + uint8_t error_code; + uint16_t error_value; + /* Use the c_type to determine which union entry to use */ + union error_spec_ip { + struct in_addr ipv4_error_node_address; + struct in6_addr ipv6_error_node_address; + } error_spec_ip; +}; + +/* SR Policy Identifier TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_pol_id { + struct pcep_object_tlv_header header; + uint32_t color; + bool is_ipv4; + union end_point_ { + struct in_addr ipv4; + struct in6_addr ipv6; + } end_point; +}; + +/*draft-ietf-spring-segment-routing-policy-06*/ +#define MAX_POLICY_NAME 256 + +/* SR Policy Name TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_pol_name { + struct pcep_object_tlv_header header; + uint16_t name_length; + char name[MAX_POLICY_NAME]; +}; + +/* SR Candidate Path Id TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_cp_id { + struct pcep_object_tlv_header header; + uint8_t proto; + uint32_t orig_asn; + struct in6_addr orig_addres; /*With ipv4 embedded*/ + uint32_t discriminator; +}; + +/* SR Candidate Preference TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_cp_pref { + struct pcep_object_tlv_header header; + uint32_t preference; +}; + +struct pcep_object_tlv_vendor_info { + struct pcep_object_tlv_header header; + uint32_t enterprise_number; + uint32_t enterprise_specific_info; +}; + +/* arbitrary TLV 65535 */ +#define MAX_ARBITRARY_SIZE 256 +struct pcep_object_tlv_arbitrary { + struct pcep_object_tlv_header header; + enum pcep_object_tlv_types arbitraty_type; + uint16_t data_length; + char data[MAX_ARBITRARY_SIZE]; +}; + +/* Objective Functions List RFC 5541 + * At least the following 6 OF codes must be supported */ +enum objective_function_codes { + PCEP_OF_CODE_MINIMUM_COST_PATH = 1, /* MCP */ + PCEP_OF_CODE_MINIMUM_LOAD_PATH = 2, /* MLP */ + PCEP_OF_CODE_MAXIMUM_BW_PATH = 3, /* MBP */ + PCEP_OF_CODE_MINIMIZE_AGGR_BW_CONSUMPTION = 4, /* MBC */ + PCEP_OF_CODE_MINIMIZE_MOST_LOADED_LINK = 5, /* MLL */ + PCEP_OF_CODE_MINIMIZE_CUMULATIVE_COST_PATHS = 6, /* MCC */ +}; + +struct pcep_object_tlv_of_list { + struct pcep_object_tlv_header header; + double_linked_list *of_list; /* list of uint16_t OF code points */ +}; + +/* + * TLV creation functions + */ + +/* + * Open Object TLVs + */ + +struct pcep_object_tlv_stateful_pce_capability * +pcep_tlv_create_stateful_pce_capability( + bool flag_u_lsp_update_capability, bool flag_s_include_db_version, + bool flag_i_lsp_instantiation_capability, bool flag_t_triggered_resync, + bool flag_d_delta_lsp_sync, bool flag_f_triggered_initial_sync); +struct pcep_object_tlv_lsp_db_version * +pcep_tlv_create_lsp_db_version(uint64_t lsp_db_version); +struct pcep_object_tlv_speaker_entity_identifier * +pcep_tlv_create_speaker_entity_id(double_linked_list *speaker_entity_id_list); +struct pcep_object_tlv_path_setup_type * +pcep_tlv_create_path_setup_type(uint8_t pst); +struct pcep_object_tlv_path_setup_type_capability * +pcep_tlv_create_path_setup_type_capability(double_linked_list *pst_list, + double_linked_list *sub_tlv_list); +struct pcep_object_tlv_sr_pce_capability * +pcep_tlv_create_sr_pce_capability(bool flag_n, bool flag_x, + uint8_t max_sid_depth); +struct pcep_object_tlv_of_list * +pcep_tlv_create_of_list(double_linked_list *of_list); + +/* + * LSP Object TLVs + */ + +struct pcep_object_tlv_ipv4_lsp_identifier * +pcep_tlv_create_ipv4_lsp_identifiers(struct in_addr *ipv4_tunnel_sender, + struct in_addr *ipv4_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in_addr *extended_tunnel_id); +struct pcep_object_tlv_ipv6_lsp_identifier * +pcep_tlv_create_ipv6_lsp_identifiers(struct in6_addr *ipv6_tunnel_sender, + struct in6_addr *extended_tunnel_id, + uint16_t lsp_id, uint16_t tunnel_id, + struct in6_addr *ipv6_tunnel_endpoint); +/* symbolic_path_name_length should NOT include the null terminator and cannot + * be zero */ +struct pcep_object_tlv_symbolic_path_name * +pcep_tlv_create_symbolic_path_name(const char *symbolic_path_name, + uint16_t symbolic_path_name_length); +struct pcep_object_tlv_lsp_error_code * +pcep_tlv_create_lsp_error_code(enum pcep_tlv_lsp_error_codes lsp_error_code); +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv4_error_spec(struct in_addr *error_node_ip, + uint8_t error_code, uint16_t error_value); +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv6_error_spec(struct in6_addr *error_node_ip, + uint8_t error_code, uint16_t error_value); + +struct pcep_object_tlv_nopath_vector * +pcep_tlv_create_nopath_vector(uint32_t error_code); +struct pcep_object_tlv_vendor_info * +pcep_tlv_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_specific_info); + +struct pcep_object_tlv_arbitrary * +pcep_tlv_create_tlv_arbitrary(const char *data, uint16_t data_length, + int tlv_id); +/* + * SRPAG (SR Association Group) TLVs + */ + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv4(uint32_t color, struct in_addr *ipv4); +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv6(uint32_t color, struct in6_addr *ipv6); +struct pcep_object_tlv_srpag_pol_name * +pcep_tlv_create_srpag_pol_name(const char *pol_name, uint16_t pol_name_length); +struct pcep_object_tlv_srpag_cp_id * +pcep_tlv_create_srpag_cp_id(uint8_t proto_origin, uint32_t asn, + struct in6_addr *in6_addr_with_mapped_ipv4, + uint32_t discriminator); +struct pcep_object_tlv_srpag_cp_pref * +pcep_tlv_create_srpag_cp_pref(uint32_t pref); + + +#ifdef __cplusplus +} +#endif + +#endif /* PCEP_TLVS_H_ */ diff --git a/pceplib/pcep_msg_tlvs_encoding.c b/pceplib/pcep_msg_tlvs_encoding.c new file mode 100644 index 0000000000..3322663dc3 --- /dev/null +++ b/pceplib/pcep_msg_tlvs_encoding.c @@ -0,0 +1,1282 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP Object TLVs. + */ + +#include +#include + +#include "pcep.h" +#include "pcep_msg_encoding.h" +#include "pcep_msg_tlvs.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_tlv_header(struct pcep_object_tlv_header *tlv_hdr, + uint16_t tlv_length, struct pcep_versioning *versioning, + uint8_t *buf); +void pcep_decode_tlv_hdr(const uint8_t *tlv_buf, + struct pcep_object_tlv_header *tlv_hdr); + +/* + * forward declarations for initialize_tlv_encoders() + */ +uint16_t pcep_encode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_path_setup_type_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_pol_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_pol_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_cpath_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_vendor_info(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_arbitrary(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_of_list(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +typedef uint16_t (*tlv_encoder_funcptr)(struct pcep_object_tlv_header *, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); + +#define MAX_TLV_ENCODER_INDEX 65533 + 1 // 65 + +#define PCEP_TLV_ENCODERS_ARGS \ + struct pcep_object_tlv_header *, struct pcep_versioning *versioning, \ + uint8_t *tlv_body_buf +uint16_t (*const tlv_encoders[MAX_TLV_ENCODER_INDEX])( + PCEP_TLV_ENCODERS_ARGS) = { + [PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = pcep_encode_tlv_no_path_vector, + [PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_encode_tlv_stateful_pce_capability, + [PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_encode_tlv_symbolic_path_name, + [PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv4_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv6_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = pcep_encode_tlv_lsp_error_code, + [PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = pcep_encode_tlv_rsvp_error_spec, + [PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = pcep_encode_tlv_lsp_db_version, + [PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_encode_tlv_speaker_entity_id, + [PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_encode_tlv_sr_pce_capability, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = pcep_encode_tlv_path_setup_type, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_encode_tlv_path_setup_type_capability, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = pcep_encode_tlv_pol_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = pcep_encode_tlv_pol_name, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = pcep_encode_tlv_cpath_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_encode_tlv_cpath_preference, + [PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = pcep_encode_tlv_vendor_info, + [PCEP_OBJ_TLV_TYPE_ARBITRARY] = pcep_encode_tlv_arbitrary, + [PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = pcep_encode_tlv_of_list, +}; +/* + * forward declarations for initialize_tlv_decoders() + */ +struct pcep_object_tlv_header * +pcep_decode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header *pcep_decode_tlv_path_setup_type_capability( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_vendor_info(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_arbitrary(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_of_list(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +typedef struct pcep_object_tlv_header *(*tlv_decoder_funcptr)( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf); + +// tlv_decoder_funcptr tlv_decoders[MAX_TLV_ENCODER_INDEX]; + +#define PCEP_TLV_DECODERS_ARGS \ + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf + +struct pcep_object_tlv_header *(*const tlv_decoders[MAX_TLV_ENCODER_INDEX])( + PCEP_TLV_DECODERS_ARGS) = { + [PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = pcep_decode_tlv_no_path_vector, + [PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_decode_tlv_stateful_pce_capability, + [PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_decode_tlv_symbolic_path_name, + [PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv4_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv6_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = pcep_decode_tlv_lsp_error_code, + [PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = pcep_decode_tlv_rsvp_error_spec, + [PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = pcep_decode_tlv_lsp_db_version, + [PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_decode_tlv_speaker_entity_id, + [PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_decode_tlv_sr_pce_capability, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = pcep_decode_tlv_path_setup_type, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_decode_tlv_path_setup_type_capability, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = pcep_decode_tlv_pol_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = pcep_decode_tlv_pol_name, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = pcep_decode_tlv_cpath_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_decode_tlv_cpath_preference, + [PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = pcep_decode_tlv_vendor_info, + [PCEP_OBJ_TLV_TYPE_ARBITRARY] = pcep_decode_tlv_arbitrary, + [PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = pcep_decode_tlv_of_list, +}; + +static void initialize_tlv_coders() +{ + static bool initialized = false; + + if (initialized == true) { + return; + } + + initialized = true; + + /* Encoders */ + /* + memset(tlv_encoders, 0, sizeof(tlv_encoder_funcptr) * + MAX_TLV_ENCODER_INDEX); tlv_encoders[PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = + pcep_encode_tlv_no_path_vector; + tlv_encoders[PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_encode_tlv_stateful_pce_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_encode_tlv_symbolic_path_name; + tlv_encoders[PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv4_lsp_identifiers; + tlv_encoders[PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv6_lsp_identifiers; + tlv_encoders[PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = + pcep_encode_tlv_lsp_error_code; + tlv_encoders[PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = + pcep_encode_tlv_rsvp_error_spec; + tlv_encoders[PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = + pcep_encode_tlv_lsp_db_version; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_encode_tlv_speaker_entity_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_encode_tlv_sr_pce_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = + pcep_encode_tlv_path_setup_type; + tlv_encoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_encode_tlv_path_setup_type_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = + pcep_encode_tlv_pol_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = + pcep_encode_tlv_pol_name; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = + pcep_encode_tlv_cpath_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_encode_tlv_cpath_preference; + tlv_encoders[PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = + pcep_encode_tlv_vendor_info; tlv_encoders[PCEP_OBJ_TLV_TYPE_ARBITRARY] = + pcep_encode_tlv_arbitrary; + tlv_encoders[PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = + pcep_encode_tlv_of_list; + */ + + /* Decoders */ + /* + memset(tlv_decoders, 0, sizeof(tlv_decoder_funcptr) * + MAX_TLV_ENCODER_INDEX); tlv_decoders[PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = + pcep_decode_tlv_no_path_vector; + tlv_decoders[PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_decode_tlv_stateful_pce_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_decode_tlv_symbolic_path_name; + tlv_decoders[PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv4_lsp_identifiers; + tlv_decoders[PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv6_lsp_identifiers; + tlv_decoders[PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = + pcep_decode_tlv_lsp_error_code; + tlv_decoders[PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = + pcep_decode_tlv_rsvp_error_spec; + tlv_decoders[PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = + pcep_decode_tlv_lsp_db_version; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_decode_tlv_speaker_entity_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_decode_tlv_sr_pce_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = + pcep_decode_tlv_path_setup_type; + tlv_decoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_decode_tlv_path_setup_type_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = + pcep_decode_tlv_pol_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = + pcep_decode_tlv_pol_name; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = + pcep_decode_tlv_cpath_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_decode_tlv_cpath_preference; + tlv_decoders[PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = + pcep_decode_tlv_vendor_info; tlv_decoders[PCEP_OBJ_TLV_TYPE_ARBITRARY] = + pcep_decode_tlv_arbitrary; + tlv_decoders[PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = + pcep_decode_tlv_of_list; + */ +} + +uint16_t pcep_encode_tlv(struct pcep_object_tlv_header *tlv_hdr, + struct pcep_versioning *versioning, uint8_t *buf) +{ + initialize_tlv_coders(); + + if (tlv_hdr->type >= MAX_TLV_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot encode unknown Object class [%d]", + __func__, tlv_hdr->type); + return 0; + } + + tlv_encoder_funcptr tlv_encoder = tlv_encoders[tlv_hdr->type]; + if (tlv_encoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object encoder found for Object class [%d]", + __func__, tlv_hdr->type); + return 0; + } + + /* Notice: The length in the TLV header does not include the TLV header, + * so the length returned from the tlv_encoder() is only the TLV body. + */ + uint16_t tlv_length = + tlv_encoder(tlv_hdr, versioning, buf + TLV_HEADER_LENGTH); + write_tlv_header(tlv_hdr, tlv_length, versioning, buf); + tlv_hdr->encoded_tlv = buf; + tlv_hdr->encoded_tlv_length = tlv_length; + + return normalize_pcep_tlv_length(tlv_length + TLV_HEADER_LENGTH); +} + +/* TLV Header format + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type (2 bytes) | Length (2 bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Value (Variable) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +void write_tlv_header(struct pcep_object_tlv_header *tlv_hdr, + uint16_t tlv_length, struct pcep_versioning *versioning, + uint8_t *buf) +{ + (void)versioning; + uint16_t *uint16_ptr = (uint16_t *)buf; + uint16_ptr[0] = htons(tlv_hdr->type); + uint16_ptr[1] = htons(tlv_length); +} + +/* + * Functions to encode TLVs + */ + +uint16_t pcep_encode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_nopath_vector *nopath_tlv = + (struct pcep_object_tlv_nopath_vector *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + *uint32_ptr = htonl(nopath_tlv->error_code); + + return LENGTH_1WORD; +} + +uint16_t +pcep_encode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_stateful_pce_capability *spc_tlv = + (struct pcep_object_tlv_stateful_pce_capability *)tlv; + tlv_body_buf[3] = + ((spc_tlv->flag_f_triggered_initial_sync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_F + : 0x00) + | (spc_tlv->flag_d_delta_lsp_sync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_D + : 0x00) + | (spc_tlv->flag_t_triggered_resync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_T + : 0x00) + | (spc_tlv->flag_i_lsp_instantiation_capability == true + ? TLV_STATEFUL_PCE_CAP_FLAG_I + : 0x00) + | (spc_tlv->flag_s_include_db_version == true + ? TLV_STATEFUL_PCE_CAP_FLAG_S + : 0x00) + | (spc_tlv->flag_u_lsp_update_capability == true + ? TLV_STATEFUL_PCE_CAP_FLAG_U + : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_symbolic_path_name *spn_tlv = + (struct pcep_object_tlv_symbolic_path_name *)tlv; + memcpy(tlv_body_buf, spn_tlv->symbolic_path_name, + spn_tlv->symbolic_path_name_length); + + return spn_tlv->symbolic_path_name_length; +} + +uint16_t +pcep_encode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp = + (struct pcep_object_tlv_ipv4_lsp_identifier *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = ipv4_lsp->ipv4_tunnel_sender.s_addr; + /* uint32_t[1] is lsp_id and tunnel_id, below */ + uint32_ptr[2] = ipv4_lsp->extended_tunnel_id.s_addr; + uint32_ptr[3] = ipv4_lsp->ipv4_tunnel_endpoint.s_addr; + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_1WORD); + uint16_ptr[0] = htons(ipv4_lsp->lsp_id); + uint16_ptr[1] = htons(ipv4_lsp->tunnel_id); + + return LENGTH_4WORDS; +} + +uint16_t +pcep_encode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_ipv6_lsp_identifier *ipv6_lsp = + (struct pcep_object_tlv_ipv6_lsp_identifier *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + encode_ipv6(&ipv6_lsp->ipv6_tunnel_sender, uint32_ptr); + encode_ipv6(&ipv6_lsp->extended_tunnel_id, uint32_ptr + 5); + encode_ipv6(&ipv6_lsp->ipv6_tunnel_endpoint, uint32_ptr + 9); + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_4WORDS); + uint16_ptr[0] = htons(ipv6_lsp->lsp_id); + uint16_ptr[1] = htons(ipv6_lsp->tunnel_id); + + return LENGTH_13WORDS; +} + +uint16_t pcep_encode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_lsp_error_code *lsp_error_tlv = + (struct pcep_object_tlv_lsp_error_code *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + *uint32_ptr = htonl(lsp_error_tlv->lsp_error_code); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + /* Same decode tlv function for both types: + pcep_create_tlv_rsvp_ipv4_error_spec(tlv); + pcep_create_tlv_rsvp_ipv6_error_spec(tlv); */ + + /* RSVP Object Header + * + * 0 1 2 3 + * +-------------+-------------+-------------+-------------+ + * | Length (bytes) | Class-Num | C-Type | + * +-------------+-------------+-------------+-------------+ + * | | + * // (Object contents) // + * | | + * +-------------+-------------+-------------+-------------+ + * + * IPv4 ERROR_SPEC object: Class = 6, C-Type = 1 + * +-------------+-------------+-------------+-------------+ + * | IPv4 Error Node Address (4 bytes) | + * +-------------+-------------+-------------+-------------+ + * | Flags | Error Code | Error Value | + * +-------------+-------------+-------------+-------------+ + * + * IPv6 ERROR_SPEC object: Class = 6, C-Type = 2 + * +-------------+-------------+-------------+-------------+ + * | IPv6 Error Node Address (16 bytes) | + * +-------------+-------------+-------------+-------------+ + * | Flags | Error Code | Error Value | + * +-------------+-------------+-------------+-------------+ + */ + + (void)versioning; + struct pcep_object_tlv_rsvp_error_spec *rsvp_hdr = + (struct pcep_object_tlv_rsvp_error_spec *)tlv; + tlv_body_buf[2] = rsvp_hdr->class_num; + tlv_body_buf[3] = rsvp_hdr->c_type; + + uint16_t *length_ptr = (uint16_t *)tlv_body_buf; + uint32_t *uint32_ptr = (uint32_t *)(tlv_body_buf + LENGTH_1WORD); + if (rsvp_hdr->c_type == RSVP_ERROR_SPEC_IPV4_CTYPE) { + *length_ptr = htons(LENGTH_3WORDS); + *uint32_ptr = + rsvp_hdr->error_spec_ip.ipv4_error_node_address.s_addr; + tlv_body_buf[LENGTH_2WORDS + 1] = rsvp_hdr->error_code; + uint16_t *uint16_ptr = + (uint16_t *)(tlv_body_buf + LENGTH_2WORDS + 2); + *uint16_ptr = htons(rsvp_hdr->error_value); + + return LENGTH_3WORDS; + } else if (rsvp_hdr->c_type == RSVP_ERROR_SPEC_IPV6_CTYPE) { + *length_ptr = htons(LENGTH_6WORDS); + encode_ipv6(&rsvp_hdr->error_spec_ip.ipv6_error_node_address, + uint32_ptr); + tlv_body_buf[LENGTH_5WORDS + 1] = rsvp_hdr->error_code; + uint16_t *uint16_ptr = + (uint16_t *)(tlv_body_buf + LENGTH_5WORDS + 2); + *uint16_ptr = htons(rsvp_hdr->error_value); + + return LENGTH_6WORDS; + } + + return 0; +} + +uint16_t pcep_encode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_lsp_db_version *lsp_db_ver = + (struct pcep_object_tlv_lsp_db_version *)tlv; + *((uint64_t *)tlv_body_buf) = htobe64(lsp_db_ver->lsp_db_version); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_speaker_entity_identifier *speaker_id = + (struct pcep_object_tlv_speaker_entity_identifier *)tlv; + if (speaker_id->speaker_entity_id_list == NULL) { + return 0; + } + + int index = 0; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + double_linked_list_node *node = + speaker_id->speaker_entity_id_list->head; + for (; node != NULL; node = node->next_node) { + uint32_ptr[index++] = htonl(*((uint32_t *)node->data)); + } + + return speaker_id->speaker_entity_id_list->num_entries * LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap = + (struct pcep_object_tlv_sr_pce_capability *)tlv; + tlv_body_buf[2] = + ((sr_pce_cap->flag_n == true ? TLV_SR_PCE_CAP_FLAG_N : 0x00) + | (sr_pce_cap->flag_x == true ? TLV_SR_PCE_CAP_FLAG_X : 0x00)); + tlv_body_buf[3] = sr_pce_cap->max_sid_depth; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_path_setup_type *pst = + (struct pcep_object_tlv_path_setup_type *)tlv; + tlv_body_buf[3] = pst->path_setup_type; + + return LENGTH_1WORD; +} + +uint16_t +pcep_encode_tlv_path_setup_type_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_path_setup_type_capability *pst_cap = + (struct pcep_object_tlv_path_setup_type_capability *)tlv; + if (pst_cap->pst_list == NULL) { + return 0; + } + + tlv_body_buf[3] = pst_cap->pst_list->num_entries; + + /* Index past the reserved and NumPSTs fields */ + int index = 4; + double_linked_list_node *node = pst_cap->pst_list->head; + for (; node != NULL; node = node->next_node) { + tlv_body_buf[index++] = *((uint8_t *)node->data); + } + + uint16_t pst_length = normalize_pcep_tlv_length( + LENGTH_1WORD + pst_cap->pst_list->num_entries); + if (pst_cap->sub_tlv_list == NULL) { + return pst_length; + } + + /* Any padding used for the PSTs should not be included in the tlv + * header length */ + index = normalize_pcep_tlv_length(index); + uint16_t sub_tlvs_length = 0; + node = pst_cap->sub_tlv_list->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_tlv_header *sub_tlv = + (struct pcep_object_tlv_header *)node->data; + uint16_t sub_tlv_length = pcep_encode_tlv(sub_tlv, versioning, + tlv_body_buf + index); + index += sub_tlv_length; + sub_tlvs_length += sub_tlv_length; + } + + return sub_tlvs_length + pst_length; +} +uint16_t pcep_encode_tlv_pol_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_pol_id *ipv4 = + (struct pcep_object_tlv_srpag_pol_id *)tlv; + if (ipv4->is_ipv4) { + uint32_ptr[0] = htonl(ipv4->color); + uint32_ptr[1] = ipv4->end_point.ipv4.s_addr; + return LENGTH_2WORDS; + } else { + struct pcep_object_tlv_srpag_pol_id *ipv6 = + (struct pcep_object_tlv_srpag_pol_id *)tlv; + uint32_ptr[0] = htonl(ipv6->color); + encode_ipv6(&ipv6->end_point.ipv6, &uint32_ptr[1]); + return LENGTH_5WORDS; + } +} + +uint16_t pcep_encode_tlv_pol_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_pol_name *pol_name_tlv = + (struct pcep_object_tlv_srpag_pol_name *)tlv; + memcpy(tlv_body_buf, pol_name_tlv->name, pol_name_tlv->name_length); + + return normalize_pcep_tlv_length(pol_name_tlv->name_length); +} + +uint16_t pcep_encode_tlv_cpath_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_cp_id *cpath_id_tlv = + (struct pcep_object_tlv_srpag_cp_id *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv_body_buf[0] = cpath_id_tlv->proto; + uint32_ptr[1] = htonl(cpath_id_tlv->orig_asn); + encode_ipv6(&cpath_id_tlv->orig_addres, &uint32_ptr[2]); + uint32_ptr[6] = htonl(cpath_id_tlv->discriminator); + + return sizeof(cpath_id_tlv->proto) + sizeof(cpath_id_tlv->orig_asn) + + sizeof(cpath_id_tlv->orig_addres) + + sizeof(cpath_id_tlv->discriminator); +} + +uint16_t pcep_encode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_cp_pref *cpath_pref_tlv = + (struct pcep_object_tlv_srpag_cp_pref *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = htonl(cpath_pref_tlv->preference); + + return sizeof(cpath_pref_tlv->preference); +} + +uint16_t pcep_encode_tlv_vendor_info(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_vendor_info *vendor_info = + (struct pcep_object_tlv_vendor_info *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = htonl(vendor_info->enterprise_number); + uint32_ptr[1] = htonl(vendor_info->enterprise_specific_info); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_tlv_arbitrary(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_arbitrary *tlv_arbitrary = + (struct pcep_object_tlv_arbitrary *)tlv; + memcpy(tlv_body_buf, tlv_arbitrary->data, tlv_arbitrary->data_length); + tlv->type = tlv_arbitrary->arbitraty_type; + + return tlv_arbitrary->data_length; +} + +uint16_t pcep_encode_tlv_of_list(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_of_list *of_list = + (struct pcep_object_tlv_of_list *)tlv; + + if (of_list->of_list == NULL) { + return 0; + } + + int index = 0; + double_linked_list_node *node = of_list->of_list->head; + while (node != NULL) { + uint16_t *of_code = (uint16_t *)node->data; + if (of_code == NULL) { + return 0; + } + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + index); + *uint16_ptr = *of_code; + index += 2; + + node = node->next_node; + } + + return of_list->of_list->num_entries * 2; +} + +/* + * Decoding functions + */ + +void pcep_decode_tlv_hdr(const uint8_t *tlv_buf, + struct pcep_object_tlv_header *tlv_hdr) +{ + memset(tlv_hdr, 0, sizeof(struct pcep_object_tlv_header)); + + uint16_t *uint16_ptr = (uint16_t *)tlv_buf; + tlv_hdr->type = ntohs(uint16_ptr[0]); + tlv_hdr->encoded_tlv_length = ntohs(uint16_ptr[1]); + tlv_hdr->encoded_tlv = tlv_buf; +} + +struct pcep_object_tlv_header *pcep_decode_tlv(const uint8_t *tlv_buf) +{ + initialize_tlv_coders(); + + struct pcep_object_tlv_header tlv_hdr; + /* Only initializes and decodes the Object Header: class, type, flags, + * and length */ + pcep_decode_tlv_hdr(tlv_buf, &tlv_hdr); + + if (tlv_hdr.type >= MAX_TLV_ENCODER_INDEX) { + pcep_log(LOG_INFO, "%s: Cannot decode unknown TLV type [%d]", + __func__, tlv_hdr.type); + return NULL; + } + + tlv_decoder_funcptr tlv_decoder = tlv_decoders[tlv_hdr.type]; + if (tlv_decoder == NULL) { + pcep_log(LOG_INFO, "%s: No TLV decoder found for TLV type [%d]", + __func__, tlv_hdr.type); + return NULL; + } + + return tlv_decoder(&tlv_hdr, tlv_buf + LENGTH_1WORD); +} + +static struct pcep_object_tlv_header * +common_tlv_create(struct pcep_object_tlv_header *hdr, uint16_t new_tlv_length) +{ + struct pcep_object_tlv_header *new_tlv = + pceplib_malloc(PCEPLIB_MESSAGES, new_tlv_length); + memset(new_tlv, 0, new_tlv_length); + memcpy(new_tlv, hdr, sizeof(struct pcep_object_tlv_header)); + + return new_tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_nopath_vector)); + + tlv->error_code = ntohl(*((uint32_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_stateful_pce_capability)); + + tlv->flag_f_triggered_initial_sync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_F); + tlv->flag_d_delta_lsp_sync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_D); + tlv->flag_t_triggered_resync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_T); + tlv->flag_i_lsp_instantiation_capability = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_I); + tlv->flag_s_include_db_version = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_S); + tlv->flag_u_lsp_update_capability = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_U); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_symbolic_path_name *tlv = + (struct pcep_object_tlv_symbolic_path_name *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_symbolic_path_name)); + + uint16_t length = tlv_hdr->encoded_tlv_length; + if (length > MAX_SYMBOLIC_PATH_NAME) { + /* TODO should we also reset the tlv_hdr->encoded_tlv_length ? + */ + length = MAX_SYMBOLIC_PATH_NAME; + pcep_log( + LOG_INFO, + "%s: Decoding Symbolic Path Name TLV, truncate path name from [%d] to [%d].\",", + __func__, tlv_hdr->encoded_tlv_length, + MAX_SYMBOLIC_PATH_NAME); + } + + tlv->symbolic_path_name_length = length; + memcpy(tlv->symbolic_path_name, tlv_body_buf, length); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv4_lsp_identifier *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_ipv4_lsp_identifier)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->ipv4_tunnel_sender.s_addr = uint32_ptr[0]; + /* uint32_t[1] is lsp_id and tunnel_id, below */ + tlv->extended_tunnel_id.s_addr = uint32_ptr[2]; + tlv->ipv4_tunnel_endpoint.s_addr = uint32_ptr[3]; + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_1WORD); + tlv->lsp_id = ntohs(uint16_ptr[0]); + tlv->tunnel_id = ntohs(uint16_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv6_lsp_identifier *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_ipv6_lsp_identifier)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + decode_ipv6(uint32_ptr, &tlv->ipv6_tunnel_sender); + decode_ipv6(uint32_ptr + 5, &tlv->extended_tunnel_id); + decode_ipv6(uint32_ptr + 9, &tlv->ipv6_tunnel_endpoint); + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_4WORDS); + tlv->lsp_id = htons(uint16_ptr[0]); + tlv->tunnel_id = htons(uint16_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_lsp_error_code *tlv = + (struct pcep_object_tlv_lsp_error_code *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_lsp_error_code)); + + tlv->lsp_error_code = ntohl(*((uint32_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint8_t class_num = tlv_body_buf[2]; + uint8_t ctype = tlv_body_buf[3]; + + if (class_num != RSVP_ERROR_SPEC_CLASS_NUM) { + pcep_log( + LOG_INFO, + "%s: Decoding RSVP Error Spec TLV, unknown class num [%d]", + __func__, class_num); + return NULL; + } + + if (ctype != RSVP_ERROR_SPEC_IPV4_CTYPE + && ctype != RSVP_ERROR_SPEC_IPV6_CTYPE) { + pcep_log(LOG_INFO, + "%s: Decoding RSVP Error Spec TLV, unknown ctype [%d]", + __func__, ctype); + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->class_num = class_num; + tlv->c_type = ctype; + + uint32_t *uint32_ptr = (uint32_t *)(tlv_body_buf + LENGTH_1WORD); + if (ctype == RSVP_ERROR_SPEC_IPV4_CTYPE) { + tlv->error_spec_ip.ipv4_error_node_address.s_addr = *uint32_ptr; + tlv->error_code = tlv_body_buf[LENGTH_2WORDS + 1]; + tlv->error_value = ntohs( + *((uint16_t *)(tlv_body_buf + LENGTH_2WORDS + 2))); + } else /* RSVP_ERROR_SPEC_IPV6_CTYPE */ + { + decode_ipv6(uint32_ptr, + &tlv->error_spec_ip.ipv6_error_node_address); + tlv->error_code = tlv_body_buf[LENGTH_5WORDS + 1]; + tlv->error_value = ntohs( + *((uint16_t *)(tlv_body_buf + LENGTH_5WORDS + 2))); + } + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_lsp_db_version *tlv = + (struct pcep_object_tlv_lsp_db_version *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_lsp_db_version)); + + tlv->lsp_db_version = be64toh(*((uint64_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_speaker_entity_identifier *tlv = + (struct pcep_object_tlv_speaker_entity_identifier *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_speaker_entity_identifier)); + + uint8_t num_entity_ids = tlv_hdr->encoded_tlv_length / LENGTH_1WORD; + if (num_entity_ids > MAX_ITERATIONS) { + num_entity_ids = MAX_ITERATIONS; + pcep_log( + LOG_INFO, + "%s: Decode Speaker Entity ID, truncating num entities from [%d] to [%d].", + __func__, num_entity_ids, MAX_ITERATIONS); + } + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->speaker_entity_id_list = dll_initialize(); + int i; + for (i = 0; i < num_entity_ids; i++) { + uint32_t *entity_id = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *entity_id = ntohl(uint32_ptr[i]); + dll_append(tlv->speaker_entity_id_list, entity_id); + } + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + (struct pcep_object_tlv_sr_pce_capability *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_sr_pce_capability)); + + tlv->flag_n = (tlv_body_buf[2] & TLV_SR_PCE_CAP_FLAG_N); + tlv->flag_x = (tlv_body_buf[2] & TLV_SR_PCE_CAP_FLAG_X); + tlv->max_sid_depth = tlv_body_buf[3]; + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_path_setup_type *tlv = + (struct pcep_object_tlv_path_setup_type *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_path_setup_type)); + + tlv->path_setup_type = tlv_body_buf[3]; + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header *pcep_decode_tlv_path_setup_type_capability( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_path_setup_type_capability *tlv = + (struct pcep_object_tlv_path_setup_type_capability *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_path_setup_type_capability)); + + uint8_t num_psts = tlv_body_buf[3]; + if (num_psts > MAX_ITERATIONS) { + pcep_log( + LOG_INFO, + "%s: Decode Path Setup Type Capability num PSTs [%d] exceeds MAX [%d] continuing anyways", + __func__, num_psts, MAX_ITERATIONS); + } + + int i; + tlv->pst_list = dll_initialize(); + for (i = 0; i < num_psts; i++) { + uint8_t *pst = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t)); + *pst = tlv_body_buf[i + LENGTH_1WORD]; + dll_append(tlv->pst_list, pst); + } + + if (tlv->header.encoded_tlv_length + == (TLV_HEADER_LENGTH + LENGTH_1WORD + num_psts)) { + return (struct pcep_object_tlv_header *)tlv; + } + + uint8_t num_iterations = 0; + tlv->sub_tlv_list = dll_initialize(); + uint16_t buf_index = normalize_pcep_tlv_length( + TLV_HEADER_LENGTH + LENGTH_1WORD + num_psts); + while ((tlv->header.encoded_tlv_length - buf_index) > TLV_HEADER_LENGTH + && num_iterations++ > MAX_ITERATIONS) { + struct pcep_object_tlv_header *sub_tlv = + pcep_decode_tlv(tlv_body_buf + buf_index); + if (sub_tlv == NULL) { + pcep_log( + LOG_INFO, + "%s: Decode PathSetupType Capability sub-TLV decode returned NULL", + __func__); + return (struct pcep_object_tlv_header *)tlv; + } + + buf_index += + normalize_pcep_tlv_length(sub_tlv->encoded_tlv_length); + dll_append(tlv->sub_tlv_list, sub_tlv); + } + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_pol_id *ipv4 = + (struct pcep_object_tlv_srpag_pol_id *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_pol_id)); + if (tlv_hdr->encoded_tlv_length == 8) { + ipv4->is_ipv4 = true; + ipv4->color = ntohl(uint32_ptr[0]); + ipv4->end_point.ipv4.s_addr = uint32_ptr[1]; + return (struct pcep_object_tlv_header *)ipv4; + } else { + ipv4->is_ipv4 = false; + struct pcep_object_tlv_srpag_pol_id *ipv6 = + (struct pcep_object_tlv_srpag_pol_id *)ipv4; + ipv6->color = ntohl(uint32_ptr[0]); + decode_ipv6(&uint32_ptr[1], &ipv6->end_point.ipv6); + return (struct pcep_object_tlv_header *)ipv6; + } +} +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_srpag_pol_name *tlv = + (struct pcep_object_tlv_srpag_pol_name *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_pol_name)); + + memcpy(tlv->name, tlv_body_buf, tlv->header.encoded_tlv_length); + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_cp_id *tlv = + (struct pcep_object_tlv_srpag_cp_id *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_cp_id)); + + tlv->proto = tlv_body_buf[0]; + tlv->orig_asn = ntohl(uint32_ptr[1]); + decode_ipv6(&uint32_ptr[2], &tlv->orig_addres); + tlv->discriminator = ntohl(uint32_ptr[6]); + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_cp_pref *tlv = + (struct pcep_object_tlv_srpag_cp_pref *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_cp_pref)); + + tlv->preference = ntohl(uint32_ptr[0]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_vendor_info(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_vendor_info *tlv = + (struct pcep_object_tlv_vendor_info *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_vendor_info)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->enterprise_number = ntohl(uint32_ptr[0]); + tlv->enterprise_specific_info = ntohl(uint32_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_arbitrary(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_arbitrary *tlv_arbitrary = + (struct pcep_object_tlv_arbitrary *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_arbitrary)); + + uint16_t length = tlv_hdr->encoded_tlv_length; + if (length > MAX_ARBITRARY_SIZE) { + /* TODO should we also reset the tlv_hdr->encoded_tlv_length ? + */ + length = MAX_ARBITRARY_SIZE; + pcep_log( + LOG_INFO, + "%s: Decoding Arbitrary TLV , truncate path name from [%d] to [%d].\",", + __func__, tlv_hdr->encoded_tlv_length, + MAX_ARBITRARY_SIZE); + } + + tlv_arbitrary->data_length = length; + tlv_arbitrary->arbitraty_type = tlv_hdr->type; + tlv_hdr->type = PCEP_OBJ_TLV_TYPE_ARBITRARY; + memcpy(tlv_arbitrary->data, tlv_body_buf, length); + + return (struct pcep_object_tlv_header *)tlv_arbitrary; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_of_list(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_of_list *of_tlv = + (struct pcep_object_tlv_of_list *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_of_list)); + + of_tlv->of_list = dll_initialize(); + uint16_t *uint16_ptr = (uint16_t *)tlv_body_buf; + int i = 0; + for (; i < tlv_hdr->encoded_tlv_length && i < MAX_ITERATIONS; i++) { + uint16_t *of_code_ptr = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint16_t)); + *of_code_ptr = ntohs(uint16_ptr[i]); + dll_append(of_tlv->of_list, of_code_ptr); + } + + return (struct pcep_object_tlv_header *)of_tlv; +} diff --git a/pceplib/pcep_msg_tools.c b/pceplib/pcep_msg_tools.c new file mode 100644 index 0000000000..1d157ec3f5 --- /dev/null +++ b/pceplib/pcep_msg_tools.c @@ -0,0 +1,465 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include + +#include "pcep_msg_tools.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +static const char *message_type_strs[] = {"NOT_IMPLEMENTED0", + "OPEN", + "KEEPALIVE", + "PCREQ", + "PCREP", + "PCNOTF", + "ERROR", + "CLOSE", + "NOT_IMPLEMENTED8", + "NOT_IMPLEMENTED9", + "REPORT", + "UPDATE", + "INITIATE", + "UNKOWN_MESSAGE_TYPE"}; + +static const char *object_class_strs[] = {"NOT_IMPLEMENTED0", + "OPEN", + "RP", + "NOPATH", + "ENDPOINTS", + "BANDWIDTH", + "METRIC", + "ERO", + "RRO", + "LSPA", + "IRO", + "SVEC", + "NOTF", + "ERROR", + "NOT_IMPLEMENTED14", + "CLOSE", + "NOT_IMPLEMENTED16", + "NOT_IMPLEMENTED17", + "NOT_IMPLEMENTED18", + "NOT_IMPLEMENTED19", + "NOT_IMPLEMENTED20", + "OBJECTIVE_FUNCTION", + "NOT_IMPLEMENTED22", + "NOT_IMPLEMENTED23", + "NOT_IMPLEMENTED24", + "NOT_IMPLEMENTED25", + "NOT_IMPLEMENTED26", + "NOT_IMPLEMENTED27", + "NOT_IMPLEMENTED28", + "NOT_IMPLEMENTED29", + "NOT_IMPLEMENTED30", + "NOT_IMPLEMENTED31", + "LSP", + "SRP", + "VENDOR_INFO", + "NOT_IMPLEMENTED35", + "INTER_LAYER", + "SWITCH_LAYER", + "REQ_ADAP_CAP", + "SERVER_IND", + "ASSOCIATION", /* 40 */ + "UNKNOWN_MESSAGE_TYPE"}; + + +double_linked_list *pcep_msg_read(int sock_fd) +{ + int ret; + uint8_t buffer[PCEP_MAX_SIZE] = {0}; + uint16_t buffer_read = 0; + + + ret = read(sock_fd, &buffer, PCEP_MAX_SIZE); + + if (ret < 0) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Failed to read from socket fd [%d] errno [%d %s]", + __func__, sock_fd, errno, strerror(errno)); + return NULL; + } else if (ret == 0) { + pcep_log(LOG_INFO, "%s: pcep_msg_read: Remote shutdown fd [%d]", + __func__, sock_fd); + return NULL; + } + + double_linked_list *msg_list = dll_initialize(); + struct pcep_message *msg = NULL; + + while ((ret - buffer_read) >= MESSAGE_HEADER_LENGTH) { + + /* Get the Message header, validate it, and return the msg + * length */ + int32_t msg_hdr_length = + pcep_decode_validate_msg_header(buffer + buffer_read); + if (msg_hdr_length < 0) { + /* If the message header is invalid, we cant keep + * reading since the length may be invalid */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Received an invalid message fd [%d]", + __func__, sock_fd); + return msg_list; + } + + /* Check if the msg_hdr_length is longer than what was read, + * in which case, we need to read the rest of the message. */ + if ((ret - buffer_read) < msg_hdr_length) { + int read_len = (msg_hdr_length - (ret - buffer_read)); + int read_ret = 0; + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Message not fully read! Trying to read %d bytes more, fd [%d]", + __func__, read_len, sock_fd); + + read_ret = read(sock_fd, &buffer[ret], read_len); + + if (read_ret != read_len) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Did not manage to read enough data (%d != %d) fd [%d]", + __func__, read_ret, read_len, sock_fd); + return msg_list; + } + } + + msg = pcep_decode_message(buffer + buffer_read); + buffer_read += msg_hdr_length; + + if (msg == NULL) { + return msg_list; + } else { + dll_append(msg_list, msg); + } + } + + return msg_list; +} + +struct pcep_message *pcep_msg_get(double_linked_list *msg_list, uint8_t type) +{ + if (msg_list == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = msg_list->head; node != NULL; node = node->next_node) { + if (((struct pcep_message *)node->data)->msg_header->type + == type) { + return (struct pcep_message *)node->data; + } + } + + return NULL; +} + +struct pcep_message *pcep_msg_get_next(double_linked_list *list, + struct pcep_message *current, + uint8_t type) +{ + if (list == NULL || current == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = list->head; node != NULL; node = node->next_node) { + if (node->data == current) { + continue; + } + + if (((struct pcep_message *)node->data)->msg_header->type + == type) { + return (struct pcep_message *)node->data; + } + } + + return NULL; +} + +struct pcep_object_header *pcep_obj_get(double_linked_list *list, + uint8_t object_class) +{ + if (list == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *obj_item; + for (obj_item = list->head; obj_item != NULL; + obj_item = obj_item->next_node) { + if (((struct pcep_object_header *)obj_item->data)->object_class + == object_class) { + return (struct pcep_object_header *)obj_item->data; + } + } + + return NULL; +} + +struct pcep_object_header *pcep_obj_get_next(double_linked_list *list, + struct pcep_object_header *current, + uint8_t object_class) +{ + if (list == NULL || current == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = list->head; node != NULL; node = node->next_node) { + if (node->data == current) { + continue; + } + + if (((struct pcep_object_header *)node->data)->object_class + == object_class) { + return (struct pcep_object_header *)node->data; + } + } + + return NULL; +} + +void pcep_obj_free_tlv(struct pcep_object_tlv_header *tlv) +{ + /* Specific TLV freeing */ + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + if (((struct pcep_object_tlv_speaker_entity_identifier *)tlv) + ->speaker_entity_id_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_speaker_entity_identifier *) + tlv) + ->speaker_entity_id_list, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + if (((struct pcep_object_tlv_path_setup_type_capability *)tlv) + ->pst_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_path_setup_type_capability *) + tlv) + ->pst_list, + PCEPLIB_MESSAGES); + } + + if (((struct pcep_object_tlv_path_setup_type_capability *)tlv) + ->sub_tlv_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_path_setup_type_capability *) + tlv) + ->sub_tlv_list, + PCEPLIB_MESSAGES); + } + break; + + default: + break; + } + + pceplib_free(PCEPLIB_MESSAGES, tlv); +} + +void pcep_obj_free_object(struct pcep_object_header *obj) +{ + /* Iterate the TLVs and free each one */ + if (obj->tlv_list != NULL) { + struct pcep_object_tlv_header *tlv; + while ((tlv = (struct pcep_object_tlv_header *) + dll_delete_first_node(obj->tlv_list)) + != NULL) { + pcep_obj_free_tlv(tlv); + } + + dll_destroy(obj->tlv_list); + } + + /* Specific object freeing */ + switch (obj->object_class) { + case PCEP_OBJ_CLASS_ERO: + case PCEP_OBJ_CLASS_IRO: + case PCEP_OBJ_CLASS_RRO: { + if (((struct pcep_object_ro *)obj)->sub_objects != NULL) { + double_linked_list_node *node = + ((struct pcep_object_ro *)obj) + ->sub_objects->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = + (struct pcep_object_ro_subobj *) + node->data; + if (ro_subobj->ro_subobj_type + == RO_SUBOBJ_TYPE_SR) { + if (((struct pcep_ro_subobj_sr *) + ro_subobj) + ->nai_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_ro_subobj_sr *) + ro_subobj) + ->nai_list, + PCEPLIB_MESSAGES); + } + } + } + dll_destroy_with_data_memtype( + ((struct pcep_object_ro *)obj)->sub_objects, + PCEPLIB_MESSAGES); + } + } break; + + case PCEP_OBJ_CLASS_SVEC: + if (((struct pcep_object_svec *)obj)->request_id_list != NULL) { + dll_destroy_with_data_memtype( + ((struct pcep_object_svec *)obj) + ->request_id_list, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_CLASS_SWITCH_LAYER: + if (((struct pcep_object_switch_layer *)obj)->switch_layer_rows + != NULL) { + dll_destroy_with_data_memtype( + ((struct pcep_object_switch_layer *)obj) + ->switch_layer_rows, + PCEPLIB_MESSAGES); + } + break; + + default: + break; + } + + pceplib_free(PCEPLIB_MESSAGES, obj); +} + +void pcep_msg_free_message(struct pcep_message *message) +{ + /* Iterate the objects and free each one */ + if (message->obj_list != NULL) { + struct pcep_object_header *obj; + while ((obj = (struct pcep_object_header *) + dll_delete_first_node(message->obj_list)) + != NULL) { + pcep_obj_free_object(obj); + } + + dll_destroy(message->obj_list); + } + + if (message->msg_header != NULL) { + pceplib_free(PCEPLIB_MESSAGES, message->msg_header); + } + + if (message->encoded_message != NULL) { + pceplib_free(PCEPLIB_MESSAGES, message->encoded_message); + } + + pceplib_free(PCEPLIB_MESSAGES, message); +} + +void pcep_msg_free_message_list(double_linked_list *list) +{ + /* Iterate the messages and free each one */ + struct pcep_message *msg; + while ((msg = (struct pcep_message *)dll_delete_first_node(list)) + != NULL) { + pcep_msg_free_message(msg); + } + + dll_destroy(list); +} + +const char *get_message_type_str(uint8_t type) +{ + uint8_t msg_type = + (type > PCEP_TYPE_INITIATE) ? PCEP_TYPE_INITIATE + 1 : type; + + return message_type_strs[msg_type]; +} + +const char *get_object_class_str(uint8_t class) +{ + uint8_t object_class = + (class > PCEP_OBJ_CLASS_SRP) ? PCEP_OBJ_CLASS_SRP + 1 : class; + + return object_class_strs[object_class]; +} + +/* Expecting a list of struct pcep_message pointers */ +void pcep_msg_print(double_linked_list *msg_list) +{ + double_linked_list_node *node; + for (node = msg_list->head; node != NULL; node = node->next_node) { + struct pcep_message *msg = (struct pcep_message *)node->data; + pcep_log(LOG_INFO, "%s: PCEP_MSG %s", __func__, + get_message_type_str(msg->msg_header->type)); + + double_linked_list_node *obj_node = + (msg->obj_list == NULL ? NULL : msg->obj_list->head); + for (; obj_node != NULL; obj_node = obj_node->next_node) { + struct pcep_object_header *obj_header = + ((struct pcep_object_header *)obj_node->data); + pcep_log( + LOG_INFO, "%s: PCEP_OBJ %s", __func__, + get_object_class_str(obj_header->object_class)); + } + } +} + +int pcep_msg_send(int sock_fd, struct pcep_message *msg) +{ + if (msg == NULL) { + return 0; + } + + return write(sock_fd, msg->encoded_message, + ntohs(msg->encoded_message_length)); +} diff --git a/pceplib/pcep_msg_tools.h b/pceplib/pcep_msg_tools.h new file mode 100644 index 0000000000..b62bdde1cf --- /dev/null +++ b/pceplib/pcep_msg_tools.h @@ -0,0 +1,71 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + */ + +#ifndef PCEP_TOOLS_H +#define PCEP_TOOLS_H + +#include +#include // struct in_addr + +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCEP_MAX_SIZE 6000 + +/* Returns a double linked list of PCEP messages */ +double_linked_list *pcep_msg_read(int sock_fd); +/* Given a double linked list of PCEP messages, return the first node that has + * the same message type */ +struct pcep_message *pcep_msg_get(double_linked_list *msg_list, uint8_t type); +/* Given a double linked list of PCEP messages, return the next node after + * current node that has the same message type */ +struct pcep_message *pcep_msg_get_next(double_linked_list *msg_list, + struct pcep_message *current, + uint8_t type); +struct pcep_object_header *pcep_obj_get(double_linked_list *list, + uint8_t object_class); +struct pcep_object_header *pcep_obj_get_next(double_linked_list *list, + struct pcep_object_header *current, + uint8_t object_class); +struct pcep_object_tlv_header *pcep_tlv_get(double_linked_list *list, + uint16_t type); +struct pcep_object_tlv_header * +pcep_tlv_get_next(double_linked_list *list, + struct pcep_object_tlv_header *current, uint16_t type); +void pcep_obj_free_tlv(struct pcep_object_tlv_header *tlv); +void pcep_obj_free_object(struct pcep_object_header *obj); +void pcep_msg_free_message(struct pcep_message *message); +void pcep_msg_free_message_list(double_linked_list *list); +void pcep_msg_print(double_linked_list *list); +const char *get_message_type_str(uint8_t type); +const char *get_object_class_str(uint8_t class); +int pcep_msg_send(int sock_fd, struct pcep_message *hdr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_pcc.c b/pceplib/pcep_pcc.c new file mode 100644 index 0000000000..2171f883cd --- /dev/null +++ b/pceplib/pcep_pcc.c @@ -0,0 +1,517 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Sample PCC implementation + */ + +#include + +#include // gethostbyname +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcep_pcc_api.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* + * PCEP PCC design spec: + * https://docs.google.com/presentation/d/1DYc3ZhYA1c_qg9A552HjhneJXQKdh_yrKW6v3NRYPtnbw/edit?usp=sharing + */ +#define MAX_SRC_IP_STR 40 +#define MAX_DST_IP_STR 40 +struct cmd_line_args { + char src_ip_str[MAX_SRC_IP_STR]; + char dest_ip_str[MAX_DST_IP_STR]; + short src_tcp_port; + short dest_tcp_port; + char tcp_md5_str[TCP_MD5SIG_MAXKEYLEN]; /* RFC 2385 */ + bool is_ipv6; + bool eventpoll; /* poll for pcep_event's, or use callback (default) */ +}; + +bool pcc_active_ = true; +pcep_session *session = NULL; +struct cmd_line_args *cmd_line_args = NULL; +/* pcep_event callback variables */ +bool pcep_event_condition = false; +struct pcep_event *event = NULL; +pthread_mutex_t pcep_event_mutex; +pthread_cond_t pcep_event_cond_var; + +static const char DEFAULT_DEST_HOSTNAME[] = "localhost"; +static const char DEFAULT_DEST_HOSTNAME_IPV6[] = "ip6-localhost"; +static const short DEFAULT_SRC_TCP_PORT = 4999; + +// Private fn's +struct cmd_line_args *get_cmdline_args(int argc, char *argv[]); +void handle_signal_action(int sig_number); +int setup_signals(void); +void send_pce_path_request_message(pcep_session *session); +void send_pce_report_message(pcep_session *session); +void print_queue_event(struct pcep_event *event); +void pcep_event_callback(void *cb_data, pcep_event *e); + +struct cmd_line_args *get_cmdline_args(int argc, char *argv[]) +{ + /* Allocate and set default values */ + struct cmd_line_args *cmd_line_args = + malloc(sizeof(struct cmd_line_args)); + memset(cmd_line_args, 0, sizeof(struct cmd_line_args)); + strlcpy(cmd_line_args->dest_ip_str, DEFAULT_DEST_HOSTNAME, + MAX_DST_IP_STR); + cmd_line_args->src_tcp_port = DEFAULT_SRC_TCP_PORT; + cmd_line_args->is_ipv6 = false; + + /* Parse the cmd_line args: + * -ipv6 + * -srcip localhost + * -destip 192.168.0.2 + * -srcport 4999 + * -dstport 4189 + * -tcpmd5 hello + * -event_poll */ + int i = 1; + for (; i < argc; ++i) { + if (strcmp(argv[i], "-help") == 0 + || strcmp(argv[i], "--help") == 0 + || strcmp(argv[i], "-h") == 0) { + pcep_log( + LOG_INFO, + "%s: pcep_pcc [-ipv6] [-srcip localhost] [-destip 192.168.0.1] [-srcport 4999] [-dstport 4189] [-tcpmd5 authstr] [-eventpoll]", + __func__); + free(cmd_line_args); + return NULL; + } else if (strcmp(argv[i], "-ipv6") == 0) { + cmd_line_args->is_ipv6 = true; + if (argc == 2) { + strlcpy(cmd_line_args->dest_ip_str, + DEFAULT_DEST_HOSTNAME_IPV6, + MAX_DST_IP_STR); + } + } else if (strcmp(argv[i], "-eventpoll") == 0) { + cmd_line_args->eventpoll = true; + } else if (strcmp(argv[i], "-srcip") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->src_ip_str, argv[++i], + MAX_SRC_IP_STR); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-srcip\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-destip") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->dest_ip_str, argv[++i], + MAX_DST_IP_STR); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-destip\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-srcport") == 0) { + if (argc >= i + 2) { + cmd_line_args->src_tcp_port = atoi(argv[++i]); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-srcport\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-destport") == 0) { + if (argc >= i + 2) { + cmd_line_args->dest_tcp_port = atoi(argv[++i]); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-destport\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-tcpmd5") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->tcp_md5_str, argv[++i], + sizeof(cmd_line_args->tcp_md5_str)); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-tcpmd5\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else { + pcep_log(LOG_ERR, "%s: Invalid cmd_line_arg[%d] = %s", + __func__, i, argv[i]); + free(cmd_line_args); + return NULL; + } + } + + return cmd_line_args; +} + +void handle_signal_action(int sig_number) +{ + if (sig_number == SIGINT) { + pcep_log(LOG_INFO, "%s: SIGINT was caught!", __func__); + pcc_active_ = false; + if (cmd_line_args->eventpoll == false) { + pthread_mutex_lock(&pcep_event_mutex); + pcep_event_condition = true; + pthread_cond_signal(&pcep_event_cond_var); + pthread_mutex_unlock(&pcep_event_mutex); + } + } else if (sig_number == SIGUSR1) { + pcep_log(LOG_INFO, "%s: SIGUSR1 was caught, dumping counters", + __func__); + dump_pcep_session_counters(session); + pceplib_memory_dump(); + } else if (sig_number == SIGUSR2) { + pcep_log(LOG_INFO, "%s: SIGUSR2 was caught, reseting counters", + __func__); + reset_pcep_session_counters(session); + } +} + + +int setup_signals() +{ + struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_signal_action; + if (sigaction(SIGINT, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + if (sigaction(SIGUSR1, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + if (sigaction(SIGUSR2, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + return 0; +} + +void send_pce_path_request_message(pcep_session *session) +{ + struct in_addr src_ipv4; + struct in_addr dst_ipv4; + inet_pton(AF_INET, "1.2.3.4", &src_ipv4); + inet_pton(AF_INET, "10.20.30.40", &dst_ipv4); + + struct pcep_object_rp *rp_object = + pcep_obj_create_rp(1, false, false, false, false, 42, NULL); + struct pcep_object_endpoints_ipv4 *ep_object = + pcep_obj_create_endpoint_ipv4(&src_ipv4, &dst_ipv4); + + struct pcep_message *path_request = + pcep_msg_create_request(rp_object, ep_object, NULL); + send_message(session, path_request, true); +} + +void send_pce_report_message(pcep_session *session) +{ + double_linked_list *report_list = dll_initialize(); + + /* SRP Path Setup Type TLV */ + struct pcep_object_tlv_path_setup_type *pst_tlv = + pcep_tlv_create_path_setup_type(SR_TE_PST); + double_linked_list *srp_tlv_list = dll_initialize(); + dll_append(srp_tlv_list, pst_tlv); + + /* + * Create the SRP object + */ + uint32_t srp_id_number = 0x10203040; + struct pcep_object_header *obj = + (struct pcep_object_header *)pcep_obj_create_srp( + false, srp_id_number, srp_tlv_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message SRP object was NULL", + __func__); + return; + } + dll_append(report_list, obj); + + /* LSP Symbolic path name TLV */ + char symbolic_path_name[] = "second-default"; + struct pcep_object_tlv_symbolic_path_name *spn_tlv = + pcep_tlv_create_symbolic_path_name(symbolic_path_name, 14); + double_linked_list *lsp_tlv_list = dll_initialize(); + dll_append(lsp_tlv_list, spn_tlv); + + /* LSP IPv4 LSP ID TLV */ + struct in_addr ipv4_tunnel_sender; + struct in_addr ipv4_tunnel_endpoint; + inet_pton(AF_INET, "9.9.1.1", &ipv4_tunnel_sender); + inet_pton(AF_INET, "9.9.2.1", &ipv4_tunnel_endpoint); + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp_id_tlv = + pcep_tlv_create_ipv4_lsp_identifiers(&ipv4_tunnel_sender, + &ipv4_tunnel_endpoint, 42, + 1, NULL); + dll_append(lsp_tlv_list, ipv4_lsp_id_tlv); + + /* + * Create the LSP object + */ + uint32_t plsp_id = 42; + enum pcep_lsp_operational_status lsp_status = + PCEP_LSP_OPERATIONAL_ACTIVE; + bool c_flag = false; /* Lsp was created by PcInitiate msg */ + bool a_flag = false; /* Admin state, active / inactive */ + bool r_flag = false; /* true if LSP has been removed */ + bool s_flag = true; /* Synchronization */ + bool d_flag = false; /* Delegate LSP to PCE */ + obj = (struct pcep_object_header *)pcep_obj_create_lsp( + plsp_id, lsp_status, c_flag, a_flag, r_flag, s_flag, d_flag, + lsp_tlv_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message LSP object was NULL", + __func__); + return; + } + dll_append(report_list, obj); + + /* Create 2 ERO NONAI sub-objects */ + double_linked_list *ero_subobj_list = dll_initialize(); + struct pcep_ro_subobj_sr *sr_subobj_nonai1 = + pcep_obj_create_ro_subobj_sr_nonai(false, 503808, true, true); + dll_append(ero_subobj_list, sr_subobj_nonai1); + + struct pcep_ro_subobj_sr *sr_subobj_nonai2 = + pcep_obj_create_ro_subobj_sr_nonai(false, 1867776, true, true); + dll_append(ero_subobj_list, sr_subobj_nonai2); + + /* Create ERO IPv4 node sub-object */ + struct in_addr sr_subobj_ipv4; + inet_pton(AF_INET, "9.9.9.1", &sr_subobj_ipv4); + struct pcep_ro_subobj_sr *sr_subobj_ipv4node = + pcep_obj_create_ro_subobj_sr_ipv4_node( + false, false, false, true, 16060, &sr_subobj_ipv4); + if (sr_subobj_ipv4node == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message ERO sub-object was NULL", + __func__); + return; + } + dll_append(ero_subobj_list, sr_subobj_ipv4node); + + /* + * Create the ERO object + */ + obj = (struct pcep_object_header *)pcep_obj_create_ero(ero_subobj_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message ERO object was NULL", + __func__); + return; + } + dll_append(report_list, obj); + + /* + * Create the Metric object + */ + obj = (struct pcep_object_header *)pcep_obj_create_metric( + PCEP_METRIC_TE, false, true, 16.0); + dll_append(report_list, obj); + + /* Create and send the report message */ + struct pcep_message *report_msg = pcep_msg_create_report(report_list); + send_message(session, report_msg, true); +} + +void print_queue_event(struct pcep_event *event) +{ + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Received Event: type [%s] on session [%d] occurred at [%ld]", + __func__, time(NULL), pthread_self(), + get_event_type_str(event->event_type), + event->session->session_id, event->event_time); + + if (event->event_type == MESSAGE_RECEIVED) { + pcep_log( + LOG_INFO, "%s: \t Event message type [%s]", __func__, + get_message_type_str(event->message->msg_header->type)); + } +} + +/* Called by pcep_session_logic when pcep_event's are ready */ +void pcep_event_callback(void *cb_data, pcep_event *e) +{ + (void)cb_data; + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] pcep_event_callback", __func__, + time(NULL), pthread_self()); + pthread_mutex_lock(&pcep_event_mutex); + event = e; + pcep_event_condition = true; + pthread_cond_signal(&pcep_event_cond_var); + pthread_mutex_unlock(&pcep_event_mutex); +} + +int main(int argc, char **argv) +{ + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] starting pcc_pcep example client", + __func__, time(NULL), pthread_self()); + + cmd_line_args = get_cmdline_args(argc, argv); + if (cmd_line_args == NULL) { + return -1; + } + + setup_signals(); + + if (cmd_line_args->eventpoll == false) { + struct pceplib_infra_config infra_config; + memset(&infra_config, 0, sizeof(infra_config)); + infra_config.pcep_event_func = pcep_event_callback; + if (!initialize_pcc_infra(&infra_config)) { + pcep_log(LOG_ERR, + "%s: Error initializing PCC with infra.", + __func__); + return -1; + } + } else { + if (!initialize_pcc()) { + pcep_log(LOG_ERR, "%s: Error initializing PCC.", + __func__); + return -1; + } + } + + pcep_configuration *config = create_default_pcep_configuration(); + config->pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = true; + config->src_pcep_port = cmd_line_args->src_tcp_port; + config->is_tcp_auth_md5 = true; + + strlcpy(config->tcp_authentication_str, cmd_line_args->tcp_md5_str, + sizeof(config->tcp_authentication_str)); + + int af = (cmd_line_args->is_ipv6 ? AF_INET6 : AF_INET); + struct hostent *host_info = + gethostbyname2(cmd_line_args->dest_ip_str, af); + if (host_info == NULL) { + pcep_log(LOG_ERR, "%s: Error getting IP address.", __func__); + return -1; + } + + if (cmd_line_args->is_ipv6) { + struct in6_addr host_address; + memcpy(&host_address, host_info->h_addr, host_info->h_length); + session = connect_pce_ipv6(config, &host_address); + } else { + struct in_addr host_address; + memcpy(&host_address, host_info->h_addr, host_info->h_length); + session = connect_pce(config, &host_address); + } + + if (session == NULL) { + pcep_log(LOG_WARNING, "%s: Error in connect_pce.", __func__); + destroy_pcep_configuration(config); + return -1; + } + + sleep(2); + + send_pce_report_message(session); + /*send_pce_path_request_message(session);*/ + + /* Wait for pcep_event's either by polling the event queue or by + * callback */ + if (cmd_line_args->eventpoll == true) { + /* Poll the pcep_event queue*/ + while (pcc_active_) { + if (event_queue_is_empty() == false) { + struct pcep_event *event = + event_queue_get_event(); + print_queue_event(event); + destroy_pcep_event(event); + } + + sleep(5); + } + } else { + /* Get events via callback and conditional variable */ + pthread_mutex_init(&pcep_event_mutex, NULL); + pthread_cond_init(&pcep_event_cond_var, NULL); + while (pcc_active_) { + pthread_mutex_lock(&pcep_event_mutex); + + /* this internal loop helps avoid spurious interrupts */ + while (!pcep_event_condition) { + pthread_cond_wait(&pcep_event_cond_var, + &pcep_event_mutex); + } + + /* Check if we have been interrupted by SIGINT */ + if (pcc_active_) { + print_queue_event(event); + destroy_pcep_event(event); + } + + pcep_event_condition = false; + pthread_mutex_unlock(&pcep_event_mutex); + } + + pthread_mutex_destroy(&pcep_event_mutex); + pthread_cond_destroy(&pcep_event_cond_var); + } + + pcep_log(LOG_NOTICE, "%s: Disconnecting from PCE", __func__); + disconnect_pce(session); + destroy_pcep_configuration(config); + free(cmd_line_args); + + if (!destroy_pcc()) { + pcep_log(LOG_NOTICE, "%s: Error stopping PCC.", __func__); + } + + pceplib_memory_dump(); + + return 0; +} diff --git a/pceplib/pcep_pcc_api.c b/pceplib/pcep_pcc_api.c new file mode 100644 index 0000000000..b7813c5a05 --- /dev/null +++ b/pceplib/pcep_pcc_api.c @@ -0,0 +1,392 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Public PCEPlib PCC API implementation + */ + +#include + +#include +#include +#include +#include + +#include "pcep_msg_messages.h" +#include "pcep_pcc_api.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" + +/* Not using an array here since the enum pcep_event_type indeces go into the + * 100's */ +const char MESSAGE_RECEIVED_STR[] = "MESSAGE_RECEIVED"; +const char PCE_CLOSED_SOCKET_STR[] = "PCE_CLOSED_SOCKET"; +const char PCE_SENT_PCEP_CLOSE_STR[] = "PCE_SENT_PCEP_CLOSE"; +const char PCE_DEAD_TIMER_EXPIRED_STR[] = "PCE_DEAD_TIMER_EXPIRED"; +const char PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED_STR[] = + "PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED"; +const char PCC_CONNECTED_TO_PCE_STR[] = "PCC_CONNECTED_TO_PCE"; +const char PCC_PCEP_SESSION_CLOSED_STR[] = "PCC_PCEP_SESSION_CLOSED"; +const char PCC_RCVD_INVALID_OPEN_STR[] = "PCC_RCVD_INVALID_OPEN"; +const char PCC_RCVD_MAX_INVALID_MSGS_STR[] = "PCC_RCVD_MAX_INVALID_MSGS"; +const char PCC_RCVD_MAX_UNKOWN_MSGS_STR[] = "PCC_RCVD_MAX_UNKOWN_MSGS"; +const char UNKNOWN_EVENT_STR[] = "UNKNOWN Event Type"; + +/* Session Logic Handle managed in pcep_session_logic.c */ +extern pcep_event_queue *session_logic_event_queue_; + +bool initialize_pcc() +{ + if (!run_session_logic()) { + pcep_log(LOG_ERR, "%s: Error initializing PCC session logic.", + __func__); + return false; + } + + return true; +} + + +bool initialize_pcc_infra(struct pceplib_infra_config *infra_config) +{ + if (infra_config == NULL) { + return initialize_pcc(); + } + + if (!run_session_logic_with_infra(infra_config)) { + pcep_log(LOG_ERR, + "%s: Error initializing PCC session logic with infra.", + __func__); + return false; + } + + return true; +} + + +/* this function is blocking */ +bool initialize_pcc_wait_for_completion() +{ + return run_session_logic_wait_for_completion(); +} + + +bool destroy_pcc() +{ + if (!stop_session_logic()) { + pcep_log(LOG_WARNING, "%s: Error stopping PCC session logic.", + __func__); + return false; + } + + return true; +} + + +pcep_configuration *create_default_pcep_configuration() +{ + pcep_configuration *config = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_configuration)); + memset(config, 0, sizeof(pcep_configuration)); + + config->keep_alive_seconds = DEFAULT_CONFIG_KEEP_ALIVE; + /* This value will possibly be overwritten later with PCE config data */ + config->keep_alive_pce_negotiated_timer_seconds = + DEFAULT_CONFIG_KEEP_ALIVE; + config->min_keep_alive_seconds = DEFAULT_MIN_CONFIG_KEEP_ALIVE; + config->max_keep_alive_seconds = DEFAULT_MAX_CONFIG_KEEP_ALIVE; + + config->dead_timer_seconds = DEFAULT_CONFIG_DEAD_TIMER; + /* This value will be overwritten later with PCE config data */ + config->dead_timer_pce_negotiated_seconds = DEFAULT_CONFIG_DEAD_TIMER; + config->min_dead_timer_seconds = DEFAULT_MIN_CONFIG_DEAD_TIMER; + config->max_dead_timer_seconds = DEFAULT_MAX_CONFIG_DEAD_TIMER; + + config->request_time_seconds = DEFAULT_CONFIG_REQUEST_TIME; + config->max_unknown_messages = DEFAULT_CONFIG_MAX_UNKNOWN_MESSAGES; + config->max_unknown_requests = DEFAULT_CONFIG_MAX_UNKNOWN_REQUESTS; + + config->socket_connect_timeout_millis = + DEFAULT_TCP_CONNECT_TIMEOUT_MILLIS; + config->support_stateful_pce_lsp_update = true; + config->support_pce_lsp_instantiation = true; + config->support_include_db_version = true; + config->lsp_db_version = 0; + config->support_lsp_triggered_resync = true; + config->support_lsp_delta_sync = true; + config->support_pce_triggered_initial_sync = true; + config->support_sr_te_pst = true; + config->pcc_can_resolve_nai_to_sid = true; + config->max_sid_depth = 0; + config->dst_pcep_port = 0; + config->src_pcep_port = 0; + config->src_ip.src_ipv4.s_addr = INADDR_ANY; + config->is_src_ipv6 = false; + config->pcep_msg_versioning = create_default_pcep_versioning(); + config->tcp_authentication_str[0] = '\0'; + config->is_tcp_auth_md5 = true; + + return config; +} + +void destroy_pcep_configuration(pcep_configuration *config) +{ + destroy_pcep_versioning(config->pcep_msg_versioning); + pceplib_free(PCEPLIB_INFRA, config); +} + +pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip) +{ + return create_pcep_session(config, pce_ip); +} + +pcep_session *connect_pce_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip) +{ + return create_pcep_session_ipv6(config, pce_ip); +} + +void disconnect_pce(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: disconnect_pce session [%p] has already been deleted", + __func__, session); + return; + } + + if (session->socket_comm_session == NULL + || session->socket_comm_session->socket_fd < 0) { + /* If the socket has already been closed, just destroy the + * session */ + destroy_pcep_session(session); + } else { + /* This will cause the session to be destroyed AFTER the close + * message is sent */ + session->destroy_session_after_write = true; + + /* Send a PCEP close message */ + close_pcep_session(session); + } +} + +void send_message(pcep_session *session, struct pcep_message *msg, + bool free_after_send) +{ + if (session == NULL || msg == NULL) { + pcep_log(LOG_DEBUG, + "%s: send_message NULL params session [%p] msg [%p]", + __func__, session, msg); + + return; + } + + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: send_message session [%p] has already been deleted", + __func__, session); + return; + } + + pcep_encode_message(msg, session->pcc_config.pcep_msg_versioning); + socket_comm_session_send_message( + session->socket_comm_session, (char *)msg->encoded_message, + msg->encoded_message_length, free_after_send); + + increment_message_tx_counters(session, msg); + + if (free_after_send == true) { + /* The encoded_message will be deleted once sent, so everything + * else in the message will be freed */ + msg->encoded_message = NULL; + pcep_msg_free_message(msg); + } +} + +/* Returns true if the queue is empty, false otherwise */ +bool event_queue_is_empty() +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_is_empty Session Logic is not initialized yet", + __func__); + return false; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + bool is_empty = + (session_logic_event_queue_->event_queue->num_entries == 0); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return is_empty; +} + + +/* Return the number of events on the queue, 0 if empty */ +uint32_t event_queue_num_events_available() +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_num_events_available Session Logic is not initialized yet", + __func__); + return 0; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + uint32_t num_events = + session_logic_event_queue_->event_queue->num_entries; + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return num_events; +} + + +/* Return the next event on the queue, NULL if empty */ +struct pcep_event *event_queue_get_event() +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_get_event Session Logic is not initialized yet", + __func__); + return NULL; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + struct pcep_event *event = (struct pcep_event *)queue_dequeue( + session_logic_event_queue_->event_queue); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return event; +} + + +/* Free the PCEP Event resources, including the PCEP message */ +void destroy_pcep_event(struct pcep_event *event) +{ + if (event == NULL) { + pcep_log(LOG_WARNING, + "%s: destroy_pcep_event cannot destroy NULL event", + __func__); + return; + } + + if (event->event_type == MESSAGE_RECEIVED && event->message != NULL) { + pcep_msg_free_message(event->message); + } + + pceplib_free(PCEPLIB_INFRA, event); +} + +const char *get_event_type_str(int event_type) +{ + switch (event_type) { + case MESSAGE_RECEIVED: + return MESSAGE_RECEIVED_STR; + break; + case PCE_CLOSED_SOCKET: + return PCE_CLOSED_SOCKET_STR; + break; + case PCE_SENT_PCEP_CLOSE: + return PCE_SENT_PCEP_CLOSE_STR; + break; + case PCE_DEAD_TIMER_EXPIRED: + return PCE_DEAD_TIMER_EXPIRED_STR; + break; + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + return PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED_STR; + break; + case PCC_CONNECTED_TO_PCE: + return PCC_CONNECTED_TO_PCE_STR; + break; + case PCC_PCEP_SESSION_CLOSED: + return PCC_PCEP_SESSION_CLOSED_STR; + break; + case PCC_RCVD_INVALID_OPEN: + return PCC_RCVD_INVALID_OPEN_STR; + break; + case PCC_RCVD_MAX_INVALID_MSGS: + return PCC_RCVD_MAX_INVALID_MSGS_STR; + break; + case PCC_RCVD_MAX_UNKOWN_MSGS: + return PCC_RCVD_MAX_UNKOWN_MSGS_STR; + break; + default: + return UNKNOWN_EVENT_STR; + break; + } +} + +void dump_pcep_session_counters(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: dump_pcep_session_counters session [%p] has already been deleted", + __func__, session); + return; + } + + /* Update the counters group name so that the PCE session connected time + * is accurate */ + time_t now = time(NULL); + char counters_name[MAX_COUNTER_STR_LENGTH] = {0}; + char ip_str[40] = {0}; + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + ip_str, 40); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + ip_str, 40); + } + snprintf(counters_name, MAX_COUNTER_STR_LENGTH, + "PCEP Session [%d], connected to [%s] for [%u seconds]", + session->session_id, ip_str, + (uint32_t)(now - session->time_connected)); + strlcpy(session->pcep_session_counters->counters_group_name, + counters_name, + sizeof(session->pcep_session_counters->counters_group_name)); + + dump_counters_group_to_log(session->pcep_session_counters); +} + +void reset_pcep_session_counters(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: reset_pcep_session_counters session [%p] has already been deleted", + session); + return; + } + + reset_group_counters(session->pcep_session_counters); +} diff --git a/pceplib/pcep_pcc_api.h b/pceplib/pcep_pcc_api.h new file mode 100644 index 0000000000..5756e23efc --- /dev/null +++ b/pceplib/pcep_pcc_api.h @@ -0,0 +1,103 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Public PCEPlib PCC API + */ + +#ifndef PCEPPCC_INCLUDE_PCEPPCCAPI_H_ +#define PCEPPCC_INCLUDE_PCEPPCCAPI_H_ + +#include + +#include "pcep_session_logic.h" +#include "pcep_timers.h" + +#define DEFAULT_PCEP_TCP_PORT 4189 +#define DEFAULT_CONFIG_KEEP_ALIVE 30 +#define DEFAULT_CONFIG_DEAD_TIMER DEFAULT_CONFIG_KEEP_ALIVE * 4 +#define DEFAULT_CONFIG_REQUEST_TIME 30 +#define DEFAULT_CONFIG_MAX_UNKNOWN_REQUESTS 5 +#define DEFAULT_CONFIG_MAX_UNKNOWN_MESSAGES 5 +#define DEFAULT_TCP_CONNECT_TIMEOUT_MILLIS 250 + +/* Acceptable MIN and MAX values used in deciding if the PCEP + * Open received from a PCE should be accepted or rejected. */ +#define DEFAULT_MIN_CONFIG_KEEP_ALIVE 5 +#define DEFAULT_MAX_CONFIG_KEEP_ALIVE 120 +#define DEFAULT_MIN_CONFIG_DEAD_TIMER DEFAULT_MIN_CONFIG_KEEP_ALIVE * 4 +#define DEFAULT_MAX_CONFIG_DEAD_TIMER DEFAULT_MAX_CONFIG_KEEP_ALIVE * 4 + +/* + * PCEP PCC library initialization/teardown functions + */ + +/* Later when this is integrated with FRR pathd, it will be changed + * to just initialize_pcc(struct pceplib_infra_config *infra_config) */ +bool initialize_pcc(void); +bool initialize_pcc_infra(struct pceplib_infra_config *infra_config); +/* this function is blocking */ +bool initialize_pcc_wait_for_completion(void); +bool destroy_pcc(void); + + +/* + * PCEP session functions + */ + +pcep_configuration *create_default_pcep_configuration(void); +void destroy_pcep_configuration(pcep_configuration *config); + +/* Uses the standard PCEP TCP src and dest port = 4189. + * To use a specific dest or src port, set them other than 0 in the + * pcep_configuration. If src_ip is not set, INADDR_ANY will be used. */ +pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip); +pcep_session *connect_pce_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip); +void disconnect_pce(pcep_session *session); +void send_message(pcep_session *session, struct pcep_message *msg, + bool free_after_send); + +void dump_pcep_session_counters(pcep_session *session); +void reset_pcep_session_counters(pcep_session *session); + +/* + * Event Queue functions + */ + +/* Returns true if the queue is empty, false otherwise */ +bool event_queue_is_empty(void); + +/* Return the number of events on the queue, 0 if empty */ +uint32_t event_queue_num_events_available(void); + +/* Return the next event on the queue, NULL if empty */ +struct pcep_event *event_queue_get_event(void); + +/* Free the PCEP Event resources, including the PCEP message */ +void destroy_pcep_event(struct pcep_event *event); + +const char *get_event_type_str(int event_type); + + +#endif /* PCEPPCC_INCLUDE_PCEPPCCAPI_H_ */ diff --git a/pceplib/pcep_session_logic.c b/pceplib/pcep_session_logic.c new file mode 100644 index 0000000000..5e4dae4900 --- /dev/null +++ b/pceplib/pcep_session_logic.c @@ -0,0 +1,683 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* + * public API function implementations for the session_logic + */ + +pcep_session_logic_handle *session_logic_handle_ = NULL; +pcep_event_queue *session_logic_event_queue_ = NULL; +int session_id_ = 0; + +void send_pcep_open(pcep_session *session); /* forward decl */ + +static bool run_session_logic_common() +{ + if (session_logic_handle_ != NULL) { + pcep_log(LOG_WARNING, + "%s: Session Logic is already initialized.", __func__); + return false; + } + + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + + session_logic_handle_->active = true; + session_logic_handle_->session_logic_condition = false; + session_logic_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + session_logic_handle_->session_event_queue = queue_initialize(); + + /* Initialize the event queue */ + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); + if (pthread_mutex_init(&(session_logic_event_queue_->event_queue_mutex), + NULL) + != 0) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic event queue mutex.", + __func__); + return false; + } + + pthread_cond_init(&(session_logic_handle_->session_logic_cond_var), + NULL); + + if (pthread_mutex_init(&(session_logic_handle_->session_logic_mutex), + NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic mutex.", + __func__); + return false; + } + + if (pthread_mutex_init(&(session_logic_handle_->session_list_mutex), + NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_list mutex.", + __func__); + return false; + } + + return true; +} + + +bool run_session_logic() +{ + if (!run_session_logic_common()) { + return false; + } + + if (pthread_create(&(session_logic_handle_->session_logic_thread), NULL, + session_logic_loop, session_logic_handle_)) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic thread.", + __func__); + return false; + } + + if (!initialize_timers(session_logic_timer_expire_handler)) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic timers.", + __func__); + return false; + } + + /* No need to call initialize_socket_comm_loop() since it will be + * called internally when the first socket_comm_session is created. */ + + return true; +} + + +bool run_session_logic_with_infra(pceplib_infra_config *infra_config) +{ + if (infra_config == NULL) { + return run_session_logic(); + } + + /* Initialize the memory infrastructure before anything gets allocated + */ + if (infra_config->pceplib_infra_mt != NULL + && infra_config->pceplib_messages_mt != NULL) { + pceplib_memory_initialize( + infra_config->pceplib_infra_mt, + infra_config->pceplib_messages_mt, + infra_config->malloc_func, infra_config->calloc_func, + infra_config->realloc_func, infra_config->strdup_func, + infra_config->free_func); + } + + if (!run_session_logic_common()) { + return false; + } + + /* Create the pcep_session_logic pthread so it can be managed externally + */ + if (infra_config->pthread_create_func != NULL) { + if (infra_config->pthread_create_func( + &(session_logic_handle_->session_logic_thread), + NULL, session_logic_loop, session_logic_handle_, + "pcep_session_logic")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external session_logic thread.", + __func__); + return false; + } + } else { + if (pthread_create( + &(session_logic_handle_->session_logic_thread), + NULL, session_logic_loop, session_logic_handle_)) { + pcep_log(LOG_ERR, + "%s: Cannot initialize session_logic thread.", + __func__); + return false; + } + } + + session_logic_event_queue_->event_callback = + infra_config->pcep_event_func; + session_logic_event_queue_->event_callback_data = + infra_config->external_infra_data; + + if (!initialize_timers_external_infra( + session_logic_timer_expire_handler, + infra_config->external_infra_data, + infra_config->timer_create_func, + infra_config->timer_cancel_func, + infra_config->pthread_create_func)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic timers with infra.", + __func__); + return false; + } + + /* We found a problem with the FRR sockets, where not all the KeepAlive + * messages were received, so if the pthread_create_func is set, the + * internal PCEPlib socket infrastructure will be used. */ + + /* For the SocketComm, the socket_read/write_func and the + * pthread_create_func are mutually exclusive. */ + if (infra_config->pthread_create_func != NULL) { + if (!initialize_socket_comm_external_infra( + infra_config->external_infra_data, NULL, NULL, + infra_config->pthread_create_func)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic socket comm with infra.", + __func__); + return false; + } + } else if (infra_config->socket_read_func != NULL + && infra_config->socket_write_func != NULL) { + if (!initialize_socket_comm_external_infra( + infra_config->external_infra_data, + infra_config->socket_read_func, + infra_config->socket_write_func, NULL)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic socket comm with infra.", + __func__); + return false; + } + } + + return true; +} + +bool run_session_logic_wait_for_completion() +{ + if (!run_session_logic()) { + return false; + } + + /* Blocking call, waits for session logic thread to complete */ + pthread_join(session_logic_handle_->session_logic_thread, NULL); + + return true; +} + + +bool stop_session_logic() +{ + if (session_logic_handle_ == NULL) { + pcep_log(LOG_WARNING, "%s: Session logic already stopped", + __func__); + return false; + } + + session_logic_handle_->active = false; + teardown_timers(); + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + pthread_join(session_logic_handle_->session_logic_thread, NULL); + + pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex)); + ordered_list_destroy(session_logic_handle_->session_list); + queue_destroy(session_logic_handle_->session_event_queue); + + /* destroy the event_queue */ + pthread_mutex_destroy(&(session_logic_event_queue_->event_queue_mutex)); + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + + /* Explicitly stop the socket comm loop started by the pcep_sessions */ + destroy_socket_comm_loop(); + + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + session_logic_handle_ = NULL; + + return true; +} + + +void close_pcep_session(pcep_session *session) +{ + close_pcep_session_with_reason(session, PCEP_CLOSE_REASON_NO); +} + +void close_pcep_session_with_reason(pcep_session *session, + enum pcep_close_reason reason) +{ + struct pcep_message *close_msg = pcep_msg_create_close(reason); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send pcep_close message for session [%d]", + __func__, time(NULL), pthread_self(), session->session_id); + + session_send_message(session, close_msg); + socket_comm_session_close_tcp_after_write(session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; +} + + +void destroy_pcep_session(pcep_session *session) +{ + if (session == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot destroy NULL session", + __func__); + return; + } + + /* Remove the session from the session_list and synchronize session + * destroy with the session_logic_loop, so that no in-flight events + * will be handled now that the session is destroyed. */ + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + ordered_list_remove_first_node_equals( + session_logic_handle_->session_list, session); + pcep_log(LOG_DEBUG, + "%s: destroy_pcep_session delete session_list sessionPtr %p", + __func__, session); + + pcep_session_cancel_timers(session); + delete_counters_group(session->pcep_session_counters); + queue_destroy_with_data(session->num_unknown_messages_time_queue); + socket_comm_session_teardown(session->socket_comm_session); + + if (session->pcc_config.pcep_msg_versioning != NULL) { + pceplib_free(PCEPLIB_INFRA, + session->pcc_config.pcep_msg_versioning); + } + + if (session->pce_config.pcep_msg_versioning != NULL) { + pceplib_free(PCEPLIB_INFRA, + session->pce_config.pcep_msg_versioning); + } + + int session_id = session->session_id; + pceplib_free(PCEPLIB_INFRA, session); + pcep_log(LOG_INFO, "%s: [%ld-%ld] session [%d] destroyed", __func__, + time(NULL), pthread_self(), session_id); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); +} + +void pcep_session_cancel_timers(pcep_session *session) +{ + if (session == NULL) { + return; + } + + if (session->timer_id_dead_timer != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_dead_timer); + } + + if (session->timer_id_keep_alive != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_keep_alive); + } + + if (session->timer_id_open_keep_wait != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_open_keep_wait); + } + + if (session->timer_id_open_keep_alive != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_open_keep_alive); + } +} + +/* Internal util function */ +static int get_next_session_id() +{ + if (session_id_ == INT_MAX) { + session_id_ = 0; + } + + return session_id_++; +} + +/* Internal util function */ +static pcep_session *create_pcep_session_pre_setup(pcep_configuration *config) +{ + if (config == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL config", + __func__); + return NULL; + } + + pcep_session *session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session)); + memset(session, 0, sizeof(pcep_session)); + session->session_id = get_next_session_id(); + session->session_state = SESSION_STATE_INITIALIZED; + session->timer_id_open_keep_wait = TIMER_ID_NOT_SET; + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + session->stateful_pce = false; + session->num_unknown_messages_time_queue = queue_initialize(); + session->pce_open_received = false; + session->pce_open_rejected = false; + session->pce_open_keep_alive_sent = false; + session->pcc_open_rejected = false; + session->pce_open_accepted = false; + session->pcc_open_accepted = false; + session->destroy_session_after_write = false; + session->lsp_db_version = config->lsp_db_version; + memcpy(&(session->pcc_config), config, sizeof(pcep_configuration)); + /* copy the pcc_config to the pce_config until we receive the open + * keep_alive response */ + memcpy(&(session->pce_config), config, sizeof(pcep_configuration)); + if (config->pcep_msg_versioning != NULL) { + session->pcc_config.pcep_msg_versioning = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memcpy(session->pcc_config.pcep_msg_versioning, + config->pcep_msg_versioning, + sizeof(struct pcep_versioning)); + session->pce_config.pcep_msg_versioning = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memcpy(session->pce_config.pcep_msg_versioning, + config->pcep_msg_versioning, + sizeof(struct pcep_versioning)); + } + + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + ordered_list_add_node(session_logic_handle_->session_list, session); + pcep_log( + LOG_DEBUG, + "%s: create_pcep_session_pre_setup add session_list sessionPtr %p", + __func__, session); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); + + return session; +} + +/* Internal util function */ +static bool create_pcep_session_post_setup(pcep_session *session) +{ + if (!socket_comm_session_connect_tcp(session->socket_comm_session)) { + pcep_log(LOG_WARNING, "%s: Cannot establish TCP socket.", + __func__); + destroy_pcep_session(session); + + return false; + } + + session->time_connected = time(NULL); + create_session_counters(session); + + send_pcep_open(session); + + session->session_state = SESSION_STATE_PCEP_CONNECTING; + session->timer_id_open_keep_wait = + create_timer(session->pcc_config.keep_alive_seconds, session); + // session->session_state = SESSION_STATE_OPENED; + + return true; +} + +pcep_session *create_pcep_session(pcep_configuration *config, + struct in_addr *pce_ip) +{ + if (pce_ip == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL pce_ip", + __func__); + return NULL; + } + + pcep_session *session = create_pcep_session_pre_setup(config); + if (session == NULL) { + return NULL; + } + + session->socket_comm_session = socket_comm_session_initialize_with_src( + NULL, session_logic_msg_ready_handler, + session_logic_message_sent_handler, + session_logic_conn_except_notifier, &(config->src_ip.src_ipv4), + ((config->src_pcep_port == 0) ? PCEP_TCP_PORT + : config->src_pcep_port), + pce_ip, + ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT + : config->dst_pcep_port), + config->socket_connect_timeout_millis, + config->tcp_authentication_str, config->is_tcp_auth_md5, + session); + if (session->socket_comm_session == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot establish socket_comm_session.", __func__); + destroy_pcep_session(session); + + return NULL; + } + + if (create_pcep_session_post_setup(session) == false) { + return NULL; + } + + return session; +} + +pcep_session *create_pcep_session_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip) +{ + if (pce_ip == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL pce_ip", + __func__); + return NULL; + } + + pcep_session *session = create_pcep_session_pre_setup(config); + if (session == NULL) { + return NULL; + } + + session->socket_comm_session = + socket_comm_session_initialize_with_src_ipv6( + NULL, session_logic_msg_ready_handler, + session_logic_message_sent_handler, + session_logic_conn_except_notifier, + &(config->src_ip.src_ipv6), + ((config->src_pcep_port == 0) ? PCEP_TCP_PORT + : config->src_pcep_port), + pce_ip, + ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT + : config->dst_pcep_port), + config->socket_connect_timeout_millis, + config->tcp_authentication_str, config->is_tcp_auth_md5, + session); + if (session->socket_comm_session == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot establish ipv6 socket_comm_session.", + __func__); + destroy_pcep_session(session); + + return NULL; + } + + if (create_pcep_session_post_setup(session) == false) { + return NULL; + } + + return session; +} + + +void session_send_message(pcep_session *session, struct pcep_message *message) +{ + pcep_encode_message(message, session->pcc_config.pcep_msg_versioning); + socket_comm_session_send_message(session->socket_comm_session, + (char *)message->encoded_message, + message->encoded_message_length, true); + + increment_message_tx_counters(session, message); + + /* The message->encoded_message will be freed in + * socket_comm_session_send_message() once sent. + * Setting to NULL here so pcep_msg_free_message() does not free it */ + message->encoded_message = NULL; + pcep_msg_free_message(message); +} + + +/* This function is also used in pcep_session_logic_states.c */ +struct pcep_message *create_pcep_open(pcep_session *session) +{ + /* create and send PCEP open + * with PCEP, the PCC sends the config the PCE should use in the open + * message, + * and the PCE will send an open with the config the PCC should use. */ + double_linked_list *tlv_list = dll_initialize(); + if (session->pcc_config.support_stateful_pce_lsp_update + || session->pcc_config.support_pce_lsp_instantiation + || session->pcc_config.support_include_db_version + || session->pcc_config.support_lsp_triggered_resync + || session->pcc_config.support_lsp_delta_sync + || session->pcc_config.support_pce_triggered_initial_sync) { + /* Prepend this TLV as the first in the list */ + dll_append( + tlv_list, + pcep_tlv_create_stateful_pce_capability( + session->pcc_config + .support_stateful_pce_lsp_update, /* U + flag + */ + session->pcc_config + .support_include_db_version, /* S flag + */ + session->pcc_config + .support_lsp_triggered_resync, /* T flag + */ + session->pcc_config + .support_lsp_delta_sync, /* D flag */ + session->pcc_config + .support_pce_triggered_initial_sync, /* F flag */ + session->pcc_config + .support_pce_lsp_instantiation)); /* I + flag + */ + } + + if (session->pcc_config.support_include_db_version) { + if (session->pcc_config.lsp_db_version != 0) { + dll_append(tlv_list, + pcep_tlv_create_lsp_db_version( + session->pcc_config.lsp_db_version)); + } + } + + if (session->pcc_config.support_sr_te_pst) { + bool flag_n = false; + bool flag_x = false; + if (session->pcc_config.pcep_msg_versioning + ->draft_ietf_pce_segment_routing_07 + == false) { + flag_n = session->pcc_config.pcc_can_resolve_nai_to_sid; + flag_x = (session->pcc_config.max_sid_depth == 0); + } + + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv = + pcep_tlv_create_sr_pce_capability( + flag_n, flag_x, + session->pcc_config.max_sid_depth); + + double_linked_list *sub_tlv_list = NULL; + if (session->pcc_config.pcep_msg_versioning + ->draft_ietf_pce_segment_routing_07 + == true) { + /* With draft07, send the sr_pce_cap_tlv as a normal TLV + */ + dll_append(tlv_list, sr_pce_cap_tlv); + } else { + /* With draft16, send the sr_pce_cap_tlv as a sub-TLV in + * the path_setup_type_capability TLV */ + sub_tlv_list = dll_initialize(); + dll_append(sub_tlv_list, sr_pce_cap_tlv); + } + + uint8_t *pst = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t)); + *pst = SR_TE_PST; + double_linked_list *pst_list = dll_initialize(); + dll_append(pst_list, pst); + dll_append(tlv_list, pcep_tlv_create_path_setup_type_capability( + pst_list, sub_tlv_list)); + } + + struct pcep_message *open_msg = pcep_msg_create_open_with_tlvs( + session->pcc_config.keep_alive_seconds, + session->pcc_config.dead_timer_seconds, session->session_id, + tlv_list); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic create open message: TLVs [%d] for session [%d]", + __func__, time(NULL), pthread_self(), tlv_list->num_entries, + session->session_id); + + return (open_msg); +} + + +void send_pcep_open(pcep_session *session) +{ + session_send_message(session, create_pcep_open(session)); +} + +/* This is a blocking call, since it is synchronized with destroy_pcep_session() + * and session_logic_loop(). It may be possible that the session has been + * deleted but API users havent been informed yet. + */ +bool session_exists(pcep_session *session) +{ + if (session_logic_handle_ == NULL) { + pcep_log(LOG_DEBUG, + "%s: session_exists session_logic_handle_ is NULL", + __func__); + return false; + } + + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + bool retval = + (ordered_list_find(session_logic_handle_->session_list, session) + != NULL); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); + + return retval; +} diff --git a/pceplib/pcep_session_logic.h b/pceplib/pcep_session_logic.h new file mode 100644 index 0000000000..a082ec66da --- /dev/null +++ b/pceplib/pcep_session_logic.h @@ -0,0 +1,290 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPSESSIONLOGIC_H_ +#define INCLUDE_PCEPSESSIONLOGIC_H_ + +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_socket_comm.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_timers.h" +#include "pcep_utils_queue.h" +#include "pcep_utils_memory.h" + +#define PCEP_TCP_PORT 4189 + +typedef struct pcep_configuration_ { + /* These are the configuration values that will + * be sent to the PCE in the PCEP Open message */ + int keep_alive_seconds; + int dead_timer_seconds; + int dead_timer_pce_negotiated_seconds; /* Config data negotiated with + PCE */ + int keep_alive_pce_negotiated_timer_seconds; /* Config data negotiated + with PCE */ + int request_time_seconds; + + /* These are the acceptable ranges of values received by + * the PCE in the initial PCEP Open Message. If a value is + * received outside of these ranges, then the Open message + * will be rejected. */ + int min_keep_alive_seconds; + int max_keep_alive_seconds; + int min_dead_timer_seconds; + int max_dead_timer_seconds; + + /* If more than this many unknown messages/requests are received + * per minute, then the session will be closed. */ + int max_unknown_messages; + int max_unknown_requests; + + /* Maximum amount of time to wait to connect to the + * PCE TCP socket before failing, in milliseconds. */ + uint32_t socket_connect_timeout_millis; + + /* Set if the PCE/PCC will support stateful PCE LSP Updates + * according to RCF8231, section 7.1.1, defaults to true. + * Will cause an additional TLV to be sent from the PCC in + * the PCEP Open */ + bool support_stateful_pce_lsp_update; + + /* RFC 8281: I-bit, the PCC allows instantiation of an LSP by a PCE */ + bool support_pce_lsp_instantiation; + + /* RFC 8232: S-bit, the PCC will include the LSP-DB-VERSION + * TLV in each LSP object */ + bool support_include_db_version; + + /* Only set if support_include_db_version is true and if the LSP-DB + * survived a restart and is available. If this has a value other than + * 0, then a LSP-DB-VERSION TLV will be sent in the OPEN object. This + * value will be copied over to the pcep_session upon init. */ + uint64_t lsp_db_version; + + /* RFC 8232: T-bit, the PCE can trigger resynchronization of + * LSPs at any point in the life of the session */ + bool support_lsp_triggered_resync; + + /* RFC 8232: D-bit, the PCEP speaker allows incremental (delta) + * State Synchronization */ + bool support_lsp_delta_sync; + + /* RFC 8232: F-bit, the PCE SHOULD trigger initial (first) + * State Synchronization */ + bool support_pce_triggered_initial_sync; + + /* draft-ietf-pce-segment-routing-16: Send a SR PCE Capability + * sub-TLV in a Path Setup Type Capability TLV with a PST = 1, + * Path is setup using SR TE. */ + bool support_sr_te_pst; + /* Used in the SR PCE Capability sub-TLV */ + bool pcc_can_resolve_nai_to_sid; + /* Used in the SR TE Capability sub-TLV, 0 means there are no max sid + * limits */ + uint8_t max_sid_depth; + + /* If set to 0, then the default 4189 PCEP port will be used */ + uint16_t dst_pcep_port; + + /* If set to 0, then the default 4189 PCEP port will be used. + * This is according to the RFC5440, Section 5 */ + uint16_t src_pcep_port; + + union src_ip { + struct in_addr src_ipv4; + struct in6_addr src_ipv6; + } src_ip; + bool is_src_ipv6; + + struct pcep_versioning *pcep_msg_versioning; + + char tcp_authentication_str[TCP_MD5SIG_MAXKEYLEN]; + bool is_tcp_auth_md5; /* true: RFC 2385, false: RFC 5925 */ + +} pcep_configuration; + + +typedef enum pcep_session_state_ { + SESSION_STATE_UNKNOWN = 0, + SESSION_STATE_INITIALIZED = 1, + SESSION_STATE_PCEP_CONNECTING = 2, + SESSION_STATE_PCEP_CONNECTED = 3 + +} pcep_session_state; + + +typedef struct pcep_session_ { + int session_id; + pcep_session_state session_state; + int timer_id_open_keep_wait; + int timer_id_open_keep_alive; + int timer_id_dead_timer; + int timer_id_keep_alive; + bool pce_open_received; + bool pce_open_rejected; + bool pce_open_accepted; + bool pce_open_keep_alive_sent; + bool pcc_open_rejected; + bool pcc_open_accepted; + bool stateful_pce; + time_t time_connected; + uint64_t lsp_db_version; + queue_handle *num_unknown_messages_time_queue; + /* set this flag when finalizing the session */ + bool destroy_session_after_write; + pcep_socket_comm_session *socket_comm_session; + /* Configuration sent from the PCC to the PCE */ + pcep_configuration pcc_config; + /* Configuration received from the PCE, to be used in the PCC */ + pcep_configuration pce_config; + struct counters_group *pcep_session_counters; + +} pcep_session; + + +typedef enum pcep_event_type { + MESSAGE_RECEIVED = 0, + PCE_CLOSED_SOCKET = 1, + PCE_SENT_PCEP_CLOSE = 2, + PCE_DEAD_TIMER_EXPIRED = 3, + PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED = 4, + PCC_CONNECTED_TO_PCE = 100, + PCC_CONNECTION_FAILURE = 101, + PCC_PCEP_SESSION_CLOSED = 102, + PCC_RCVD_INVALID_OPEN = 103, + PCC_SENT_INVALID_OPEN = 104, + PCC_RCVD_MAX_INVALID_MSGS = 105, + PCC_RCVD_MAX_UNKOWN_MSGS = 106 + +} pcep_event_type; + + +typedef struct pcep_event { + enum pcep_event_type event_type; + time_t event_time; + struct pcep_message *message; + pcep_session *session; + +} pcep_event; + +typedef void (*pceplib_pcep_event_callback)(void *cb_data, pcep_event *); +typedef int (*pthread_create_callback)(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); + + +typedef struct pcep_event_queue { + /* The event_queue and event_callback are mutually exclusive. + * If the event_callback is configured, then the event_queue + * will not be used. */ + queue_handle *event_queue; + pthread_mutex_t event_queue_mutex; + pceplib_pcep_event_callback event_callback; + void *event_callback_data; + +} pcep_event_queue; + + +typedef struct pceplib_infra_config { + /* Memory infrastructure */ + void *pceplib_infra_mt; + void *pceplib_messages_mt; + pceplib_malloc_func malloc_func; + pceplib_calloc_func calloc_func; + pceplib_realloc_func realloc_func; + pceplib_strdup_func strdup_func; + pceplib_free_func free_func; + + /* External Timer and Socket infrastructure */ + void *external_infra_data; + ext_timer_create timer_create_func; + ext_timer_cancel timer_cancel_func; + ext_socket_write socket_write_func; + ext_socket_read socket_read_func; + + /* External pcep_event infrastructure */ + pceplib_pcep_event_callback pcep_event_func; + + /* Callback to create pthreads */ + pthread_create_callback pthread_create_func; + +} pceplib_infra_config; + +/* + * Counters Sub-groups definitions + */ +typedef enum pcep_session_counters_subgroup_ids { + COUNTER_SUBGROUP_ID_RX_MSG = 0, + COUNTER_SUBGROUP_ID_TX_MSG = 1, + COUNTER_SUBGROUP_ID_RX_OBJ = 2, + COUNTER_SUBGROUP_ID_TX_OBJ = 3, + COUNTER_SUBGROUP_ID_RX_SUBOBJ = 4, + COUNTER_SUBGROUP_ID_TX_SUBOBJ = 5, + COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ = 6, + COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ = 7, + COUNTER_SUBGROUP_ID_RX_TLV = 8, + COUNTER_SUBGROUP_ID_TX_TLV = 9, + COUNTER_SUBGROUP_ID_EVENT = 10 + +} pcep_session_counters_subgroup_ids; + +bool run_session_logic(void); +bool run_session_logic_with_infra(pceplib_infra_config *infra_config); + +bool run_session_logic_wait_for_completion(void); + +bool stop_session_logic(void); + +/* Uses the standard PCEP TCP dest port = 4189 and an ephemeral src port. + * To use a specific dest or src port, set them other than 0 in the + * pcep_configuration. */ +pcep_session *create_pcep_session(pcep_configuration *config, + struct in_addr *pce_ip); +pcep_session *create_pcep_session_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip); + +/* Send a PCEP close for this pcep_session */ +void close_pcep_session(pcep_session *session); +void close_pcep_session_with_reason(pcep_session *session, + enum pcep_close_reason); + +/* Destroy the PCEP session, a PCEP close should have + * already been sent with close_pcep_session() */ +void destroy_pcep_session(pcep_session *session); + +void pcep_session_cancel_timers(pcep_session *session); + +/* Increments transmitted message counters, additionally counters for the + * objects, sub-objects, and TLVs in the message will be incremented. Received + * counters are incremented internally. */ +void increment_message_tx_counters(pcep_session *session, + struct pcep_message *message); + +bool session_exists(pcep_session *session); + +#endif /* INCLUDE_PCEPSESSIONLOGIC_H_ */ diff --git a/pceplib/pcep_session_logic_counters.c b/pceplib/pcep_session_logic_counters.c new file mode 100644 index 0000000000..a6bd41b4f1 --- /dev/null +++ b/pceplib/pcep_session_logic_counters.c @@ -0,0 +1,450 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * PCEP session logic counters configuration. + */ + +#include +#include + +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" + +void increment_message_counters(pcep_session *session, + struct pcep_message *message, bool is_rx); + +void create_session_counters(pcep_session *session) +{ + /* + * Message RX and TX counters + */ + struct counters_subgroup *rx_msg_subgroup = create_counters_subgroup( + "RX Message counters", COUNTER_SUBGROUP_ID_RX_MSG, + PCEP_TYPE_MAX + 1); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_OPEN, + "Message Open"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_KEEPALIVE, + "Message KeepAlive"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCREQ, + "Message PcReq"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCREP, + "Message PcRep"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCNOTF, + "Message Notify"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_ERROR, + "Message Error"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_CLOSE, + "Message Close"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_REPORT, + "Message Report"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_UPDATE, + "Message Update"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_INITIATE, + "Message Initiate"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_START_TLS, + "Message StartTls"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_MAX, + "Message Erroneous"); + + struct counters_subgroup *tx_msg_subgroup = + clone_counters_subgroup(rx_msg_subgroup, "TX Message counters", + COUNTER_SUBGROUP_ID_TX_MSG); + + /* + * Object RX and TX counters + */ + + /* For the Endpoints, the ID will be either 64 or 65, so setting + * num_counters to 100 */ + struct counters_subgroup *rx_obj_subgroup = create_counters_subgroup( + "RX Object counters", COUNTER_SUBGROUP_ID_RX_OBJ, 100); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_OPEN, + "Object Open"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_RP, + "Object RP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_NOPATH, + "Object Nopath"); + create_subgroup_counter( + rx_obj_subgroup, + ((PCEP_OBJ_CLASS_ENDPOINTS << 4) | PCEP_OBJ_TYPE_ENDPOINT_IPV4), + "Object Endpoint IPv4"); + create_subgroup_counter( + rx_obj_subgroup, + ((PCEP_OBJ_CLASS_ENDPOINTS << 4) | PCEP_OBJ_TYPE_ENDPOINT_IPV6), + "Object Endpoint IPv6"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_BANDWIDTH, + "Object Bandwidth"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_METRIC, + "Object Metric"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ERO, + "Object ERO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_RRO, + "Object RRO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_LSPA, + "Object LSPA"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_IRO, + "Object IRO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SVEC, + "Object SVEC"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_NOTF, + "Object Notify"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ERROR, + "Object Error"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_CLOSE, + "Object Close"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_LSP, + "Object LSP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SRP, + "Object SRP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_VENDOR_INFO, + "Object Vendor Info"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_INTER_LAYER, + "Object Inter-Layer"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SWITCH_LAYER, + "Object Switch-Layer"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_REQ_ADAP_CAP, + "Object Requested Adap-Cap"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SERVER_IND, + "Object Server-Indication"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ASSOCIATION, + "Object Association"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_MAX, + "Object Unknown"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_MAX + 1, + "Object Erroneous"); + + struct counters_subgroup *tx_obj_subgroup = + clone_counters_subgroup(rx_obj_subgroup, "TX Object counters", + COUNTER_SUBGROUP_ID_TX_OBJ); + + /* + * Sub-Object RX and TX counters + */ + struct counters_subgroup *rx_subobj_subgroup = create_counters_subgroup( + "RX RO Sub-Object counters", COUNTER_SUBGROUP_ID_RX_SUBOBJ, + RO_SUBOBJ_UNKNOWN + 2); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_IPV4, + "RO Sub-Object IPv4"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_IPV6, + "RO Sub-Object IPv6"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_LABEL, + "RO Sub-Object Label"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_UNNUM, + "RO Sub-Object Unnum"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_ASN, + "RO Sub-Object ASN"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_SR, + "RO Sub-Object SR"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_UNKNOWN, + "RO Sub-Object Unknown"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_UNKNOWN + 1, + "RO Sub-Object Erroneous"); + + struct counters_subgroup *tx_subobj_subgroup = clone_counters_subgroup( + rx_subobj_subgroup, "TX RO Sub-Object counters", + COUNTER_SUBGROUP_ID_TX_SUBOBJ); + + /* + * RO SR Sub-Object RX and TX counters + */ + struct counters_subgroup *rx_subobj_sr_nai_subgroup = + create_counters_subgroup("RX RO SR NAI Sub-Object counters", + COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ, + PCEP_SR_SUBOBJ_NAI_UNKNOWN + 1); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_ABSENT, + "RO Sub-Object SR NAI absent"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, + "RO Sub-Object SR NAI IPv4 Node"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, + "RO Sub-Object SR NAI IPv6 Node"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, + "RO Sub-Object SR NAI IPv4 Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, + "RO Sub-Object SR NAI IPv6 Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, + "RO Sub-Object SR NAI Unnumbered IPv4 Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, + "RO Sub-Object SR NAI Link Local IPv6 Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_UNKNOWN, + "RO Sub-Object SR NAI Unknown"); + + struct counters_subgroup *tx_subobj_sr_nai_subgroup = + clone_counters_subgroup(rx_subobj_sr_nai_subgroup, + "TX RO SR NAI Sub-Object counters", + COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ); + + /* + * TLV RX and TX counters + */ + struct counters_subgroup *rx_tlv_subgroup = create_counters_subgroup( + "RX TLV counters", COUNTER_SUBGROUP_ID_RX_TLV, + PCEP_OBJ_TLV_TYPE_UNKNOWN + 1); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR, + "TLV No Path Vector"); + create_subgroup_counter(rx_tlv_subgroup, PCEP_OBJ_TLV_TYPE_VENDOR_INFO, + "TLV Vendor Info"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY, + "TLV Stateful PCE Capability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME, + "TLV Symbolic Path Name"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS, + "TLV IPv4 LSP Identifier"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS, + "TLV IPv6 LSP Identifier"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE, + "TLV LSP Error Code"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + "TLV RSVP Error Spec"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION, + "TLV LSP DB Version"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID, + "TLV Speaker Entity ID"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY, + "TLV SR PCE Capability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE, + "TLV Path Setup Type"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY, + "TLV Path Setup Type Capability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + "TLV SR Policy PolId"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME, + "TLV SR Policy PolName"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID, + "TLV SR Policy CpathId"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE, + "TLV SR Policy CpathRef"); + create_subgroup_counter(rx_tlv_subgroup, PCEP_OBJ_TLV_TYPE_UNKNOWN, + "TLV Unknown"); + + struct counters_subgroup *tx_tlv_subgroup = clone_counters_subgroup( + rx_tlv_subgroup, "TX TLV counters", COUNTER_SUBGROUP_ID_TX_TLV); + + /* + * PCEP Event counters + */ + struct counters_subgroup *events_subgroup = create_counters_subgroup( + "Events counters", COUNTER_SUBGROUP_ID_EVENT, MAX_COUNTERS); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCC_CONNECT, + "PCC connect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT, + "PCE connect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCC_DISCONNECT, + "PCC disconnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT, + "PCE disconnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE, + "Timer KeepAlive expired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER, + "Timer DeadTimer expired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT, + "Timer OpenKeepWait expired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE, + "Timer OpenKeepAlive expired"); + + /* + * Create the parent counters group + */ + time_t now = time(NULL); + char counters_name[MAX_COUNTER_STR_LENGTH] = {0}; + char ip_str[40] = {0}; + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + ip_str, 40); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + ip_str, 40); + } + snprintf(counters_name, MAX_COUNTER_STR_LENGTH, + "PCEP Session [%d], connected to [%s] for [%u seconds]", + session->session_id, ip_str, + (uint32_t)(now - session->time_connected)); + /* The (time(NULL) - session->time_connected) will probably be 0, + * so the group name will be updated when the counters are dumped */ + session->pcep_session_counters = + create_counters_group(counters_name, MAX_COUNTER_GROUPS); + + /* + * Add all the subgroups to the parent counters group + */ + add_counters_subgroup(session->pcep_session_counters, rx_msg_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_msg_subgroup); + add_counters_subgroup(session->pcep_session_counters, rx_obj_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_obj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + rx_subobj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + tx_subobj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + rx_subobj_sr_nai_subgroup); + add_counters_subgroup(session->pcep_session_counters, + tx_subobj_sr_nai_subgroup); + add_counters_subgroup(session->pcep_session_counters, rx_tlv_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_tlv_subgroup); + add_counters_subgroup(session->pcep_session_counters, events_subgroup); +} + +/* Internal util function used by increment_message_rx_counters or + * increment_message_tx_counters */ +void increment_message_counters(pcep_session *session, + struct pcep_message *message, bool is_rx) +{ + uint16_t counter_subgroup_id_msg = (is_rx ? COUNTER_SUBGROUP_ID_RX_MSG + : COUNTER_SUBGROUP_ID_TX_MSG); + uint16_t counter_subgroup_id_obj = (is_rx ? COUNTER_SUBGROUP_ID_RX_OBJ + : COUNTER_SUBGROUP_ID_TX_OBJ); + uint16_t counter_subgroup_id_subobj = + (is_rx ? COUNTER_SUBGROUP_ID_RX_SUBOBJ + : COUNTER_SUBGROUP_ID_TX_SUBOBJ); + uint16_t counter_subgroup_id_ro_sr_subobj = + (is_rx ? COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ + : COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ); + uint16_t counter_subgroup_id_tlv = (is_rx ? COUNTER_SUBGROUP_ID_RX_TLV + : COUNTER_SUBGROUP_ID_TX_TLV); + + increment_counter(session->pcep_session_counters, + counter_subgroup_id_msg, message->msg_header->type); + + /* Iterate the objects */ + double_linked_list_node *obj_node = + (message->obj_list == NULL ? NULL : message->obj_list->head); + for (; obj_node != NULL; obj_node = obj_node->next_node) { + struct pcep_object_header *obj = + (struct pcep_object_header *)obj_node->data; + + /* Handle class: PCEP_OBJ_CLASS_ENDPOINTS, + * type: PCEP_OBJ_TYPE_ENDPOINT_IPV4 or + * PCEP_OBJ_TYPE_ENDPOINT_IPV6 */ + uint16_t obj_counter_id = + (obj->object_class == PCEP_OBJ_CLASS_ENDPOINTS + ? (obj->object_class << 4) | obj->object_type + : obj->object_class); + + increment_counter(session->pcep_session_counters, + counter_subgroup_id_obj, obj_counter_id); + + /* Iterate the RO Sub-objects */ + if (obj->object_class == PCEP_OBJ_CLASS_ERO + || obj->object_class == PCEP_OBJ_CLASS_IRO + || obj->object_class == PCEP_OBJ_CLASS_RRO) { + struct pcep_object_ro *ro_obj = + (struct pcep_object_ro *)obj; + + double_linked_list_node *ro_subobj_node = + (ro_obj->sub_objects == NULL + ? NULL + : ro_obj->sub_objects->head); + for (; ro_subobj_node != NULL; + ro_subobj_node = ro_subobj_node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = + (struct pcep_object_ro_subobj *) + ro_subobj_node->data; + increment_counter( + session->pcep_session_counters, + counter_subgroup_id_subobj, + ro_subobj->ro_subobj_type); + + /* Handle the ro subobj type RO_SUBOBJ_TYPE_SR + * different NAI types */ + if (ro_subobj->ro_subobj_type + == RO_SUBOBJ_TYPE_SR) { + struct pcep_ro_subobj_sr *ro_sr_subobj = + (struct pcep_ro_subobj_sr *) + ro_subobj; + increment_counter( + session->pcep_session_counters, + counter_subgroup_id_ro_sr_subobj, + ro_sr_subobj->nai_type); + } + } + } + + /* Iterate the TLVs */ + double_linked_list_node *tlv_node = + (obj->tlv_list == NULL ? NULL : obj->tlv_list->head); + for (; tlv_node != NULL; tlv_node = tlv_node->next_node) { + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)tlv_node->data; + increment_counter(session->pcep_session_counters, + counter_subgroup_id_tlv, tlv->type); + } + } +} + +void increment_message_rx_counters(pcep_session *session, + struct pcep_message *message) +{ + increment_message_counters(session, message, true); +} + +void increment_message_tx_counters(pcep_session *session, + struct pcep_message *message) +{ + increment_message_counters(session, message, false); +} + +void increment_event_counters( + pcep_session *session, + pcep_session_counters_event_counter_ids counter_id) +{ + increment_counter(session->pcep_session_counters, + COUNTER_SUBGROUP_ID_EVENT, counter_id); +} diff --git a/pceplib/pcep_session_logic_internals.h b/pceplib/pcep_session_logic_internals.h new file mode 100644 index 0000000000..004459b918 --- /dev/null +++ b/pceplib/pcep_session_logic_internals.h @@ -0,0 +1,109 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Internal Session Logic declarations, not intended to be in the public API. + */ + +#ifndef SRC_PCEPSESSIONLOGICINTERNALS_H_ +#define SRC_PCEPSESSIONLOGICINTERNALS_H_ + + +#include +#include + +#include "pcep_msg_tools.h" + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_queue.h" + + +typedef struct pcep_session_logic_handle_ { + pthread_t session_logic_thread; + pthread_mutex_t session_logic_mutex; + pthread_cond_t session_logic_cond_var; + bool session_logic_condition; + bool active; + + ordered_list_handle *session_list; + pthread_mutex_t session_list_mutex; + /* Internal timers and socket events */ + queue_handle *session_event_queue; + +} pcep_session_logic_handle; + + +/* Used internally for Session events: message received, timer expired, + * or socket closed */ +typedef struct pcep_session_event_ { + pcep_session *session; + int expired_timer_id; + double_linked_list *received_msg_list; + bool socket_closed; + +} pcep_session_event; + +/* Event Counters counter-id definitions */ +typedef enum pcep_session_counters_event_counter_ids { + PCEP_EVENT_COUNTER_ID_PCC_CONNECT = 0, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT = 1, + PCEP_EVENT_COUNTER_ID_PCC_DISCONNECT = 2, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT = 3, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE = 4, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER = 5, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT = 6, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE = 7 + +} pcep_session_counters_event_counter_ids; + +/* functions implemented in pcep_session_logic_loop.c */ +void *session_logic_loop(void *data); +int session_logic_msg_ready_handler(void *data, int socket_fd); +void session_logic_message_sent_handler(void *data, int socket_fd); +void session_logic_conn_except_notifier(void *data, int socket_fd); +void session_logic_timer_expire_handler(void *data, int timer_id); + +void handle_timer_event(pcep_session_event *event); +void handle_socket_comm_event(pcep_session_event *event); +void session_send_message(pcep_session *session, struct pcep_message *message); + +/* defined in pcep_session_logic_states.c */ +void send_pcep_error(pcep_session *session, enum pcep_error_type error_type, + enum pcep_error_value error_value); +void enqueue_event(pcep_session *session, pcep_event_type event_type, + struct pcep_message *message); +void increment_unknown_message(pcep_session *session); + +/* defined in pcep_session_logic_counters.c */ +void create_session_counters(pcep_session *session); +void increment_event_counters( + pcep_session *session, + pcep_session_counters_event_counter_ids counter_id); +void increment_message_rx_counters(pcep_session *session, + struct pcep_message *message); + +/* defined in pcep_session_logic.c, also used in pcep_session_logic_states.c */ +struct pcep_message *create_pcep_open(pcep_session *session); + +#endif /* SRC_PCEPSESSIONLOGICINTERNALS_H_ */ diff --git a/pceplib/pcep_session_logic_loop.c b/pceplib/pcep_session_logic_loop.c new file mode 100644 index 0000000000..5705ff2000 --- /dev/null +++ b/pceplib/pcep_session_logic_loop.c @@ -0,0 +1,360 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +#include +#include +#include + +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* global var needed for callback handlers */ +extern pcep_session_logic_handle *session_logic_handle_; + +/* internal util function to create session_event's */ +static pcep_session_event *create_session_event(pcep_session *session) +{ + pcep_session_event *event = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session_event)); + event->session = session; + event->expired_timer_id = TIMER_ID_NOT_SET; + event->received_msg_list = NULL; + event->socket_closed = false; + + return event; +} + + +/* A function pointer to this function is passed to pcep_socket_comm + * for each pcep_session creation, so it will be called whenever + * messages are ready to be read. This function will be called + * by the socket_comm thread. + * This function will decode the read PCEP message and give it + * to the session_logic_loop so it can be handled by the session_logic + * state machine. */ +int session_logic_msg_ready_handler(void *data, int socket_fd) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle msg_ready with NULL data", + __func__); + return -1; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a message ready notification while the session logic is not active", + __func__); + return -1; + } + + pcep_session *session = (pcep_session *)data; + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + + /* This event will ultimately be handled by handle_socket_comm_event() + * in pcep_session_logic_states.c */ + pcep_session_event *rcvd_msg_event = create_session_event(session); + + int msg_length = 0; + double_linked_list *msg_list = pcep_msg_read(socket_fd); + + if (msg_list == NULL) { + /* The socket was closed, or there was a socket read error */ + pcep_log(LOG_INFO, + "%s: PCEP connection closed for session [%d]", + __func__, session->session_id); + dll_destroy(msg_list); + rcvd_msg_event->socket_closed = true; + socket_comm_session_teardown(session->socket_comm_session); + pcep_session_cancel_timers(session); + session->socket_comm_session = NULL; + session->session_state = SESSION_STATE_INITIALIZED; + enqueue_event(session, PCE_CLOSED_SOCKET, NULL); + } else if (msg_list->num_entries == 0) { + /* Invalid message received */ + increment_unknown_message(session); + } else { + /* Just logging the first of potentially several messages + * received */ + struct pcep_message *msg = + ((struct pcep_message *)msg_list->head->data); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] session_logic_msg_ready_handler received message of type [%d] len [%d] on session [%d]", + __func__, time(NULL), pthread_self(), + msg->msg_header->type, msg->encoded_message_length, + session->session_id); + + rcvd_msg_event->received_msg_list = msg_list; + msg_length = msg->encoded_message_length; + } + + queue_enqueue(session_logic_handle_->session_event_queue, + rcvd_msg_event); + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + + return msg_length; +} + + +/* A function pointer to this function was passed to pcep_socket_comm, + * so it will be called when a message is sent. This is useful since + * message sending is asynchronous, and there are times that actions + * need to be performed only after a message has been sent. */ +void session_logic_message_sent_handler(void *data, int socket_fd) +{ + (void)socket_fd; + + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle msg_sent with NULL data", __func__); + return; + } + + pcep_session *session = (pcep_session *)data; + if (session->destroy_session_after_write == true) { + /* Do not call destroy until all of the queued messages are + * written */ + if (session->socket_comm_session != NULL + && session->socket_comm_session->message_queue->num_entries + == 0) { + destroy_pcep_session(session); + } + } else { + /* Reset the keep alive timer for every message sent on + * the session, only if the session is not destroyed */ + if (session->timer_id_keep_alive == TIMER_ID_NOT_SET) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic set keep alive timer [%d secs] for session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session->session_id); + session->timer_id_keep_alive = create_timer( + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session); + } else { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic reset keep alive timer [%d secs] for session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session->session_id); + reset_timer(session->timer_id_keep_alive); + } + } +} + + +/* A function pointer to this function was passed to pcep_socket_comm, + * so it will be called whenever the socket is closed. this function + * will be called by the socket_comm thread. */ +void session_logic_conn_except_notifier(void *data, int socket_fd) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle conn_except with NULL data", + __func__); + return; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a connection exception notification while the session logic is not active", + __func__); + return; + } + + pcep_session *session = (pcep_session *)data; + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic session_logic_conn_except_notifier socket closed [%d], session [%d]", + __func__, time(NULL), pthread_self(), socket_fd, + session->session_id); + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + pcep_session_event *socket_event = create_session_event(session); + socket_event->socket_closed = true; + queue_enqueue(session_logic_handle_->session_event_queue, socket_event); + session_logic_handle_->session_logic_condition = true; + + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); +} + + +/* + * this method is the timer expire handler, and will only + * pass the event to the session_logic loop and notify it + * that there is a timer available. this function will be + * called by the timers thread. + */ +void session_logic_timer_expire_handler(void *data, int timer_id) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot handle timer with NULL data", + __func__); + return; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a timer expiration while the session logic is not active", + __func__); + return; + } + + pcep_log(LOG_INFO, "%s: [%ld-%ld] timer expired handler timer_id [%d]", + __func__, time(NULL), pthread_self(), timer_id); + pcep_session_event *expired_timer_event = + create_session_event((pcep_session *)data); + expired_timer_event->expired_timer_id = timer_id; + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + queue_enqueue(session_logic_handle_->session_event_queue, + expired_timer_event); + + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); +} + + +/* + * session_logic event loop + * this function is called upon thread creation from pcep_session_logic.c + */ +void *session_logic_loop(void *data) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot start session_logic_loop with NULL data", + __func__); + + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting session_logic_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_session_logic_handle *session_logic_handle = + (pcep_session_logic_handle *)data; + + while (session_logic_handle->active) { + /* Mutex locking for session_logic_loop condition variable */ + pthread_mutex_lock( + &(session_logic_handle->session_logic_mutex)); + + /* this internal loop helps avoid spurious interrupts */ + while (!session_logic_handle->session_logic_condition) { + pthread_cond_wait( + &(session_logic_handle->session_logic_cond_var), + &(session_logic_handle->session_logic_mutex)); + } + + pcep_session_event *event = queue_dequeue( + session_logic_handle->session_event_queue); + while (event != NULL) { + if (event->session == NULL) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Invalid session_logic_loop event [%s] with NULL session", + __func__, time(NULL), pthread_self(), + (event->expired_timer_id + != TIMER_ID_NOT_SET) + ? "timer" + : "message"); + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle + ->session_event_queue); + continue; + } + + /* Check if the session still exists, and synchronize + * possible session destroy */ + pcep_log( + LOG_DEBUG, + "%s: session_logic_loop checking session_list sessionPtr %p", + __func__, event->session); + pthread_mutex_lock( + &(session_logic_handle->session_list_mutex)); + if (ordered_list_find( + session_logic_handle->session_list, + event->session) + == NULL) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] In-flight event [%s] for destroyed session being discarded", + __func__, time(NULL), pthread_self(), + (event->expired_timer_id + != TIMER_ID_NOT_SET) + ? "timer" + : "message"); + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle + ->session_event_queue); + pthread_mutex_unlock( + &(session_logic_handle_ + ->session_list_mutex)); + continue; + } + + if (event->expired_timer_id != TIMER_ID_NOT_SET) { + handle_timer_event(event); + } + + if (event->received_msg_list != NULL) { + handle_socket_comm_event(event); + } + + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle->session_event_queue); + + pthread_mutex_unlock( + &(session_logic_handle_->session_list_mutex)); + } + + session_logic_handle->session_logic_condition = false; + pthread_mutex_unlock( + &(session_logic_handle->session_logic_mutex)); + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Finished session_logic_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} diff --git a/pceplib/pcep_session_logic_states.c b/pceplib/pcep_session_logic_states.c new file mode 100644 index 0000000000..5fac667655 --- /dev/null +++ b/pceplib/pcep_session_logic_states.c @@ -0,0 +1,1133 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +#define TIMER_OPEN_KEEP_ALIVE_SECONDS 1 + +/* Session Logic Handle managed in pcep_session_logic.c */ +extern pcep_event_queue *session_logic_event_queue_; +void send_keep_alive(pcep_session *session); +void send_pcep_error_with_object(pcep_session *session, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct pcep_object_header *object); +void reset_dead_timer(pcep_session *session); +bool verify_pcep_open_object(pcep_session *session, + struct pcep_object_open *open_object); +void send_reconciled_pcep_open(pcep_session *session, + struct pcep_message *error_msg); +bool handle_pcep_update(pcep_session *session, struct pcep_message *upd_msg); +bool handle_pcep_initiate(pcep_session *session, struct pcep_message *init_msg); +bool check_and_send_open_keep_alive(pcep_session *session); +void log_pcc_pce_connection(pcep_session *session); +bool handle_pcep_open(pcep_session *session, struct pcep_message *open_msg); + +/* + * util functions called by the state handling below + */ + +void send_keep_alive(pcep_session *session) +{ + struct pcep_message *keep_alive_msg = pcep_msg_create_keepalive(); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send keep_alive message for session [%d]", + __func__, time(NULL), pthread_self(), session->session_id); + + session_send_message(session, keep_alive_msg); + + /* The keep alive timer will be (re)set once the message + * is sent in session_logic_message_sent_handler() */ +} + + +/* Send an error message with the corrected or offending object */ +void send_pcep_error_with_object(pcep_session *session, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct pcep_object_header *object) +{ + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, object); + struct pcep_message *error_msg = pcep_msg_create_error_with_objects( + error_type, error_value, obj_list); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send error message with object [%d][%d] for session [%d]", + __func__, time(NULL), pthread_self(), error_type, error_value, + session->session_id); + + session_send_message(session, error_msg); +} + + +void send_pcep_error(pcep_session *session, enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + struct pcep_message *error_msg = + pcep_msg_create_error(error_type, error_value); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send error message [%d][%d] for session [%d]", + __func__, time(NULL), pthread_self(), error_type, error_value, + session->session_id); + + session_send_message(session, error_msg); +} + + +void reset_dead_timer(pcep_session *session) +{ + /* Default to configured dead_timer if its not set yet or set to 0 by + * the PCE */ + int dead_timer_seconds = + (session->pcc_config.dead_timer_pce_negotiated_seconds == 0) + ? session->pcc_config.dead_timer_seconds + : session->pcc_config.dead_timer_pce_negotiated_seconds; + + if (session->timer_id_dead_timer == TIMER_ID_NOT_SET) { + session->timer_id_dead_timer = + create_timer(dead_timer_seconds, session); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic set dead timer [%d secs] id [%d] for session [%d]", + __func__, time(NULL), pthread_self(), + dead_timer_seconds, session->timer_id_dead_timer, + session->session_id); + } else { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic reset dead timer [%d secs] id [%d] for session [%d]", + __func__, time(NULL), pthread_self(), + dead_timer_seconds, session->timer_id_dead_timer, + session->session_id); + reset_timer(session->timer_id_dead_timer); + } +} + + +void enqueue_event(pcep_session *session, pcep_event_type event_type, + struct pcep_message *message) +{ + if (event_type == MESSAGE_RECEIVED && message == NULL) { + pcep_log( + LOG_WARNING, + "%s: enqueue_event cannot enqueue a NULL message session [%d]", + __func__, session->session_id); + return; + } + + pcep_event *event = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event)); + memset(event, 0, sizeof(pcep_event)); + + event->session = session; + event->event_type = event_type; + event->event_time = time(NULL); + event->message = message; + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + if (session_logic_event_queue_->event_callback != NULL) { + session_logic_event_queue_->event_callback( + session_logic_event_queue_->event_callback_data, event); + } else { + queue_enqueue(session_logic_event_queue_->event_queue, event); + } + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); +} + +/* Verify the received PCEP Open object parameters are acceptable. If not, + * update the unacceptable value(s) with an acceptable value so it can be sent + * back to the sender. */ +bool verify_pcep_open_object(pcep_session *session, + struct pcep_object_open *open_object) +{ + int retval = true; + + if (open_object->open_keepalive + < session->pcc_config.min_keep_alive_seconds) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open Keep Alive value [%d] min [%d]", + __func__, open_object->open_keepalive, + session->pcc_config.min_keep_alive_seconds); + open_object->open_keepalive = + session->pcc_config.min_keep_alive_seconds; + retval = false; + } else if (open_object->open_keepalive + > session->pcc_config.max_keep_alive_seconds) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open Keep Alive value [%d] max [%d]", + __func__, open_object->open_keepalive, + session->pcc_config.max_keep_alive_seconds); + open_object->open_keepalive = + session->pcc_config.max_keep_alive_seconds; + retval = false; + } + + if (open_object->open_deadtimer + < session->pcc_config.min_dead_timer_seconds) { + pcep_log(LOG_INFO, + "%s: Rejecting unsupported Open Dead Timer value [%d]", + __func__, open_object->open_deadtimer); + open_object->open_deadtimer = + session->pcc_config.min_dead_timer_seconds; + retval = false; + } else if (open_object->open_deadtimer + > session->pcc_config.max_dead_timer_seconds) { + pcep_log(LOG_INFO, + "%s: Rejecting unsupported Open Dead Timer value [%d]", + __func__, open_object->open_deadtimer); + open_object->open_deadtimer = + session->pcc_config.max_dead_timer_seconds; + retval = false; + } + + /* Check for Open Object TLVs */ + if (pcep_object_has_tlvs((struct pcep_object_header *)open_object) + == false) { + /* There are no TLVs, all done */ + return retval; + } + + double_linked_list_node *tlv_node = open_object->header.tlv_list->head; + while (tlv_node != NULL) { + struct pcep_object_tlv_header *tlv = tlv_node->data; + tlv_node = tlv_node->next_node; + + /* Supported Open Object TLVs */ + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + break; + + default: + /* TODO how to handle unrecognized TLV ?? */ + pcep_log( + LOG_INFO, + "%s: Unhandled OPEN Object TLV type: %d, length %d", + __func__, tlv->type, tlv->encoded_tlv_length); + break; + } + + /* Verify the STATEFUL-PCE-CAPABILITY TLV */ + if (tlv->type == PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY) { + struct pcep_object_tlv_stateful_pce_capability + *pce_cap_tlv = + (struct + pcep_object_tlv_stateful_pce_capability + *)tlv; + + /* If the U flag is set, then the PCE is + * capable of updating LSP parameters */ + if (pce_cap_tlv->flag_u_lsp_update_capability) { + if (session->pce_config + .support_stateful_pce_lsp_update + == false) { + /* Turn off the U bit, as it is not + * supported */ + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open STATEFUL-PCE-CAPABILITY TLV U flag", + __func__); + pce_cap_tlv + ->flag_u_lsp_update_capability = + false; + retval = false; + } else { + session->stateful_pce = true; + pcep_log( + LOG_INFO, + "%s: Setting PCEP session [%d] STATEFUL to support LSP updates", + __func__, session->session_id); + } + } + /* TODO the rest of the flags are not implemented yet */ + else if (pce_cap_tlv->flag_s_include_db_version) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV S Include DB Version flag", + __func__); + } else if ( + pce_cap_tlv + ->flag_i_lsp_instantiation_capability) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV I LSP Instantiation Capability flag", + __func__); + } else if (pce_cap_tlv->flag_t_triggered_resync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV T Triggered Resync flag", + __func__); + } else if (pce_cap_tlv->flag_d_delta_lsp_sync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV D Delta LSP Sync flag", + __func__); + } else if (pce_cap_tlv->flag_f_triggered_initial_sync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV F Triggered Initial Sync flag", + __func__); + } + } else if (tlv->type == PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION) { + if (session->pce_config.support_include_db_version + == false) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open LSP DB VERSION TLV", + __func__); + /* Remove this TLV from the list */ + dll_delete_node(open_object->header.tlv_list, + tlv_node); + retval = false; + } + } + } + + return retval; +} + + +bool handle_pcep_open(pcep_session *session, struct pcep_message *open_msg) +{ + /* Open Message validation and errors according to: + * https://tools.ietf.org/html/rfc5440#section-7.15 */ + + if (session->session_state != SESSION_STATE_PCEP_CONNECTING + && session->session_state != SESSION_STATE_INITIALIZED) { + pcep_log( + LOG_INFO, + "%s: Received unexpected OPEN, current session state [%d, replying with error]", + __func__, session->session_state); + send_pcep_error(session, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + if (session->pce_open_received == true + && session->pce_open_rejected == false) { + pcep_log(LOG_INFO, + "%s: Received duplicate OPEN, replying with error", + __func__); + send_pcep_error(session, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + struct pcep_object_open *open_object = + (struct pcep_object_open *)pcep_obj_get(open_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + if (open_object == NULL) { + pcep_log( + LOG_INFO, + "%s: Received OPEN message with no OPEN object, replying with error", + __func__); + send_pcep_error(session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + /* Check for additional Open Msg objects */ + if (open_msg->obj_list->num_entries > 1) { + pcep_log( + LOG_INFO, + "%s: Found additional unsupported objects in the Open message, replying with error", + __func__); + send_pcep_error(session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + session->pce_open_received = true; + + /* Verify the open object parameters and TLVs */ + if (verify_pcep_open_object(session, open_object) == false) { + enqueue_event(session, PCC_RCVD_INVALID_OPEN, NULL); + if (session->pce_open_rejected) { + /* The Open message was already rejected once, so + * according to the spec, send an error message and + * close the TCP connection. */ + pcep_log( + LOG_INFO, + "%s: Received 2 consecutive unsupported Open messages, closing the connection.", + __func__); + send_pcep_error( + session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE); + socket_comm_session_close_tcp_after_write( + session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; + enqueue_event(session, PCC_CONNECTION_FAILURE, NULL); + } else { + session->pce_open_rejected = true; + /* Clone the object here, since the encapsulating + * message will be deleted in handle_socket_comm_event() + * most likely before this error message is sent */ + struct pcep_object_open *cloned_open_object = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_object_open)); + memcpy(cloned_open_object, open_object, + sizeof(struct pcep_object_open)); + open_object->header.tlv_list = NULL; + cloned_open_object->header.encoded_object = NULL; + cloned_open_object->header.encoded_object_length = 0; + send_pcep_error_with_object( + session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG, + &cloned_open_object->header); + } + + return false; + } + + /* + * Open Message accepted + * Sending the keep-alive response will be managed the function caller + */ + + session->timer_id_open_keep_alive = + create_timer(TIMER_OPEN_KEEP_ALIVE_SECONDS, session); + session->pcc_config.dead_timer_pce_negotiated_seconds = + (int)open_object->open_deadtimer; + /* Cancel the timer so we can change the dead_timer value */ + cancel_timer(session->timer_id_dead_timer); + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + reset_dead_timer(session); + + return true; +} + + +/* The original PCEP Open message sent to the PCE was rejected, + * try to reconcile the differences and re-send a new Open. */ +void send_reconciled_pcep_open(pcep_session *session, + struct pcep_message *error_msg) +{ + struct pcep_message *open_msg = create_pcep_open(session); + + struct pcep_object_open *error_open_obj = + (struct pcep_object_open *)pcep_obj_get(error_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + if (error_open_obj == NULL) { + /* Nothing to reconcile, send the same Open message again */ + pcep_log( + LOG_INFO, + "%s: No Open object received in Error, sending the same Open message", + __func__); + session_send_message(session, open_msg); + return; + } + + struct pcep_object_open *open_obj = + (struct pcep_object_open *)pcep_obj_get(open_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + + if (error_open_obj->open_deadtimer + != session->pce_config.dead_timer_seconds) { + if (error_open_obj->open_deadtimer + >= session->pce_config.min_dead_timer_seconds + && error_open_obj->open_deadtimer + <= session->pce_config.max_dead_timer_seconds) { + open_obj->open_deadtimer = + error_open_obj->open_deadtimer; + session->pcc_config.dead_timer_pce_negotiated_seconds = + error_open_obj->open_deadtimer; + pcep_log( + LOG_INFO, + "%s: Open deadtimer value [%d] rejected, using PCE value [%d]", + __func__, + session->pcc_config.dead_timer_seconds, + session->pcc_config + .dead_timer_pce_negotiated_seconds); + /* Reset the timer with the new value */ + cancel_timer(session->timer_id_dead_timer); + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + reset_dead_timer(session); + } else { + pcep_log( + LOG_INFO, + "%s: Can not reconcile Open with suggested deadtimer [%d]", + __func__, error_open_obj->open_deadtimer); + } + } + + if (error_open_obj->open_keepalive + != session->pce_config.keep_alive_seconds) { + if (error_open_obj->open_keepalive + >= session->pce_config.min_keep_alive_seconds + && error_open_obj->open_keepalive + <= session->pce_config.max_keep_alive_seconds) { + open_obj->open_keepalive = + error_open_obj->open_keepalive; + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds = + error_open_obj->open_keepalive; + pcep_log( + LOG_INFO, + "%s: Open keep alive value [%d] rejected, using PCE value [%d]", + __func__, + session->pcc_config.keep_alive_seconds, + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds); + /* Cancel the timer, the timer will be set again with + * the new value when this open message is sent */ + cancel_timer(session->timer_id_keep_alive); + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + } else { + pcep_log( + LOG_INFO, + "%s: Can not reconcile Open with suggested keepalive [%d]", + __func__, error_open_obj->open_keepalive); + } + } + + /* TODO reconcile the TLVs */ + + session_send_message(session, open_msg); + reset_timer(session->timer_id_open_keep_alive); +} + + +bool handle_pcep_update(pcep_session *session, struct pcep_message *upd_msg) +{ + /* Update Message validation and errors according to: + * https://tools.ietf.org/html/rfc8231#section-6.2 */ + + if (upd_msg->obj_list == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Message has no objects", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + /* Verify the mandatory objects are present */ + struct pcep_object_header *obj = + pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_SRP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing SRP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_LSP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing LSP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_ERO); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing ERO object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING); + return false; + } + + /* Verify the objects are are in the correct order */ + double_linked_list_node *node = upd_msg->obj_list->head; + struct pcep_object_srp *srp_object = + (struct pcep_object_srp *)node->data; + if (srp_object->header.object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: First object must be an SRP, found [%d]", + __func__, srp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_lsp *lsp_object = + (struct pcep_object_lsp *)node->data; + if (lsp_object->header.object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: Second object must be an LSP, found [%d]", + __func__, lsp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_ro *ero_object = node->data; + if (ero_object->header.object_class != PCEP_OBJ_CLASS_ERO) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: Third object must be an ERO, found [%d]", + __func__, ero_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING); + return false; + } + + return true; +} + +bool handle_pcep_initiate(pcep_session *session, struct pcep_message *init_msg) +{ + /* Instantiate Message validation and errors according to: + * https://tools.ietf.org/html/rfc8281#section-5 */ + + if (init_msg->obj_list == NULL) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: Message has no objects", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + /* Verify the mandatory objects are present */ + struct pcep_object_header *obj = + pcep_obj_get(init_msg->obj_list, PCEP_OBJ_CLASS_SRP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcInitiate message: Missing SRP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(init_msg->obj_list, PCEP_OBJ_CLASS_LSP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcInitiate message: Missing LSP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + /* Verify the objects are are in the correct order */ + double_linked_list_node *node = init_msg->obj_list->head; + struct pcep_object_srp *srp_object = + (struct pcep_object_srp *)node->data; + if (srp_object->header.object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: First object must be an SRP, found [%d]", + __func__, srp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_lsp *lsp_object = + (struct pcep_object_lsp *)node->data; + if (lsp_object->header.object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: Second object must be an LSP, found [%d]", + __func__, lsp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + /* There may be more optional objects */ + return true; +} + +void increment_unknown_message(pcep_session *session) +{ + /* https://tools.ietf.org/html/rfc5440#section-6.9 + * If a PCC/PCE receives unrecognized messages at a rate equal or + * greater than MAX-UNKNOWN-MESSAGES unknown message requests per + * minute, the PCC/PCE MUST send a PCEP CLOSE message */ + + time_t *unknown_message_time = + pceplib_malloc(PCEPLIB_INFRA, sizeof(time_t)); + *unknown_message_time = time(NULL); + time_t expire_time = *unknown_message_time + 60; + queue_enqueue(session->num_unknown_messages_time_queue, + unknown_message_time); + + /* Purge any entries older than 1 minute. The oldest entries are at the + * queue head */ + queue_node *time_node = session->num_unknown_messages_time_queue->head; + while (time_node != NULL) { + if (*((time_t *)time_node->data) > expire_time) { + pceplib_free( + PCEPLIB_INFRA, + queue_dequeue( + session->num_unknown_messages_time_queue)); + time_node = + session->num_unknown_messages_time_queue->head; + } else { + time_node = NULL; + } + } + + if ((int)session->num_unknown_messages_time_queue->num_entries + >= session->pcc_config.max_unknown_messages) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Max unknown messages reached [%d] closing session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config.max_unknown_messages, + session->session_id); + + close_pcep_session_with_reason(session, + PCEP_CLOSE_REASON_UNREC_MSG); + enqueue_event(session, PCC_RCVD_MAX_UNKOWN_MSGS, NULL); + } +} + +bool check_and_send_open_keep_alive(pcep_session *session) +{ + if (session->pce_open_received == true + && session->pce_open_rejected == false + && session->pce_open_keep_alive_sent == false) { + /* Send the PCE Open keep-alive response if it hasnt been sent + * yet */ + cancel_timer(session->timer_id_open_keep_alive); + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + send_keep_alive(session); + session->pce_open_keep_alive_sent = true; + + return true; + } + + return false; +} + +void log_pcc_pce_connection(pcep_session *session) +{ + if (session->socket_comm_session == NULL) { + /* This only happens in UT */ + return; + } + + char src_ip_buf[40] = {0}, dst_ip_buf[40] = {0}; + uint16_t src_port, dst_port; + + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv6.sin6_addr, + src_ip_buf, sizeof(src_ip_buf)); + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + dst_ip_buf, sizeof(dst_ip_buf)); + src_port = htons(session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv6.sin6_port); + dst_port = htons(session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_port); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv4.sin_addr, + src_ip_buf, sizeof(src_ip_buf)); + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + dst_ip_buf, sizeof(dst_ip_buf)); + src_port = htons(session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv4.sin_port); + dst_port = htons(session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_port); + } + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Successful PCC [%s:%d] connection to PCE [%s:%d] session [%d] fd [%d]", + __func__, time(NULL), pthread_self(), src_ip_buf, src_port, + dst_ip_buf, dst_port, session->session_id, + session->socket_comm_session->socket_fd); +} + +/* + * these functions are called by session_logic_loop() from + * pcep_session_logic_loop.c these functions are executed in the + * session_logic_loop thread, and the mutex is locked before calling these + * functions, so they are thread safe. + */ + +/* state machine handling for expired timers */ +void handle_timer_event(pcep_session_event *event) +{ + if (event == NULL) { + pcep_log(LOG_INFO, "%s: handle_timer_event NULL event", + __func__); + return; + } + + pcep_session *session = event->session; + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic handle_timer_event: session [%d] event timer_id [%d] " + "session timers [OKW, OKA, DT, KA] [%d, %d, %d, %d]", + __func__, time(NULL), pthread_self(), session->session_id, + event->expired_timer_id, session->timer_id_open_keep_wait, + session->timer_id_open_keep_alive, session->timer_id_dead_timer, + session->timer_id_keep_alive); + + /* + * these timer expirations are independent of the session state + */ + if (event->expired_timer_id == session->timer_id_dead_timer) { + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER); + close_pcep_session_with_reason(session, + PCEP_CLOSE_REASON_DEADTIMER); + enqueue_event(session, PCE_DEAD_TIMER_EXPIRED, NULL); + return; + } else if (event->expired_timer_id == session->timer_id_keep_alive) { + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE); + send_keep_alive(session); + return; + } + + /* + * handle timers that depend on the session state + */ + switch (session->session_state) { + case SESSION_STATE_PCEP_CONNECTING: + if (event->expired_timer_id + == session->timer_id_open_keep_wait) { + /* close the TCP session */ + pcep_log( + LOG_INFO, + "%s: handle_timer_event open_keep_wait timer expired for session [%d]", + __func__, session->session_id); + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT); + socket_comm_session_close_tcp_after_write( + session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; + session->timer_id_open_keep_wait = TIMER_ID_NOT_SET; + enqueue_event(session, PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED, + NULL); + } + + if (event->expired_timer_id + == session->timer_id_open_keep_alive) { + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE); + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + if (check_and_send_open_keep_alive(session) == true) { + if (session->pcc_open_accepted == true + && session->session_state + != SESSION_STATE_PCEP_CONNECTED) { + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + return; + } + break; + + case SESSION_STATE_INITIALIZED: + case SESSION_STATE_PCEP_CONNECTED: + default: + pcep_log( + LOG_INFO, + "%s: handle_timer_event unrecognized state transition, timer_id [%d] state [%d] session [%d]", + __func__, event->expired_timer_id, + session->session_state, session->session_id); + break; + } +} + +/* State machine handling for received messages. + * This event was created in session_logic_msg_ready_handler() in + * pcep_session_logic_loop.c */ +void handle_socket_comm_event(pcep_session_event *event) +{ + if (event == NULL) { + pcep_log(LOG_INFO, "%s: handle_socket_comm_event NULL event", + __func__); + return; + } + + pcep_session *session = event->session; + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic handle_socket_comm_event: session [%d] num messages [%d] socket_closed [%d]", + __func__, time(NULL), pthread_self(), session->session_id, + (event->received_msg_list == NULL + ? -1 + : (int)event->received_msg_list->num_entries), + event->socket_closed); + + /* + * independent of the session state + */ + if (event->socket_closed) { + pcep_log( + LOG_INFO, + "%s: handle_socket_comm_event socket closed for session [%d]", + __func__, session->session_id); + socket_comm_session_close_tcp(session->socket_comm_session); + enqueue_event(session, PCE_CLOSED_SOCKET, NULL); + if (session->session_state == SESSION_STATE_PCEP_CONNECTING) { + enqueue_event(session, PCC_CONNECTION_FAILURE, NULL); + } + session->session_state = SESSION_STATE_INITIALIZED; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT); + return; + } + + reset_dead_timer(session); + + if (event->received_msg_list == NULL) { + return; + } + + /* Message received on socket */ + double_linked_list_node *msg_node; + for (msg_node = event->received_msg_list->head; msg_node != NULL; + msg_node = msg_node->next_node) { + bool message_enqueued = false; + struct pcep_message *msg = + (struct pcep_message *)msg_node->data; + pcep_log(LOG_INFO, "%s: \t %s message", __func__, + get_message_type_str(msg->msg_header->type)); + + increment_message_rx_counters(session, msg); + + switch (msg->msg_header->type) { + case PCEP_TYPE_OPEN: + /* handle_pcep_open() checks session state, and for + * duplicate erroneous open messages, and replies with + * error messages as needed. It also sets + * pce_open_received. */ + if (handle_pcep_open(session, msg) == true) { + /* PCE Open Message Accepted */ + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + session->pce_open_accepted = true; + session->pce_open_rejected = false; + if (session->pcc_open_accepted) { + /* If both the PCC and PCE Opens are + * accepted, then the session is + * connected */ + + check_and_send_open_keep_alive(session); + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + break; + + case PCEP_TYPE_KEEPALIVE: + if (session->session_state + == SESSION_STATE_PCEP_CONNECTING) { + /* PCC Open Message Accepted */ + cancel_timer(session->timer_id_open_keep_wait); + session->timer_id_open_keep_wait = + TIMER_ID_NOT_SET; + session->pcc_open_accepted = true; + session->pcc_open_rejected = false; + check_and_send_open_keep_alive(session); + + if (session->pce_open_accepted) { + /* If both the PCC and PCE Opens are + * accepted, then the session is + * connected */ + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCC_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + /* The dead_timer was already reset above, so nothing + * extra to do here */ + break; + + case PCEP_TYPE_PCREP: + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + case PCEP_TYPE_CLOSE: + session->session_state = SESSION_STATE_INITIALIZED; + socket_comm_session_close_tcp( + session->socket_comm_session); + /* TODO should we also enqueue the message, so they can + * see the reasons?? */ + enqueue_event(session, PCE_SENT_PCEP_CLOSE, NULL); + /* TODO could this duplicate the disconnect counter with + * socket close ?? */ + increment_event_counters( + session, PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT); + break; + + case PCEP_TYPE_PCREQ: + /* The PCC does not support receiving PcReq messages */ + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + break; + + case PCEP_TYPE_REPORT: + /* The PCC does not support receiving Report messages */ + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + break; + + case PCEP_TYPE_UPDATE: + /* Should reply with a PcRpt */ + if (handle_pcep_update(session, msg) == true) { + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + } + break; + + case PCEP_TYPE_INITIATE: + /* Should reply with a PcRpt */ + if (handle_pcep_initiate(session, msg) == true) { + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + } + break; + + case PCEP_TYPE_PCNOTF: + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + case PCEP_TYPE_ERROR: + if (msg->obj_list != NULL + && msg->obj_list->num_entries > 0) { + struct pcep_object_header *obj_hdr = + pcep_obj_get(msg->obj_list, + PCEP_OBJ_CLASS_ERROR); + if (obj_hdr != NULL) { + struct pcep_object_error *error_obj = + (struct pcep_object_error *) + obj_hdr; + pcep_log( + LOG_DEBUG, + "%s: Error object [type, value] = [%s, %s]", + __func__, + get_error_type_str( + error_obj->error_type), + get_error_value_str( + error_obj->error_type, + error_obj + ->error_value)); + } + } + + if (session->session_state + == SESSION_STATE_PCEP_CONNECTING) { + /* A PCC_CONNECTION_FAILURE event will be sent + * when the socket is closed, if the state is + * SESSION_STATE_PCEP_CONNECTING, in case the + * PCE allows more than 2 failed open messages. + */ + pcep_log(LOG_INFO, + "%s: PCC Open message rejected by PCE", + __func__); + session->pcc_open_rejected = true; + send_reconciled_pcep_open(session, msg); + enqueue_event(session, PCC_SENT_INVALID_OPEN, + NULL); + } + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + default: + pcep_log(LOG_INFO, "%s: \t UnSupported message", + __func__); + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + increment_unknown_message(session); + break; + } + + /* if the message was enqueued, dont free it yet */ + if (message_enqueued == false) { + pcep_msg_free_message(msg); + } + } + dll_destroy(event->received_msg_list); +} diff --git a/pceplib/pcep_socket_comm.c b/pceplib/pcep_socket_comm.c new file mode 100644 index 0000000000..e22eb6e675 --- /dev/null +++ b/pceplib/pcep_socket_comm.c @@ -0,0 +1,781 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of public API functions. + */ + +#include + +#include +#include +#include // gethostbyname +#include +#include +#include // close + +#include // sockets etc. +#include // sockets etc. +#include // sockets etc. + +#include "pcep.h" +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_internals.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_queue.h" + +bool initialize_socket_comm_pre(void); +bool socket_comm_session_initialize_post( + pcep_socket_comm_session *socket_comm_session); + +pcep_socket_comm_handle *socket_comm_handle_ = NULL; + + +/* simple compare method callback used by pcep_utils_ordered_list + * for ordered list insertion. */ +int socket_fd_node_compare(void *list_entry, void *new_entry) +{ + return ((pcep_socket_comm_session *)new_entry)->socket_fd + - ((pcep_socket_comm_session *)list_entry)->socket_fd; +} + + +bool initialize_socket_comm_pre() +{ + socket_comm_handle_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_handle)); + memset(socket_comm_handle_, 0, sizeof(pcep_socket_comm_handle)); + + socket_comm_handle_->active = true; + socket_comm_handle_->num_active_sessions = 0; + socket_comm_handle_->read_list = + ordered_list_initialize(socket_fd_node_compare); + socket_comm_handle_->write_list = + ordered_list_initialize(socket_fd_node_compare); + socket_comm_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + FD_ZERO(&socket_comm_handle_->except_master_set); + FD_ZERO(&socket_comm_handle_->read_master_set); + FD_ZERO(&socket_comm_handle_->write_master_set); + + if (pthread_mutex_init(&(socket_comm_handle_->socket_comm_mutex), NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm mutex.", + __func__); + pceplib_free(PCEPLIB_INFRA, socket_comm_handle_); + socket_comm_handle_ = NULL; + + return false; + } + + return true; +} + +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func) +{ + if (socket_comm_handle_ != NULL) { + /* already initialized */ + return true; + } + + if (initialize_socket_comm_pre() == false) { + return false; + } + + /* Notice: If the thread_create_func is set, then both the + * socket_read_cb and the socket_write_cb SHOULD be NULL. */ + if (thread_create_func != NULL) { + if (thread_create_func( + &(socket_comm_handle_->socket_comm_thread), NULL, + socket_comm_loop, socket_comm_handle_, + "pceplib_timers")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external socket_comm thread.", + __func__); + return false; + } + } + + socket_comm_handle_->external_infra_data = external_infra_data; + socket_comm_handle_->socket_write_func = socket_write_cb; + socket_comm_handle_->socket_read_func = socket_read_cb; + + return true; +} + +bool initialize_socket_comm_loop() +{ + if (socket_comm_handle_ != NULL) { + /* already initialized */ + return true; + } + + if (initialize_socket_comm_pre() == false) { + return false; + } + + /* Launch socket comm loop pthread */ + if (pthread_create(&(socket_comm_handle_->socket_comm_thread), NULL, + socket_comm_loop, socket_comm_handle_)) { + pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm thread.", + __func__); + return false; + } + + return true; +} + + +bool destroy_socket_comm_loop() +{ + socket_comm_handle_->active = false; + + pthread_join(socket_comm_handle_->socket_comm_thread, NULL); + ordered_list_destroy(socket_comm_handle_->read_list); + ordered_list_destroy(socket_comm_handle_->write_list); + ordered_list_destroy(socket_comm_handle_->session_list); + pthread_mutex_destroy(&(socket_comm_handle_->socket_comm_mutex)); + + pceplib_free(PCEPLIB_INFRA, socket_comm_handle_); + socket_comm_handle_ = NULL; + + return true; +} + +/* Internal common init function */ +static pcep_socket_comm_session *socket_comm_session_initialize_pre( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + /* check that not both message handlers were set */ + if (message_handler != NULL && message_ready_handler != NULL) { + pcep_log( + LOG_WARNING, + "%s: Only one of can be set.", + __func__); + return NULL; + } + + /* check that at least one message handler was set */ + if (message_handler == NULL && message_ready_handler == NULL) { + pcep_log( + LOG_WARNING, + "%s: At least one of must be set.", + __func__); + return NULL; + } + + if (!initialize_socket_comm_loop()) { + pcep_log(LOG_WARNING, + "%s: ERROR: cannot initialize socket_comm_loop.", + __func__); + + return NULL; + } + + /* initialize everything for a pcep_session socket_comm */ + + pcep_socket_comm_session *socket_comm_session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_session)); + memset(socket_comm_session, 0, sizeof(pcep_socket_comm_session)); + + socket_comm_handle_->num_active_sessions++; + socket_comm_session->close_after_write = false; + socket_comm_session->session_data = session_data; + socket_comm_session->message_handler = message_handler; + socket_comm_session->message_ready_to_read_handler = + message_ready_handler; + socket_comm_session->message_sent_handler = msg_sent_notifier; + socket_comm_session->conn_except_notifier = notifier; + socket_comm_session->message_queue = queue_initialize(); + socket_comm_session->connect_timeout_millis = connect_timeout_millis; + socket_comm_session->external_socket_data = NULL; + if (tcp_authentication_str != NULL) { + socket_comm_session->is_tcp_auth_md5 = is_tcp_auth_md5; + strlcpy(socket_comm_session->tcp_authentication_str, + tcp_authentication_str, + sizeof(socket_comm_session->tcp_authentication_str)); + } + + return socket_comm_session; +} + +/* Internal common init function */ +bool socket_comm_session_initialize_post( + pcep_socket_comm_session *socket_comm_session) +{ + /* If we dont use SO_REUSEADDR, the socket will take 2 TIME_WAIT + * periods before being closed in the kernel if bind() was called */ + int reuse_addr = 1; + if (setsockopt(socket_comm_session->socket_fd, SOL_SOCKET, SO_REUSEADDR, + &reuse_addr, sizeof(int)) + < 0) { + pcep_log( + LOG_WARNING, + "%s: Error in setsockopt() SO_REUSEADDR errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown(socket_comm_session); + + return false; + } + + struct sockaddr *src_sock_addr = + (socket_comm_session->is_ipv6 + ? (struct sockaddr *)&( + socket_comm_session->src_sock_addr + .src_sock_addr_ipv6) + : (struct sockaddr *)&( + socket_comm_session->src_sock_addr + .src_sock_addr_ipv4)); + int addr_len = (socket_comm_session->is_ipv6 + ? sizeof(socket_comm_session->src_sock_addr + .src_sock_addr_ipv6) + : sizeof(socket_comm_session->src_sock_addr + .src_sock_addr_ipv4)); + if (bind(socket_comm_session->socket_fd, src_sock_addr, addr_len) + == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot bind address to socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown(socket_comm_session); + + return false; + } + + /* Register the session as active with the Socket Comm Loop */ + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + ordered_list_add_node(socket_comm_handle_->session_list, + socket_comm_session); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + /* dont connect to the destination yet, since the PCE will have a timer + * for max time between TCP connect and PCEP open. we'll connect later + * when we send the PCEP open. */ + + return true; +} + + +pcep_socket_comm_session *socket_comm_session_initialize( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *dest_ip, + short dest_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + return socket_comm_session_initialize_with_src( + message_handler, message_ready_handler, msg_sent_notifier, + notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); +} + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dest_ip, + short dest_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + return socket_comm_session_initialize_with_src_ipv6( + message_handler, message_ready_handler, msg_sent_notifier, + notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); +} + + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dest_ip, short dest_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + if (dest_ip == NULL) { + pcep_log(LOG_WARNING, "%s: dest_ipv4 is NULL", __func__); + return NULL; + } + + pcep_socket_comm_session *socket_comm_session = + socket_comm_session_initialize_pre( + message_handler, message_ready_handler, + msg_sent_notifier, notifier, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); + if (socket_comm_session == NULL) { + return NULL; + } + + socket_comm_session->socket_fd = + socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket_comm_session->socket_fd == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot create ipv4 socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown( + socket_comm_session); // socket_comm_session freed + // inside fn so NOLINT next. + + return NULL; // NOLINT(clang-analyzer-unix.Malloc) + } + + socket_comm_session->is_ipv6 = false; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = + AF_INET; + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_family = + AF_INET; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dest_port); + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_port = + htons(src_port); + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr + .s_addr = dest_ip->s_addr; + if (src_ip != NULL) { + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr + .s_addr = src_ip->s_addr; + } else { + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr + .s_addr = INADDR_ANY; + } + + if (socket_comm_session_initialize_post(socket_comm_session) == false) { + return NULL; + } + + return socket_comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dest_ip, short dest_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + if (dest_ip == NULL) { + pcep_log(LOG_WARNING, "%s: dest_ipv6 is NULL", __func__); + return NULL; + } + + pcep_socket_comm_session *socket_comm_session = + socket_comm_session_initialize_pre( + message_handler, message_ready_handler, + msg_sent_notifier, notifier, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); + if (socket_comm_session == NULL) { + return NULL; + } + + socket_comm_session->socket_fd = + socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (socket_comm_session->socket_fd == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot create ipv6 socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown( + socket_comm_session); // socket_comm_session freed + // inside fn so NOLINT next. + + return NULL; // NOLINT(clang-analyzer-unix.Malloc) + } + + socket_comm_session->is_ipv6 = true; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = + AF_INET6; + socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_family = + AF_INET6; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dest_port); + socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_port = + htons(src_port); + memcpy(&socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6 + .sin6_addr, + dest_ip, sizeof(struct in6_addr)); + if (src_ip != NULL) { + memcpy(&socket_comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + src_ip, sizeof(struct in6_addr)); + } else { + socket_comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr = in6addr_any; + } + + if (socket_comm_session_initialize_post(socket_comm_session) == false) { + return NULL; + } + + return socket_comm_session; +} + + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_connect_tcp NULL socket_comm_session.", + __func__); + return NULL; + } + + /* Set the socket to non-blocking, so connect() does not block */ + int fcntl_arg; + if ((fcntl_arg = fcntl(socket_comm_session->socket_fd, F_GETFL, NULL)) + < 0) { + pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_GETFL) [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + + fcntl_arg |= O_NONBLOCK; + if (fcntl(socket_comm_session->socket_fd, F_SETFL, fcntl_arg) < 0) { + pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_SETFL) [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + +#if HAVE_DECL_TCP_MD5SIG + /* TCP authentication, currently only TCP MD5 RFC2385 is supported */ + if (socket_comm_session->tcp_authentication_str[0] != '\0') { +#if defined(linux) || defined(GNU_LINUX) + struct tcp_md5sig sig; + memset(&sig, 0, sizeof(sig)); + if (socket_comm_session->is_ipv6) { + memcpy(&sig.tcpm_addr, + &socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6, + sizeof(struct sockaddr_in6)); + } else { + memcpy(&sig.tcpm_addr, + &socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4, + sizeof(struct sockaddr_in)); + } + sig.tcpm_keylen = + strlen(socket_comm_session->tcp_authentication_str); + memcpy(sig.tcpm_key, + socket_comm_session->tcp_authentication_str, + sig.tcpm_keylen); +#else + int sig = 1; +#endif + if (setsockopt(socket_comm_session->socket_fd, IPPROTO_TCP, + TCP_MD5SIG, &sig, sizeof(sig)) + == -1) { + pcep_log(LOG_ERR, "%s: Failed to setsockopt(): [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + } +#endif + + int connect_result = 0; + if (socket_comm_session->is_ipv6) { + connect_result = connect( + socket_comm_session->socket_fd, + (struct sockaddr *)&(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6), + sizeof(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6)); + } else { + connect_result = connect( + socket_comm_session->socket_fd, + (struct sockaddr *)&(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4), + sizeof(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4)); + } + + if (connect_result < 0) { + if (errno == EINPROGRESS) { + /* Calculate the configured timeout in seconds and + * microseconds */ + struct timeval tv; + if (socket_comm_session->connect_timeout_millis + > 1000) { + tv.tv_sec = socket_comm_session + ->connect_timeout_millis + / 1000; + tv.tv_usec = (socket_comm_session + ->connect_timeout_millis + - (tv.tv_sec * 1000)) + * 1000; + } else { + tv.tv_sec = 0; + tv.tv_usec = socket_comm_session + ->connect_timeout_millis + * 1000; + } + + /* Use select to wait a max timeout for connect + * https://stackoverflow.com/questions/2597608/c-socket-connection-timeout + */ + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(socket_comm_session->socket_fd, &fdset); + if (select(socket_comm_session->socket_fd + 1, NULL, + &fdset, NULL, &tv) + > 0) { + int so_error; + socklen_t len = sizeof(so_error); + getsockopt(socket_comm_session->socket_fd, + SOL_SOCKET, SO_ERROR, &so_error, + &len); + if (so_error) { + pcep_log( + LOG_WARNING, + "%s: TCP connect failed on socket_fd [%d].", + __func__, + socket_comm_session->socket_fd); + return false; + } + } else { + pcep_log( + LOG_WARNING, + "%s: TCP connect timed-out on socket_fd [%d].", + __func__, + socket_comm_session->socket_fd); + return false; + } + } else { + pcep_log( + LOG_WARNING, + "%s: TCP connect, error connecting on socket_fd [%d] errno [%d %s]", + __func__, socket_comm_session->socket_fd, errno, + strerror(errno)); + return false; + } + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + /* once the TCP connection is open, we should be ready to read at any + * time */ + ordered_list_add_node(socket_comm_handle_->read_list, + socket_comm_session); + + if (socket_comm_handle_->socket_read_func != NULL) { + socket_comm_handle_->socket_read_func( + socket_comm_handle_->external_infra_data, + &socket_comm_session->external_socket_data, + socket_comm_session->socket_fd, socket_comm_handle_); + } + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_close_tcp NULL socket_comm_session.", + __func__); + return false; + } + + pcep_log(LOG_DEBUG, + "%s: socket_comm_session_close_tcp close() socket fd [%d]", + __func__, socket_comm_session->socket_fd); + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + ordered_list_remove_first_node_equals(socket_comm_handle_->read_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->write_list, + socket_comm_session); + // TODO should it be close() or shutdown()?? + close(socket_comm_session->socket_fd); + socket_comm_session->socket_fd = -1; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_close_tcp_after_write NULL socket_comm_session.", + __func__); + return false; + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + socket_comm_session->close_after_write = true; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_teardown(pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle_ == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot teardown NULL socket_comm_handle", + __func__); + return false; + } + + if (socket_comm_session == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot teardown NULL session", + __func__); + return false; + } + + if (comm_session_exists_locking(socket_comm_handle_, + socket_comm_session) + == false) { + pcep_log(LOG_WARNING, + "%s: Cannot teardown session that does not exist", + __func__); + return false; + } + + if (socket_comm_session->socket_fd >= 0) { + shutdown(socket_comm_session->socket_fd, SHUT_RDWR); + close(socket_comm_session->socket_fd); + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + queue_destroy(socket_comm_session->message_queue); + ordered_list_remove_first_node_equals(socket_comm_handle_->session_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->read_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->write_list, + socket_comm_session); + socket_comm_handle_->num_active_sessions--; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm_session fd [%d] destroyed, [%d] sessions remaining", + __func__, time(NULL), pthread_self(), + socket_comm_session->socket_fd, + socket_comm_handle_->num_active_sessions); + + pceplib_free(PCEPLIB_INFRA, socket_comm_session); + + /* It would be nice to call destroy_socket_comm_loop() here if + * socket_comm_handle_->num_active_sessions == 0, but this function + * will usually be called from the message_sent_notifier callback, + * which gets called in the middle of the socket_comm_loop, and that + * is dangerous, so destroy_socket_comm_loop() must be called upon + * application exit. */ + + return true; +} + + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool free_after_send) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_send_message NULL socket_comm_session.", + __func__); + return; + } + + pcep_socket_comm_queued_message *queued_message = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(pcep_socket_comm_queued_message)); + queued_message->encoded_message = encoded_message; + queued_message->msg_length = msg_length; + queued_message->free_after_send = free_after_send; + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + + /* Do not proceed if the socket_comm_session has been deleted */ + if (ordered_list_find(socket_comm_handle_->session_list, + socket_comm_session) + == NULL) { + /* Should never get here, only if the session was deleted and + * someone still tries to write on it */ + pcep_log( + LOG_WARNING, + "%s: Cannot write a message on a deleted socket comm session, discarding message", + __func__); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + pceplib_free(PCEPLIB_MESSAGES, queued_message); + + return; + } + + /* Do not proceed if the socket has been closed */ + if (socket_comm_session->socket_fd < 0) { + /* Should never get here, only if the session was deleted and + * someone still tries to write on it */ + pcep_log( + LOG_WARNING, + "%s: Cannot write a message on a closed socket, discarding message", + __func__); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + pceplib_free(PCEPLIB_MESSAGES, queued_message); + + return; + } + + queue_enqueue(socket_comm_session->message_queue, queued_message); + + /* Add it to the write list only if its not already there */ + if (ordered_list_find(socket_comm_handle_->write_list, + socket_comm_session) + == NULL) { + ordered_list_add_node(socket_comm_handle_->write_list, + socket_comm_session); + } + + if (socket_comm_handle_->socket_write_func != NULL) { + socket_comm_handle_->socket_write_func( + socket_comm_handle_->external_infra_data, + &socket_comm_session->external_socket_data, + socket_comm_session->socket_fd, socket_comm_handle_); + } + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); +} diff --git a/pceplib/pcep_socket_comm.h b/pceplib/pcep_socket_comm.h new file mode 100644 index 0000000000..797ffda860 --- /dev/null +++ b/pceplib/pcep_socket_comm.h @@ -0,0 +1,198 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Declaration of public API functions. + */ + +#ifndef INCLUDE_PCEPSOCKETCOMM_H_ +#define INCLUDE_PCEPSOCKETCOMM_H_ + +#include "pcep.h" +#include // sockaddr_in +#include +#include + +#include "pcep_utils_queue.h" + +#define MAX_RECVD_MSG_SIZE 2048 + +/* + * A socket_comm_session can be initialized with 1 of 2 types of mutually + * exclusive message callbacks: + * - message_received_handler : the socket_comm library reads the message and + * calls the callback with the message_data and message_length. this callback + * should be used for smaller/simpler messages. + * - message_ready_to_read_handler : the socket_comm library will call this + * callback when a message is ready to be read on a socket_fd. this callback + * should be used if the + */ + +/* message received handler that receives the message data and message length */ +typedef void (*message_received_handler)(void *session_data, + const char *message_data, + unsigned int message_length); +/* message ready received handler that should read the message on socket_fd + * and return the number of bytes read */ +typedef int (*message_ready_to_read_handler)(void *session_data, int socket_fd); +/* callback handler called when a messages is sent */ +typedef void (*message_sent_notifier)(void *session_data, int socket_fd); +/* callback handler called when the socket is closed */ +typedef void (*connection_except_notifier)(void *session_data, int socket_fd); + +/* Function pointers when an external socket infrastructure is used */ +typedef int (*ext_socket_write)(void *infra_data, void **infra_socket_data, + int fd, void *data); +typedef int (*ext_socket_read)(void *infra_data, void **infra_socket_data, + int fd, void *data); +typedef int (*ext_socket_pthread_create_callback)( + pthread_t *pthread_id, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *data, const char *thread_name); + +typedef struct pcep_socket_comm_session_ { + message_received_handler message_handler; + message_ready_to_read_handler message_ready_to_read_handler; + message_sent_notifier message_sent_handler; + connection_except_notifier conn_except_notifier; + union src_sock_addr { + struct sockaddr_in src_sock_addr_ipv4; + struct sockaddr_in6 src_sock_addr_ipv6; + } src_sock_addr; + union dest_sock_addr { + struct sockaddr_in dest_sock_addr_ipv4; + struct sockaddr_in6 dest_sock_addr_ipv6; + } dest_sock_addr; + bool is_ipv6; + uint32_t connect_timeout_millis; + int socket_fd; + void *session_data; + queue_handle *message_queue; + char received_message[MAX_RECVD_MSG_SIZE]; + int received_bytes; + bool close_after_write; + void *external_socket_data; /* used for external socket infra */ + char tcp_authentication_str[TCP_MD5SIG_MAXKEYLEN + + 1]; /* should be used with is_tcp_auth_md5 + flag */ + bool is_tcp_auth_md5; /* flag to distinguish between rfc 2385 (md5) and + rfc 5925 (tcp-ao) */ + +} pcep_socket_comm_session; + + +/* Need to document that when the msg_rcv_handler is called, the data needs + * to be handled in the same function call, else it may be overwritten by + * the next read from this socket */ + + +/* Initialize the Socket Comm infrastructure, with either an internal pthread + * or with an external infrastructure. + * If an internal pthread infrastructure is to be used, then it is not necessary + * to explicitly call initialize_socket_comm_loop() as it will be called + * internally when a socket comm session is initialized. */ + +/* Initialize the Socket Comm infrastructure with an internal pthread */ +bool initialize_socket_comm_loop(void); +/* Initialize the Socket Comm infrastructure with an external infrastructure. + * Notice: If the thread_create_func is set, then both the socket_read_cb + * and the socket_write_cb SHOULD be NULL. */ +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func); + +/* The msg_rcv_handler and msg_ready_handler are mutually exclusive, and only + * one can be set (as explained above), else NULL will be returned. */ +pcep_socket_comm_session * +socket_comm_session_initialize(message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, + struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, + const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dst_ip, + short dst_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +bool socket_comm_session_teardown( + pcep_socket_comm_session *socket_comm_session); + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session); + +/* Immediately close the TCP connection, irregardless if there are pending + * messages to be sent. */ +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session); + +/* Sets a flag to close the TCP connection either after all the pending messages + * are written, or if there are no pending messages, the next time the socket is + * checked to be writeable. */ +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session); + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool free_after_send); + +/* If an external Socket infra like FRR is used, then these functions will + * be called when a socket is ready to read/write in the external infra. + * Implemented in pcep_socket_comm_loop.c */ +int pceplib_external_socket_read(int fd, void *payload); +int pceplib_external_socket_write(int fd, void *payload); + +/* the socket comm loop is started internally by + * socket_comm_session_initialize() + * but needs to be explicitly stopped with this call. */ +bool destroy_socket_comm_loop(void); + +int socket_fd_node_compare(void *list_entry, void *new_entry); + +#endif /* INCLUDE_PCEPSOCKETCOMM_H_ */ diff --git a/pceplib/pcep_socket_comm_internals.h b/pceplib/pcep_socket_comm_internals.h new file mode 100644 index 0000000000..4445a14fac --- /dev/null +++ b/pceplib/pcep_socket_comm_internals.h @@ -0,0 +1,69 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef SRC_PCEPSOCKETCOMMINTERNALS_H_ +#define SRC_PCEPSOCKETCOMMINTERNALS_H_ + +#include +#include + +#include "pcep_utils_ordered_list.h" +#include "pcep_socket_comm.h" + + +typedef struct pcep_socket_comm_handle_ { + bool active; + pthread_t socket_comm_thread; + pthread_mutex_t socket_comm_mutex; + fd_set read_master_set; + fd_set write_master_set; + fd_set except_master_set; + /* ordered_list of socket_descriptors to read from */ + ordered_list_handle *read_list; + /* ordered_list of socket_descriptors to write to */ + ordered_list_handle *write_list; + ordered_list_handle *session_list; + int num_active_sessions; + void *external_infra_data; + ext_socket_write socket_write_func; + ext_socket_read socket_read_func; + +} pcep_socket_comm_handle; + + +typedef struct pcep_socket_comm_queued_message_ { + const char *encoded_message; + int msg_length; + bool free_after_send; + +} pcep_socket_comm_queued_message; + + +/* Functions implemented in pcep_socket_comm_loop.c */ +void *socket_comm_loop(void *data); +bool comm_session_exists(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session); +bool comm_session_exists_locking(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session); + +#endif /* SRC_PCEPSOCKETCOMMINTERNALS_H_ */ diff --git a/pceplib/pcep_socket_comm_loop.c b/pceplib/pcep_socket_comm_loop.c new file mode 100644 index 0000000000..8346c93025 --- /dev/null +++ b/pceplib/pcep_socket_comm_loop.c @@ -0,0 +1,486 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include +#include + +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_loop.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_message(int socket_fd, const char *message, unsigned int msg_length); +unsigned int read_message(int socket_fd, char *received_message, + unsigned int max_message_size); +int build_fd_sets(pcep_socket_comm_handle *socket_comm_handle); +void handle_writes(pcep_socket_comm_handle *socket_comm_handle); +void handle_excepts(pcep_socket_comm_handle *socket_comm_handle); + +bool comm_session_exists(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle == NULL) { + return false; + } + + return (ordered_list_find(socket_comm_handle->session_list, + socket_comm_session) + != NULL); +} + + +bool comm_session_exists_locking(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle == NULL) { + return false; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + bool exists = + comm_session_exists(socket_comm_handle, socket_comm_session); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return exists; +} + + +void write_message(int socket_fd, const char *message, unsigned int msg_length) +{ + ssize_t bytes_sent = 0; + unsigned int total_bytes_sent = 0; + + while ((uint32_t)bytes_sent < msg_length) { + bytes_sent = write(socket_fd, message + total_bytes_sent, + msg_length); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm writing on socket fd [%d] msg_lenth [%u] bytes sent [%d]", + __func__, time(NULL), pthread_self(), socket_fd, + msg_length, bytes_sent); + + if (bytes_sent < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + pcep_log(LOG_WARNING, "%s: send() failure", + __func__); + + return; + } + } else { + total_bytes_sent += bytes_sent; + } + } +} + + +unsigned int read_message(int socket_fd, char *received_message, + unsigned int max_message_size) +{ + /* TODO what if bytes_read == max_message_size? there could be more to + * read */ + unsigned int bytes_read = + read(socket_fd, received_message, max_message_size); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm read message bytes_read [%u] on socket fd [%d]", + __func__, time(NULL), pthread_self(), bytes_read, socket_fd); + + return bytes_read; +} + + +int build_fd_sets(pcep_socket_comm_handle *socket_comm_handle) +{ + int max_fd = 0; + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + + FD_ZERO(&socket_comm_handle->except_master_set); + FD_ZERO(&socket_comm_handle->read_master_set); + ordered_list_node *node = socket_comm_handle->read_list->head; + pcep_socket_comm_session *comm_session; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + if (comm_session->socket_fd > max_fd) { + max_fd = comm_session->socket_fd; + } + + /*pcep_log(LOG_DEBUG, ld] socket_comm::build_fdSets set + ready_toRead + [%d]", __func__, time(NULL), comm_session->socket_fd);*/ + FD_SET(comm_session->socket_fd, + &socket_comm_handle->read_master_set); + FD_SET(comm_session->socket_fd, + &socket_comm_handle->except_master_set); + node = node->next_node; + } + + FD_ZERO(&socket_comm_handle->write_master_set); + node = socket_comm_handle->write_list->head; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + if (comm_session->socket_fd > max_fd) { + max_fd = comm_session->socket_fd; + } + + /*pcep_log(LOG_DEBUG, "%s: [%ld] socket_comm::build_fdSets set + ready_toWrite [%d]", __func__, time(NULL), + comm_session->socket_fd);*/ + FD_SET(comm_session->socket_fd, + &socket_comm_handle->write_master_set); + FD_SET(comm_session->socket_fd, + &socket_comm_handle->except_master_set); + node = node->next_node; + } + + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return max_fd + 1; +} + + +void handle_reads(pcep_socket_comm_handle *socket_comm_handle) +{ + + /* + * iterate all the socket_fd's in the read_list. it may be that not + * all of them have something to read. dont remove the socket_fd + * from the read_list since messages could come at any time. + */ + + /* Notice: Only locking the mutex when accessing the read_list, + * since the read callbacks may end up calling back into the socket + * comm module to write messages which could be a deadlock. */ + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + ordered_list_node *node = socket_comm_handle->read_list->head; + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + while (node != NULL) { + pcep_socket_comm_session *comm_session = + (pcep_socket_comm_session *)node->data; + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + node = node->next_node; + if (!comm_session_exists(socket_comm_handle, comm_session)) { + /* This comm_session has been deleted, move on to the + * next one */ + pthread_mutex_unlock( + &(socket_comm_handle->socket_comm_mutex)); + continue; + } + + int is_set = FD_ISSET(comm_session->socket_fd, + &(socket_comm_handle->read_master_set)); + /* Upon read failure, the comm_session might be free'd, so we + * cant store the received_bytes in the comm_session, until we + * know the read was successful. */ + int received_bytes = 0; + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + if (is_set) { + FD_CLR(comm_session->socket_fd, + &(socket_comm_handle->read_master_set)); + + /* either read the message locally, or call the + * message_ready_handler to read it */ + if (comm_session->message_handler != NULL) { + received_bytes = read_message( + comm_session->socket_fd, + comm_session->received_message, + MAX_RECVD_MSG_SIZE); + if (received_bytes > 0) { + /* Send the received message to the + * handler */ + comm_session->received_bytes = + received_bytes; + comm_session->message_handler( + comm_session->session_data, + comm_session->received_message, + comm_session->received_bytes); + } + } else { + /* Tell the handler a message is ready to be + * read. The comm_session may be destroyed in + * this call, if + * there is an error reading or if the socket is + * closed. */ + received_bytes = + comm_session + ->message_ready_to_read_handler( + comm_session + ->session_data, + comm_session + ->socket_fd); + } + + /* handle the read results */ + if (received_bytes == 0) { + if (comm_session_exists_locking( + socket_comm_handle, comm_session)) { + comm_session->received_bytes = 0; + /* the socket was closed */ + /* TODO should we define a socket except + * enum? or will the only time we call + * this is when the socket is closed?? + */ + if (comm_session->conn_except_notifier + != NULL) { + comm_session->conn_except_notifier( + comm_session + ->session_data, + comm_session + ->socket_fd); + } + + /* stop reading from the socket if its + * closed */ + pthread_mutex_lock( + &(socket_comm_handle + ->socket_comm_mutex)); + ordered_list_remove_first_node_equals( + socket_comm_handle->read_list, + comm_session); + pthread_mutex_unlock( + &(socket_comm_handle + ->socket_comm_mutex)); + } + } else if (received_bytes < 0) { + /* TODO should we call conn_except_notifier() + * here ? */ + pcep_log( + LOG_WARNING, + "%s: Error on socket fd [%d] : errno [%d][%s]", + __func__, comm_session->socket_fd, + errno, strerror(errno)); + } else { + comm_session->received_bytes = received_bytes; + } + } + } +} + + +void handle_writes(pcep_socket_comm_handle *socket_comm_handle) +{ + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + + /* + * iterate all the socket_fd's in the write_list. it may be that not + * all of them are ready to be written to. only remove the socket_fd + * from the list if it is ready to be written to. + */ + + ordered_list_node *node = socket_comm_handle->write_list->head; + pcep_socket_comm_session *comm_session; + bool msg_written; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + node = node->next_node; + msg_written = false; + + if (!comm_session_exists(socket_comm_handle, comm_session)) { + /* This comm_session has been deleted, move on to the + * next one */ + continue; + } + + if (FD_ISSET(comm_session->socket_fd, + &(socket_comm_handle->write_master_set))) { + /* only remove the entry from the list, if it is written + * to */ + ordered_list_remove_first_node_equals( + socket_comm_handle->write_list, comm_session); + FD_CLR(comm_session->socket_fd, + &(socket_comm_handle->write_master_set)); + + /* dequeue all the comm_session messages and send them + */ + pcep_socket_comm_queued_message *queued_message = + queue_dequeue(comm_session->message_queue); + while (queued_message != NULL) { + msg_written = true; + write_message(comm_session->socket_fd, + queued_message->encoded_message, + queued_message->msg_length); + if (queued_message->free_after_send) { + pceplib_free(PCEPLIB_MESSAGES, + (void *)queued_message + ->encoded_message); + } + pceplib_free(PCEPLIB_MESSAGES, queued_message); + queued_message = queue_dequeue( + comm_session->message_queue); + } + } + + /* check if the socket should be closed after writing */ + if (comm_session->close_after_write == true) { + if (comm_session->message_queue->num_entries == 0) { + /* TODO check to make sure modifying the + * write_list while iterating it doesnt cause + * problems. */ + pcep_log( + LOG_DEBUG, + "%s: handle_writes close() socket fd [%d]", + __func__, comm_session->socket_fd); + ordered_list_remove_first_node_equals( + socket_comm_handle->read_list, + comm_session); + ordered_list_remove_first_node_equals( + socket_comm_handle->write_list, + comm_session); + close(comm_session->socket_fd); + comm_session->socket_fd = -1; + } + } + + if (comm_session->message_sent_handler != NULL + && msg_written == true) { + /* Unlocking to allow the message_sent_handler to + * make calls like destroy_socket_comm_session */ + pthread_mutex_unlock( + &(socket_comm_handle->socket_comm_mutex)); + comm_session->message_sent_handler( + comm_session->session_data, + comm_session->socket_fd); + pthread_mutex_lock( + &(socket_comm_handle->socket_comm_mutex)); + } + } + + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); +} + + +void handle_excepts(pcep_socket_comm_handle *socket_comm_handle) +{ + /* TODO finish this */ + (void)socket_comm_handle; +} + + +/* pcep_socket_comm::initialize_socket_comm_loop() will create a thread and + * invoke this method */ +void *socket_comm_loop(void *data) +{ + if (data == NULL) { + pcep_log( + LOG_WARNING, + "%s: Cannot start socket_comm_loop with NULL pcep_socketcomm_handle", + __func__); + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting socket_comm_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)data; + struct timeval timer; + int max_fd; + + while (socket_comm_handle->active) { + /* check the FD's every 1/4 sec, 250 milliseconds */ + timer.tv_sec = 0; + timer.tv_usec = 250000; + max_fd = build_fd_sets(socket_comm_handle); + + if (select(max_fd, &(socket_comm_handle->read_master_set), + &(socket_comm_handle->write_master_set), + &(socket_comm_handle->except_master_set), &timer) + < 0) { + /* TODO handle the error */ + pcep_log( + LOG_WARNING, + "%s: ERROR socket_comm_loop on select : errno [%d][%s]", + __func__, errno, strerror(errno)); + } + + handle_reads(socket_comm_handle); + handle_writes(socket_comm_handle); + handle_excepts(socket_comm_handle); + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Finished socket_comm_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} + +int pceplib_external_socket_read(int fd, void *payload) +{ + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)payload; + if (socket_comm_handle == NULL) { + return -1; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + FD_SET(fd, &(socket_comm_handle->read_master_set)); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + handle_reads(socket_comm_handle); + + /* Get the socket_comm_session */ + pcep_socket_comm_session find_session = {.socket_fd = fd}; + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + ordered_list_node *node = + ordered_list_find(socket_comm_handle->read_list, &find_session); + + /* read again */ + if (node != NULL) { + socket_comm_handle->socket_read_func( + socket_comm_handle->external_infra_data, + &((pcep_socket_comm_session *)node) + ->external_socket_data, + fd, socket_comm_handle); + } + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return 0; +} + +int pceplib_external_socket_write(int fd, void *payload) +{ + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)payload; + if (socket_comm_handle == NULL) { + return -1; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + FD_SET(fd, &(socket_comm_handle->write_master_set)); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + handle_writes(socket_comm_handle); + + /* TODO do we need to cancel this FD from writing?? */ + + return 0; +} diff --git a/pceplib/pcep_socket_comm_loop.h b/pceplib/pcep_socket_comm_loop.h new file mode 100644 index 0000000000..3ca2c037fa --- /dev/null +++ b/pceplib/pcep_socket_comm_loop.h @@ -0,0 +1,32 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEPSOCKETCOMMLOOP_H_ +#define PCEPSOCKETCOMMLOOP_H_ + +void handle_reads(pcep_socket_comm_handle *socket_comm_handle); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/pcep_socket_comm_mock.c b/pceplib/pcep_socket_comm_mock.c new file mode 100644 index 0000000000..069d0cf998 --- /dev/null +++ b/pceplib/pcep_socket_comm_mock.c @@ -0,0 +1,363 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * This module is built into a separate library, and is used by several + * other modules for unit testing, so that real sockets dont have to be + * created. + */ + +#include +#include +#include +#include + +#include + +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_mock.h" +#include "pcep_utils_queue.h" + +/* reset_mock_socket_comm_info() should be used before each test */ +mock_socket_comm_info mock_socket_metadata; + +void setup_mock_socket_comm_info(void) +{ + mock_socket_metadata.socket_comm_session_initialize_times_called = 0; + mock_socket_metadata.socket_comm_session_initialize_src_times_called = + 0; + mock_socket_metadata.socket_comm_session_teardown_times_called = 0; + mock_socket_metadata.socket_comm_session_connect_tcp_times_called = 0; + mock_socket_metadata.socket_comm_session_send_message_times_called = 0; + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called = 0; + mock_socket_metadata.socket_comm_session_close_tcp_times_called = 0; + mock_socket_metadata.destroy_socket_comm_loop_times_called = 0; + mock_socket_metadata.send_message_save_message = false; + mock_socket_metadata.sent_message_list = dll_initialize(); +} + +void teardown_mock_socket_comm_info(void) +{ + dll_destroy(mock_socket_metadata.sent_message_list); +} + +void reset_mock_socket_comm_info(void) +{ + teardown_mock_socket_comm_info(); + setup_mock_socket_comm_info(); +} + +mock_socket_comm_info *get_mock_socket_comm_info(void) +{ + return &mock_socket_metadata; +} + +void verify_socket_comm_times_called(int initialized, int teardown, int connect, + int send_message, + int close_tcp_after_write, int close_tcp, + int destroy) +{ + CU_ASSERT_EQUAL(initialized, + mock_socket_metadata + .socket_comm_session_initialize_times_called); + CU_ASSERT_EQUAL( + teardown, + mock_socket_metadata.socket_comm_session_teardown_times_called); + CU_ASSERT_EQUAL(connect, + mock_socket_metadata + .socket_comm_session_connect_tcp_times_called); + CU_ASSERT_EQUAL(send_message, + mock_socket_metadata + .socket_comm_session_send_message_times_called); + CU_ASSERT_EQUAL( + close_tcp_after_write, + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called); + CU_ASSERT_EQUAL(close_tcp, + mock_socket_metadata + .socket_comm_session_close_tcp_times_called); + CU_ASSERT_EQUAL( + destroy, + mock_socket_metadata.destroy_socket_comm_loop_times_called); +} + + +/* + * Mock the socket_comm functions used by session_logic for Unit Testing + */ + +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func) +{ + (void)external_infra_data; + (void)socket_read_cb; + (void)socket_write_cb; + (void)thread_create_func; + + mock_socket_metadata + .socket_comm_initialize_external_infra_times_called++; + + return true; +} + +bool destroy_socket_comm_loop() +{ + mock_socket_metadata.destroy_socket_comm_loop_times_called++; + + return false; +} + +pcep_socket_comm_session * +socket_comm_session_initialize(message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, + struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, + const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = false; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = AF_INET; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dst_port); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr.s_addr = + dst_ip->s_addr; + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dst_ip, + short dst_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = true; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dst_port); + memcpy(&comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_addr, + dst_ip, sizeof(struct in6_addr)); + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_src_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = false; + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_family = AF_INET; + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_port = + htons(src_port); + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr = + ((src_ip == NULL) ? INADDR_ANY : src_ip->s_addr); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = AF_INET; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dst_port); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr.s_addr = + dst_ip->s_addr; + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_src_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = true; + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_port = + htons(src_port); + if (src_ip == NULL) { + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_addr = + in6addr_any; + } else { + memcpy(&comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + src_ip, sizeof(struct in6_addr)); + } + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dst_port); + memcpy(&comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_addr, + dst_ip, sizeof(struct in6_addr)); + + return comm_session; +} + +bool socket_comm_session_teardown(pcep_socket_comm_session *socket_comm_session) +{ + mock_socket_metadata.socket_comm_session_teardown_times_called++; + + if (socket_comm_session != NULL) { + queue_destroy(socket_comm_session->message_queue); + free(socket_comm_session); + } + + return true; +} + + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata.socket_comm_session_connect_tcp_times_called++; + + return true; +} + + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool delete_after_send) +{ + (void)socket_comm_session; + (void)msg_length; + + mock_socket_metadata.socket_comm_session_send_message_times_called++; + + if (mock_socket_metadata.send_message_save_message == true) { + /* the caller/test case is responsible for freeing the message + */ + dll_append(mock_socket_metadata.sent_message_list, + (char *)encoded_message); + } else { + if (delete_after_send == true) { + free((void *)encoded_message); + } + } + + return; +} + + +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called++; + + return true; +} + + +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata.socket_comm_session_close_tcp_times_called++; + + return true; +} diff --git a/pceplib/pcep_socket_comm_mock.h b/pceplib/pcep_socket_comm_mock.h new file mode 100644 index 0000000000..ca0e38b803 --- /dev/null +++ b/pceplib/pcep_socket_comm_mock.h @@ -0,0 +1,67 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +/* + * This module is built into a separate library, and is used by several + * other modules for unit testing, so that real sockets dont have to be + * created. + */ + +#ifndef PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ +#define PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ + +#include + +#include "pcep_utils_double_linked_list.h" + +typedef struct mock_socket_comm_info_ { + int socket_comm_initialize_external_infra_times_called; + int socket_comm_session_initialize_times_called; + int socket_comm_session_initialize_src_times_called; + int socket_comm_session_teardown_times_called; + int socket_comm_session_connect_tcp_times_called; + int socket_comm_session_send_message_times_called; + int socket_comm_session_close_tcp_after_write_times_called; + int socket_comm_session_close_tcp_times_called; + int destroy_socket_comm_loop_times_called; + + /* TODO later if necessary, we can add return values for + * those functions that return something */ + + /* Used to access messages sent with socket_comm_session_send_message() + */ + bool send_message_save_message; + double_linked_list *sent_message_list; + +} mock_socket_comm_info; + +void setup_mock_socket_comm_info(void); +void teardown_mock_socket_comm_info(void); +void reset_mock_socket_comm_info(void); +bool destroy_socket_comm_loop(void); + +mock_socket_comm_info *get_mock_socket_comm_info(void); +void verify_socket_comm_times_called(int initialized, int teardown, int connect, + int send_message, int close_after_write, + int close, int destroy); + +#endif /* PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ */ diff --git a/pceplib/pcep_timer_internals.h b/pceplib/pcep_timer_internals.h new file mode 100644 index 0000000000..8221c78baa --- /dev/null +++ b/pceplib/pcep_timer_internals.h @@ -0,0 +1,76 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEPTIMERINTERNALS_H_ +#define PCEPTIMERINTERNALS_H_ + +#include +#include + +#include "pcep_utils_ordered_list.h" + +/* Function pointer to be called when timers expire. + * Parameters: + * void *data - passed into create_timer + * int timer_id - the timer_id returned by create_timer + */ +typedef void (*timer_expire_handler)(void *, int); + +/* Function pointer when an external timer infrastructure is used */ +typedef void (*ext_timer_create)(void *infra_data, void **timer, int seconds, + void *data); +typedef void (*ext_timer_cancel)(void **timer); +typedef int (*ext_pthread_create_callback)(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); + +typedef struct pcep_timer_ { + time_t expire_time; + uint16_t sleep_seconds; + int timer_id; + void *data; + void *external_timer; + +} pcep_timer; + +typedef struct pcep_timers_context_ { + ordered_list_handle *timer_list; + bool active; + timer_expire_handler expire_handler; + pthread_t event_loop_thread; + pthread_mutex_t timer_list_lock; + void *external_timer_infra_data; + ext_timer_create timer_create_func; + ext_timer_cancel timer_cancel_func; + +} pcep_timers_context; + +/* functions implemented in pcep_timers_loop.c */ +void *event_loop(void *context); + + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/pcep_timers.c b/pceplib/pcep_timers.c new file mode 100644 index 0000000000..e9d9d4b21d --- /dev/null +++ b/pceplib/pcep_timers.c @@ -0,0 +1,482 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of public API timer functions. + */ + +#include +#include +#include +#include +#include + +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" + +static pcep_timers_context *timers_context_ = NULL; +static int timer_id_ = 0; + + +/* simple compare method callback used by pcep_utils_ordered_list + * for ordered list insertion. */ +int timer_list_node_compare(void *list_entry, void *new_entry) +{ + /* return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry */ + return ((pcep_timer *)new_entry)->expire_time + - ((pcep_timer *)list_entry)->expire_time; +} + + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its timer_id. */ +int timer_list_node_timer_id_compare(void *list_entry, void *new_entry) +{ + return ((pcep_timer *)new_entry)->timer_id + - ((pcep_timer *)list_entry)->timer_id; +} + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its address. */ +int timer_list_node_timer_ptr_compare(void *list_entry, void *new_entry) +{ + return ((char *)new_entry - (char *)list_entry); +} + +/* internal util method */ +static pcep_timers_context *create_timers_context_() +{ + if (timers_context_ == NULL) { + timers_context_ = pceplib_malloc(PCEPLIB_INFRA, + sizeof(pcep_timers_context)); + memset(timers_context_, 0, sizeof(pcep_timers_context)); + timers_context_->active = false; + } + + return timers_context_; +} + + +/* Internal util function */ +static bool initialize_timers_common(timer_expire_handler expire_handler) +{ + if (expire_handler == NULL) { + /* Cannot have a NULL handler function */ + return false; + } + + timers_context_ = create_timers_context_(); + + if (timers_context_->active == true) { + /* already initialized */ + return false; + } + + timers_context_->active = true; + timers_context_->timer_list = + ordered_list_initialize(timer_list_node_compare); + timers_context_->expire_handler = expire_handler; + + if (pthread_mutex_init(&(timers_context_->timer_list_lock), NULL) + != 0) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the mutex", + __func__); + return false; + } + + return true; +} + +bool initialize_timers(timer_expire_handler expire_handler) +{ + if (initialize_timers_common(expire_handler) == false) { + return false; + } + + if (pthread_create(&(timers_context_->event_loop_thread), NULL, + event_loop, timers_context_)) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the thread", + __func__); + return false; + } + + return true; +} + +bool initialize_timers_external_infra( + timer_expire_handler expire_handler, void *external_timer_infra_data, + ext_timer_create timer_create_func, ext_timer_cancel timer_cancel_func, + ext_pthread_create_callback thread_create_func) +{ + if (initialize_timers_common(expire_handler) == false) { + return false; + } + + if (thread_create_func != NULL) { + if (thread_create_func(&(timers_context_->event_loop_thread), + NULL, event_loop, timers_context_, + "pceplib_timers")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external timers thread.", + __func__); + return false; + } + } else { + if (pthread_create(&(timers_context_->event_loop_thread), NULL, + event_loop, timers_context_)) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the thread", + __func__); + return false; + } + } + + timers_context_->external_timer_infra_data = external_timer_infra_data; + timers_context_->timer_create_func = timer_create_func; + timers_context_->timer_cancel_func = timer_cancel_func; + + return true; +} + +/* + * This function is only used to tear_down the timer data. + * Only the timer data is deleted, not the list itself, + * which is deleted by ordered_list_destroy(). + */ +void free_all_timers(pcep_timers_context *timers_context) +{ + pthread_mutex_lock(&timers_context->timer_list_lock); + + ordered_list_node *timer_node = timers_context->timer_list->head; + + while (timer_node != NULL) { + if (timer_node->data != NULL) { + pceplib_free(PCEPLIB_INFRA, timer_node->data); + } + timer_node = timer_node->next_node; + } + + pthread_mutex_unlock(&timers_context->timer_list_lock); +} + + +bool teardown_timers() +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, but they are not initialized", + __func__); + return false; + } + + if (timers_context_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, but they are not active", + __func__); + return false; + } + + timers_context_->active = false; + if (timers_context_->event_loop_thread != 0) { + /* TODO this does not build + * Instead of calling pthread_join() which could block if the + thread + * is blocked, try joining for at most 1 second. + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + int retval = + pthread_timedjoin_np(timers_context_->event_loop_thread, NULL, + &ts); if (retval != 0) + { + pcep_log(LOG_WARNING, "%s: thread did not stop after 1 + second waiting on it.", __func__); + } + */ + pthread_join(timers_context_->event_loop_thread, NULL); + } + + free_all_timers(timers_context_); + ordered_list_destroy(timers_context_->timer_list); + + if (pthread_mutex_destroy(&(timers_context_->timer_list_lock)) != 0) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, cannot destroy the mutex", + __func__); + } + + pceplib_free(PCEPLIB_INFRA, timers_context_); + timers_context_ = NULL; + + return true; +} + + +int get_next_timer_id() +{ + if (timer_id_ == INT_MAX) { + timer_id_ = 0; + } + + return timer_id_++; +} + +int create_timer(uint16_t sleep_seconds, void *data) +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to create a timer: the timers have not been initialized", + __func__); + return -1; + } + + pcep_timer *timer = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timer)); + memset(timer, 0, sizeof(pcep_timer)); + timer->data = data; + timer->sleep_seconds = sleep_seconds; + timer->expire_time = time(NULL) + sleep_seconds; + + pthread_mutex_lock(&timers_context_->timer_list_lock); + timer->timer_id = get_next_timer_id(); + + /* implemented in pcep_utils_ordered_list.c */ + if (ordered_list_add_node(timers_context_->timer_list, timer) == NULL) { + pceplib_free(PCEPLIB_INFRA, timer); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to create a timer, cannot add the timer to the timer list", + __func__); + + return -1; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_create_func) { + timers_context_->timer_create_func( + timers_context_->external_timer_infra_data, + &timer->external_timer, sleep_seconds, timer); + } + + return timer->timer_id; +} + + +bool cancel_timer(int timer_id) +{ + static pcep_timer compare_timer; + + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to cancel a timer: the timers have not been initialized", + __func__); + return false; + } + + pthread_mutex_lock(&timers_context_->timer_list_lock); + + compare_timer.timer_id = timer_id; + pcep_timer *timer_toRemove = ordered_list_remove_first_node_equals2( + timers_context_->timer_list, &compare_timer, + timer_list_node_timer_id_compare); + if (timer_toRemove == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to cancel a timer [%d] that does not exist", + __func__, timer_id); + return false; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_cancel_func) { + timers_context_->timer_cancel_func( + &timer_toRemove->external_timer); + } + + pceplib_free(PCEPLIB_INFRA, timer_toRemove); + + return true; +} + + +bool reset_timer(int timer_id) +{ + static pcep_timer compare_timer; + + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to reset a timer: the timers have not been initialized", + __func__); + + return false; + } + + pthread_mutex_lock(&timers_context_->timer_list_lock); + + compare_timer.timer_id = timer_id; + ordered_list_node *timer_to_reset_node = + ordered_list_find2(timers_context_->timer_list, &compare_timer, + timer_list_node_timer_id_compare); + if (timer_to_reset_node == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log(LOG_WARNING, + "%s: Trying to reset a timer node that does not exist", + __func__); + + return false; + } + + pcep_timer *timer_to_reset = timer_to_reset_node->data; + if (timer_to_reset == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log(LOG_WARNING, + "%s: Trying to reset a timer that does not exist", + __func__); + + return false; + } + + /* First check if the timer to reset already has the same expire time, + * which means multiple reset_timer() calls were made on the same timer + * in the same second */ + time_t expire_time = time(NULL) + timer_to_reset->sleep_seconds; + if (timer_to_reset->expire_time == expire_time) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + return true; + } + + ordered_list_remove_node2(timers_context_->timer_list, + timer_to_reset_node); + + timer_to_reset->expire_time = expire_time; + if (ordered_list_add_node(timers_context_->timer_list, timer_to_reset) + == NULL) { + pceplib_free(PCEPLIB_INFRA, timer_to_reset); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to reset a timer, cannot add the timer to the timer list", + __func__); + + return false; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_cancel_func) { + /* Keeping this log for now, since in older versions of FRR the + * timer cancellation was blocking. This allows us to see how + * long the it takes.*/ + pcep_log(LOG_DEBUG, "%s: Reseting timer [%d] with callback", + __func__, timer_to_reset->timer_id); + timers_context_->timer_cancel_func( + &timer_to_reset->external_timer); + timer_to_reset->external_timer = NULL; + } + + if (timers_context_->timer_create_func) { + timers_context_->timer_create_func( + timers_context_->external_timer_infra_data, + &timer_to_reset->external_timer, + timer_to_reset->sleep_seconds, timer_to_reset); + /* Keeping this log for now, since in older versions of FRR the + * timer cancellation was blocking. This allows us to see how + * long the it takes.*/ + pcep_log(LOG_DEBUG, "%s: Reset timer [%d] with callback", + __func__, timer_to_reset->timer_id); + } + + return true; +} + + +void pceplib_external_timer_expire_handler(void *data) +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: External timer expired but timers_context is not initialized", + __func__); + return; + } + + if (timers_context_->expire_handler == NULL) { + pcep_log( + LOG_WARNING, + "%s: External timer expired but expire_handler is not initialized", + __func__); + return; + } + + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: External timer expired with NULL data", __func__); + return; + } + + pcep_timer *timer = (pcep_timer *)data; + pthread_mutex_lock(&timers_context_->timer_list_lock); + ordered_list_node *timer_node = + ordered_list_find2(timers_context_->timer_list, timer, + timer_list_node_timer_ptr_compare); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + /* Cannot continue if the timer does not exist */ + if (timer_node == NULL) { + pcep_log( + LOG_WARNING, + "%s: pceplib_external_timer_expire_handler timer [%p] id [%d] does not exist", + __func__, timer, timer->timer_id); + return; + } + + timers_context_->expire_handler(timer->data, timer->timer_id); + + pthread_mutex_lock(&timers_context_->timer_list_lock); + ordered_list_remove_node2(timers_context_->timer_list, timer_node); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + pceplib_free(PCEPLIB_INFRA, timer); +} diff --git a/pceplib/pcep_timers.h b/pceplib/pcep_timers.h new file mode 100644 index 0000000000..b2cc6ec546 --- /dev/null +++ b/pceplib/pcep_timers.h @@ -0,0 +1,92 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Public API for pcep_timers + */ + +#ifndef PCEPTIMERS_H_ +#define PCEPTIMERS_H_ + +#include +#include + +#include "pcep_timer_internals.h" + +#define TIMER_ID_NOT_SET -1 + +/* + * Initialize the timers module. + * The timer_expire_handler function pointer will be called each time a timer + * expires. Return true for successful initialization, false otherwise. + */ +bool initialize_timers(timer_expire_handler expire_handler); + +/* + * Initialize the timers module with an external back-end infrastructure, like + * FRR. + */ +bool initialize_timers_external_infra( + timer_expire_handler expire_handler, void *external_timer_infra_data, + ext_timer_create timer_create_func, ext_timer_cancel timer_cancel_func, + ext_pthread_create_callback thread_create_func); + +/* + * Teardown the timers module. + */ +bool teardown_timers(void); + +/* + * Create a new timer for "sleep_seconds" seconds. + * If the timer expires before being cancelled, the timer_expire_handler + * passed to initialize_timers() will be called with the pointer to "data". + * Returns a timer_id <= 0 that can be used to cancel_timer. + * Returns < 0 on error. + */ +int create_timer(uint16_t sleep_seconds, void *data); + +/* + * Cancel a timer created with create_timer(). + * Returns true if the timer was found and cancelled, false otherwise. + */ +bool cancel_timer(int timer_id); + +/* + * Reset an previously created timer, maintaining the same timer_id. + * Returns true if the timer was found and reset, false otherwise. + */ +bool reset_timer(int timer_id); + +/* + * If an external timer infra like FRR is used, then this function + * will be called when the timers expire in the external infra. + */ +void pceplib_external_timer_expire_handler(void *data); + +int timer_list_node_compare(void *list_entry, void *new_entry); +int timer_list_node_timer_id_compare(void *list_entry, void *new_entry); +int timer_list_node_timer_ptr_compare(void *list_entry, void *new_entry); +void free_all_timers(pcep_timers_context *timers_context); +int get_next_timer_id(void); + +#endif /* PCEPTIMERS_H_ */ diff --git a/pceplib/pcep_timers_event_loop.c b/pceplib/pcep_timers_event_loop.c new file mode 100644 index 0000000000..932a53eb2a --- /dev/null +++ b/pceplib/pcep_timers_event_loop.c @@ -0,0 +1,106 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include +#include + +#include "pcep_timers_event_loop.h" +#include "pcep_timer_internals.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* For each expired timer: remove the timer from the list, call the + * expire_handler, and free the timer. */ +void walk_and_process_timers(pcep_timers_context *timers_context) +{ + pthread_mutex_lock(&timers_context->timer_list_lock); + + bool keep_walking = true; + ordered_list_node *timer_node = timers_context->timer_list->head; + time_t now = time(NULL); + pcep_timer *timer_data; + + /* the timers are sorted by expire_time, so we will only + * remove the top node each time through the loop */ + while (timer_node != NULL && keep_walking) { + timer_data = (pcep_timer *)timer_node->data; + if (timer_data->expire_time <= now) { + timer_node = timer_node->next_node; + ordered_list_remove_first_node( + timers_context->timer_list); + /* call the timer expired handler */ + timers_context->expire_handler(timer_data->data, + timer_data->timer_id); + pceplib_free(PCEPLIB_INFRA, timer_data); + } else { + keep_walking = false; + } + } + + pthread_mutex_unlock(&timers_context->timer_list_lock); +} + + +/* pcep_timers::initialize() will create a thread and invoke this method */ +void *event_loop(void *context) +{ + if (context == NULL) { + pcep_log( + LOG_WARNING, + "%s: pcep_timers_event_loop cannot start event_loop with NULL data", + __func__); + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting timers_event_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_timers_context *timers_context = (pcep_timers_context *)context; + struct timeval timer; + int retval; + + while (timers_context->active) { + /* check the timers every half second */ + timer.tv_sec = 0; + timer.tv_usec = 500000; + + do { + /* if the select() call gets interrupted, select() will + * set the remaining time in timer, so we need to call + * it again. + */ + retval = select(0, NULL, NULL, NULL, &timer); + } while (retval != 0 && errno == EINTR); + + walk_and_process_timers(timers_context); + } + + pcep_log(LOG_WARNING, "%s: [%ld-%ld] Finished timers_event_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} diff --git a/pceplib/pcep_timers_event_loop.h b/pceplib/pcep_timers_event_loop.h new file mode 100644 index 0000000000..c4a7264da3 --- /dev/null +++ b/pceplib/pcep_timers_event_loop.h @@ -0,0 +1,34 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_EVENT_LOOP_H_ +#define PCEP_TIMERS_EVENT_LOOP_H_ + +#include "pcep_timer_internals.h" + +void walk_and_process_timers(pcep_timers_context *timers_context); + +#endif diff --git a/pceplib/pcep_utils_counters.c b/pceplib/pcep_utils_counters.c new file mode 100644 index 0000000000..d8078f683f --- /dev/null +++ b/pceplib/pcep_utils_counters.c @@ -0,0 +1,475 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of PCEP Counters. + */ + +#include + +#include +#include +#include + +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +struct counters_group *create_counters_group(const char *group_name, + uint16_t max_subgroups) +{ + if (group_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters group: group_name is NULL.", + __func__); + return NULL; + } + + if (max_subgroups > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters group: max_subgroups [%d] is larger than max the [%d].", + __func__, max_subgroups, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_group *group = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_group)); + memset(group, 0, sizeof(struct counters_group)); + group->subgroups = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_subgroup *) + * (max_subgroups + 1)); + memset(group->subgroups, 0, + sizeof(struct counters_subgroup *) * (max_subgroups + 1)); + + strlcpy(group->counters_group_name, group_name, + sizeof(group->counters_group_name)); + group->max_subgroups = max_subgroups; + group->start_time = time(NULL); + + return group; +} + +struct counters_subgroup *create_counters_subgroup(const char *subgroup_name, + uint16_t subgroup_id, + uint16_t max_counters) +{ + if (subgroup_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: subgroup_name is NULL.", + __func__); + return NULL; + } + + if (max_counters > MAX_COUNTERS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: max_counters [%d] is larger than the max [%d].", + __func__, max_counters, MAX_COUNTERS); + return NULL; + } + + if (subgroup_id > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: subgroup_id [%d] is larger than max the [%d].", + __func__, subgroup_id, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_subgroup *subgroup = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_subgroup)); + memset(subgroup, 0, sizeof(struct counters_subgroup)); + subgroup->counters = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct counter *) * (max_counters + 1)); + memset(subgroup->counters, 0, + sizeof(struct counter *) * (max_counters + 1)); + + strlcpy(subgroup->counters_subgroup_name, subgroup_name, + sizeof(subgroup->counters_subgroup_name)); + subgroup->subgroup_id = subgroup_id; + subgroup->max_counters = max_counters; + + return subgroup; +} + +struct counters_subgroup * +clone_counters_subgroup(struct counters_subgroup *subgroup, + const char *subgroup_name, uint16_t subgroup_id) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: input counters_subgroup is NULL.", + __func__); + return NULL; + } + + if (subgroup_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: subgroup_name is NULL.", + __func__); + return NULL; + } + + if (subgroup_id > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: subgroup_id [%d] is larger than max the [%d].", + __func__, subgroup_id, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_subgroup *cloned_subgroup = create_counters_subgroup( + subgroup_name, subgroup_id, subgroup->max_counters); + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + create_subgroup_counter(cloned_subgroup, + counter->counter_id, + counter->counter_name); + } + } + + return cloned_subgroup; +} + +bool add_counters_subgroup(struct counters_group *group, + struct counters_subgroup *subgroup) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_group is NULL.", + __func__); + return false; + } + + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_subgroup is NULL.", + __func__); + return false; + } + + if (subgroup->subgroup_id >= group->max_subgroups) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_subgroup id [%d] is larger than the group max_subgroups [%d].", + __func__, subgroup->subgroup_id, group->max_subgroups); + return false; + } + + group->num_subgroups++; + group->subgroups[subgroup->subgroup_id] = subgroup; + + return true; +} + +bool create_subgroup_counter(struct counters_subgroup *subgroup, + uint32_t counter_id, const char *counter_name) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counters_subgroup is NULL.", + __func__); + return false; + } + + if (counter_id >= subgroup->max_counters) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counter_id [%d] is larger than the subgroup max_counters [%d].", + __func__, counter_id, subgroup->max_counters); + return false; + } + + if (counter_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counter_name is NULL.", + __func__); + return NULL; + } + + struct counter *counter = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counter)); + memset(counter, 0, sizeof(struct counter)); + counter->counter_id = counter_id; + strlcpy(counter->counter_name, counter_name, + sizeof(counter->counter_name)); + + subgroup->num_counters++; + subgroup->counters[counter->counter_id] = counter; + + return true; +} + +bool delete_counters_group(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot delete group counters: counters_group is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + delete_counters_subgroup(subgroup); + } + } + + pceplib_free(PCEPLIB_INFRA, group->subgroups); + pceplib_free(PCEPLIB_INFRA, group); + + return true; +} + +bool delete_counters_subgroup(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL || subgroup->counters == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot delete subgroup counters: counters_subgroup is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + pceplib_free(PCEPLIB_INFRA, counter); + } + } + + pceplib_free(PCEPLIB_INFRA, subgroup->counters); + pceplib_free(PCEPLIB_INFRA, subgroup); + + return true; +} + +bool reset_group_counters(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot reset group counters: counters_group is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + reset_subgroup_counters(subgroup); + } + } + + group->start_time = time(NULL); + + return true; +} + +bool reset_subgroup_counters(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot reset subgroup counters: counters_subgroup is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + counter->counter_value = 0; + } + } + + return true; +} + +bool increment_counter(struct counters_group *group, uint16_t subgroup_id, + uint16_t counter_id) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_group is NULL.", + __func__); + return false; + } + + if (subgroup_id >= group->max_subgroups) { + pcep_log( + LOG_DEBUG, + "%s: Cannot increment counter: subgroup_id [%d] is larger than the group max_subgroups [%d].", + __func__, subgroup_id, group->max_subgroups); + return false; + } + + struct counters_subgroup *subgroup = group->subgroups[subgroup_id]; + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_subgroup in counters_group is NULL.", + __func__); + return false; + } + + return increment_subgroup_counter(subgroup, counter_id); +} + +bool increment_subgroup_counter(struct counters_subgroup *subgroup, + uint16_t counter_id) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_subgroup is NULL.", + __func__); + return false; + } + + if (counter_id >= subgroup->max_counters) { + pcep_log( + LOG_DEBUG, + "%s: Cannot increment counter: counter_id [%d] is larger than the subgroup max_counters [%d].", + __func__, counter_id, subgroup->max_counters); + return false; + } + + if (subgroup->counters[counter_id] == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: No counter exists for counter_id [%d].", + __func__, counter_id); + return false; + } + + subgroup->counters[counter_id]->counter_value++; + + return true; +} + +bool dump_counters_group_to_log(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot dump group counters to log: counters_group is NULL.", + __func__); + return false; + } + + time_t now = time(NULL); + pcep_log( + LOG_INFO, + "%s: PCEP Counters group:\n %s \n Sub-Groups [%d] \n Active for [%d seconds]", + __func__, group->counters_group_name, group->num_subgroups, + (now - group->start_time)); + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + dump_counters_subgroup_to_log(subgroup); + } + } + + return true; +} + +bool dump_counters_subgroup_to_log(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot dump subgroup counters to log: counters_subgroup is NULL.", + __func__); + return false; + } + + pcep_log(LOG_INFO, + "%s: \tPCEP Counters sub-group [%s] with [%d] counters", + __func__, subgroup->counters_subgroup_name, + subgroup->num_counters); + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + pcep_log(LOG_INFO, "%s: \t\t%s %d", __func__, + counter->counter_name, counter->counter_value); + } + } + + return true; +} + +struct counters_subgroup *find_subgroup(const struct counters_group *group, + uint16_t subgroup_id) +{ + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + if (subgroup->subgroup_id == subgroup_id) { + return subgroup; + } + } + } + + return NULL; +} + +uint32_t subgroup_counters_total(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + return 0; + } + uint32_t counter_total = 0; + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + counter_total += counter->counter_value; + } + } + + return counter_total; +} diff --git a/pceplib/pcep_utils_counters.h b/pceplib/pcep_utils_counters.h new file mode 100644 index 0000000000..240e9758b7 --- /dev/null +++ b/pceplib/pcep_utils_counters.h @@ -0,0 +1,232 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +/* + * Definitions of PCEP Counters. + */ + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Example Counter group with sub-groups and counters + * + * pcep_counters { + * counters_group_rx { + * message_open; + * message_keepalive; + * message_pcreq; + * } + * counters_group_tx { + * message_open; + * message_keepalive; + * message_pcreq; + * } + * counters_group_events { + * pcc_connect; + * pce_connect; + * pcc_disconnect; + * pce_disconnect; + * } + * } + * + * To create the above structure of groups, sub-groups, and counters, do the + * following: + * + * struct counters_subgroup *rx_subgroup = create_counters_subgroup("rx + * counters", 3); struct counters_subgroup *tx_subgroup = + * create_counters_subgroup("tx counters", 3); struct counters_subgroup + * *events_subgroup = create_counters_subgroup("events counters", 4); + * + * Use message_id: PCEP_TYPE_OPEN=1 + * create_subgroup_counter(rx_subgroup, 1, "Message Open"); + * create_subgroup_counter(rx_subgroup, 2, "Message KeepAlive"); + * create_subgroup_counter(rx_subgroup, 3, "Message PcReq"); + * + * create_subgroup_counter(tx_subgroup, 1, "Message Open"); + * create_subgroup_counter(tx_subgroup, 2, "Message KeepAlive"); + * create_subgroup_counter(tx_subgroup, 3, "Message PcReq"); + * + * create_subgroup_counter(events_subgroup, 1, "PCC Connect"); + * create_subgroup_counter(events_subgroup, 2, "PCE Connect"); + * create_subgroup_counter(events_subgroup, 3, "PCC Disconnect"); + * create_subgroup_counter(events_subgroup, 4, "PCE Disconnect"); + * + * struct counters_group *cntrs_group = create_counters_group("PCEP Counters", + * 3); add_counters_subgroup(cntrs_group, rx_subgroup); + * add_counters_subgroup(cntrs_group, tx_subgroup); + * add_counters_subgroup(cntrs_group, events_subgroup); + */ + +#define MAX_COUNTER_STR_LENGTH 128 +#define MAX_COUNTER_GROUPS 500 +#define MAX_COUNTERS 500 + +struct counter { + uint16_t counter_id; + char counter_name[MAX_COUNTER_STR_LENGTH]; + uint32_t counter_value; +}; + +struct counters_subgroup { + char counters_subgroup_name[MAX_COUNTER_STR_LENGTH]; + uint16_t subgroup_id; + uint16_t num_counters; + uint16_t max_counters; + /* Array of (struct counter *) allocated when the subgroup is created. + * The array is indexed by the struct counter->counter_id */ + struct counter **counters; +}; + +struct counters_group { + char counters_group_name[MAX_COUNTER_STR_LENGTH]; + uint16_t num_subgroups; + uint16_t max_subgroups; + time_t start_time; + /* Array of (struct counters_subgroup *) allocated when the group is + * created. The subgroup is indexed by the (struct counters_subgroup + * *)->subgroup_id */ + struct counters_subgroup **subgroups; +}; + +/* + * Create a counters group with the given group_name and number of subgroups. + * Subgroup_ids are 0-based, so take that into account when setting + * max_subgroups. Return true on success or false if group_name is NULL or + * max_subgroups >= MAX_COUNTER_GROUPS. + */ +struct counters_group *create_counters_group(const char *group_name, + uint16_t max_subgroups); + +/* + * Create a counters subgroup with the given subgroup_name, subgroup_id and + * number of counters. The subgroup_id is 0-based. counter_ids are 0-based, so + * take that into account when setting max_counters. Return true on success or + * false if subgroup_name is NULL, subgroup_id >= MAX_COUNTER_GROUPS, or + * max_counters >= MAX_COUNTERS. + */ +struct counters_subgroup *create_counters_subgroup(const char *subgroup_name, + uint16_t subgroup_id, + uint16_t max_counters); + +/* + * Add a counter_subgroup to a counter_group. + * Return true on success or false if group is NULL or if subgroup is NULL. + */ +bool add_counters_subgroup(struct counters_group *group, + struct counters_subgroup *subgroup); + +/* + * Clone a subgroup and set a new name and subgroup_id for the new subgroup. + * This is useful for RX and TX counters: just create the RX counters and clone + * it for the TX counters. + */ +struct counters_subgroup * +clone_counters_subgroup(struct counters_subgroup *subgroup, + const char *subgroup_name, uint16_t subgroup_id); + +/* + * Create a counter in a subgroup with the given counter_id and counter_name. + * The counter_id is 0-based. + * Return true on success or false if subgroup is NULL, counter_id >= + * MAX_COUNTERS, or if counter_name is NULL. + */ +bool create_subgroup_counter(struct counters_subgroup *subgroup, + uint32_t counter_id, const char *counter_name); + +/* + * Delete the counters_group and recursively delete all subgroups and their + * counters. Return true on success or false if group is NULL. + */ +bool delete_counters_group(struct counters_group *group); + +/* + * Delete the counters_subgroup and all its counters counters. + * Return true on success or false if subgroup is NULL. + */ +bool delete_counters_subgroup(struct counters_subgroup *subgroup); + +/* + * Reset all the counters in all sub-groups contained in this group. + * Return true on success or false if group is NULL. + */ +bool reset_group_counters(struct counters_group *group); + +/* + * Reset all the counters in this subgroup. + * Return true on success or false if subgroup is NULL. + */ +bool reset_subgroup_counters(struct counters_subgroup *subgroup); + +/* + * Increment a counter given a counter_group, subgroup_id, and counter_id. + * Return true on success or false if group is NULL, subgroup_id >= + * MAX_COUNTER_GROUPS, or counter_id >= MAX_COUNTERS. + */ +bool increment_counter(struct counters_group *group, uint16_t subgroup_id, + uint16_t counter_id); + +/* + * Increment a counter given the counter_subgroup and counter_id. + * Return true on success or false if subgroup is NULL or counter_id >= + * MAX_COUNTERS. + */ +bool increment_subgroup_counter(struct counters_subgroup *subgroup, + uint16_t counter_id); + +/* + * Dump the counter_group info and all its counter_subgroups. + * Return true on success or false if group is NULL. + */ +bool dump_counters_group_to_log(struct counters_group *group); + +/* + * Dump all the counters in a counter_subgroup. + * Return true on success or false if subgroup is NULL. + */ +bool dump_counters_subgroup_to_log(struct counters_subgroup *subgroup); + +/* + * Search for a counters_subgroup by subgroup_id in a counters_group + * and return it, if found, else return NULL. + */ +struct counters_subgroup *find_subgroup(const struct counters_group *group, + uint16_t subgroup_id); + +/* + * Given a counters_subgroup, return the sum of all the counters. + */ +uint32_t subgroup_counters_total(struct counters_subgroup *subgroup); + +#ifdef __cplusplus +} +#endif + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ */ diff --git a/pceplib/pcep_utils_double_linked_list.c b/pceplib/pcep_utils_double_linked_list.c new file mode 100644 index 0000000000..acdcee0598 --- /dev/null +++ b/pceplib/pcep_utils_double_linked_list.c @@ -0,0 +1,262 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +#include +#include + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +double_linked_list *dll_initialize() +{ + double_linked_list *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list)); + if (handle != NULL) { + memset(handle, 0, sizeof(double_linked_list)); + handle->num_entries = 0; + handle->head = NULL; + handle->tail = NULL; + } else { + pcep_log(LOG_WARNING, + "%s: dll_initialize cannot allocate memory for handle", + __func__); + return NULL; + } + + return handle; +} + + +void dll_destroy(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, + "%s: dll_destroy cannot destroy NULL handle", + __func__); + return; + } + + double_linked_list_node *node = handle->head; + while (node != NULL) { + double_linked_list_node *node_to_delete = node; + node = node->next_node; + pceplib_free(PCEPLIB_INFRA, node_to_delete); + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void dll_destroy_with_data_memtype(double_linked_list *handle, + void *data_memory_type) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, + "%s: dll_destroy_with_data cannot destroy NULL handle", + __func__); + return; + } + + double_linked_list_node *node = handle->head; + while (node != NULL) { + double_linked_list_node *node_to_delete = node; + pceplib_free(data_memory_type, node->data); + node = node->next_node; + pceplib_free(PCEPLIB_INFRA, node_to_delete); + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void dll_destroy_with_data(double_linked_list *handle) +{ + /* Default to destroying the data with the INFRA mem type */ + dll_destroy_with_data_memtype(handle, PCEPLIB_INFRA); +} + + +/* Creates a node and adds it as the first item in the list */ +double_linked_list_node *dll_prepend(double_linked_list *handle, void *data) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_prepend_data NULL handle", + __func__); + return NULL; + } + + /* Create the new node */ + double_linked_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list_node)); + memset(new_node, 0, sizeof(double_linked_list_node)); + new_node->data = data; + + if (handle->head == NULL) { + handle->head = new_node; + handle->tail = new_node; + } else { + new_node->next_node = handle->head; + handle->head->prev_node = new_node; + handle->head = new_node; + } + + (handle->num_entries)++; + + return new_node; +} + + +/* Creates a node and adds it as the last item in the list */ +double_linked_list_node *dll_append(double_linked_list *handle, void *data) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_append_data NULL handle", + __func__); + return NULL; + } + + /* Create the new node */ + double_linked_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list_node)); + memset(new_node, 0, sizeof(double_linked_list_node)); + new_node->data = data; + + if (handle->head == NULL) { + handle->head = new_node; + handle->tail = new_node; + } else { + new_node->prev_node = handle->tail; + handle->tail->next_node = new_node; + handle->tail = new_node; + } + + (handle->num_entries)++; + + return new_node; +} + + +/* Delete the first node in the list, and return the data */ +void *dll_delete_first_node(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_first_node NULL handle", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + double_linked_list_node *delete_node = handle->head; + void *data = delete_node->data; + + if (delete_node->next_node == NULL) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else { + handle->head = delete_node->next_node; + handle->head->prev_node = NULL; + } + + pceplib_free(PCEPLIB_INFRA, delete_node); + (handle->num_entries)--; + + return data; +} + + +/* Delete the last node in the list, and return the data */ +void *dll_delete_last_node(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_last_node NULL handle", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + double_linked_list_node *delete_node = handle->tail; + void *data = delete_node->data; + + if (delete_node->prev_node == NULL) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else { + handle->tail = delete_node->prev_node; + handle->tail->next_node = NULL; + } + + pceplib_free(PCEPLIB_INFRA, delete_node); + (handle->num_entries)--; + + return data; +} + + +/* Delete the designated node in the list, and return the data */ +void *dll_delete_node(double_linked_list *handle, double_linked_list_node *node) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_node NULL handle", + __func__); + return NULL; + } + + if (node == NULL) { + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *data = node->data; + + if (handle->head == handle->tail) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else if (handle->head == node) { + handle->head = node->next_node; + handle->head->prev_node = NULL; + } else if (handle->tail == node) { + handle->tail = node->prev_node; + handle->tail->next_node = NULL; + } else { + /* Its somewhere in the middle of the list */ + node->next_node->prev_node = node->prev_node; + node->prev_node->next_node = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + (handle->num_entries)--; + + return data; +} diff --git a/pceplib/pcep_utils_double_linked_list.h b/pceplib/pcep_utils_double_linked_list.h new file mode 100644 index 0000000000..4fe01726ad --- /dev/null +++ b/pceplib/pcep_utils_double_linked_list.h @@ -0,0 +1,72 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ + +typedef struct double_linked_list_node_ { + struct double_linked_list_node_ *prev_node; + struct double_linked_list_node_ *next_node; + void *data; + +} double_linked_list_node; + + +typedef struct double_linked_list_ { + double_linked_list_node *head; + double_linked_list_node *tail; + unsigned int num_entries; + +} double_linked_list; + + +/* Initialize a double linked list */ +double_linked_list *dll_initialize(void); + +/* Destroy a double linked list, by freeing the handle and nodes, + * user data will not be freed, and may be leaked if not handled + * externally. */ +void dll_destroy(double_linked_list *handle); +/* Destroy a double linked list, by freeing the handle and nodes, + * and the user data. */ +void dll_destroy_with_data(double_linked_list *handle); +void dll_destroy_with_data_memtype(double_linked_list *handle, + void *data_memory_type); + +/* Creates a node and adds it as the first item in the list */ +double_linked_list_node *dll_prepend(double_linked_list *handle, void *data); + +/* Creates a node and adds it as the last item in the list */ +double_linked_list_node *dll_append(double_linked_list *handle, void *data); + +/* Delete the first node in the list, and return the data */ +void *dll_delete_first_node(double_linked_list *handle); + +/* Delete the last node in the list, and return the data */ +void *dll_delete_last_node(double_linked_list *handle); + +/* Delete the designated node in the list, and return the data */ +void *dll_delete_node(double_linked_list *handle, + double_linked_list_node *node); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ */ diff --git a/pceplib/pcep_utils_logging.c b/pceplib/pcep_utils_logging.c new file mode 100644 index 0000000000..65e1abbc03 --- /dev/null +++ b/pceplib/pcep_utils_logging.c @@ -0,0 +1,82 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include "pcep_utils_logging.h" + +/* Forward declaration */ +int pcep_stdout_logger(int priority, const char *format, va_list args); + +static pcep_logger_func logger_func = pcep_stdout_logger; +static int logging_level_ = LOG_INFO; + +void register_logger(pcep_logger_func logger) +{ + logger_func = logger; +} + +void set_logging_level(int level) +{ + logging_level_ = level; +} + +int get_logging_level() +{ + return logging_level_; +} + +void pcep_log(int priority, const char *format, ...) +{ + va_list va; + va_start(va, format); + logger_func(priority, format, va); + va_end(va); +} + +void pcep_log_hexbytes(int priority, const char *message, const uint8_t *bytes, + uint8_t bytes_len) +{ + char byte_str[2048] = {0}; + int i = 0; + + snprintf(byte_str, 2048, "%s ", message); + for (; i < bytes_len; i++) { + snprintf(byte_str, 2048, "%02x ", bytes[i]); + } + snprintf(byte_str, 2048, "\n"); + + pcep_log(priority, "%s", byte_str); +} + +/* Defined with a return type to match the FRR logging signature. + * Assuming glibc printf() is thread-safe. */ +int pcep_stdout_logger(int priority, const char *format, va_list args) +{ + if (priority <= logging_level_) { + vprintf(format, args); + printf("\n"); + } + + return 0; +} diff --git a/pceplib/pcep_utils_logging.h b/pceplib/pcep_utils_logging.h new file mode 100644 index 0000000000..24ea495bd8 --- /dev/null +++ b/pceplib/pcep_utils_logging.h @@ -0,0 +1,66 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ + +#include /* Logging levels */ +#include /* va_list */ +#include /* uint8_t */ + +/* + * The logging defined here i intended to provide the infrastructure to + * be able to plug-in an external logger, primarily the FRR logger. There + * will be a default internal logger implemented that will write to stdout, + * but any other advanced logging features should be implemented externally. + */ + +/* Only the following logging levels from syslog.h should be used: + * + * LOG_DEBUG - For all messages that are enabled by optional debugging + * features, typically preceded by "if (IS...DEBUG...)" + * LOG_INFO - Information that may be of interest, but + * everything seems to be working properly. + * LOG_NOTICE - Only for message pertaining to daemon startup or shutdown. + * LOG_WARNING - Warning conditions: unexpected events, but the daemon + * believes it can continue to operate correctly. + * LOG_ERR - Error situations indicating malfunctions. + * Probably requires attention. + */ + + +/* The signature of this logger function is the same as the FRR logger */ +typedef int (*pcep_logger_func)(int, const char *, va_list); +void register_logger(pcep_logger_func logger); + +/* These functions only take affect when using the internal stdout logger */ +void set_logging_level(int level); +int get_logging_level(void); + +/* Log messages either to a previously registered + * logger or to the internal default stdout logger. */ +void pcep_log(int priority, const char *format, ...); +void pcep_log_hexbytes(int priority, const char *message, const uint8_t *bytes, + uint8_t bytes_len); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ */ diff --git a/pceplib/pcep_utils_memory.c b/pceplib/pcep_utils_memory.c new file mode 100644 index 0000000000..7362e3433b --- /dev/null +++ b/pceplib/pcep_utils_memory.c @@ -0,0 +1,220 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* Set default values for memory function pointers */ +static pceplib_malloc_func mfunc = NULL; +static pceplib_calloc_func cfunc = NULL; +static pceplib_realloc_func rfunc = NULL; +static pceplib_strdup_func sfunc = NULL; +static pceplib_free_func ffunc = NULL; + +/* Internal memory types */ +struct pceplib_memory_type pceplib_infra_mt = { + .memory_type_name = "PCEPlib Infrastructure memory", + .total_bytes_allocated = 0, + .num_allocates = 0, + .total_bytes_freed = 0, + .num_frees = 0}; +struct pceplib_memory_type pceplib_messages_mt = { + .memory_type_name = "PCEPlib Messages memory", + .total_bytes_allocated = 0, + .num_allocates = 0, + .total_bytes_freed = 0, + .num_frees = 0}; + +/* The memory type pointers default to the internal memory types */ +void *PCEPLIB_INFRA = &pceplib_infra_mt; +void *PCEPLIB_MESSAGES = &pceplib_messages_mt; + +/* Initialize memory function pointers and memory type pointers */ +bool pceplib_memory_initialize(void *pceplib_infra_mt, + void *pceplib_messages_mt, + pceplib_malloc_func mf, pceplib_calloc_func cf, + pceplib_realloc_func rf, pceplib_strdup_func sf, + pceplib_free_func ff) +{ + PCEPLIB_INFRA = (pceplib_infra_mt ? pceplib_infra_mt : PCEPLIB_INFRA); + PCEPLIB_MESSAGES = + (pceplib_messages_mt ? pceplib_messages_mt : PCEPLIB_MESSAGES); + + mfunc = (mf ? mf : mfunc); + cfunc = (cf ? cf : cfunc); + rfunc = (rf ? rf : rfunc); + sfunc = (sf ? sf : sfunc); + ffunc = (ff ? ff : ffunc); + + return true; +} + +void pceplib_memory_reset() +{ + pceplib_infra_mt.total_bytes_allocated = 0; + pceplib_infra_mt.num_allocates = 0; + pceplib_infra_mt.total_bytes_freed = 0; + pceplib_infra_mt.num_frees = 0; + + pceplib_messages_mt.total_bytes_allocated = 0; + pceplib_messages_mt.num_allocates = 0; + pceplib_messages_mt.total_bytes_freed = 0; + pceplib_messages_mt.num_frees = 0; +} + +void pceplib_memory_dump() +{ + if (PCEPLIB_INFRA) { + pcep_log( + LOG_INFO, + "%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]", + __func__, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->memory_type_name, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->num_allocates, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->total_bytes_allocated, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->num_frees); + } + + if (PCEPLIB_MESSAGES) { + pcep_log( + LOG_INFO, + "%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]", + __func__, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->memory_type_name, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->num_allocates, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->total_bytes_allocated, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->num_frees); + } +} + +/* PCEPlib memory functions: + * They either call the supplied function pointers, or use the internal + * implementations, which just increment simple counters and call the + * C stdlib memory implementations. */ + +void *pceplib_malloc(void *mem_type, size_t size) +{ + if (mfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return malloc(size); + } else { + return mfunc(mem_type, size); + } +} + +void *pceplib_calloc(void *mem_type, size_t size) +{ + if (cfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return calloc(1, size); + } else { + return cfunc(mem_type, size); + } +} + +void *pceplib_realloc(void *mem_type, void *ptr, size_t size) +{ + if (rfunc == NULL) { + if (mem_type != NULL) { + /* TODO should add previous allocated bytes to + * total_bytes_freed */ + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return realloc(ptr, size); + } else { + return rfunc(mem_type, ptr, size); + } +} + +void *pceplib_strdup(void *mem_type, const char *str) +{ + if (sfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += strlen(str); + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return strdup(str); + } else { + return sfunc(mem_type, str); + } +} + +void pceplib_free(void *mem_type, void *ptr) +{ + if (ffunc == NULL) { + if (mem_type != NULL) { + /* TODO in order to increment total_bytes_freed, we need + * to keep track of the bytes allocated per pointer. + * Currently not implemented. */ + ((struct pceplib_memory_type *)mem_type)->num_frees++; + if (((struct pceplib_memory_type *)mem_type) + ->num_allocates + < ((struct pceplib_memory_type *)mem_type) + ->num_frees) { + pcep_log( + LOG_ERR, + "%s: pceplib_free MT N_Alloc < N_Free: MemType [%s] NumAllocates [%d] NumFrees [%d]", + __func__, + ((struct pceplib_memory_type *)mem_type) + ->memory_type_name, + ((struct pceplib_memory_type *)mem_type) + ->num_allocates, + ((struct pceplib_memory_type *)mem_type) + ->num_frees); + } + } + + return free(ptr); + } else { + return ffunc(mem_type, ptr); + } +} diff --git a/pceplib/pcep_utils_memory.h b/pceplib/pcep_utils_memory.h new file mode 100644 index 0000000000..4624a91a1c --- /dev/null +++ b/pceplib/pcep_utils_memory.h @@ -0,0 +1,89 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ + +#include +#include +#include + +/* This module is intended to be used primarily with FRR's memory module, + * which has memory groups and memory types, although any memory infrastructure + * can be used that has memory types or the memory types in this module can be + * set to NULL. The PCEPlib can be used stand-alone, in which case the simple + * internal memory type system will be used. + */ + +/* These memory function pointers are modeled after the memory functions + * in frr/lib/memory.h */ +typedef void *(*pceplib_malloc_func)(void *mem_type, size_t size); +typedef void *(*pceplib_calloc_func)(void *mem_type, size_t size); +typedef void *(*pceplib_realloc_func)(void *mem_type, void *ptr, size_t size); +typedef void *(*pceplib_strdup_func)(void *mem_type, const char *str); +typedef void (*pceplib_free_func)(void *mem_type, void *ptr); + +/* Either an internal pceplib_memory_type pointer + * or could be an FRR memory type pointer */ +extern void *PCEPLIB_INFRA; +extern void *PCEPLIB_MESSAGES; + +/* Internal PCEPlib memory type */ +struct pceplib_memory_type { + char memory_type_name[64]; + uint32_t total_bytes_allocated; + uint32_t num_allocates; + uint32_t total_bytes_freed; /* currently not used */ + uint32_t num_frees; +}; + +/* Initialize this module by passing in the 2 memory types used in the PCEPlib + * and by passing in the different memory allocation/free function pointers. + * Any of these parameters can be NULL, in which case an internal implementation + * will be used. + */ +bool pceplib_memory_initialize(void *pceplib_infra_mt, + void *pceplib_messages_mt, + pceplib_malloc_func mfunc, + pceplib_calloc_func cfunc, + pceplib_realloc_func rfunc, + pceplib_strdup_func sfunc, + pceplib_free_func ffunc); + +/* Reset the internal allocation/free counters. Used mainly for internal + * testing. */ +void pceplib_memory_reset(void); +void pceplib_memory_dump(void); + +/* Memory functions to be used throughout the PCEPlib. Internally, these + * functions will either used the function pointers passed in via + * pceplib_memory_initialize() or a simple internal implementation. The + * internal implementations just increment the internal memory type + * counters and call the C stdlib memory functions. + */ +void *pceplib_malloc(void *mem_type, size_t size); +void *pceplib_calloc(void *mem_type, size_t size); +void *pceplib_realloc(void *mem_type, void *ptr, size_t size); +void *pceplib_strdup(void *mem_type, const char *str); +void pceplib_free(void *mem_type, void *ptr); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ */ diff --git a/pceplib/pcep_utils_ordered_list.c b/pceplib/pcep_utils_ordered_list.c new file mode 100644 index 0000000000..f5c7f70240 --- /dev/null +++ b/pceplib/pcep_utils_ordered_list.c @@ -0,0 +1,322 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" + +/* Compare function that simply compares pointers. + * return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ +int pointer_compare_function(void *list_entry, void *new_entry) +{ + return (char *)new_entry - (char *)list_entry; +} + +ordered_list_handle *ordered_list_initialize(ordered_compare_function func_ptr) +{ + ordered_list_handle *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_handle)); + memset(handle, 0, sizeof(ordered_list_handle)); + handle->head = NULL; + handle->num_entries = 0; + handle->compare_function = func_ptr; + + return handle; +} + + +/* free all the ordered_list_node resources and the ordered_list_handle. + * it is assumed that the user is responsible fore freeing the data + * pointed to by the nodes. + */ +void ordered_list_destroy(ordered_list_handle *handle) +{ + if (handle == NULL) { + return; + } + + ordered_list_node *node = handle->head; + ordered_list_node *next; + + while (node != NULL) { + next = node->next_node; + pceplib_free(PCEPLIB_INFRA, node); + node = next; + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +ordered_list_node *ordered_list_add_node(ordered_list_handle *handle, + void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_add_node, the list has not been initialized", + __func__); + return NULL; + } + handle->num_entries++; + + ordered_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_node)); + memset(new_node, 0, sizeof(ordered_list_node)); + new_node->data = data; + new_node->next_node = NULL; + + /* check if its an empty list */ + if (handle->head == NULL) { + handle->head = new_node; + + return new_node; + } + + ordered_list_node *prev_node = handle->head; + ordered_list_node *node = prev_node; + int compare_result; + + while (node != NULL) { + compare_result = handle->compare_function(node->data, data); + if (compare_result < 0) { + /* insert the node */ + new_node->next_node = node; + if (handle->head == node) { + /* add it at the beginning of the list */ + handle->head = new_node; + } else { + prev_node->next_node = new_node; + } + + return new_node; + } + + /* keep searching with the next node in the list */ + prev_node = node; + node = node->next_node; + } + + /* at the end of the list, add it here */ + prev_node->next_node = new_node; + + return new_node; +} + + +ordered_list_node *ordered_list_find2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_find2, the list has not been initialized", + __func__); + return NULL; + } + + ordered_list_node *node = handle->head; + int compare_result; + + while (node != NULL) { + compare_result = compare_func(node->data, data); + if (compare_result == 0) { + return node; + } else { + node = node->next_node; + } + } + + return NULL; +} + + +ordered_list_node *ordered_list_find(ordered_list_handle *handle, void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_find, the list has not been initialized", + __func__); + return NULL; + } + + return ordered_list_find2(handle, data, handle->compare_function); +} + + +void *ordered_list_remove_first_node(ordered_list_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + handle->num_entries--; + + void *data = handle->head->data; + ordered_list_node *next_node = handle->head->next_node; + pceplib_free(PCEPLIB_INFRA, handle->head); + handle->head = next_node; + + return data; +} + + +void * +ordered_list_remove_first_node_equals2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node_equals2, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + ordered_list_node *prev_node = handle->head; + ordered_list_node *node = prev_node; + bool keep_walking = true; + void *return_data = NULL; + int compare_result; + + while (node != NULL && keep_walking) { + compare_result = compare_func(node->data, data); + if (compare_result == 0) { + return_data = node->data; + keep_walking = false; + handle->num_entries--; + + /* adjust the corresponding pointers accordingly */ + if (handle->head == node) { + /* its the first node in the list */ + handle->head = node->next_node; + } else { + prev_node->next_node = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + } else { + prev_node = node; + node = node->next_node; + } + } + + return return_data; +} + + +void *ordered_list_remove_first_node_equals(ordered_list_handle *handle, + void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node_equals, the list has not been initialized", + __func__); + return NULL; + } + + return ordered_list_remove_first_node_equals2(handle, data, + handle->compare_function); +} + + +void *ordered_list_remove_node(ordered_list_handle *handle, + ordered_list_node *prev_node, + ordered_list_node *node_toRemove) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_node, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *return_data = node_toRemove->data; + handle->num_entries--; + + if (node_toRemove == handle->head) { + handle->head = node_toRemove->next_node; + } else { + prev_node->next_node = node_toRemove->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node_toRemove); + + return return_data; +} + +void *ordered_list_remove_node2(ordered_list_handle *handle, + ordered_list_node *node_to_remove) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_node2, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + ordered_list_node *node = handle->head; + ordered_list_node *prev_node = handle->head; + + while (node != NULL) { + if (node == node_to_remove) { + return (ordered_list_remove_node(handle, prev_node, + node)); + } else { + prev_node = node; + node = node->next_node; + } + } + + return NULL; +} diff --git a/pceplib/pcep_utils_ordered_list.h b/pceplib/pcep_utils_ordered_list.h new file mode 100644 index 0000000000..ec132dc164 --- /dev/null +++ b/pceplib/pcep_utils_ordered_list.h @@ -0,0 +1,109 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPUTILSORDEREDLIST_H_ +#define INCLUDE_PCEPUTILSORDEREDLIST_H_ + +#include + +typedef struct ordered_list_node_ { + struct ordered_list_node_ *next_node; + void *data; + +} ordered_list_node; + +/* The implementation of this function will receive a pointer to the + * new data to be inserted and a pointer to the list_entry, and should + * return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ +typedef int (*ordered_compare_function)(void *list_entry, void *new_entry); + +/* Compare function that compares pointers */ +int pointer_compare_function(void *list_entry, void *new_entry); + +typedef struct ordered_list_handle_ { + ordered_list_node *head; + unsigned int num_entries; + ordered_compare_function compare_function; + +} ordered_list_handle; + +ordered_list_handle *ordered_list_initialize(ordered_compare_function func_ptr); +void ordered_list_destroy(ordered_list_handle *handle); + +/* Add a new ordered_list_node to the list, using the ordered_compare_function + * to determine where in the list to add it. The newly created ordered_list_node + * will be returned. + */ +ordered_list_node *ordered_list_add_node(ordered_list_handle *handle, + void *data); + +/* Find an entry in the ordered_list using the ordered_compare_function to + * compare the data passed in. + * Return the node if found, NULL otherwise. + */ +ordered_list_node *ordered_list_find(ordered_list_handle *handle, void *data); + +/* The same as the previous function, but with a specific orderedComparefunction + */ +ordered_list_node *ordered_list_find2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func); + +/* Remove the first entry in the list and return the data it points to. + * Will return NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_first_node(ordered_list_handle *handle); + +/* Remove the first entry in the list that has the same data, using the + * ordered_compare_function, and return the data it points to. + * Will return NULL if the handle is NULL or if the list is empty or + * if no entry is found that equals data. + */ +void *ordered_list_remove_first_node_equals(ordered_list_handle *handle, + void *data); + +/* The same as the previous function, but with a specific orderedComparefunction + */ +void *ordered_list_remove_first_node_equals2(ordered_list_handle *handle, + void *data, + ordered_compare_function func_ptr); + +/* Remove the node "node_to_remove" and adjust the "prev_node" pointers + * accordingly, returning the data pointed to by "node_to_remove". Will return + * NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_node(ordered_list_handle *handle, + ordered_list_node *prev_node, + ordered_list_node *node_to_remove); + +/* Remove the node "node_to_remove" by searching for it in the entire list, + * returning the data pointed to by "node_to_remove". + * Will return NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_node2(ordered_list_handle *handle, + ordered_list_node *node_to_remove); + +#endif /* INCLUDE_PCEPUTILSORDEREDLIST_H_ */ diff --git a/pceplib/pcep_utils_queue.c b/pceplib/pcep_utils_queue.c new file mode 100644 index 0000000000..e8c3f2be0e --- /dev/null +++ b/pceplib/pcep_utils_queue.c @@ -0,0 +1,150 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_queue.h" + +queue_handle *queue_initialize() +{ + /* Set the max_entries to 0 to disable it */ + return queue_initialize_with_size(0); +} + + +queue_handle *queue_initialize_with_size(unsigned int max_entries) +{ + queue_handle *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(queue_handle)); + memset(handle, 0, sizeof(queue_handle)); + handle->max_entries = max_entries; + + return handle; +} + + +void queue_destroy(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_destroy, the queue has not been initialized", + __func__); + return; + } + + while (queue_dequeue(handle) != NULL) { + } + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void queue_destroy_with_data(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_destroy_with_data, the queue has not been initialized", + __func__); + return; + } + + void *data = queue_dequeue(handle); + while (data != NULL) { + pceplib_free(PCEPLIB_INFRA, data); + data = queue_dequeue(handle); + } + pceplib_free(PCEPLIB_INFRA, handle); +} + + +queue_node *queue_enqueue(queue_handle *handle, void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_enqueue, the queue has not been initialized", + __func__); + return NULL; + } + + if (handle->max_entries > 0 + && handle->num_entries >= handle->max_entries) { + pcep_log( + LOG_DEBUG, + "%s: queue_enqueue, cannot enqueue: max entries hit [%u]", + handle->num_entries); + return NULL; + } + + queue_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(queue_node)); + memset(new_node, 0, sizeof(queue_node)); + new_node->data = data; + new_node->next_node = NULL; + + (handle->num_entries)++; + if (handle->head == NULL) { + /* its the first entry in the queue */ + handle->head = handle->tail = new_node; + } else { + handle->tail->next_node = new_node; + handle->tail = new_node; + } + + return new_node; +} + + +void *queue_dequeue(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_dequeue, the queue has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *node_data = handle->head->data; + queue_node *node = handle->head; + (handle->num_entries)--; + if (handle->head == handle->tail) { + /* its the last entry in the queue */ + handle->head = handle->tail = NULL; + } else { + handle->head = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + + return node_data; +} diff --git a/pceplib/pcep_utils_queue.h b/pceplib/pcep_utils_queue.h new file mode 100644 index 0000000000..838067640e --- /dev/null +++ b/pceplib/pcep_utils_queue.h @@ -0,0 +1,49 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPUTILSQUEUE_H_ +#define INCLUDE_PCEPUTILSQUEUE_H_ + +typedef struct queue_node_ { + struct queue_node_ *next_node; + void *data; + +} queue_node; + +typedef struct queue_handle_ { + queue_node *head; + queue_node *tail; + unsigned int num_entries; + /* Set to 0 to disable */ + unsigned int max_entries; + +} queue_handle; + +queue_handle *queue_initialize(void); +queue_handle *queue_initialize_with_size(unsigned int max_entries); +void queue_destroy(queue_handle *handle); +void queue_destroy_with_data(queue_handle *handle); +queue_node *queue_enqueue(queue_handle *handle, void *data); +void *queue_dequeue(queue_handle *handle); + +#endif /* INCLUDE_PCEPUTILSQUEUE_H_ */ diff --git a/pceplib/subdir.am b/pceplib/subdir.am new file mode 100644 index 0000000000..eee2ec28c7 --- /dev/null +++ b/pceplib/subdir.am @@ -0,0 +1,62 @@ +if PATHD_PCEP + +noinst_LTLIBRARIES = pceplib/libpcep_pcc.la pceplib/libsocket_comm_mock.la +pceplib_libpcep_pcc_la_CFLAGS = -fPIC +pceplib_libpcep_pcc_la_SOURCES = pceplib/pcep_msg_messages.c \ + pceplib/pcep_msg_objects.c \ + pceplib/pcep_msg_tlvs.c \ + pceplib/pcep_msg_tools.c \ + pceplib/pcep_msg_messages_encoding.c \ + pceplib/pcep_msg_objects_encoding.c \ + pceplib/pcep_msg_tlvs_encoding.c \ + pceplib/pcep_msg_object_error_types.c \ + pceplib/pcep_pcc_api.c \ + pceplib/pcep_session_logic.c \ + pceplib/pcep_session_logic_loop.c \ + pceplib/pcep_session_logic_states.c \ + pceplib/pcep_session_logic_counters.c \ + pceplib/pcep_socket_comm_loop.c \ + pceplib/pcep_socket_comm.c \ + pceplib/pcep_timers_event_loop.c \ + pceplib/pcep_timers.c \ + pceplib/pcep_utils_counters.c \ + pceplib/pcep_utils_double_linked_list.c \ + pceplib/pcep_utils_logging.c \ + pceplib/pcep_utils_memory.c \ + pceplib/pcep_utils_ordered_list.c \ + pceplib/pcep_utils_queue.c + +if PATHD_PCEP_TEST +# SocketComm Mock library used for Unit Testing +pceplib_libsocket_comm_mock_la_SOURCES = pceplib/pcep_socket_comm_mock.c +endif + +noinst_HEADERS += pceplib/pcep.h \ + pceplib/pcep_msg_encoding.h \ + pceplib/pcep_msg_messages.h \ + pceplib/pcep_msg_object_error_types.h \ + pceplib/pcep_msg_objects.h \ + pceplib/pcep_msg_tlvs.h \ + pceplib/pcep_msg_tools.h \ + pceplib/pcep_pcc_api.h \ + pceplib/pcep_session_logic.h \ + pceplib/pcep_session_logic_internals.h \ + pceplib/pcep_socket_comm.h \ + pceplib/pcep_socket_comm_internals.h \ + pceplib/pcep_socket_comm_loop.h \ + pceplib/pcep_socket_comm_mock.h \ + pceplib/pcep_timer_internals.h \ + pceplib/pcep_timers.h \ + pceplib/pcep_timers_event_loop.h \ + pceplib/pcep_utils_counters.h \ + pceplib/pcep_utils_double_linked_list.h \ + pceplib/pcep_utils_logging.h \ + pceplib/pcep_utils_memory.h \ + pceplib/pcep_utils_ordered_list.h \ + pceplib/pcep_utils_queue.h + +noinst_PROGRAMS += pceplib/pcep_pcc +pceplib_pcep_pcc_SOURCES = pceplib/pcep_pcc.c +pceplib_pcep_pcc_LDADD = pceplib/libpcep_pcc.la lib/libfrr.la -lpthread + +endif diff --git a/pceplib/test/pcep_msg_messages_test.c b/pceplib/test/pcep_msg_messages_test.c new file mode 100644 index 0000000000..10b678bcec --- /dev/null +++ b/pceplib/test/pcep_msg_messages_test.c @@ -0,0 +1,498 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_messages_test.h" + +/* + * Notice: + * All of these message Unit Tests encode the created messages by explicitly + * calling pcep_encode_message() thus testing the message creation and the + * message encoding. + */ + +static struct pcep_versioning *versioning = NULL; + +int pcep_messages_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_messages_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_messages_test_setup() +{ + versioning = create_default_pcep_versioning(); +} + +void pcep_messages_test_teardown() +{ + destroy_pcep_versioning(versioning); +} + +void test_pcep_msg_create_open() +{ + uint8_t keepalive = 30; + uint8_t deadtimer = 60; + uint8_t sid = 255; + + struct pcep_message *message = + pcep_msg_create_open(keepalive, deadtimer, sid); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_OPEN, + PCEP_OBJ_TYPE_OPEN)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_open *open_obj = + (struct pcep_object_open *)message->obj_list->head->data; + CU_ASSERT_EQUAL(open_obj->header.object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(open_obj->header.object_type, PCEP_OBJ_TYPE_OPEN); + + CU_ASSERT_EQUAL(open_obj->open_deadtimer, deadtimer); + CU_ASSERT_EQUAL(open_obj->open_keepalive, keepalive); + CU_ASSERT_EQUAL(open_obj->open_sid, sid); + CU_ASSERT_EQUAL(open_obj->open_version, PCEP_OBJECT_OPEN_VERSION); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_request() +{ + /* First test with NULL objects */ + struct pcep_message *message = + pcep_msg_create_request(NULL, NULL, NULL); + CU_ASSERT_PTR_NULL(message); + + /* Test IPv4 */ + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct in_addr src_addr, dst_addr; + struct pcep_object_endpoints_ipv4 *ipv4_obj = + pcep_obj_create_endpoint_ipv4(&src_addr, &dst_addr); + message = pcep_msg_create_request(rp_obj, ipv4_obj, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv4_obj->header)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + /* Test IPv6 */ + rp_obj = pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct in6_addr src_addr_ipv6, dst_addr_ipv6; + struct pcep_object_endpoints_ipv6 *ipv6_obj = + pcep_obj_create_endpoint_ipv6(&src_addr_ipv6, &dst_addr_ipv6); + message = pcep_msg_create_request_ipv6(rp_obj, ipv6_obj, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv6_obj->header)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + /* The objects get deleted with the message, so they need to be created + * again */ + rp_obj = pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + ipv4_obj = pcep_obj_create_endpoint_ipv4(&src_addr, &dst_addr); + struct pcep_object_bandwidth *bandwidth_obj = + pcep_obj_create_bandwidth(4.2); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, bandwidth_obj); + message = pcep_msg_create_request(rp_obj, ipv4_obj, obj_list); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv4_obj->header) + + pcep_object_get_length_by_hdr( + &bandwidth_obj->header)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_request_svec() +{ +} + + +void test_pcep_msg_create_reply_nopath() +{ + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct pcep_object_nopath *nopath_obj = pcep_obj_create_nopath( + false, false, PCEP_NOPATH_TLV_ERR_NO_TLV); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, nopath_obj); + + struct pcep_message *message = pcep_msg_create_reply(rp_obj, obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + (MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&nopath_obj->header))); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_reply() +{ + /* First test with NULL ero and rp objects */ + struct pcep_message *message = pcep_msg_create_reply(NULL, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 0); + CU_ASSERT_EQUAL(message->encoded_message_length, MESSAGE_HEADER_LENGTH); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + double_linked_list *ero_subobj_list = dll_initialize(); + struct pcep_object_ro_subobj *ero_subobj = + (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_32label(true, 1, 10); + dll_append(ero_subobj_list, ero_subobj); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + double_linked_list *object_list = dll_initialize(); + dll_append(object_list, ero); + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + message = pcep_msg_create_reply(rp_obj, object_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + OBJECT_HEADER_LENGTH + + OBJECT_RO_SUBOBJ_HEADER_LENGTH + + 6 /* size of the 32label */); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_close() +{ + uint8_t reason = PCEP_CLOSE_REASON_UNREC_MSG; + + struct pcep_message *message = pcep_msg_create_close(reason); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_CLOSE, + PCEP_OBJ_TYPE_CLOSE)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_CLOSE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_close *close_obj = + (struct pcep_object_close *)message->obj_list->head->data; + CU_ASSERT_EQUAL(close_obj->header.object_class, PCEP_OBJ_CLASS_CLOSE); + CU_ASSERT_EQUAL(close_obj->header.object_type, PCEP_OBJ_TYPE_CLOSE); + CU_ASSERT_EQUAL(close_obj->reason, reason); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_error() +{ + uint8_t error_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + uint8_t error_value = PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT; + + struct pcep_message *message = + pcep_msg_create_error(error_type, error_value); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_ERROR, + PCEP_OBJ_TYPE_ERROR)); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_ERROR); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_error *error_obj = + (struct pcep_object_error *)message->obj_list->head->data; + CU_ASSERT_EQUAL(error_obj->header.object_class, PCEP_OBJ_CLASS_ERROR); + CU_ASSERT_EQUAL(error_obj->header.object_type, PCEP_OBJ_TYPE_ERROR); + + CU_ASSERT_EQUAL(error_obj->error_type, error_type); + CU_ASSERT_EQUAL(error_obj->error_value, error_value); + pcep_msg_free_message(message); +} + + +void test_pcep_msg_create_keepalive() +{ + struct pcep_message *message = pcep_msg_create_keepalive(); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 0); + CU_ASSERT_EQUAL(message->encoded_message_length, MESSAGE_HEADER_LENGTH); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_KEEPALIVE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_report() +{ + double_linked_list *obj_list = dll_initialize(); + + /* Should return NULL if obj_list is empty */ + struct pcep_message *message = pcep_msg_create_report(NULL); + CU_ASSERT_PTR_NULL(message); + + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(obj_list, lsp); + message = pcep_msg_create_report(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + lsp->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_REPORT); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_update() +{ + double_linked_list *obj_list = dll_initialize(); + double_linked_list *ero_subobj_list = dll_initialize(); + + struct pcep_message *message = pcep_msg_create_update(NULL); + CU_ASSERT_PTR_NULL(message); + + /* Should return NULL if obj_list is empty */ + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NULL(message); + + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + /* Should return NULL if obj_list does not have 3 entries */ + dll_append(obj_list, srp); + dll_append(obj_list, lsp); + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NULL(message); + + dll_append(obj_list, ero); + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + srp->header.encoded_object_length + + lsp->header.encoded_object_length + + ero->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_initiate() +{ + double_linked_list *obj_list = dll_initialize(); + double_linked_list *ero_subobj_list = dll_initialize(); + + /* Should return NULL if obj_list is empty */ + struct pcep_message *message = pcep_msg_create_initiate(NULL); + CU_ASSERT_PTR_NULL(message); + + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + /* Should return NULL if obj_list does not have 2 entries */ + dll_append(obj_list, srp); + message = pcep_msg_create_initiate(obj_list); + CU_ASSERT_PTR_NULL(message); + + dll_append(obj_list, lsp); + dll_append(obj_list, ero); + message = pcep_msg_create_initiate(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + srp->header.encoded_object_length + + lsp->header.encoded_object_length + + ero->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_notify(void) +{ + struct pcep_object_notify *notify_obj = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + + /* Should return NULL if the notify obj is empty */ + struct pcep_message *message = pcep_msg_create_notify(NULL, NULL); + CU_ASSERT_PTR_NULL(message); + + message = pcep_msg_create_notify(notify_obj, NULL); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + notify_obj->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCNOTF); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); + + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, rp_obj); + notify_obj = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + + message = pcep_msg_create_notify(notify_obj, obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + notify_obj->header.encoded_object_length + + rp_obj->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCNOTF); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} diff --git a/pceplib/test/pcep_msg_messages_test.h b/pceplib/test/pcep_msg_messages_test.h new file mode 100644 index 0000000000..a3295c74eb --- /dev/null +++ b/pceplib/test/pcep_msg_messages_test.h @@ -0,0 +1,48 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_MSG_TEST_H_ +#define PCEP_MSG_MSG_TEST_H_ + +/* functions to be tested from pcep-messages.c */ +int pcep_messages_test_suite_setup(void); +int pcep_messages_test_suite_teardown(void); +void pcep_messages_test_setup(void); +void pcep_messages_test_teardown(void); +void test_pcep_msg_create_open(void); +void test_pcep_msg_create_request(void); +void test_pcep_msg_create_request_svec(void); +void test_pcep_msg_create_reply_nopath(void); +void test_pcep_msg_create_reply(void); +void test_pcep_msg_create_close(void); +void test_pcep_msg_create_error(void); +void test_pcep_msg_create_keepalive(void); +void test_pcep_msg_create_report(void); +void test_pcep_msg_create_update(void); +void test_pcep_msg_create_initiate(void); +void test_pcep_msg_create_notify(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_msg_messages_tests.c b/pceplib/test/pcep_msg_messages_tests.c new file mode 100644 index 0000000000..bd85a16530 --- /dev/null +++ b/pceplib/test/pcep_msg_messages_tests.c @@ -0,0 +1,256 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_msg_messages_test.h" +#include "pcep_msg_tools_test.h" +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_object_error_types_test.h" +#include "pcep_msg_tlvs_test.h" +#include "pcep_msg_objects_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + CU_pSuite messages_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Messages Test Suite", pcep_messages_test_suite_setup, + pcep_messages_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_messages_test_setup, pcep_messages_test_teardown); + CU_add_test(messages_suite, "test_pcep_msg_create_open", + test_pcep_msg_create_open); + CU_add_test(messages_suite, "test_pcep_msg_create_request", + test_pcep_msg_create_request); + CU_add_test(messages_suite, "test_pcep_msg_create_request_svec", + test_pcep_msg_create_request_svec); + CU_add_test(messages_suite, "test_pcep_msg_create_reply_nopath", + test_pcep_msg_create_reply_nopath); + CU_add_test(messages_suite, "test_pcep_msg_create_reply", + test_pcep_msg_create_reply); + CU_add_test(messages_suite, "test_pcep_msg_create_close", + test_pcep_msg_create_close); + CU_add_test(messages_suite, "test_pcep_msg_create_error", + test_pcep_msg_create_error); + CU_add_test(messages_suite, "test_pcep_msg_create_keepalive", + test_pcep_msg_create_keepalive); + CU_add_test(messages_suite, "test_pcep_msg_create_report", + test_pcep_msg_create_report); + CU_add_test(messages_suite, "test_pcep_msg_create_update", + test_pcep_msg_create_update); + CU_add_test(messages_suite, "test_pcep_msg_create_initiate", + test_pcep_msg_create_initiate); + CU_add_test(messages_suite, "test_pcep_msg_create_notify", + test_pcep_msg_create_notify); + + CU_pSuite tlvs_suite = CU_add_suite_with_setup_and_teardown( + "PCEP TLVs Test Suite", pcep_tlvs_test_suite_setup, + pcep_tlvs_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_tlvs_test_setup, pcep_tlvs_test_teardown); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_stateful_pce_capability", + test_pcep_tlv_create_stateful_pce_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_speaker_entity_id", + test_pcep_tlv_create_speaker_entity_id); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_lsp_db_version", + test_pcep_tlv_create_lsp_db_version); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_path_setup_type", + test_pcep_tlv_create_path_setup_type); + CU_add_test(tlvs_suite, + "test_pcep_tlv_create_path_setup_type_capability", + test_pcep_tlv_create_path_setup_type_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_sr_pce_capability", + test_pcep_tlv_create_sr_pce_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_symbolic_path_name", + test_pcep_tlv_create_symbolic_path_name); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_ipv4_lsp_identifiers", + test_pcep_tlv_create_ipv4_lsp_identifiers); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_ipv6_lsp_identifiers", + test_pcep_tlv_create_ipv6_lsp_identifiers); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_id_ipv4", + test_pcep_tlv_create_srpag_pol_id_ipv4); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_id_ipv6", + test_pcep_tlv_create_srpag_pol_id_ipv6); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_name", + test_pcep_tlv_create_srpag_pol_name); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_cp_id", + test_pcep_tlv_create_srpag_cp_id); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_cp_pref", + test_pcep_tlv_create_srpag_cp_pref); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_lsp_error_code", + test_pcep_tlv_create_lsp_error_code); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_rsvp_ipv4_error_spec", + test_pcep_tlv_create_rsvp_ipv4_error_spec); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_rsvp_ipv6_error_spec", + test_pcep_tlv_create_rsvp_ipv6_error_spec); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_nopath_vector", + test_pcep_tlv_create_nopath_vector); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_arbitrary", + test_pcep_tlv_create_arbitrary); + + CU_pSuite objects_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Objects Test Suite", pcep_objects_test_suite_setup, + pcep_objects_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_objects_test_setup, pcep_objects_test_teardown); + CU_add_test(objects_suite, "test_pcep_obj_create_open", + test_pcep_obj_create_open); + CU_add_test(objects_suite, "test_pcep_obj_create_open", + test_pcep_obj_create_open_with_tlvs); + CU_add_test(objects_suite, "test_pcep_obj_create_rp", + test_pcep_obj_create_rp); + CU_add_test(objects_suite, "test_pcep_obj_create_nopath", + test_pcep_obj_create_nopath); + CU_add_test(objects_suite, "test_pcep_obj_create_enpoint_ipv4", + test_pcep_obj_create_endpoint_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_enpoint_ipv6", + test_pcep_obj_create_endpoint_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_association_ipv4", + test_pcep_obj_create_association_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_association_ipv6", + test_pcep_obj_create_association_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_bandwidth", + test_pcep_obj_create_bandwidth); + CU_add_test(objects_suite, "test_pcep_obj_create_metric", + test_pcep_obj_create_metric); + CU_add_test(objects_suite, "test_pcep_obj_create_lspa", + test_pcep_obj_create_lspa); + CU_add_test(objects_suite, "test_pcep_obj_create_svec", + test_pcep_obj_create_svec); + CU_add_test(objects_suite, "test_pcep_obj_create_error", + test_pcep_obj_create_error); + CU_add_test(objects_suite, "test_pcep_obj_create_close", + test_pcep_obj_create_close); + CU_add_test(objects_suite, "test_pcep_obj_create_srp", + test_pcep_obj_create_srp); + CU_add_test(objects_suite, "test_pcep_obj_create_lsp", + test_pcep_obj_create_lsp); + CU_add_test(objects_suite, "test_pcep_obj_create_vendor_info", + test_pcep_obj_create_vendor_info); + + CU_add_test(objects_suite, "test_pcep_obj_create_ero", + test_pcep_obj_create_ero); + CU_add_test(objects_suite, "test_pcep_obj_create_rro", + test_pcep_obj_create_rro); + CU_add_test(objects_suite, "test_pcep_obj_create_iro", + test_pcep_obj_create_iro); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_ipv4", + test_pcep_obj_create_ro_subobj_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_ipv6", + test_pcep_obj_create_ro_subobj_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_unnum", + test_pcep_obj_create_ro_subobj_unnum); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_32label", + test_pcep_obj_create_ro_subobj_32label); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_asn", + test_pcep_obj_create_ro_subobj_asn); + + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_nonai", + test_pcep_obj_create_ro_subobj_sr_nonai); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_ipv4_node", + test_pcep_obj_create_ro_subobj_sr_ipv4_node); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_ipv6_node", + test_pcep_obj_create_ro_subobj_sr_ipv6_node); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_ipv4_adj", + test_pcep_obj_create_ro_subobj_sr_ipv4_adj); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_ipv6_adj", + test_pcep_obj_create_ro_subobj_sr_ipv6_adj); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj", + test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj", + test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj); + + CU_pSuite tools_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Tools Test Suite", pcep_tools_test_suite_setup, + pcep_tools_test_suite_teardown, pcep_tools_test_setup, + pcep_tools_test_teardown); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate", + test_pcep_msg_read_pcep_initiate); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate2", + test_pcep_msg_read_pcep_initiate2); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_update", + test_pcep_msg_read_pcep_update); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open", + test_pcep_msg_read_pcep_open); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open_initiate", + test_pcep_msg_read_pcep_open_initiate); + CU_add_test(tools_suite, "test_validate_message_header", + test_validate_message_header); + CU_add_test(tools_suite, "test_validate_message_objects", + test_validate_message_objects); + CU_add_test(tools_suite, "test_validate_message_objects_invalid", + test_validate_message_objects_invalid); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open_cisco_pce", + test_pcep_msg_read_pcep_open_cisco_pce); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_update_cisco_pce", + test_pcep_msg_read_pcep_update_cisco_pce); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_report_cisco_pcc", + test_pcep_msg_read_pcep_report_cisco_pcc); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate_cisco_pcc", + test_pcep_msg_read_pcep_initiate_cisco_pcc); + + CU_pSuite obj_errors_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Object Error Types Test Suite", + pcep_object_error_types_test_suite_setup, + pcep_object_error_types_test_suite_teardown, + pcep_object_error_types_test_setup, + pcep_object_error_types_test_teardown); + CU_add_test(obj_errors_suite, "test_get_error_type_str", + test_get_error_type_str); + CU_add_test(obj_errors_suite, "test_get_error_value_str", + test_get_error_value_str); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_msg_object_error_types_test.c b/pceplib/test/pcep_msg_object_error_types_test.c new file mode 100644 index 0000000000..7275eaf098 --- /dev/null +++ b/pceplib/test/pcep_msg_object_error_types_test.c @@ -0,0 +1,84 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_object_error_types_test.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +int pcep_object_error_types_test_suite_setup(void) +{ + pceplib_memory_reset(); + set_logging_level(LOG_DEBUG); + return 0; +} + +int pcep_object_error_types_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_object_error_types_test_setup(void) +{ +} + +void pcep_object_error_types_test_teardown(void) +{ +} + +void test_get_error_type_str() +{ + const char *error_type_str; + int i = 0; + for (; i < MAX_ERROR_TYPE; i++) { + error_type_str = get_error_type_str(i); + CU_ASSERT_PTR_NOT_NULL(error_type_str); + } + + CU_ASSERT_PTR_NULL(get_error_type_str(-1)); + CU_ASSERT_PTR_NULL(get_error_type_str(MAX_ERROR_TYPE)); +} + +void test_get_error_value_str() +{ + const char *error_value_str; + int i = 0, j = 0; + + for (; i < MAX_ERROR_TYPE; i++) { + for (; j < MAX_ERROR_VALUE; j++) { + error_value_str = get_error_value_str(i, j); + CU_ASSERT_PTR_NOT_NULL(error_value_str); + } + } + + CU_ASSERT_PTR_NULL(get_error_value_str(-1, 0)); + CU_ASSERT_PTR_NULL(get_error_value_str(MAX_ERROR_TYPE, 0)); + CU_ASSERT_PTR_NULL(get_error_value_str(1, -1)); + CU_ASSERT_PTR_NULL(get_error_value_str(1, MAX_ERROR_VALUE)); +} diff --git a/pceplib/test/pcep_msg_object_error_types_test.h b/pceplib/test/pcep_msg_object_error_types_test.h new file mode 100644 index 0000000000..863517d1e3 --- /dev/null +++ b/pceplib/test/pcep_msg_object_error_types_test.h @@ -0,0 +1,37 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_OBJECT_ERROR_TYPES_TEST_ +#define PCEP_MSG_OBJECT_ERROR_TYPES_TEST_ + +int pcep_object_error_types_test_suite_setup(void); +int pcep_object_error_types_test_suite_teardown(void); +void pcep_object_error_types_test_setup(void); +void pcep_object_error_types_test_teardown(void); +void test_get_error_type_str(void); +void test_get_error_value_str(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_msg_objects_test.c b/pceplib/test/pcep_msg_objects_test.c new file mode 100644 index 0000000000..a4c069945c --- /dev/null +++ b/pceplib/test/pcep_msg_objects_test.c @@ -0,0 +1,1289 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_objects_test.h" + +/* + * Notice: + * All of these object Unit Tests encode the created objects by explicitly + * calling pcep_encode_object() thus testing the object creation and the object + * encoding. All APIs expect IPs to be in network byte order. + */ + +static struct pcep_versioning *versioning = NULL; +static uint8_t object_buf[2000]; + +void reset_objects_buffer(void); + +int pcep_objects_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_objects_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void reset_objects_buffer() +{ + memset(object_buf, 0, 2000); +} + +void pcep_objects_test_setup() +{ + versioning = create_default_pcep_versioning(); + reset_objects_buffer(); +} + +void pcep_objects_test_teardown() +{ + destroy_pcep_versioning(versioning); +} + +/* Internal util verification function */ +static void verify_pcep_obj_header2(uint8_t obj_class, uint8_t obj_type, + uint16_t obj_length, const uint8_t *obj_buf) +{ + /* Object Header + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Object-Class | OT |Res|P|I| Object Length (bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + /* Not using CU_ASSERT_EQUAL here, so that in case of failure, + * we can provide more info in the error message. */ + if (obj_buf[0] != obj_class) { + fprintf(stderr, + "Test failure obj_class expected [%d] found [%d]\n", + obj_class, obj_buf[0]); + CU_FAIL("Object Header Class"); + } + + uint8_t found8 = (obj_buf[1] >> 4) & 0x0f; + if (obj_type != found8) { + fprintf(stderr, + "Test failure obj_class [%d] obj_type expected [%d] found [%d]\n", + obj_class, obj_type, found8); + CU_FAIL("Object Header Type"); + } + + uint8_t exp8 = 0; + found8 = obj_buf[1] & 0x0f; + if (exp8 != found8) { + fprintf(stderr, + "Test failure obj_class [%d] flags expected [%d] found [%d]\n", + obj_class, exp8, found8); + CU_FAIL("Object Header Flags"); + } + + uint16_t found16 = ntohs(*((uint16_t *)(obj_buf + 2))); + if (obj_length != found16) { + fprintf(stderr, + "Test failure obj_class [%d] obj_length expected [%d] found [%d]\n", + obj_class, obj_length, found16); + CU_FAIL("Object Header Length"); + } +} + +/* Internal util verification function */ +static void verify_pcep_obj_header(uint8_t obj_class, uint8_t obj_type, + struct pcep_object_header *obj_hdr) +{ + verify_pcep_obj_header2(obj_class, obj_type, + pcep_object_get_length_by_hdr(obj_hdr), + obj_hdr->encoded_object); +} + +void test_pcep_obj_create_open() +{ + uint8_t deadtimer = 60; + uint8_t keepalive = 30; + uint8_t sid = 1; + + struct pcep_object_open *open = + pcep_obj_create_open(keepalive, deadtimer, sid, NULL); + + CU_ASSERT_PTR_NOT_NULL(open); + pcep_encode_object(&open->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN, + &open->header); + + CU_ASSERT_EQUAL(open->header.encoded_object[4], + (PCEP_OBJECT_OPEN_VERSION << 5) & 0xe0); + CU_ASSERT_EQUAL(open->header.encoded_object[4] & 0x1f, 0); + CU_ASSERT_EQUAL(open->header.encoded_object[5], keepalive); + CU_ASSERT_EQUAL(open->header.encoded_object[6], deadtimer); + CU_ASSERT_EQUAL(open->header.encoded_object[7], sid); + + pcep_obj_free_object((struct pcep_object_header *)open); +} + +void test_pcep_obj_create_open_with_tlvs() +{ + uint8_t deadtimer = 60; + uint8_t keepalive = 30; + uint8_t sid = 1; + double_linked_list *tlv_list = dll_initialize(); + + struct pcep_object_tlv_stateful_pce_capability *tlv = + pcep_tlv_create_stateful_pce_capability(true, true, true, true, + true, true); + dll_append(tlv_list, tlv); + struct pcep_object_open *open = + pcep_obj_create_open(keepalive, deadtimer, sid, tlv_list); + + CU_ASSERT_PTR_NOT_NULL(open); + pcep_encode_object(&open->header, versioning, object_buf); + verify_pcep_obj_header2(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN, + pcep_object_get_length_by_hdr(&open->header) + + sizeof(uint32_t) * 2, + open->header.encoded_object); + CU_ASSERT_PTR_NOT_NULL(open->header.tlv_list); + CU_ASSERT_EQUAL(open->header.tlv_list->num_entries, 1); + + CU_ASSERT_EQUAL(open->header.encoded_object[4], + (PCEP_OBJECT_OPEN_VERSION << 5) & 0xe0); + CU_ASSERT_EQUAL(open->header.encoded_object[4] & 0x1f, 0); + CU_ASSERT_EQUAL(open->header.encoded_object[5], keepalive); + CU_ASSERT_EQUAL(open->header.encoded_object[6], deadtimer); + CU_ASSERT_EQUAL(open->header.encoded_object[7], sid); + + pcep_obj_free_object((struct pcep_object_header *)open); +} + +void test_pcep_obj_create_rp() +{ + uint32_t reqid = 15; + uint8_t invalid_priority = 100; + uint8_t priority = 7; + + struct pcep_object_rp *rp = pcep_obj_create_rp( + invalid_priority, true, false, false, true, reqid, NULL); + CU_ASSERT_PTR_NULL(rp); + + rp = pcep_obj_create_rp(priority, true, false, false, true, reqid, + NULL); + CU_ASSERT_PTR_NOT_NULL(rp); + pcep_encode_object(&rp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP, + &rp->header); + + CU_ASSERT_EQUAL(rp->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(rp->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(rp->header.encoded_object[6], 0); + CU_ASSERT_EQUAL((rp->header.encoded_object[7] & 0x07), priority); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & OBJECT_RP_FLAG_R); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & OBJECT_RP_FLAG_OF); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & ~OBJECT_RP_FLAG_B); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & ~OBJECT_RP_FLAG_O); + CU_ASSERT_EQUAL(*((uint32_t *)(rp->header.encoded_object + 8)), + htonl(reqid)); + + pcep_obj_free_object((struct pcep_object_header *)rp); +} + +void test_pcep_obj_create_nopath() +{ + uint8_t ni = 8; + uint32_t errorcode = 42; + + struct pcep_object_nopath *nopath = + pcep_obj_create_nopath(ni, true, errorcode); + + CU_ASSERT_PTR_NOT_NULL(nopath); + pcep_encode_object(&nopath->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH, + &nopath->header); + + CU_ASSERT_EQUAL(nopath->header.encoded_object[4], ni); + CU_ASSERT_TRUE(nopath->header.encoded_object[5] & OBJECT_NOPATH_FLAG_C); + CU_ASSERT_EQUAL(nopath->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(nopath->header.encoded_object[7], 0); + + /* Verify the TLV */ + CU_ASSERT_PTR_NOT_NULL(nopath->header.tlv_list); + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *) + nopath->header.tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(tlv->header.type, 1); + CU_ASSERT_EQUAL(tlv->error_code, errorcode); + + CU_ASSERT_EQUAL(*((uint16_t *)(nopath->header.encoded_object + 8)), + htons(PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR)); + CU_ASSERT_EQUAL(*((uint16_t *)(nopath->header.encoded_object + 10)), + htons(4)); + CU_ASSERT_EQUAL(*((uint32_t *)(nopath->header.encoded_object + 12)), + htonl(errorcode)); + + pcep_obj_free_object((struct pcep_object_header *)nopath); +} +void test_pcep_obj_create_association_ipv4() +{ + + uint16_t all_assoc_groups = 0xffff; + struct in_addr src; + inet_pton(AF_INET, "192.168.1.2", &src); + + struct pcep_object_association_ipv4 *assoc = + pcep_obj_create_association_ipv4( + false, PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE, + all_assoc_groups, src); + CU_ASSERT_PTR_NOT_NULL(assoc); + CU_ASSERT_EQUAL(assoc->association_type, + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE); + CU_ASSERT_EQUAL(assoc->association_id, all_assoc_groups); + CU_ASSERT_EQUAL(assoc->header.object_class, PCEP_OBJ_CLASS_ASSOCIATION); + CU_ASSERT_EQUAL(assoc->header.object_type, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4); + CU_ASSERT_EQUAL(assoc->src.s_addr, src.s_addr); + + pcep_obj_free_object((struct pcep_object_header *)assoc); +} + +void test_pcep_obj_create_association_ipv6() +{ + uint32_t all_assoc_groups = 0xffff; + struct in6_addr src; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src); + + struct pcep_object_association_ipv6 *assoc = + pcep_obj_create_association_ipv6( + false, PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE, + all_assoc_groups, src); + CU_ASSERT_PTR_NOT_NULL(assoc); + CU_ASSERT_EQUAL(assoc->association_type, + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE); + CU_ASSERT_EQUAL(assoc->association_id, all_assoc_groups); + CU_ASSERT_EQUAL(assoc->header.object_class, PCEP_OBJ_CLASS_ASSOCIATION); + CU_ASSERT_EQUAL(assoc->header.object_type, + PCEP_OBJ_TYPE_ASSOCIATION_IPV6); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[0], + (src.__in6_u.__u6_addr32[0])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[1], + (src.__in6_u.__u6_addr32[1])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[2], + (src.__in6_u.__u6_addr32[2])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[3], + (src.__in6_u.__u6_addr32[3])); + + pcep_obj_free_object((struct pcep_object_header *)assoc); +} + +void test_pcep_obj_create_endpoint_ipv4() +{ + struct in_addr src_ipv4, dst_ipv4; + inet_pton(AF_INET, "192.168.1.2", &src_ipv4); + inet_pton(AF_INET, "172.168.1.2", &dst_ipv4); + + struct pcep_object_endpoints_ipv4 *ipv4 = + pcep_obj_create_endpoint_ipv4(NULL, NULL); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(&src_ipv4, NULL); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(NULL, &dst_ipv4); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(&src_ipv4, &dst_ipv4); + CU_ASSERT_PTR_NOT_NULL(ipv4); + pcep_encode_object(&ipv4->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV4, &ipv4->header); + CU_ASSERT_EQUAL(*((uint32_t *)(ipv4->header.encoded_object + 4)), + src_ipv4.s_addr); + CU_ASSERT_EQUAL(*((uint32_t *)(ipv4->header.encoded_object + 8)), + dst_ipv4.s_addr); + + pcep_obj_free_object((struct pcep_object_header *)ipv4); +} + +void test_pcep_obj_create_endpoint_ipv6() +{ + struct in6_addr src_ipv6, dst_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8446", &dst_ipv6); + + struct pcep_object_endpoints_ipv6 *ipv6 = + pcep_obj_create_endpoint_ipv6(NULL, NULL); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(&src_ipv6, NULL); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(NULL, &dst_ipv6); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(&src_ipv6, &dst_ipv6); + CU_ASSERT_PTR_NOT_NULL(ipv6); + pcep_encode_object(&ipv6->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV6, &ipv6->header); + uint32_t *uint32_ptr = (uint32_t *)(ipv6->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], src_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], src_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], src_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], src_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[4], dst_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[5], dst_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[6], dst_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[7], dst_ipv6.__in6_u.__u6_addr32[3]); + + pcep_obj_free_object((struct pcep_object_header *)ipv6); +} + +void test_pcep_obj_create_bandwidth() +{ + /* 1.8 => binary 1.11001101 + * exponent = 127 => 0111 1111 + * fraction = 1100 1101 0000 0000 0000 000 */ + float bandwidth = 1.8; + + struct pcep_object_bandwidth *bw = pcep_obj_create_bandwidth(bandwidth); + + CU_ASSERT_PTR_NOT_NULL(bw); + pcep_encode_object(&bw->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_REQ, &bw->header); + CU_ASSERT_EQUAL(bw->header.encoded_object[4], 0x3f); + CU_ASSERT_EQUAL(bw->header.encoded_object[5], 0xe6); + CU_ASSERT_EQUAL(bw->header.encoded_object[6], 0x66); + CU_ASSERT_EQUAL(bw->header.encoded_object[7], 0x66); + + pcep_obj_free_object((struct pcep_object_header *)bw); +} + +void test_pcep_obj_create_metric() +{ + uint8_t type = PCEP_METRIC_BORDER_NODE_COUNT; + /* https://en.wikipedia.org/wiki/IEEE_754-1985 + * 0.15625 = 1/8 + 1/32 = binary 0.00101 = 1.01 x 10^-3 + * Exponent bias = 127, so exponent = (127-3) = 124 = 0111 1100 + * Sign Exponent Fraction + * (8 bits) (23 bits) + * 0.15625 => 0 0111 1100 010 0000 ... 0000 */ + float value = 0.15625; + + struct pcep_object_metric *metric = + pcep_obj_create_metric(type, true, true, value); + + CU_ASSERT_PTR_NOT_NULL(metric); + pcep_encode_object(&metric->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC, + &metric->header); + CU_ASSERT_EQUAL(metric->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(metric->header.encoded_object[5], 0); + CU_ASSERT_TRUE(metric->header.encoded_object[6] & OBJECT_METRIC_FLAC_B); + CU_ASSERT_TRUE(metric->header.encoded_object[6] & OBJECT_METRIC_FLAC_C); + CU_ASSERT_EQUAL(metric->header.encoded_object[7], type); + /* See comments above for explanation of these values */ + CU_ASSERT_EQUAL(metric->header.encoded_object[8], 0x3e); + CU_ASSERT_EQUAL(metric->header.encoded_object[9], 0x20); + CU_ASSERT_EQUAL(metric->header.encoded_object[10], 0x00); + CU_ASSERT_EQUAL(metric->header.encoded_object[11], 0x00); + + pcep_obj_free_object((struct pcep_object_header *)metric); +} + +void test_pcep_obj_create_lspa() +{ + uint32_t exclude_any = 10; + uint32_t include_any = 20; + uint32_t include_all = 30; + uint8_t prio = 0; + uint8_t hold_prio = 10; + + struct pcep_object_lspa *lspa = pcep_obj_create_lspa( + exclude_any, include_any, include_all, prio, hold_prio, true); + + CU_ASSERT_PTR_NOT_NULL(lspa); + pcep_encode_object(&lspa->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA, + &lspa->header); + uint32_t *uint32_ptr = (uint32_t *)(lspa->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(exclude_any)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(include_any)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(include_all)); + CU_ASSERT_EQUAL(lspa->header.encoded_object[16], prio); + CU_ASSERT_EQUAL(lspa->header.encoded_object[17], hold_prio); + CU_ASSERT_TRUE(lspa->header.encoded_object[18] & OBJECT_LSPA_FLAG_L); + CU_ASSERT_EQUAL(lspa->header.encoded_object[19], 0); + + pcep_obj_free_object((struct pcep_object_header *)lspa); +} + +void test_pcep_obj_create_svec() +{ + struct pcep_object_svec *svec = + pcep_obj_create_svec(true, true, true, NULL); + CU_ASSERT_PTR_NULL(svec); + + double_linked_list *id_list = dll_initialize(); + uint32_t *uint32_ptr = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *uint32_ptr = 10; + dll_append(id_list, uint32_ptr); + + svec = pcep_obj_create_svec(true, true, true, id_list); + CU_ASSERT_PTR_NOT_NULL(svec); + pcep_encode_object(&svec->header, versioning, object_buf); + verify_pcep_obj_header2(PCEP_OBJ_CLASS_SVEC, PCEP_OBJ_TYPE_SVEC, + (OBJECT_HEADER_LENGTH + sizeof(uint32_t) * 2), + svec->header.encoded_object); + CU_ASSERT_EQUAL(svec->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(svec->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(svec->header.encoded_object[6], 0); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_S); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_N); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_L); + CU_ASSERT_EQUAL(*((uint32_t *)(svec->header.encoded_object + 8)), + htonl(*uint32_ptr)); + + pcep_obj_free_object((struct pcep_object_header *)svec); +} + +void test_pcep_obj_create_error() +{ + uint8_t error_type = PCEP_ERRT_SESSION_FAILURE; + uint8_t error_value = PCEP_ERRV_RECVD_INVALID_OPEN_MSG; + + struct pcep_object_error *error = + pcep_obj_create_error(error_type, error_value); + + CU_ASSERT_PTR_NOT_NULL(error); + pcep_encode_object(&error->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR, + &error->header); + CU_ASSERT_EQUAL(error->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(error->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(error->header.encoded_object[6], error_type); + CU_ASSERT_EQUAL(error->header.encoded_object[7], error_value); + + pcep_obj_free_object((struct pcep_object_header *)error); +} + +void test_pcep_obj_create_close() +{ + uint8_t reason = PCEP_CLOSE_REASON_DEADTIMER; + + struct pcep_object_close *close = pcep_obj_create_close(reason); + + CU_ASSERT_PTR_NOT_NULL(close); + pcep_encode_object(&close->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_CLOSE, PCEP_OBJ_TYPE_CLOSE, + &close->header); + CU_ASSERT_EQUAL(close->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[7], reason); + + pcep_obj_free_object((struct pcep_object_header *)close); +} + +void test_pcep_obj_create_srp() +{ + bool lsp_remove = true; + uint32_t srp_id_number = 0x89674523; + struct pcep_object_srp *srp = + pcep_obj_create_srp(lsp_remove, srp_id_number, NULL); + + CU_ASSERT_PTR_NOT_NULL(srp); + pcep_encode_object(&srp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP, + &srp->header); + CU_ASSERT_EQUAL(srp->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(srp->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(srp->header.encoded_object[6], 0); + CU_ASSERT_TRUE(srp->header.encoded_object[7] & OBJECT_SRP_FLAG_R); + CU_ASSERT_EQUAL(*((uint32_t *)(srp->header.encoded_object + 8)), + htonl(srp_id_number)); + + pcep_obj_free_object((struct pcep_object_header *)srp); +} + +void test_pcep_obj_create_lsp() +{ + uint32_t plsp_id = 0x000fffff; + enum pcep_lsp_operational_status status = PCEP_LSP_OPERATIONAL_ACTIVE; + bool c_flag = true; + bool a_flag = true; + bool r_flag = true; + bool s_flag = true; + bool d_flag = true; + + /* Should return for invalid plsp_id */ + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(0x001fffff, status, c_flag, a_flag, r_flag, + s_flag, d_flag, NULL); + CU_ASSERT_PTR_NULL(lsp); + + /* Should return for invalid status */ + lsp = pcep_obj_create_lsp(plsp_id, 8, c_flag, a_flag, r_flag, s_flag, + d_flag, NULL); + CU_ASSERT_PTR_NULL(lsp); + + lsp = pcep_obj_create_lsp(plsp_id, status, c_flag, a_flag, r_flag, + s_flag, d_flag, NULL); + + CU_ASSERT_PTR_NOT_NULL(lsp); + pcep_encode_object(&lsp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP, + &lsp->header); + CU_ASSERT_EQUAL((ntohl(*((uint32_t *)(lsp->header.encoded_object + 4))) + >> 12) & 0x000fffff, + plsp_id); + CU_ASSERT_EQUAL((lsp->header.encoded_object[7] >> 4) & 0x07, status); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_A); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_C); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_D); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_R); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_S); + + pcep_obj_free_object((struct pcep_object_header *)lsp); +} + +void test_pcep_obj_create_vendor_info() +{ + uint32_t enterprise_number = 0x01020304; + uint32_t enterprise_specific_info = 0x05060708; + + struct pcep_object_vendor_info *obj = pcep_obj_create_vendor_info( + enterprise_number, enterprise_specific_info); + + CU_ASSERT_PTR_NOT_NULL(obj); + pcep_encode_object(&obj->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_VENDOR_INFO, + PCEP_OBJ_TYPE_VENDOR_INFO, &obj->header); + uint32_t *uint32_ptr = (uint32_t *)(obj->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(enterprise_number)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(enterprise_specific_info)); + + pcep_obj_free_object((struct pcep_object_header *)obj); +} + +/* Internal test function. The only difference between pcep_obj_create_ero(), + * pcep_obj_create_iro(), and pcep_obj_create_rro() is the object_class + * and the object_type. + */ +typedef struct pcep_object_ro *(*ro_func)(double_linked_list *); +static void test_pcep_obj_create_object_common(ro_func func_to_test, + uint8_t object_class, + uint8_t object_type) +{ + double_linked_list *ero_list = dll_initialize(); + + struct pcep_object_ro *ero = func_to_test(NULL); + CU_ASSERT_PTR_NOT_NULL(ero); + pcep_encode_object(&ero->header, versioning, object_buf); + verify_pcep_obj_header2(object_class, object_type, OBJECT_HEADER_LENGTH, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); + + reset_objects_buffer(); + ero = func_to_test(ero_list); + CU_ASSERT_PTR_NOT_NULL(ero); + pcep_encode_object(&ero->header, versioning, object_buf); + verify_pcep_obj_header2(object_class, object_type, OBJECT_HEADER_LENGTH, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); + + reset_objects_buffer(); + struct pcep_ro_subobj_32label *ro_subobj = + pcep_obj_create_ro_subobj_32label(false, 0, 101); + ero_list = dll_initialize(); + dll_append(ero_list, ro_subobj); + ero = func_to_test(ero_list); + CU_ASSERT_PTR_NOT_NULL(ero); + pcep_encode_object(&ero->header, versioning, object_buf); + /* 4 bytes for obj header + + * 2 bytes for ro_subobj header + + * 2 bytes for lable c-type and flags + + * 4 bytes for label */ + verify_pcep_obj_header2(object_class, object_type, + OBJECT_HEADER_LENGTH + sizeof(uint32_t) * 2, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); +} + +void test_pcep_obj_create_ero() +{ + test_pcep_obj_create_object_common( + pcep_obj_create_ero, PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO); +} + +void test_pcep_obj_create_rro() +{ + test_pcep_obj_create_object_common( + pcep_obj_create_rro, PCEP_OBJ_CLASS_RRO, PCEP_OBJ_TYPE_RRO); +} + +void test_pcep_obj_create_iro() +{ + test_pcep_obj_create_object_common( + pcep_obj_create_iro, PCEP_OBJ_CLASS_IRO, PCEP_OBJ_TYPE_IRO); +} + +/* Internal util function to wrap an RO Subobj in a RO and encode it */ +static struct pcep_object_ro *encode_ro_subobj(struct pcep_object_ro_subobj *sr) +{ + double_linked_list *sr_subobj_list = dll_initialize(); + dll_append(sr_subobj_list, sr); + struct pcep_object_ro *ro = pcep_obj_create_ero(sr_subobj_list); + pcep_encode_object(&ro->header, versioning, object_buf); + + return ro; +} + +static void verify_pcep_obj_ro_header(struct pcep_object_ro *ro, + struct pcep_object_ro_subobj *ro_subobj, + uint8_t ro_subobj_type, bool loose_hop, + uint16_t length) +{ + (void)ro_subobj; + + verify_pcep_obj_header2(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO, length, + ro->header.encoded_object); + + /* TODO consider printing the stack trace: + * https://stackoverflow.com/questions/105659/how-can-one-grab-a-stack-trace-in-c + */ + + /* Not using CU_ASSERT_EQUAL here, so that in case of failure, + * we can provide more info in the error message. */ + uint8_t found_type = (ro->header.encoded_object[4] + & 0x7f); /* remove the Loose hop bit */ + if (found_type != ro_subobj_type) { + fprintf(stderr, + "Test failure ro_sub_obj_type expected [%d] found [%d]\n", + ro_subobj_type, found_type); + CU_FAIL("Sub Object Header Type"); + } + + bool loose_hop_found = (ro->header.encoded_object[4] & 0x80); + if (loose_hop != loose_hop_found) { + fprintf(stderr, + "Test failure ro_sub_obj Loose Hop bit expected [%d] found [%d]\n", + loose_hop, loose_hop_found); + CU_FAIL("Sub Object Header Loose Hop bit"); + } + + if (length - 4 != ro->header.encoded_object[5]) { + fprintf(stderr, + "Test failure ro_sub_obj length expected [%d] found [%d]\n", + length - 4, ro->header.encoded_object[5]); + CU_FAIL("Sub Object Length"); + } +} + +static void +verify_pcep_obj_ro_sr_header(struct pcep_object_ro *ro, + struct pcep_object_ro_subobj *ro_subobj, + uint8_t nai_type, bool loose_hop, uint16_t length) +{ + verify_pcep_obj_ro_header(ro, ro_subobj, RO_SUBOBJ_TYPE_SR, loose_hop, + length); + uint8_t found_nai_type = ((ro->header.encoded_object[6] >> 4) & 0x0f); + if (nai_type != found_nai_type) { + fprintf(stderr, + "Test failure ro_sr_sub_obj nai_type expected [%d] found [%d]\n", + nai_type, found_nai_type); + CU_FAIL("Sub Object SR NAI Type"); + } +} + +void test_pcep_obj_create_ro_subobj_ipv4() +{ + struct in_addr ro_ipv4; + inet_pton(AF_INET, "192.168.1.2", &ro_ipv4); + uint8_t prefix_len = 8; + + struct pcep_ro_subobj_ipv4 *ipv4 = + pcep_obj_create_ro_subobj_ipv4(true, NULL, prefix_len, false); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_ro_subobj_ipv4(false, &ro_ipv4, prefix_len, + true); + CU_ASSERT_PTR_NOT_NULL(ipv4); + struct pcep_object_ro *ro = encode_ro_subobj(&ipv4->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv4->ro_subobj, RO_SUBOBJ_TYPE_IPV4, + false, sizeof(uint32_t) * 3); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 6)), + ro_ipv4.s_addr); + CU_ASSERT_EQUAL(ro->header.encoded_object[10], prefix_len); + CU_ASSERT_TRUE(ro->header.encoded_object[11] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + ipv4 = pcep_obj_create_ro_subobj_ipv4(true, &ro_ipv4, prefix_len, + false); + CU_ASSERT_PTR_NOT_NULL(ipv4); + ro = encode_ro_subobj(&ipv4->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv4->ro_subobj, RO_SUBOBJ_TYPE_IPV4, + true, sizeof(uint32_t) * 3); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 6)), + ro_ipv4.s_addr); + CU_ASSERT_EQUAL(ro->header.encoded_object[10], prefix_len); + CU_ASSERT_EQUAL(ro->header.encoded_object[11], 0); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_ipv6() +{ + struct in6_addr ro_ipv6; + uint8_t prefix_len = 16; + + struct pcep_ro_subobj_ipv6 *ipv6 = + pcep_obj_create_ro_subobj_ipv6(true, NULL, prefix_len, true); + CU_ASSERT_PTR_NULL(ipv6); + + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ro_ipv6); + ipv6 = pcep_obj_create_ro_subobj_ipv6(false, &ro_ipv6, prefix_len, + true); + CU_ASSERT_PTR_NOT_NULL(ipv6); + struct pcep_object_ro *ro = encode_ro_subobj(&ipv6->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv6->ro_subobj, RO_SUBOBJ_TYPE_IPV6, + false, sizeof(uint32_t) * 6); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 6); + CU_ASSERT_EQUAL(uint32_ptr[0], ro_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ro_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ro_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ro_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(ro->header.encoded_object[22], prefix_len); + CU_ASSERT_TRUE(ro->header.encoded_object[23] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + ipv6 = pcep_obj_create_ro_subobj_ipv6(true, &ro_ipv6, prefix_len, + false); + CU_ASSERT_PTR_NOT_NULL(ipv6); + ro = encode_ro_subobj(&ipv6->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv6->ro_subobj, RO_SUBOBJ_TYPE_IPV6, + true, sizeof(uint32_t) * 6); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 6); + CU_ASSERT_EQUAL(uint32_ptr[0], ro_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ro_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ro_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ro_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(ro->header.encoded_object[22], prefix_len); + CU_ASSERT_EQUAL(ro->header.encoded_object[23], 0); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_unnum() +{ + struct in_addr router_id; + uint32_t if_id = 123; + + struct pcep_ro_subobj_unnum *unnum = + pcep_obj_create_ro_subobj_unnum(NULL, if_id); + CU_ASSERT_PTR_NULL(unnum); + + inet_pton(AF_INET, "192.168.1.2", &router_id); + unnum = pcep_obj_create_ro_subobj_unnum(&router_id, if_id); + CU_ASSERT_PTR_NOT_NULL(unnum); + struct pcep_object_ro *ro = encode_ro_subobj(&unnum->ro_subobj); + verify_pcep_obj_ro_header(ro, &unnum->ro_subobj, RO_SUBOBJ_TYPE_UNNUM, + false, sizeof(uint32_t) * 4); + CU_ASSERT_EQUAL(ro->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(ro->header.encoded_object[7], 0); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + router_id.s_addr); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 12)), + htonl(if_id)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_32label() +{ + uint8_t class_type = 1; + uint32_t label = 0xeeffaabb; + + struct pcep_ro_subobj_32label *label32 = + pcep_obj_create_ro_subobj_32label(true, class_type, label); + CU_ASSERT_PTR_NOT_NULL(label32); + struct pcep_object_ro *ro = encode_ro_subobj(&label32->ro_subobj); + verify_pcep_obj_ro_header(ro, &label32->ro_subobj, RO_SUBOBJ_TYPE_LABEL, + false, sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[6] + & OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL); + CU_ASSERT_EQUAL(ro->header.encoded_object[7], class_type); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + htonl(label)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_asn() +{ + uint16_t asn = 0x0102; + + struct pcep_ro_subobj_asn *asn_obj = pcep_obj_create_ro_subobj_asn(asn); + CU_ASSERT_PTR_NOT_NULL(asn_obj); + struct pcep_object_ro *ro = encode_ro_subobj(&asn_obj->ro_subobj); + verify_pcep_obj_ro_header(ro, &asn_obj->ro_subobj, RO_SUBOBJ_TYPE_ASN, + false, sizeof(uint32_t) * 2); + CU_ASSERT_EQUAL(*((uint16_t *)(ro->header.encoded_object + 6)), + htons(asn)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_nonai() +{ + uint32_t sid = 0x01020304; + + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_nonai(false, sid, false, false); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_ABSENT, false, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_nonai(true, sid, true, true); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_ABSENT, true, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv4_node() +{ + uint32_t sid = 0x01020304; + struct in_addr ipv4_node_id; + inet_pton(AF_INET, "192.168.1.2", &ipv4_node_id); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, ipv4_node_id) */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv4_node( + true, false, true, true, sid, NULL); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv4_node(true, true, false, false, + sid, &ipv4_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, true, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + ipv4_node_id.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET, "192.168.1.2", &ipv4_node_id); + sr = pcep_obj_create_ro_subobj_sr_ipv4_node(false, false, true, true, + sid, &ipv4_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, false, + sizeof(uint32_t) * 4); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + htonl(sid)); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 12)), + ipv4_node_id.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv6_node() +{ + uint32_t sid = 0x01020304; + struct in6_addr ipv6_node_id; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ipv6_node_id); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, ipv6_node_id) */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv6_node( + false, true, true, true, sid, NULL); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv6_node(true, true, true, true, sid, + &ipv6_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, true, + sizeof(uint32_t) * 6); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], ipv6_node_id.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ipv6_node_id.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ipv6_node_id.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ipv6_node_id.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ipv6_node_id); + sr = pcep_obj_create_ro_subobj_sr_ipv6_node(false, false, true, true, + sid, &ipv6_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, false, + sizeof(uint32_t) * 7); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], ipv6_node_id.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], ipv6_node_id.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], ipv6_node_id.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], ipv6_node_id.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv4_adj() +{ + struct in_addr local_ipv4; + struct in_addr remote_ipv4; + inet_pton(AF_INET, "192.168.1.2", &local_ipv4); + inet_pton(AF_INET, "172.168.1.2", &remote_ipv4); + + uint32_t sid = ENCODE_SR_ERO_SID(3, 7, 0, 188); + CU_ASSERT_EQUAL(sid, 16060); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv4, remote_ipv4) + */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv4_adj( + false, true, true, true, sid, NULL, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(false, true, true, true, sid, + &local_ipv4, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(false, true, true, true, sid, + NULL, &remote_ipv4); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(true, true, true, true, sid, + &local_ipv4, &remote_ipv4); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, true, + sizeof(uint32_t) * 4); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv4.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[1], remote_ipv4.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + inet_pton(AF_INET, "192.168.1.2", &local_ipv4); + inet_pton(AF_INET, "172.168.1.2", &remote_ipv4); + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj( + false, false, true, true, sid, &local_ipv4, &remote_ipv4); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, false, + sizeof(uint32_t) * 5); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv4.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], remote_ipv4.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv6_adj() +{ + uint32_t sid = 0x01020304; + struct in6_addr local_ipv6; + struct in6_addr remote_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv6, remote_ipv6) + */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv6_adj( + false, true, true, true, sid, NULL, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(false, true, true, true, sid, + &local_ipv6, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(false, true, true, true, sid, + NULL, &remote_ipv6); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(true, true, true, true, sid, + &local_ipv6, &remote_ipv6); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, true, + sizeof(uint32_t) * 10); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[3]); + + CU_ASSERT_EQUAL(uint32_ptr[4], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj( + false, false, true, false, sid, &local_ipv6, &remote_ipv6); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, false, + sizeof(uint32_t) * 11); + /* All flags are false */ + CU_ASSERT_EQUAL(ro->header.encoded_object[7], 0); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_ipv6.__in6_u.__u6_addr32[3]); + + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj() +{ + uint32_t sid = 0x01020304; + uint32_t local_node_id = 0x11223344; + uint32_t local_if_id = 0x55667788; + uint32_t remote_node_id = 0x99aabbcc; + uint32_t remote_if_id = 0xddeeff11; + + /* (loose_hop, sid_absent, c_flag, m_flag, + sid, local_node_id, local_if_id, remote_node_id, remote_if_id) */ + + /* Test the sid is absent */ + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + true, true, true, true, sid, local_node_id, local_if_id, + remote_node_id, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, true, + sizeof(uint32_t) * 6); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_node_id); + CU_ASSERT_EQUAL(uint32_ptr[1], local_if_id); + CU_ASSERT_EQUAL(uint32_ptr[2], remote_node_id); + CU_ASSERT_EQUAL(uint32_ptr[3], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + false, false, true, true, sid, local_node_id, local_if_id, + remote_node_id, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, false, + sizeof(uint32_t) * 7); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_node_id); + CU_ASSERT_EQUAL(uint32_ptr[2], local_if_id); + CU_ASSERT_EQUAL(uint32_ptr[3], remote_node_id); + CU_ASSERT_EQUAL(uint32_ptr[4], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* TODO Test draft07 types */ +} + +void test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj() +{ + uint32_t sid = 0x01020304; + uint32_t local_if_id = 0x11002200; + uint32_t remote_if_id = 0x00110022; + struct in6_addr local_ipv6; + struct in6_addr remote_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv6, local_if_id, + * remote_ipv6, remote_if_id */ + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, NULL, local_if_id, NULL, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, &local_ipv6, local_if_id, NULL, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, NULL, local_if_id, &remote_ipv6, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + true, true, true, true, sid, &local_ipv6, local_if_id, + &remote_ipv6, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, true, + sizeof(uint32_t) * 12); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_if_id); + + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[9], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, false, true, true, sid, &local_ipv6, local_if_id, + &remote_ipv6, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, false, + sizeof(uint32_t) * 13); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[5], local_if_id); + + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[9], remote_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[10], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); +} diff --git a/pceplib/test/pcep_msg_objects_test.h b/pceplib/test/pcep_msg_objects_test.h new file mode 100644 index 0000000000..0f08193a59 --- /dev/null +++ b/pceplib/test/pcep_msg_objects_test.h @@ -0,0 +1,64 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + + +#ifndef PCEP_MSG_OBJECTS_TEST_H_ +#define PCEP_MSG_OBJECTS_TEST_H_ + +int pcep_objects_test_suite_setup(void); +int pcep_objects_test_suite_teardown(void); +void pcep_objects_test_setup(void); +void pcep_objects_test_teardown(void); +void test_pcep_obj_create_open(void); +void test_pcep_obj_create_open_with_tlvs(void); +void test_pcep_obj_create_rp(void); +void test_pcep_obj_create_nopath(void); +void test_pcep_obj_create_endpoint_ipv4(void); +void test_pcep_obj_create_endpoint_ipv6(void); +void test_pcep_obj_create_association_ipv4(void); +void test_pcep_obj_create_association_ipv6(void); +void test_pcep_obj_create_bandwidth(void); +void test_pcep_obj_create_metric(void); +void test_pcep_obj_create_lspa(void); +void test_pcep_obj_create_svec(void); +void test_pcep_obj_create_error(void); +void test_pcep_obj_create_close(void); +void test_pcep_obj_create_srp(void); +void test_pcep_obj_create_lsp(void); +void test_pcep_obj_create_vendor_info(void); +void test_pcep_obj_create_ero(void); +void test_pcep_obj_create_rro(void); +void test_pcep_obj_create_iro(void); +void test_pcep_obj_create_ro_subobj_ipv4(void); +void test_pcep_obj_create_ro_subobj_ipv6(void); +void test_pcep_obj_create_ro_subobj_unnum(void); +void test_pcep_obj_create_ro_subobj_32label(void); +void test_pcep_obj_create_ro_subobj_asn(void); +void test_pcep_obj_create_ro_subobj_sr_nonai(void); +void test_pcep_obj_create_ro_subobj_sr_ipv4_node(void); +void test_pcep_obj_create_ro_subobj_sr_ipv6_node(void); +void test_pcep_obj_create_ro_subobj_sr_ipv4_adj(void); +void test_pcep_obj_create_ro_subobj_sr_ipv6_adj(void); +void test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj(void); +void test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj(void); + +#endif diff --git a/pceplib/test/pcep_msg_tests_valgrind.sh b/pceplib/test/pcep_msg_tests_valgrind.sh new file mode 100755 index 0000000000..4a9a99939d --- /dev/null +++ b/pceplib/test/pcep_msg_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_msg_tests diff --git a/pceplib/test/pcep_msg_tlvs_test.c b/pceplib/test/pcep_msg_tlvs_test.c new file mode 100644 index 0000000000..878e4d62af --- /dev/null +++ b/pceplib/test/pcep_msg_tlvs_test.c @@ -0,0 +1,671 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_tlvs_test.h" + +/* + * Notice: + * All of these TLV Unit Tests encode the created TLVs by explicitly calling + * pcep_encode_tlv() thus testing the TLV creation and the TLV encoding. + * All APIs expect IPs to be in network byte order. + */ + +static struct pcep_versioning *versioning = NULL; +static uint8_t tlv_buf[2000]; + +void reset_tlv_buffer(void); + +int pcep_tlvs_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_tlvs_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void reset_tlv_buffer() +{ + memset(tlv_buf, 0, 2000); +} + +void pcep_tlvs_test_setup() +{ + versioning = create_default_pcep_versioning(); + reset_tlv_buffer(); +} + +void pcep_tlvs_test_teardown() +{ + destroy_pcep_versioning(versioning); +} + +void test_pcep_tlv_create_stateful_pce_capability() +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + pcep_tlv_create_stateful_pce_capability(true, true, true, true, + true, true); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_TRUE(tlv->flag_u_lsp_update_capability); + CU_ASSERT_TRUE(tlv->flag_s_include_db_version); + CU_ASSERT_TRUE(tlv->flag_i_lsp_instantiation_capability); + CU_ASSERT_TRUE(tlv->flag_t_triggered_resync); + CU_ASSERT_TRUE(tlv->flag_d_delta_lsp_sync); + CU_ASSERT_TRUE(tlv->flag_f_triggered_initial_sync); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0x3f); + /* TODO add a new function: verify_tlv_header(tlv->header.encoded_tlv) + * to all tests */ + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_speaker_entity_id() +{ + struct pcep_object_tlv_speaker_entity_identifier *tlv = + pcep_tlv_create_speaker_entity_id(NULL); + CU_ASSERT_PTR_NULL(tlv); + + double_linked_list *list = dll_initialize(); + tlv = pcep_tlv_create_speaker_entity_id(list); + CU_ASSERT_PTR_NULL(tlv); + + uint32_t *speaker_entity = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *speaker_entity = 42; + dll_append(list, speaker_entity); + tlv = pcep_tlv_create_speaker_entity_id(list); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_PTR_NOT_NULL(tlv->speaker_entity_id_list); + CU_ASSERT_EQUAL(tlv->speaker_entity_id_list->num_entries, 1); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(*speaker_entity)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_lsp_db_version() +{ + uint64_t lsp_db_version = 0xf005ba11ba5eba11; + struct pcep_object_tlv_lsp_db_version *tlv = + pcep_tlv_create_lsp_db_version(lsp_db_version); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint64_t)); + CU_ASSERT_EQUAL(tlv->lsp_db_version, lsp_db_version); + CU_ASSERT_EQUAL(*((uint64_t *)(tlv->header.encoded_tlv + 4)), + be64toh(lsp_db_version)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_path_setup_type() +{ + uint8_t pst = 0x89; + + struct pcep_object_tlv_path_setup_type *tlv = + pcep_tlv_create_path_setup_type(pst); + CU_ASSERT_PTR_NOT_NULL(tlv); + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_EQUAL(tlv->path_setup_type, pst); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x000000FF & pst)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_path_setup_type_capability() +{ + /* The sub_tlv list is optional */ + + /* Should return NULL if pst_list is NULL */ + struct pcep_object_tlv_path_setup_type_capability *tlv = + pcep_tlv_create_path_setup_type_capability(NULL, NULL); + CU_ASSERT_PTR_NULL(tlv); + + /* Should return NULL if pst_list is empty */ + double_linked_list *pst_list = dll_initialize(); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, NULL); + CU_ASSERT_PTR_NULL(tlv); + + /* Should still return NULL if pst_list is NULL */ + double_linked_list *sub_tlv_list = dll_initialize(); + tlv = pcep_tlv_create_path_setup_type_capability(NULL, sub_tlv_list); + CU_ASSERT_PTR_NULL(tlv); + + /* Should still return NULL if pst_list is empty */ + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NULL(tlv); + + /* Test only populating the pst list */ + uint8_t *pst1 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + uint8_t *pst2 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + uint8_t *pst3 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + *pst1 = 1; + *pst2 = 2; + *pst3 = 3; + dll_append(pst_list, pst1); + dll_append(pst_list, pst2); + dll_append(pst_list, pst3); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 2); + CU_ASSERT_PTR_NOT_NULL(tlv->pst_list); + CU_ASSERT_EQUAL(tlv->pst_list->num_entries, 3); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000003)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(0x01020300)); + pcep_obj_free_tlv(&tlv->header); + + /* Now test populating both the pst_list and the sub_tlv_list */ + reset_tlv_buffer(); + struct pcep_object_tlv_header *sub_tlv = + (struct pcep_object_tlv_header *) + pcep_tlv_create_sr_pce_capability(true, true, 0); + pst_list = dll_initialize(); + sub_tlv_list = dll_initialize(); + pst1 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + *pst1 = 1; + dll_append(pst_list, pst1); + dll_append(sub_tlv_list, sub_tlv); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + sizeof(uint32_t) * 2 + TLV_HEADER_LENGTH + + sub_tlv->encoded_tlv_length); + CU_ASSERT_PTR_NOT_NULL(tlv->pst_list); + CU_ASSERT_PTR_NOT_NULL(tlv->sub_tlv_list); + uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + uint16_t *uint16_ptr = (uint16_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(tlv->header.encoded_tlv_length)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000001)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(0x01000000)); + /* Verify the Sub-TLV */ + uint16_ptr = (uint16_t *)(tlv->header.encoded_tlv + 12); + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(4)); + CU_ASSERT_EQUAL(uint16_ptr[2], 0); + CU_ASSERT_EQUAL(uint16_ptr[3], htons(0x0300)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_sr_pce_capability() +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + pcep_tlv_create_sr_pce_capability(true, true, 8); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + uint16_t *uint16_ptr = (uint16_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(tlv->header.encoded_tlv_length)); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000308)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_symbolic_path_name() +{ + /* char *symbolic_path_name, uint16_t symbolic_path_name_length); */ + char path_name[16] = "Some Path Name"; + uint16_t path_name_length = 14; + struct pcep_object_tlv_symbolic_path_name *tlv = + pcep_tlv_create_symbolic_path_name(path_name, path_name_length); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, path_name_length); + /* Test the padding is correct */ + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[4]), + &path_name[0], 4)); + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[8]), + &path_name[4], 4)); + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[12]), + &path_name[8], 4)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[16], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[17], 'e'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[18], 0); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[19], 0); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_symbolic_path_name(path_name, 3); + CU_ASSERT_PTR_NOT_NULL(tlv); + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 3); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[4], 'S'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[5], 'o'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[6], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_ipv4_lsp_identifiers() +{ + struct in_addr sender_ip, endpoint_ip; + uint16_t lsp_id = 7; + uint16_t tunnel_id = 16; + struct in_addr extended_tunnel_id; + extended_tunnel_id.s_addr = 256; + inet_pton(AF_INET, "192.168.1.1", &sender_ip); + inet_pton(AF_INET, "192.168.1.2", &endpoint_ip); + + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + pcep_tlv_create_ipv4_lsp_identifiers(NULL, &endpoint_ip, lsp_id, + tunnel_id, + &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers( + &sender_ip, NULL, lsp_id, tunnel_id, &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers( + NULL, NULL, lsp_id, tunnel_id, &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers(&sender_ip, &endpoint_ip, + lsp_id, tunnel_id, + &extended_tunnel_id); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 4); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], sender_ip.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + CU_ASSERT_EQUAL(uint32_ptr[3], extended_tunnel_id.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[4], endpoint_ip.s_addr); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_ipv4_lsp_identifiers(&sender_ip, &endpoint_ip, + lsp_id, tunnel_id, NULL); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 4); + uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], sender_ip.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + CU_ASSERT_EQUAL(uint32_ptr[3], INADDR_ANY); + CU_ASSERT_EQUAL(uint32_ptr[4], endpoint_ip.s_addr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_ipv6_lsp_identifiers() +{ + struct in6_addr sender_ip, endpoint_ip; + uint16_t lsp_id = 3; + uint16_t tunnel_id = 16; + uint32_t extended_tunnel_id[4]; + + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &sender_ip); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8446", &endpoint_ip); + extended_tunnel_id[0] = 1; + extended_tunnel_id[1] = 2; + extended_tunnel_id[2] = 3; + extended_tunnel_id[3] = 4; + + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + pcep_tlv_create_ipv6_lsp_identifiers( + NULL, &endpoint_ip, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + &sender_ip, NULL, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + NULL, NULL, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + &sender_ip, &endpoint_ip, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 52); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[5], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + + pcep_obj_free_tlv(&tlv->header); +} +void test_pcep_tlv_create_srpag_pol_id_ipv4() +{ + uint32_t color = 1; + struct in_addr src; + inet_pton(AF_INET, "192.168.1.2", &src); + + struct pcep_object_tlv_srpag_pol_id *tlv = + pcep_tlv_create_srpag_pol_id_ipv4(color, (void *)&src); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID)); + CU_ASSERT_EQUAL( + tlv->header.encoded_tlv_length, + (8 /*draft-barth-pce-segment-routing-policy-cp-04#5.1*/)); + CU_ASSERT_EQUAL(tlv->color, (color)); + uint32_t aux_color = htonl(color); // Is color right encoded + CU_ASSERT_EQUAL(0, memcmp(&tlv_buf[0] + TLV_HEADER_LENGTH, &aux_color, + sizeof(color))); + CU_ASSERT_EQUAL(tlv->end_point.ipv4.s_addr, (src.s_addr)); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_pol_id *dec_tlv = + (struct pcep_object_tlv_srpag_pol_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->color, dec_tlv->color); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} +void test_pcep_tlv_create_srpag_pol_id_ipv6() +{ + + uint32_t color = 1; + struct in6_addr src; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src); + + struct pcep_object_tlv_srpag_pol_id *tlv = + pcep_tlv_create_srpag_pol_id_ipv6(color, &src); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID)); + CU_ASSERT_EQUAL( + tlv->header.encoded_tlv_length, + (20 /*draft-barth-pce-segment-routing-policy-cp-04#5.1*/)); + CU_ASSERT_EQUAL(tlv->color, (color)); + CU_ASSERT_EQUAL(0, memcmp(&tlv->end_point.ipv6, &src, sizeof(src))); + + uint32_t aux_color = htonl(color); + CU_ASSERT_EQUAL(0, memcmp(&aux_color, tlv_buf + TLV_HEADER_LENGTH, + sizeof(tlv->color))); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_pol_id *dec_tlv = + (struct pcep_object_tlv_srpag_pol_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->color, dec_tlv->color); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} +void test_pcep_tlv_create_srpag_pol_name() +{ + const char *pol_name = "Some Pol Name"; + + struct pcep_object_tlv_srpag_pol_name *tlv = + pcep_tlv_create_srpag_pol_name(pol_name, strlen(pol_name)); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + (normalize_pcep_tlv_length(strlen(pol_name)))); + CU_ASSERT_EQUAL(0, strcmp(pol_name, (char *)tlv->name)); + + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_cp_id() +{ + // draft-ietf-spring-segment-routing-policy-06.pdf#2.3 + // 10 PCEP, 20 BGP SR Policy, 30 Via Configuration + uint8_t proto_origin = 10; + uint32_t ASN = 0; + struct in6_addr with_mapped_ipv4; + inet_pton(AF_INET6, "::ffff:192.0.2.128", &with_mapped_ipv4); + uint32_t discriminator = 0; + + struct pcep_object_tlv_srpag_cp_id *tlv = pcep_tlv_create_srpag_cp_id( + proto_origin, ASN, &with_mapped_ipv4, discriminator); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + (sizeof(proto_origin) + sizeof(ASN) + + sizeof(with_mapped_ipv4) + sizeof(discriminator))); + CU_ASSERT_EQUAL(tlv->proto, (proto_origin)); + CU_ASSERT_EQUAL(tlv->orig_asn, (ASN)); + CU_ASSERT_EQUAL(0, memcmp(&tlv->orig_addres, &with_mapped_ipv4, + sizeof(with_mapped_ipv4))); + CU_ASSERT_EQUAL(tlv->discriminator, (discriminator)); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_cp_id *dec_tlv = + (struct pcep_object_tlv_srpag_cp_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->proto, dec_tlv->proto); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_cp_pref() +{ + uint32_t preference_default = 100; + + struct pcep_object_tlv_srpag_cp_pref *tlv = + pcep_tlv_create_srpag_cp_pref(preference_default); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE)); + printf(" encoded length vs sizeof pref (%d) vs (%ld)\n", + tlv->header.encoded_tlv_length, sizeof(preference_default)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + sizeof(preference_default)); + CU_ASSERT_EQUAL(tlv->preference, (preference_default)); + uint32_t aux_pref = htonl(preference_default); // Is pref right encoded + CU_ASSERT_EQUAL(0, memcmp(tlv_buf + TLV_HEADER_LENGTH, &aux_pref, + sizeof(preference_default))); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_cp_pref *dec_tlv = + (struct pcep_object_tlv_srpag_cp_pref *)dec_hdr; + CU_ASSERT_EQUAL(tlv->preference, dec_tlv->preference); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} +void test_pcep_tlv_create_lsp_error_code() +{ + struct pcep_object_tlv_lsp_error_code *tlv = + pcep_tlv_create_lsp_error_code( + PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], + htonl(PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_rsvp_ipv4_error_spec() +{ + struct in_addr error_node_ip; + inet_pton(AF_INET, "192.168.1.1", &error_node_ip); + uint8_t error_code = 8; + uint16_t error_value = 0xaabb; + + struct pcep_object_tlv_rsvp_error_spec *tlv = + pcep_tlv_create_rsvp_ipv4_error_spec(NULL, error_code, + error_value); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_rsvp_ipv4_error_spec(&error_node_ip, error_code, + error_value); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 12); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_rsvp_ipv6_error_spec() +{ + struct in6_addr error_node_ip; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &error_node_ip); + uint8_t error_code = 8; + uint16_t error_value = 0xaabb; + + struct pcep_object_tlv_rsvp_error_spec *tlv = + pcep_tlv_create_rsvp_ipv6_error_spec(NULL, error_code, + error_value); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_rsvp_ipv6_error_spec(&error_node_ip, error_code, + error_value); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 24); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_nopath_vector() +{ + uint32_t enterprise_number = 0x01020304; + uint32_t enterprise_specific_info = 0x05060708; + + struct pcep_object_tlv_vendor_info *tlv = pcep_tlv_create_vendor_info( + enterprise_number, enterprise_specific_info); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_VENDOR_INFO); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 8); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(enterprise_number)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(enterprise_specific_info)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_arbitrary() +{ + char data[16] = "Some Data"; + uint16_t data_length = 9; + uint16_t tlv_id_unknown = 1; // 65505; // Whatever id to be created + struct pcep_object_tlv_arbitrary *tlv = pcep_tlv_create_tlv_arbitrary( + data, data_length, tlv_id_unknown); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, tlv_id_unknown); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, data_length); + /* Test the padding is correct */ + CU_ASSERT_EQUAL( + 0, strncmp((char *)&(tlv->header.encoded_tlv[4]), &data[0], 4)); + CU_ASSERT_EQUAL( + 0, strncmp((char *)&(tlv->header.encoded_tlv[8]), &data[4], 4)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[11], 't'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[12], 'a'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[13], 0); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[14], 0); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_tlv_arbitrary(data, 3, tlv_id_unknown); + CU_ASSERT_PTR_NOT_NULL(tlv); + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, tlv_id_unknown); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 3); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[4], 'S'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[5], 'o'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[6], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0); + + pcep_obj_free_tlv(&tlv->header); +} diff --git a/pceplib/test/pcep_msg_tlvs_test.h b/pceplib/test/pcep_msg_tlvs_test.h new file mode 100644 index 0000000000..a961d7e473 --- /dev/null +++ b/pceplib/test/pcep_msg_tlvs_test.h @@ -0,0 +1,51 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +#ifndef PCEP_MSG_TLVS_TEST_H_ +#define PCEP_MSG_TLVS_TEST_H_ + +int pcep_tlvs_test_suite_setup(void); +int pcep_tlvs_test_suite_teardown(void); +void pcep_tlvs_test_setup(void); +void pcep_tlvs_test_teardown(void); +void test_pcep_tlv_create_stateful_pce_capability(void); +void test_pcep_tlv_create_speaker_entity_id(void); +void test_pcep_tlv_create_lsp_db_version(void); +void test_pcep_tlv_create_path_setup_type(void); +void test_pcep_tlv_create_path_setup_type_capability(void); +void test_pcep_tlv_create_sr_pce_capability(void); +void test_pcep_tlv_create_symbolic_path_name(void); +void test_pcep_tlv_create_ipv4_lsp_identifiers(void); +void test_pcep_tlv_create_ipv6_lsp_identifiers(void); +void test_pcep_tlv_create_lsp_error_code(void); +void test_pcep_tlv_create_rsvp_ipv4_error_spec(void); +void test_pcep_tlv_create_rsvp_ipv6_error_spec(void); +void test_pcep_tlv_create_srpag_pol_id_ipv4(void); +void test_pcep_tlv_create_srpag_pol_id_ipv6(void); +void test_pcep_tlv_create_srpag_pol_name(void); +void test_pcep_tlv_create_srpag_cp_id(void); +void test_pcep_tlv_create_srpag_cp_pref(void); +void test_pcep_tlv_create_nopath_vector(void); +void test_pcep_tlv_create_arbitrary(void); + + +#endif diff --git a/pceplib/test/pcep_msg_tools_test.c b/pceplib/test/pcep_msg_tools_test.c new file mode 100644 index 0000000000..a1260b1186 --- /dev/null +++ b/pceplib/test/pcep_msg_tools_test.c @@ -0,0 +1,1258 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_tools.h" +#include "pcep_msg_tools_test.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +const uint8_t any_obj_class = 255; + +uint16_t pcep_open_hexbyte_strs_length = 28; +const char *pcep_open_odl_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "1e", + "78", "55", "00", "10", "00", "04", "00", "00", "00", "3f", + "00", "1a", "00", "04", "00", "00", "00", "00"}; + +/* PCEP INITIATE str received from ODL with 4 objects: [SRP, LSP, Endpoints, + * ERO] The LSP has a SYMBOLIC_PATH_NAME TLV. The ERO has 2 IPV4 Endpoints. */ +uint16_t pcep_initiate_hexbyte_strs_length = 68; +const char *pcep_initiate_hexbyte_strs[] = { + "20", "0c", "00", "44", "21", "12", "00", "0c", "00", "00", "00", "00", + "00", "00", "00", "01", "20", "10", "00", "14", "00", "00", "00", "09", + "00", "11", "00", "08", "66", "61", "39", "33", "33", "39", "32", "39", + "04", "10", "00", "0c", "7f", "00", "00", "01", "28", "28", "28", "28", + "07", "10", "00", "14", "01", "08", "0a", "00", "01", "01", "18", "00", + "01", "08", "0a", "00", "07", "04", "18", "00"}; + +uint16_t pcep_initiate2_hexbyte_strs_length = 72; +const char *pcep_initiate2_hexbyte_strs[] = { + "20", "0c", "00", "48", "21", "12", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "14", "00", "00", "00", "09", "00", "11", "00", "08", + "36", "65", "31", "31", "38", "39", "32", "31", "04", "10", "00", "0c", + "c0", "a8", "14", "05", "01", "01", "01", "01", "07", "10", "00", "10", + "05", "0c", "10", "01", "03", "e8", "a0", "00", "01", "01", "01", "01"}; + +uint16_t pcep_update_hexbyte_strs_length = 48; +const char *pcep_update_hexbyte_strs[] = { + "20", "0b", "00", "30", "21", "12", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "08", "00", "02", "a0", "09", "07", "10", "00", "10", + "05", "0c", "10", "01", "03", "e8", "a0", "00", "01", "01", "01", "01"}; + +/* Test that pcep_msg_read() can read multiple messages in 1 call */ +uint16_t pcep_open_initiate_hexbyte_strs_length = 100; +const char *pcep_open_initiate_odl_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "1e", "78", "55", + "00", "10", "00", "04", "00", "00", "00", "3f", "00", "1a", "00", "04", + "00", "00", "00", "00", "20", "0c", "00", "48", "21", "12", "00", "14", + "00", "00", "00", "00", "00", "00", "00", "01", "00", "1c", "00", "04", + "00", "00", "00", "01", "20", "10", "00", "14", "00", "00", "00", "09", + "00", "11", "00", "08", "36", "65", "31", "31", "38", "39", "32", "31", + "04", "10", "00", "0c", "c0", "a8", "14", "05", "01", "01", "01", "01", + "07", "10", "00", "10", "05", "0c", "10", "01", "03", "e8", "a0", "00", + "01", "01", "01", "01"}; + +uint16_t pcep_open_cisco_pce_hexbyte_strs_length = 28; +const char *pcep_open_cisco_pce_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "3c", + "78", "00", "00", "10", "00", "04", "00", "00", "00", "05", + "00", "1a", "00", "04", "00", "00", "00", "0a"}; + +uint16_t pcep_update_cisco_pce_hexbyte_strs_length = 100; +const char *pcep_update_cisco_pce_hexbyte_strs[] = { + "20", "0b", "00", "64", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "18", "80", "00", "f0", "89", "00", "07", "00", "0c", + "00", "00", "00", "09", "00", "03", "00", "04", "00", "00", "00", "01", + "07", "10", "00", "28", "24", "0c", "10", "01", "04", "65", "50", "00", + "0a", "0a", "0a", "05", "24", "0c", "10", "01", "04", "65", "20", "00", + "0a", "0a", "0a", "02", "24", "0c", "10", "01", "04", "65", "10", "00", + "0a", "0a", "0a", "01", "06", "10", "00", "0c", "00", "00", "00", "02", + "41", "f0", "00", "00"}; + +uint16_t pcep_report_cisco_pcc_hexbyte_strs_length = 148; +const char *pcep_report_cisco_pcc_hexbyte_strs[] = { + "20", "0a", "00", "94", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "3c", "80", "00", "f0", "09", "00", "12", "00", "10", + "0a", "0a", "0a", "06", "00", "02", "00", "0f", "0a", "0a", "0a", "06", + "0a", "0a", "0a", "01", "00", "11", "00", "0d", "63", "66", "67", "5f", + "52", "36", "2d", "74", "6f", "2d", "52", "31", "00", "00", "00", "00", + "ff", "e1", "00", "06", "00", "00", "05", "dd", "70", "00", "00", "00", + "07", "10", "00", "04", "09", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "00", "00", "00", "07", "07", "01", "00", + "05", "12", "00", "08", "00", "00", "00", "00", "05", "52", "00", "08", + "00", "00", "00", "00", "06", "10", "00", "0c", "00", "00", "00", "02", + "00", "00", "00", "00", "06", "10", "00", "0c", "00", "00", "01", "04", + "41", "80", "00", "00"}; + +/* Cisco PcInitiate with the following objects: + * SRP, LSP, Endpoint, Inter-layer, Switch-layer, ERO + */ +uint16_t pcep_initiate_cisco_pcc_hexbyte_strs_length = 104; +const char *pcep_initiate_cisco_pcc_hexbyte_strs[] = { + "20", "0c", "00", "68", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "30", "00", "00", "00", "89", "00", "11", "00", "13", + "50", "4f", "4c", "31", "5f", "50", "43", "49", "4e", "49", "54", "41", + "54", "45", "5f", "54", "45", "53", "54", "00", "00", "07", "00", "0c", + "00", "00", "00", "09", "00", "03", "00", "04", "00", "00", "00", "01", + "04", "10", "00", "0c", "0a", "0a", "0a", "0a", "0a", "0a", "0a", "04", + "24", "10", "00", "08", "00", "00", "01", "4d", "25", "10", "00", "08", + "00", "00", "00", "64", "07", "10", "00", "04"}; + +struct pcep_message *create_message(uint8_t msg_type, uint8_t obj1_class, + uint8_t obj2_class, uint8_t obj3_class, + uint8_t obj4_class); +int convert_hexstrs_to_binary(const char *hexbyte_strs[], + uint16_t hexbyte_strs_length); + +int pcep_tools_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_tools_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_tools_test_setup(void) +{ +} + +void pcep_tools_test_teardown(void) +{ +} + +/* Reads an array of hexbyte strs, and writes them to a temporary file. + * The caller should close the returned file. */ +int convert_hexstrs_to_binary(const char *hexbyte_strs[], + uint16_t hexbyte_strs_length) +{ + int fd = fileno(tmpfile()); + + int i = 0; + for (; i < hexbyte_strs_length; i++) { + uint8_t byte = (uint8_t)strtol(hexbyte_strs[i], 0, 16); + if (write(fd, (char *)&byte, 1) < 0) { + return -1; + } + } + + /* Go back to the beginning of the file */ + lseek(fd, 0, SEEK_SET); + return fd; +} + +static bool pcep_obj_has_tlv(struct pcep_object_header *obj_hdr) +{ + if (obj_hdr->tlv_list == NULL) { + return false; + } + + return (obj_hdr->tlv_list->num_entries > 0); +} + +void test_pcep_msg_read_pcep_initiate() +{ + int fd = convert_hexstrs_to_binary(pcep_initiate_hexbyte_strs, + pcep_initiate_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate_hexbyte_strs_length); + + /* Verify each of the object types */ + + /* SRP object */ + double_linked_list_node *node = msg->obj_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* LSP object and its TLV*/ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_EQUAL(((struct pcep_object_lsp *)obj_hdr)->plsp_id, 0); + CU_ASSERT_TRUE(((struct pcep_object_lsp *)obj_hdr)->flag_d); + CU_ASSERT_TRUE(((struct pcep_object_lsp *)obj_hdr)->flag_a); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_s); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_r); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_c); + + /* LSP TLV */ + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 8); + + /* Endpoints object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 2); + double_linked_list_node *subobj_node = ero_subobj_list->head; + struct pcep_object_ro_subobj *subobj_hdr = + (struct pcep_object_ro_subobj *)subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_IPV4); + struct in_addr ero_subobj_ip; + inet_pton(AF_INET, "10.0.1.1", &ero_subobj_ip); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->ip_addr.s_addr, + ero_subobj_ip.s_addr); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->prefix_length, 24); + + subobj_hdr = + (struct pcep_object_ro_subobj *)subobj_node->next_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_IPV4); + inet_pton(AF_INET, "10.0.7.4", &ero_subobj_ip); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->ip_addr.s_addr, + ero_subobj_ip.s_addr); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->prefix_length, 24); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + + +void test_pcep_msg_read_pcep_initiate2() +{ + int fd = convert_hexstrs_to_binary(pcep_initiate2_hexbyte_strs, + pcep_initiate2_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate2_hexbyte_strs_length); + + /* Verify each of the object types */ + + /* SRP object */ + double_linked_list_node *node = msg->obj_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + /* TODO test the TLVs */ + + /* LSP object and its TLV*/ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + + /* LSP TLV */ + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 8); + + /* Endpoints object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 16); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 0); + double_linked_list_node *subobj_node = ero_subobj_list->head; + CU_ASSERT_PTR_NULL(subobj_node); + /* We no longer support draft07 SR sub-object type=5, and only support + type=36 struct pcep_object_ro_subobj *subobj_hdr = (struct + pcep_object_ro_subobj *) subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_SR); + struct pcep_ro_subobj_sr *subobj_sr = (struct pcep_ro_subobj_sr *) + subobj_hdr; CU_ASSERT_EQUAL(subobj_sr->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); CU_ASSERT_TRUE(subobj_sr->flag_m); + CU_ASSERT_FALSE(subobj_sr->flag_c); + CU_ASSERT_FALSE(subobj_sr->flag_s); + CU_ASSERT_FALSE(subobj_sr->flag_f); + CU_ASSERT_EQUAL(subobj_sr->sid, 65576960); + CU_ASSERT_EQUAL(*((uint32_t *) subobj_sr->nai_list->head->data), + 0x01010101); + */ + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_open() +{ + int fd = convert_hexstrs_to_binary(pcep_open_odl_hexbyte_strs, + pcep_open_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + + /* Verify the Open message */ + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)msg->obj_list->head->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_OPEN); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 24); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + + /* Open TLV: Stateful PCE Capability */ + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 2); + double_linked_list_node *tlv_node = obj_hdr->tlv_list->head; + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)tlv_node->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + + /* Open TLV: SR PCE Capability */ + tlv_node = tlv_node->next_node; + tlv = (struct pcep_object_tlv_header *)tlv_node->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_update() +{ + int fd = convert_hexstrs_to_binary(pcep_update_hexbyte_strs, + pcep_update_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 3); + + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_update_hexbyte_strs_length); + + /* Verify each of the object types */ + + double_linked_list_node *node = msg->obj_list->head; + + /* SRP object */ + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + + /* SRP TLV */ + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + /* TODO verify the path setup type */ + + /* LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 16); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 0); + double_linked_list_node *subobj_node = ero_subobj_list->head; + CU_ASSERT_PTR_NULL(subobj_node); + /* We no longer support draft07 SR sub-object type=5, and only support + type=36 struct pcep_object_ro_subobj *subobj_hdr = (struct + pcep_object_ro_subobj *) subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_SR); + struct pcep_ro_subobj_sr *subobj_sr = (struct pcep_ro_subobj_sr *) + subobj_hdr; CU_ASSERT_EQUAL(subobj_sr->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); CU_ASSERT_TRUE(subobj_sr->flag_m); + CU_ASSERT_FALSE(subobj_sr->flag_c); + CU_ASSERT_FALSE(subobj_sr->flag_s); + CU_ASSERT_FALSE(subobj_sr->flag_f); + CU_ASSERT_EQUAL(subobj_sr->sid, 65576960); + CU_ASSERT_EQUAL(*((uint32_t *) subobj_sr->nai_list->head->data), + 0x01010101); + */ + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_open_initiate() +{ + int fd = convert_hexstrs_to_binary( + pcep_open_initiate_odl_hexbyte_strs, + pcep_open_initiate_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 2); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + + msg = (struct pcep_message *)msg_list->head->next_node->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate2_hexbyte_strs_length); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_open_cisco_pce() +{ + int fd = convert_hexstrs_to_binary( + pcep_open_cisco_pce_hexbyte_strs, + pcep_open_cisco_pce_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + + /* Open object */ + struct pcep_object_open *open = + (struct pcep_object_open *)msg->obj_list->head->data; + CU_ASSERT_EQUAL(open->header.object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(open->header.object_type, PCEP_OBJ_TYPE_OPEN); + CU_ASSERT_EQUAL(open->header.encoded_object_length, 24); + CU_ASSERT_EQUAL(open->open_deadtimer, 120); + CU_ASSERT_EQUAL(open->open_keepalive, 60); + CU_ASSERT_EQUAL(open->open_sid, 0); + CU_ASSERT_EQUAL(open->open_version, 1); + CU_ASSERT_PTR_NOT_NULL(open->header.tlv_list); + CU_ASSERT_EQUAL(open->header.tlv_list->num_entries, 2); + + /* Stateful PCE Capability TLV */ + double_linked_list_node *tlv_node = open->header.tlv_list->head; + struct pcep_object_tlv_stateful_pce_capability *pce_cap_tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + tlv_node->data; + CU_ASSERT_EQUAL(pce_cap_tlv->header.type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(pce_cap_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_TRUE(pce_cap_tlv->flag_u_lsp_update_capability); + CU_ASSERT_TRUE(pce_cap_tlv->flag_i_lsp_instantiation_capability); + CU_ASSERT_FALSE(pce_cap_tlv->flag_s_include_db_version); + CU_ASSERT_FALSE(pce_cap_tlv->flag_t_triggered_resync); + CU_ASSERT_FALSE(pce_cap_tlv->flag_d_delta_lsp_sync); + CU_ASSERT_FALSE(pce_cap_tlv->flag_f_triggered_initial_sync); + + /* SR PCE Capability TLV */ + tlv_node = tlv_node->next_node; + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv = + (struct pcep_object_tlv_sr_pce_capability *)tlv_node->data; + CU_ASSERT_EQUAL(sr_pce_cap_tlv->header.type, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(sr_pce_cap_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_FALSE(sr_pce_cap_tlv->flag_n); + CU_ASSERT_FALSE(sr_pce_cap_tlv->flag_x); + CU_ASSERT_EQUAL(sr_pce_cap_tlv->max_sid_depth, 10); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_update_cisco_pce() +{ + int fd = convert_hexstrs_to_binary( + pcep_update_cisco_pce_hexbyte_strs, + pcep_update_cisco_pce_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_update_cisco_pce_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 1); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* SRP Path Setup Type TLV */ + double_linked_list_node *tlv_node = srp->header.tlv_list->head; + struct pcep_object_tlv_path_setup_type *pst_tlv = + (struct pcep_object_tlv_path_setup_type *)tlv_node->data; + CU_ASSERT_EQUAL(pst_tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(pst_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(pst_tlv->path_setup_type, 1); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 24); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(lsp->plsp_id, 524303); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_c); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* LSP Vendor Info TLV */ + tlv_node = lsp->header.tlv_list->head; + struct pcep_object_tlv_vendor_info *vendor_tlv = + (struct pcep_object_tlv_vendor_info *)tlv_node->data; + CU_ASSERT_EQUAL(vendor_tlv->header.type, PCEP_OBJ_TLV_TYPE_VENDOR_INFO); + CU_ASSERT_EQUAL(vendor_tlv->header.encoded_tlv_length, 12); + CU_ASSERT_EQUAL(vendor_tlv->enterprise_number, 9); + CU_ASSERT_EQUAL(vendor_tlv->enterprise_specific_info, 0x00030004); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 40); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + CU_ASSERT_PTR_NOT_NULL(ero->sub_objects); + CU_ASSERT_EQUAL(ero->sub_objects->num_entries, 3); + + /* ERO Subobjects */ + double_linked_list_node *ero_subobj_node = ero->sub_objects->head; + struct pcep_ro_subobj_sr *sr_subobj_ipv4_node = + (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73748480); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a05)); + + ero_subobj_node = ero_subobj_node->next_node; + sr_subobj_ipv4_node = (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73736192); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a02)); + + ero_subobj_node = ero_subobj_node->next_node; + sr_subobj_ipv4_node = (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73732096); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a01)); + + /* Metric object */ + obj_node = obj_node->next_node; + struct pcep_object_metric *metric = + (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_FALSE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_TE); + CU_ASSERT_EQUAL(metric->value, 30.0); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_report_cisco_pcc() +{ + int fd = convert_hexstrs_to_binary( + pcep_report_cisco_pcc_hexbyte_strs, + pcep_report_cisco_pcc_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_REPORT); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_report_cisco_pcc_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 8); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 0); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* SRP Path Setup Type TLV */ + double_linked_list_node *tlv_node = srp->header.tlv_list->head; + struct pcep_object_tlv_path_setup_type *pst_tlv = + (struct pcep_object_tlv_path_setup_type *)tlv_node->data; + CU_ASSERT_EQUAL(pst_tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(pst_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(pst_tlv->path_setup_type, 1); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 60); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + /* The TLV with ID 65505 is not recognized, and its not in the list */ + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 2); + CU_ASSERT_EQUAL(lsp->plsp_id, 524303); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_FALSE(lsp->flag_c); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* LSP IPv4 LSP Identifier TLV */ + tlv_node = lsp->header.tlv_list->head; + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp_id = + (struct pcep_object_tlv_ipv4_lsp_identifier *)tlv_node->data; + CU_ASSERT_EQUAL(ipv4_lsp_id->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(ipv4_lsp_id->header.encoded_tlv_length, 16); + CU_ASSERT_EQUAL(ipv4_lsp_id->ipv4_tunnel_sender.s_addr, + htonl(0x0a0a0a06)); + CU_ASSERT_EQUAL(ipv4_lsp_id->ipv4_tunnel_endpoint.s_addr, + htonl(0x0a0a0a01)); + CU_ASSERT_EQUAL(ipv4_lsp_id->extended_tunnel_id.s_addr, + htonl(0x0a0a0a06)); + CU_ASSERT_EQUAL(ipv4_lsp_id->tunnel_id, 15); + CU_ASSERT_EQUAL(ipv4_lsp_id->lsp_id, 2); + + /* LSP Symbolic Path Name TLV */ + tlv_node = tlv_node->next_node; + struct pcep_object_tlv_symbolic_path_name *sym_path_name = + (struct pcep_object_tlv_symbolic_path_name *)tlv_node->data; + CU_ASSERT_EQUAL(sym_path_name->header.type, + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(sym_path_name->header.encoded_tlv_length, 13); + CU_ASSERT_EQUAL(sym_path_name->symbolic_path_name_length, 13); + CU_ASSERT_EQUAL( + strncmp(sym_path_name->symbolic_path_name, "cfg_R6-to-R1", 13), + 0); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 4); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + CU_ASSERT_PTR_NOT_NULL(ero->sub_objects); + CU_ASSERT_EQUAL(ero->sub_objects->num_entries, 0); + + /* LSPA object */ + obj_node = obj_node->next_node; + struct pcep_object_lspa *lspa = + (struct pcep_object_lspa *)obj_node->data; + CU_ASSERT_EQUAL(lspa->header.object_class, PCEP_OBJ_CLASS_LSPA); + CU_ASSERT_EQUAL(lspa->header.object_type, PCEP_OBJ_TYPE_LSPA); + CU_ASSERT_EQUAL(lspa->header.encoded_object_length, 20); + CU_ASSERT_PTR_NULL(lspa->header.tlv_list); + CU_ASSERT_TRUE(lspa->flag_local_protection); + CU_ASSERT_EQUAL(lspa->holding_priority, 7); + CU_ASSERT_EQUAL(lspa->setup_priority, 7); + CU_ASSERT_EQUAL(lspa->lspa_include_all, 0); + CU_ASSERT_EQUAL(lspa->lspa_include_any, 0); + CU_ASSERT_EQUAL(lspa->lspa_exclude_any, 0); + + /* Bandwidth object 1 */ + obj_node = obj_node->next_node; + struct pcep_object_bandwidth *bandwidth = + (struct pcep_object_bandwidth *)obj_node->data; + CU_ASSERT_EQUAL(bandwidth->header.object_class, + PCEP_OBJ_CLASS_BANDWIDTH); + CU_ASSERT_EQUAL(bandwidth->header.object_type, + PCEP_OBJ_TYPE_BANDWIDTH_REQ); + CU_ASSERT_EQUAL(bandwidth->header.encoded_object_length, 8); + CU_ASSERT_EQUAL(bandwidth->bandwidth, 0); + + /* Bandwidth object 2 */ + obj_node = obj_node->next_node; + bandwidth = (struct pcep_object_bandwidth *)obj_node->data; + CU_ASSERT_EQUAL(bandwidth->header.object_class, + PCEP_OBJ_CLASS_BANDWIDTH); + CU_ASSERT_EQUAL(bandwidth->header.object_type, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO); + CU_ASSERT_EQUAL(bandwidth->header.encoded_object_length, 8); + CU_ASSERT_EQUAL(bandwidth->bandwidth, 0); + + /* Metric object 1 */ + obj_node = obj_node->next_node; + struct pcep_object_metric *metric = + (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_FALSE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_TE); + CU_ASSERT_EQUAL(metric->value, 0); + + /* Metric object 2 */ + obj_node = obj_node->next_node; + metric = (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_TRUE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_AGGREGATE_BW); + CU_ASSERT_EQUAL(metric->value, 16.0); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_pcep_msg_read_pcep_initiate_cisco_pcc() +{ + int fd = convert_hexstrs_to_binary( + pcep_initiate_cisco_pcc_hexbyte_strs, + pcep_initiate_cisco_pcc_hexbyte_strs_length); + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate_cisco_pcc_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 6); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 1); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 48); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 2); + CU_ASSERT_EQUAL(lsp->plsp_id, 0); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_TRUE(lsp->flag_c); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* Endpoint object */ + obj_node = obj_node->next_node; + struct pcep_object_endpoints_ipv4 *endpoint = + (struct pcep_object_endpoints_ipv4 *)obj_node->data; + CU_ASSERT_EQUAL(endpoint->header.object_class, + PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(endpoint->header.object_type, + PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(endpoint->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(endpoint->header.tlv_list); + CU_ASSERT_EQUAL(endpoint->src_ipv4.s_addr, htonl(0x0a0a0a0a)); + CU_ASSERT_EQUAL(endpoint->dst_ipv4.s_addr, htonl(0x0a0a0a04)); + + /* Inter-Layer object */ + obj_node = obj_node->next_node; + struct pcep_object_inter_layer *inter_layer = + (struct pcep_object_inter_layer *)obj_node->data; + CU_ASSERT_EQUAL(inter_layer->header.object_class, + PCEP_OBJ_CLASS_INTER_LAYER); + CU_ASSERT_EQUAL(inter_layer->header.object_type, + PCEP_OBJ_TYPE_INTER_LAYER); + CU_ASSERT_EQUAL(inter_layer->header.encoded_object_length, 8); + CU_ASSERT_PTR_NULL(inter_layer->header.tlv_list); + CU_ASSERT_TRUE(inter_layer->flag_i); + CU_ASSERT_FALSE(inter_layer->flag_m); + CU_ASSERT_TRUE(inter_layer->flag_t); + + /* Switch-Layer object */ + obj_node = obj_node->next_node; + struct pcep_object_switch_layer *switch_layer = + (struct pcep_object_switch_layer *)obj_node->data; + CU_ASSERT_EQUAL(switch_layer->header.object_class, + PCEP_OBJ_CLASS_SWITCH_LAYER); + CU_ASSERT_EQUAL(switch_layer->header.object_type, + PCEP_OBJ_TYPE_SWITCH_LAYER); + CU_ASSERT_EQUAL(switch_layer->header.encoded_object_length, 8); + CU_ASSERT_PTR_NULL(switch_layer->header.tlv_list); + CU_ASSERT_PTR_NOT_NULL(switch_layer->switch_layer_rows); + CU_ASSERT_EQUAL(switch_layer->switch_layer_rows->num_entries, 1); + struct pcep_object_switch_layer_row *switch_layer_row = + (struct pcep_object_switch_layer_row *) + switch_layer->switch_layer_rows->head->data; + CU_ASSERT_EQUAL(switch_layer_row->lsp_encoding_type, 0); + CU_ASSERT_EQUAL(switch_layer_row->switching_type, 0); + CU_ASSERT_FALSE(switch_layer_row->flag_i); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 4); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + + pcep_msg_free_message_list(msg_list); + close(fd); +} + +void test_validate_message_header() +{ + uint8_t pcep_message_invalid_version[] = {0x40, 0x01, 0x04, 0x00}; + uint8_t pcep_message_invalid_flags[] = {0x22, 0x01, 0x04, 0x00}; + uint8_t pcep_message_invalid_length[] = {0x20, 0x01, 0x00, 0x00}; + uint8_t pcep_message_invalid_type[] = {0x20, 0xff, 0x04, 0x00}; + uint8_t pcep_message_valid[] = {0x20, 0x01, 0x00, 0x04}; + + /* Verify invalid message header version */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_version) + < 0); + + /* Verify invalid message header flags */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_flags) + < 0); + + /* Verify invalid message header lengths */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_length) + < 0); + pcep_message_invalid_length[3] = 0x05; + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_length) + < 0); + + /* Verify invalid message header types */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_type) < 0); + pcep_message_invalid_type[1] = 0x00; + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_type) < 0); + + /* Verify a valid message header */ + CU_ASSERT_EQUAL(pcep_decode_validate_msg_header(pcep_message_valid), 4); +} + +/* Internal util function */ +struct pcep_message *create_message(uint8_t msg_type, uint8_t obj1_class, + uint8_t obj2_class, uint8_t obj3_class, + uint8_t obj4_class) +{ + struct pcep_message *msg = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + msg->obj_list = dll_initialize(); + msg->msg_header = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_message_header)); + msg->msg_header->type = msg_type; + msg->encoded_message = NULL; + + if (obj1_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj1_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj2_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj2_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj3_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj3_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj4_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj4_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + return msg; +} + +void test_validate_message_objects() +{ + /* Valid Open message */ + struct pcep_message *msg = + create_message(PCEP_TYPE_OPEN, PCEP_OBJ_CLASS_OPEN, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid KeepAlive message */ + msg = create_message(PCEP_TYPE_KEEPALIVE, 0, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid PcReq message */ + /* Using object_class=255 to verify it can take any object */ + msg = create_message(PCEP_TYPE_PCREQ, PCEP_OBJ_CLASS_RP, + PCEP_OBJ_CLASS_ENDPOINTS, any_obj_class, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid PcRep message */ + msg = create_message(PCEP_TYPE_PCREP, PCEP_OBJ_CLASS_RP, any_obj_class, + 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Notify message */ + msg = create_message(PCEP_TYPE_PCNOTF, PCEP_OBJ_CLASS_NOTF, + any_obj_class, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Error message */ + msg = create_message(PCEP_TYPE_ERROR, PCEP_OBJ_CLASS_ERROR, + any_obj_class, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Close message */ + msg = create_message(PCEP_TYPE_CLOSE, PCEP_OBJ_CLASS_CLOSE, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Report message */ + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Update message */ + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Initiate message */ + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); +} + +void test_validate_message_objects_invalid() +{ + /* unsupported message ID = 0 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + struct pcep_message *msg = create_message(0, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Open message + * {PCEP_OBJ_CLASS_OPEN, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_OPEN, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_OPEN, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_OPEN, PCEP_OBJ_CLASS_OPEN, any_obj_class, + 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* KeepAlive message + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_KEEPALIVE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* PcReq message + * {PCEP_OBJ_CLASS_RP, PCEP_OBJ_CLASS_ENDPOINTS, ANY_OBJECT, ANY_OBJECT} + */ + msg = create_message(PCEP_TYPE_PCREQ, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCREQ, PCEP_OBJ_CLASS_RP, any_obj_class, + 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* PcRep message + * {PCEP_OBJ_CLASS_RP, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_PCREP, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCREP, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Notify message + * {PCEP_OBJ_CLASS_NOTF, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_PCNOTF, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCNOTF, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Error message + * {PCEP_OBJ_CLASS_ERROR, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_ERROR, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_ERROR, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Close message + * {PCEP_OBJ_CLASS_CLOSE, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_CLOSE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_CLOSE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* unsupported message ID = 8 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(8, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* unsupported message ID = 9 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(9, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Report message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_REPORT, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Update message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_UPDATE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Initiate message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_INITIATE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); +} diff --git a/pceplib/test/pcep_msg_tools_test.h b/pceplib/test/pcep_msg_tools_test.h new file mode 100644 index 0000000000..dc66390801 --- /dev/null +++ b/pceplib/test/pcep_msg_tools_test.h @@ -0,0 +1,48 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_TOOLS_TEST_H_ +#define PCEP_MSG_TOOLS_TEST_H_ + + +int pcep_tools_test_suite_setup(void); +int pcep_tools_test_suite_teardown(void); +void pcep_tools_test_setup(void); +void pcep_tools_test_teardown(void); +void test_pcep_msg_read_pcep_initiate(void); +void test_pcep_msg_read_pcep_initiate2(void); +void test_pcep_msg_read_pcep_update(void); +void test_pcep_msg_read_pcep_open(void); +void test_pcep_msg_read_pcep_open_initiate(void); +void test_validate_message_header(void); +void test_validate_message_objects(void); +void test_validate_message_objects_invalid(void); +void test_pcep_msg_read_pcep_open_cisco_pce(void); +void test_pcep_msg_read_pcep_update_cisco_pce(void); +void test_pcep_msg_read_pcep_report_cisco_pcc(void); +void test_pcep_msg_read_pcep_initiate_cisco_pcc(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_pcc_api_test.c b/pceplib/test/pcep_pcc_api_test.c new file mode 100644 index 0000000000..c227dc1a3d --- /dev/null +++ b/pceplib/test/pcep_pcc_api_test.c @@ -0,0 +1,285 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include // gethostbyname +#include +#include +#include + +#include + +#include "pcep_pcc_api.h" +#include "pcep_pcc_api_test.h" +#include "pcep_socket_comm_mock.h" +#include "pcep_utils_memory.h" + +extern pcep_event_queue *session_logic_event_queue_; +extern const char MESSAGE_RECEIVED_STR[]; +extern const char UNKNOWN_EVENT_STR[]; + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_pcc_api_test_suite_setup() +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_pcc_api_test_suite_teardown() +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_pcc_api_test_setup() +{ + setup_mock_socket_comm_info(); +} + + +void pcep_pcc_api_test_teardown() +{ + teardown_mock_socket_comm_info(); +} + +/* + * Unit test cases + */ + +void test_initialize_pcc() +{ + CU_ASSERT_TRUE(initialize_pcc()); + /* Give the PCC time to initialize */ + sleep(1); + CU_ASSERT_TRUE(destroy_pcc()); +} + +void test_connect_pce() +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_connect_pce_ipv6() +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct in6_addr dest_address; + dest_address.__in6_u.__u6_addr32[0] = 0; + dest_address.__in6_u.__u6_addr32[1] = 0; + dest_address.__in6_u.__u6_addr32[2] = 0; + dest_address.__in6_u.__u6_addr32[3] = htonl(1); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce_ipv6(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_TRUE(session->socket_comm_session->is_ipv6); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_connect_pce_with_src_ip() +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config->src_ip.src_ipv4.s_addr = 0x0a0a0102; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_disconnect_pce() +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + disconnect_pce(session); + + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 2); + + /* First there should be an open message from connect_pce() */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Then there should be a close message from disconnect_pce() */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_CLOSE); + + pcep_msg_free_message(msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + + +void test_send_message() +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + + initialize_pcc(); + + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + pcep_session *session = connect_pce(config, &dest_address); + verify_socket_comm_times_called(0, 0, 1, 1, 0, 0, 0); + + struct pcep_message *msg = pcep_msg_create_keepalive(); + send_message(session, msg, false); + + verify_socket_comm_times_called(0, 0, 1, 2, 0, 0, 0); + + pcep_msg_free_message(msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + + destroy_pcc(); +} + +void test_event_queue() +{ + /* This initializes the event_queue */ + CU_ASSERT_TRUE(initialize_pcc()); + + /* Verify correct behavior when the queue is empty */ + CU_ASSERT_TRUE(event_queue_is_empty()); + CU_ASSERT_EQUAL(event_queue_num_events_available(), 0); + CU_ASSERT_PTR_NULL(event_queue_get_event()); + destroy_pcep_event(NULL); + + /* Create an empty event and put it on the queue */ + pcep_event *event = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event)); + memset(event, 0, sizeof(pcep_event)); + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + queue_enqueue(session_logic_event_queue_->event_queue, event); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + /* Verify correct behavior when there is an entry in the queue */ + CU_ASSERT_FALSE(event_queue_is_empty()); + CU_ASSERT_EQUAL(event_queue_num_events_available(), 1); + pcep_event *queued_event = event_queue_get_event(); + CU_ASSERT_PTR_NOT_NULL(queued_event); + CU_ASSERT_PTR_EQUAL(event, queued_event); + destroy_pcep_event(queued_event); + + CU_ASSERT_TRUE(destroy_pcc()); +} + +void test_get_event_type_str() +{ + CU_ASSERT_EQUAL(strcmp(get_event_type_str(MESSAGE_RECEIVED), + MESSAGE_RECEIVED_STR), + 0); + CU_ASSERT_EQUAL(strcmp(get_event_type_str(1000), UNKNOWN_EVENT_STR), 0); +} diff --git a/pceplib/test/pcep_pcc_api_test.h b/pceplib/test/pcep_pcc_api_test.h new file mode 100644 index 0000000000..d3db96e0ce --- /dev/null +++ b/pceplib/test/pcep_pcc_api_test.h @@ -0,0 +1,43 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_PCC_API_TEST_ +#define PCEP_PCC_API_TEST_ + +int pcep_pcc_api_test_suite_setup(void); +int pcep_pcc_api_test_suite_teardown(void); +void pcep_pcc_api_test_setup(void); +void pcep_pcc_api_test_teardown(void); +void test_initialize_pcc(void); +void test_connect_pce(void); +void test_connect_pce_ipv6(void); +void test_connect_pce_with_src_ip(void); +void test_disconnect_pce(void); +void test_send_message(void); +void test_event_queue(void); +void test_get_event_type_str(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_pcc_api_tests.c b/pceplib/test/pcep_pcc_api_tests.c new file mode 100644 index 0000000000..04895d6340 --- /dev/null +++ b/pceplib/test/pcep_pcc_api_tests.c @@ -0,0 +1,88 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_pcc_api_test.h" + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_pcc_api_suite = CU_add_suite_with_setup_and_teardown( + "PCEP PCC API Test Suite", + pcep_pcc_api_test_suite_setup, // suite setup and cleanup + // function pointers + pcep_pcc_api_test_suite_teardown, + pcep_pcc_api_test_setup, // test case setup function pointer + pcep_pcc_api_test_teardown); // test case teardown function + // pointer + + CU_add_test(test_pcc_api_suite, "test_initialize_pcc", + test_initialize_pcc); + CU_add_test(test_pcc_api_suite, "test_connect_pce", test_connect_pce); + CU_add_test(test_pcc_api_suite, "test_connect_pce_ipv6", + test_connect_pce_ipv6); + CU_add_test(test_pcc_api_suite, "test_connect_pce_with_src_ip", + test_connect_pce_with_src_ip); + CU_add_test(test_pcc_api_suite, "test_disconnect_pce", + test_disconnect_pce); + CU_add_test(test_pcc_api_suite, "test_send_message", test_send_message); + CU_add_test(test_pcc_api_suite, "test_event_queue", test_event_queue); + CU_add_test(test_pcc_api_suite, "test_get_event_type_str", + test_get_event_type_str); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_pcc_api_tests_valgrind.sh b/pceplib/test/pcep_pcc_api_tests_valgrind.sh new file mode 100755 index 0000000000..74494b7521 --- /dev/null +++ b/pceplib/test/pcep_pcc_api_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_pcc_api_tests diff --git a/pceplib/test/pcep_session_logic_loop_test.c b/pceplib/test/pcep_session_logic_loop_test.c new file mode 100644 index 0000000000..38fabd4ccd --- /dev/null +++ b/pceplib/test/pcep_session_logic_loop_test.c @@ -0,0 +1,219 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_memory.h" +#include "pcep_session_logic_loop_test.h" + + +extern pcep_session_logic_handle *session_logic_handle_; +extern pcep_event_queue *session_logic_event_queue_; + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_loop_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_loop_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_loop_test_setup() +{ + /* We need to setup the session_logic_handle_ without starting the + * thread */ + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + session_logic_handle_->active = true; + session_logic_handle_->session_logic_condition = false; + session_logic_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + session_logic_handle_->session_event_queue = queue_initialize(); + pthread_cond_init(&(session_logic_handle_->session_logic_cond_var), + NULL); + pthread_mutex_init(&(session_logic_handle_->session_logic_mutex), NULL); + pthread_mutex_init(&(session_logic_handle_->session_list_mutex), NULL); + + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + memset(session_logic_event_queue_, 0, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); +} + + +void pcep_session_logic_loop_test_teardown() +{ + ordered_list_destroy(session_logic_handle_->session_list); + queue_destroy(session_logic_handle_->session_event_queue); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex)); + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + session_logic_handle_ = NULL; + + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + session_logic_event_queue_ = NULL; +} + + +/* + * Test cases + */ + +void test_session_logic_loop_null_data() +{ + /* Just testing that it does not core dump */ + session_logic_loop(NULL); +} + + +void test_session_logic_loop_inactive() +{ + session_logic_handle_->active = false; + + session_logic_loop(session_logic_handle_); +} + + +void test_session_logic_msg_ready_handler() +{ + /* Just testing that it does not core dump */ + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(NULL, 0), -1); + + /* Read from an empty file should return 0, thus + * session_logic_msg_ready_handler returns -1 */ + int fd = fileno(tmpfile()); + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(&session, fd), 0); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL(socket_event); + CU_ASSERT_TRUE(socket_event->socket_closed); + pceplib_free(PCEPLIB_INFRA, socket_event); + + /* A pcep_session_event should be created */ + struct pcep_versioning *versioning = create_default_pcep_versioning(); + struct pcep_message *keep_alive_msg = pcep_msg_create_keepalive(); + pcep_encode_message(keep_alive_msg, versioning); + int retval = write(fd, (char *)keep_alive_msg->encoded_message, + keep_alive_msg->encoded_message_length); + CU_ASSERT_TRUE(retval > 0); + lseek(fd, 0, SEEK_SET); + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(&session, fd), + keep_alive_msg->encoded_message_length); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL(socket_event); + CU_ASSERT_FALSE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, TIMER_ID_NOT_SET); + CU_ASSERT_PTR_NOT_NULL(socket_event->received_msg_list); + pcep_msg_free_message_list(socket_event->received_msg_list); + pcep_msg_free_message(keep_alive_msg); + destroy_pcep_versioning(versioning); + pceplib_free(PCEPLIB_INFRA, socket_event); + close(fd); +} + + +void test_session_logic_conn_except_notifier() +{ + /* Just testing that it does not core dump */ + session_logic_conn_except_notifier(NULL, 1); + + /* A pcep_session_event should be created */ + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + session_logic_conn_except_notifier(&session, 10); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL_FATAL(socket_event); + CU_ASSERT_TRUE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, TIMER_ID_NOT_SET); + CU_ASSERT_PTR_NULL(socket_event->received_msg_list); + + pceplib_free(PCEPLIB_INFRA, socket_event); +} + + +void test_session_logic_timer_expire_handler() +{ + /* Just testing that it does not core dump */ + session_logic_timer_expire_handler(NULL, 42); + + /* A pcep_session_event should be created */ + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + session_logic_timer_expire_handler(&session, 42); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL_FATAL(socket_event); + CU_ASSERT_FALSE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, 42); + CU_ASSERT_PTR_NULL(socket_event->received_msg_list); + + pceplib_free(PCEPLIB_INFRA, socket_event); +} diff --git a/pceplib/test/pcep_session_logic_loop_test.h b/pceplib/test/pcep_session_logic_loop_test.h new file mode 100644 index 0000000000..ae3c3e3753 --- /dev/null +++ b/pceplib/test/pcep_session_logic_loop_test.h @@ -0,0 +1,40 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_LOOP_TEST_H_ +#define PCEP_SESSION_LOGIC_LOOP_TEST_H_ + +int pcep_session_logic_loop_test_suite_setup(void); +int pcep_session_logic_loop_test_suite_teardown(void); +void pcep_session_logic_loop_test_setup(void); +void pcep_session_logic_loop_test_teardown(void); +void test_session_logic_loop_null_data(void); +void test_session_logic_loop_inactive(void); +void test_session_logic_msg_ready_handler(void); +void test_session_logic_conn_except_notifier(void); +void test_session_logic_timer_expire_handler(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_states_test.c b/pceplib/test/pcep_session_logic_states_test.c new file mode 100644 index 0000000000..f75c16e397 --- /dev/null +++ b/pceplib/test/pcep_session_logic_states_test.c @@ -0,0 +1,919 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include + +#include + +#include "pcep_socket_comm_mock.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_session_logic_states_test.h" + +/* Functions being tested */ +extern pcep_session_logic_handle *session_logic_handle_; +extern pcep_event_queue *session_logic_event_queue_; + +static pcep_session_event event; +static pcep_session session; +/* A message list is a dll of struct pcep_messages_list_node items */ +static double_linked_list *msg_list; +struct pcep_message *message; +static bool free_msg_list; +static bool msg_enqueued; +/* Forward declaration */ +void destroy_message_for_test(void); +void create_message_for_test(uint8_t msg_type, bool free_msg_list_at_teardown, + bool was_msg_enqueued); +void test_handle_timer_event_open_keep_alive(void); + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_states_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_states_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_states_test_setup() +{ + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + memset(session_logic_event_queue_, 0, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); + + memset(&session, 0, sizeof(pcep_session)); + session.pcc_config.keep_alive_seconds = 5; + session.pcc_config.keep_alive_pce_negotiated_timer_seconds = 5; + session.pcc_config.min_keep_alive_seconds = 1; + session.pcc_config.max_keep_alive_seconds = 10; + session.pcc_config.dead_timer_seconds = 5; + session.pcc_config.dead_timer_pce_negotiated_seconds = 5; + session.pcc_config.min_dead_timer_seconds = 1; + session.pcc_config.max_dead_timer_seconds = 10; + session.pcc_config.max_unknown_messages = 2; + memcpy(&session.pce_config, &session.pcc_config, + sizeof(pcep_configuration)); + session.num_unknown_messages_time_queue = queue_initialize(); + + memset(&event, 0, sizeof(pcep_session_event)); + event.socket_closed = false; + event.session = &session; + + setup_mock_socket_comm_info(); + free_msg_list = false; + msg_enqueued = false; +} + + +void pcep_session_logic_states_test_teardown() +{ + destroy_message_for_test(); + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + session_logic_handle_ = NULL; + session_logic_event_queue_ = NULL; + queue_destroy_with_data(session.num_unknown_messages_time_queue); + teardown_mock_socket_comm_info(); +} + +void create_message_for_test(uint8_t msg_type, bool free_msg_list_at_teardown, + bool was_msg_enqueued) +{ + /* See the comments in destroy_message_for_test() about these 2 + * variables */ + free_msg_list = free_msg_list_at_teardown; + msg_enqueued = was_msg_enqueued; + + message = pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + memset(message, 0, sizeof(struct pcep_message)); + + message->msg_header = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_message_header)); + memset(message->msg_header, 0, sizeof(struct pcep_message_header)); + message->obj_list = dll_initialize(); + message->msg_header->type = msg_type; + + msg_list = dll_initialize(); + dll_append(msg_list, message); + event.received_msg_list = msg_list; +} + +void destroy_message_for_test() +{ + /* Some test cases internally free the message list, so we dont + * want to double free it */ + if (free_msg_list == true) { + /* This will destroy both the msg_list and the obj_list */ + pcep_msg_free_message_list(msg_list); + } + + /* Some tests cause the message to be enqueued and dont delete it, + * so we have to delete it here */ + if (msg_enqueued == true) { + pcep_msg_free_message(message); + } +} + +/* + * Test cases + */ + +void test_handle_timer_event_dead_timer() +{ + /* Dead Timer expired */ + event.expired_timer_id = session.timer_id_dead_timer = 100; + + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_dead_timer, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_DEAD_TIMER_EXPIRED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* verify_socket_comm_times_called( + * initialized, teardown, connect, send_message, close_after_write, + * close, destroy); */ + verify_socket_comm_times_called(0, 0, 0, 1, 1, 0, 0); +} + + +void test_handle_timer_event_keep_alive() +{ + /* Keep Alive timer expired */ + event.expired_timer_id = session.timer_id_keep_alive = 200; + + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_keep_alive, TIMER_ID_NOT_SET); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); +} + + +void test_handle_timer_event_open_keep_wait() +{ + /* Open Keep Wait timer expired */ + event.expired_timer_id = session.timer_id_open_keep_wait = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 1, 0, 0); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* If the state is not SESSION_STATE_PCEP_CONNECTED, then nothing should + * happen */ + reset_mock_socket_comm_info(); + session.session_state = SESSION_STATE_UNKNOWN; + event.expired_timer_id = session.timer_id_open_keep_wait = 300; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, 300); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_UNKNOWN); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); +} + + +void test_handle_timer_event_open_keep_alive() +{ + /* Open Keep Alive timer expired, but the Keep Alive should not be sent + * since the PCE Open has not been received yet */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = false; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + + /* Open Keep Alive timer expired, the Keep Alive should be sent, + * but the session should not be connected, since the PCC Open + * has not been accepted yet */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = true; + session.pce_open_rejected = false; + session.pcc_open_accepted = false; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + + /* Open Keep Alive timer expired, the Keep Alive should be sent, + * and the session is connected */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = true; + session.pce_open_rejected = false; + session.pcc_open_accepted = true; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); +} + + +void test_handle_socket_comm_event_null_params() +{ + /* Verify it doesnt core dump */ + handle_socket_comm_event(NULL); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + reset_mock_socket_comm_info(); + + event.received_msg_list = NULL; + handle_socket_comm_event(&event); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); +} + + +void test_handle_socket_comm_event_close() +{ + event.socket_closed = true; + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 1, 0); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_open() +{ + /* + * Test when a PCE Open is received, but the PCC Open has not been + * accepted yet + */ + create_message_for_test(PCEP_TYPE_OPEN, false, true); + struct pcep_object_open *open_object = + pcep_obj_create_open(1, 1, 1, NULL); + dll_append(message->obj_list, open_object); + session.pcc_open_accepted = false; + session.pce_open_received = false; + session.pce_open_accepted = false; + session.timer_id_open_keep_alive = 100; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_accepted); + CU_ASSERT_FALSE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_NOT_EQUAL(session.timer_id_open_keep_alive, 100); + /* A keep alive response should NOT be sent yet */ + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_OPEN, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* + * Test when a PCE Open is received, and the PCC Open has been accepted + */ + create_message_for_test(PCEP_TYPE_OPEN, false, true); + reset_mock_socket_comm_info(); + open_object = pcep_obj_create_open(1, 1, 1, NULL); + dll_append(message->obj_list, open_object); + session.pcc_open_accepted = true; + session.pce_open_received = false; + session.pce_open_accepted = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_accepted); + CU_ASSERT_FALSE(session.pce_open_rejected); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + /* A keep alive response should be sent, accepting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_OPEN, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTED_TO_PCE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* + * Send a 2nd Open, an error should be sent + */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_error *error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_RECVD_INVALID_OPEN_MSG, + error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_open_error() +{ + /* Test when the PCE rejects the PCC Open with an Error + * that a "corrected" Open message is sent. */ + + create_message_for_test(PCEP_TYPE_ERROR, false, true); + struct pcep_object_error *error_object = pcep_obj_create_error( + PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG); + struct pcep_object_open *error_open_object = + pcep_obj_create_open(1, 1, 1, NULL); + /* The configured [Keep-alive, Dead-timer] values are [5, 5], + * this error open object will request they be changed to [10, 10] */ + error_open_object->open_keepalive = 10; + error_open_object->open_deadtimer = 10; + dll_append(message->obj_list, error_object); + dll_append(message->obj_list, error_open_object); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* Another Open should be sent */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_SENT_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + + /* Check the Corrected Open Message */ + + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg_corrected = + pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg_corrected); + struct pcep_object_open *open_object_corrected = + (struct pcep_object_open *)pcep_obj_get( + open_msg_corrected->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_object_corrected); + /* Verify the Keep-alive and Dead timers have been negotiated */ + CU_ASSERT_EQUAL(error_open_object->open_keepalive, + open_object_corrected->open_keepalive); + CU_ASSERT_EQUAL(error_open_object->open_deadtimer, + open_object_corrected->open_deadtimer); + CU_ASSERT_EQUAL(session.pcc_config.dead_timer_pce_negotiated_seconds, + open_object_corrected->open_deadtimer); + CU_ASSERT_EQUAL( + session.pcc_config.keep_alive_pce_negotiated_timer_seconds, + open_object_corrected->open_keepalive); + CU_ASSERT_NOT_EQUAL( + session.pcc_config.dead_timer_pce_negotiated_seconds, + session.pcc_config.dead_timer_seconds); + CU_ASSERT_NOT_EQUAL( + session.pcc_config.keep_alive_pce_negotiated_timer_seconds, + session.pcc_config.keep_alive_seconds); + + pcep_msg_free_message(open_msg_corrected); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_keep_alive() +{ + /* Test when a Keep Alive is received, but the PCE Open has not been + * received yet */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_accepted = false; + session.pce_open_received = false; + session.pcc_open_accepted = false; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + + /* Test when a Keep Alive is received, and the PCE Open has been + * received and accepted */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_received = true; + session.pce_open_accepted = true; + session.pcc_open_accepted = false; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + + /* Test when a Keep Alive is received, and the PCE Open has been + * received and rejected */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_received = true; + session.pce_open_accepted = false; + session.pce_open_rejected = true; + session.pce_open_keep_alive_sent = false; + session.pcc_open_accepted = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + + /* The session is considered connected, when both the + * PCE and PCC Open messages have been accepted */ + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTED_TO_PCE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_pcrep() +{ + create_message_for_test(PCEP_TYPE_PCREP, false, true); + struct pcep_object_rp *rp = + pcep_obj_create_rp(1, true, true, true, true, 1, NULL); + dll_append(message->obj_list, rp); + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_pcreq() +{ + create_message_for_test(PCEP_TYPE_PCREQ, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* The PCC does not support receiving PcReq messages, so an error should + * be sent */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *error_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(error_msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, error_msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, error_msg->obj_list->num_entries); + struct pcep_object_error *obj = error_msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, obj->error_value); + pcep_msg_free_message(error_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_report() +{ + create_message_for_test(PCEP_TYPE_REPORT, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* The PCC does not support receiving Report messages, so an error + * should be sent */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *error_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(error_msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, error_msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, error_msg->obj_list->num_entries); + struct pcep_object_error *obj = error_msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, obj->error_value); + pcep_msg_free_message(error_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_update() +{ + create_message_for_test(PCEP_TYPE_UPDATE, false, true); + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + double_linked_list *ero_subobj_list = dll_initialize(); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + struct pcep_object_metric *metric = + pcep_obj_create_metric(PCEP_METRIC_TE, false, true, 16.0); + dll_append(message->obj_list, srp); + dll_append(message->obj_list, lsp); + dll_append(message->obj_list, ero); + dll_append(message->obj_list, metric); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_UPDATE, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_initiate() +{ + create_message_for_test(PCEP_TYPE_INITIATE, false, true); + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(message->obj_list, srp); + dll_append(message->obj_list, lsp); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_INITIATE, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_notify() +{ + create_message_for_test(PCEP_TYPE_PCNOTF, false, true); + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_PCNOTF, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_error() +{ + create_message_for_test(PCEP_TYPE_ERROR, false, true); + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_unknown_msg() +{ + create_message_for_test(13, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* Sending an unsupported message type, so an error should be sent, + * but the connection should remain open, since max_unknown_messages = 2 + */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_error *error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + destroy_message_for_test(); + + /* Send another unsupported message type, an error should be sent and + * the connection should be closed, since max_unknown_messages = 2 */ + create_message_for_test(13, false, false); + reset_mock_socket_comm_info(); + mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + verify_socket_comm_times_called(0, 0, 0, 2, 1, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_MAX_UNKOWN_MSGS, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* Verify the error message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the Close message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(PCEP_TYPE_CLOSE, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_close *close_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_CLOSE, close_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_CLOSE, close_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_CLOSE_REASON_UNREC_MSG, close_obj->reason); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_connection_failure(void) +{ + /* + * Test when 2 invalid Open messages are received that a + * PCC_CONNECTION_FAILURE event is generated. + */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + struct pcep_object_open *open_object = + pcep_obj_create_open(1, 1, 1, NULL); + /* Make the Open message invalid */ + open_object->open_deadtimer = + session.pcc_config.max_dead_timer_seconds + 1; + dll_append(message->obj_list, open_object); + session.pce_open_received = false; + session.pce_open_accepted = false; + session.pce_open_rejected = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* An error response should be sent, rejecting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* Send the same erroneous Open again */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + open_object = pcep_obj_create_open(1, 1, 1, NULL); + /* Make the Open message invalid */ + open_object->open_deadtimer = + session.pcc_config.max_dead_timer_seconds + 1; + dll_append(message->obj_list, open_object); + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + /* An error response should be sent, rejecting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 1, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTION_FAILURE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + destroy_message_for_test(); + + /* + * Test when 2 invalid Open messages are sent that a + * PCC_CONNECTION_FAILURE event is generated. + */ + create_message_for_test(PCEP_TYPE_ERROR, false, false); + reset_mock_socket_comm_info(); + struct pcep_object_error *error_object = pcep_obj_create_error( + PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG); + dll_append(message->obj_list, error_object); + session.pcc_open_accepted = false; + session.pcc_open_rejected = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pcc_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* Another Open should be sent */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_SENT_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* Send a socket close while connecting, which should + * generate a PCC_CONNECTION_FAILURE event */ + reset_mock_socket_comm_info(); + event.socket_closed = true; + event.received_msg_list = NULL; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pcc_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 1, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTION_FAILURE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} diff --git a/pceplib/test/pcep_session_logic_states_test.h b/pceplib/test/pcep_session_logic_states_test.h new file mode 100644 index 0000000000..e42b501ed9 --- /dev/null +++ b/pceplib/test/pcep_session_logic_states_test.h @@ -0,0 +1,52 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_STATES_TEST_H +#define PCEP_SESSION_LOGIC_STATES_TEST_H + +int pcep_session_logic_states_test_suite_setup(void); +int pcep_session_logic_states_test_suite_teardown(void); +void pcep_session_logic_states_test_setup(void); +void pcep_session_logic_states_test_teardown(void); +void test_handle_timer_event_dead_timer(void); +void test_handle_timer_event_keep_alive(void); +void test_handle_timer_event_open_keep_wait(void); +void test_handle_socket_comm_event_null_params(void); +void test_handle_socket_comm_event_close(void); +void test_handle_socket_comm_event_open(void); +void test_handle_socket_comm_event_open_error(void); +void test_handle_socket_comm_event_keep_alive(void); +void test_handle_socket_comm_event_pcrep(void); +void test_handle_socket_comm_event_pcreq(void); +void test_handle_socket_comm_event_report(void); +void test_handle_socket_comm_event_update(void); +void test_handle_socket_comm_event_initiate(void); +void test_handle_socket_comm_event_notify(void); +void test_handle_socket_comm_event_error(void); +void test_handle_socket_comm_event_unknown_msg(void); +void test_connection_failure(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_test.c b/pceplib/test/pcep_session_logic_test.c new file mode 100644 index 0000000000..66db4fbaea --- /dev/null +++ b/pceplib/test/pcep_session_logic_test.c @@ -0,0 +1,360 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include + +#include "pcep_socket_comm_mock.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_test.h" + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_test_setup() +{ + setup_mock_socket_comm_info(); +} + + +void pcep_session_logic_test_teardown() +{ + stop_session_logic(); + teardown_mock_socket_comm_info(); +} + + +/* + * Test cases + */ + +void test_run_stop_session_logic() +{ + CU_ASSERT_TRUE(run_session_logic()); + CU_ASSERT_TRUE(stop_session_logic()); +} + + +void test_run_session_logic_twice() +{ + CU_ASSERT_TRUE(run_session_logic()); + CU_ASSERT_FALSE(run_session_logic()); +} + + +void test_session_logic_without_run() +{ + /* Verify the functions that depend on run_session_logic() being called + */ + CU_ASSERT_FALSE(stop_session_logic()); +} + + +void test_create_pcep_session_null_params() +{ + pcep_configuration config; + struct in_addr pce_ip; + + CU_ASSERT_PTR_NULL(create_pcep_session(NULL, NULL)); + CU_ASSERT_PTR_NULL(create_pcep_session(NULL, &pce_ip)); + CU_ASSERT_PTR_NULL(create_pcep_session(&config, NULL)); +} + + +void test_create_destroy_pcep_session() +{ + pcep_session *session; + pcep_configuration config; + struct in_addr pce_ip; + + run_session_logic(); + + memset(&config, 0, sizeof(pcep_configuration)); + config.keep_alive_seconds = 5; + config.dead_timer_seconds = 5; + config.request_time_seconds = 5; + config.max_unknown_messages = 5; + config.max_unknown_requests = 5; + inet_pton(AF_INET, "127.0.0.1", &(pce_ip)); + + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Should be an Open, with no TLVs: length = 12 */ + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(open_msg->encoded_message_length, 12); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_create_destroy_pcep_session_ipv6() +{ + pcep_session *session; + pcep_configuration config; + struct in6_addr pce_ip; + + run_session_logic(); + + memset(&config, 0, sizeof(pcep_configuration)); + config.keep_alive_seconds = 5; + config.dead_timer_seconds = 5; + config.request_time_seconds = 5; + config.max_unknown_messages = 5; + config.max_unknown_requests = 5; + config.is_src_ipv6 = true; + inet_pton(AF_INET6, "::1", &pce_ip); + + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + session = create_pcep_session_ipv6(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_TRUE(session->socket_comm_session->is_ipv6); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Should be an Open, with no TLVs: length = 12 */ + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(open_msg->encoded_message_length, 12); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_create_pcep_session_open_tlvs() +{ + pcep_session *session; + struct in_addr pce_ip; + struct pcep_message *open_msg; + struct pcep_object_header *open_obj; + pcep_configuration config; + memset(&config, 0, sizeof(pcep_configuration)); + config.pcep_msg_versioning = create_default_pcep_versioning(); + inet_pton(AF_INET, "127.0.0.1", &(pce_ip)); + + run_session_logic(); + + /* Verify the created Open message only has 1 TLV: + * pcep_tlv_create_stateful_pce_capability() */ + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_stateful_pce_lsp_update = true; + config.pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = false; + config.support_sr_te_pst = false; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->data) + ->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the created Open message only has 2 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_include_db_version = true; + config.lsp_db_version = 100; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 2); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->data) + ->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->next_node->data) + ->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + + /* Verify the created Open message only has 4 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() + * pcep_tlv_create_sr_pce_capability() + * pcep_tlv_create_path_setup_type_capability() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_sr_te_pst = true; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 3); + double_linked_list_node *tlv_node = open_obj->tlv_list->head; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the created Open message only has 4 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() + * pcep_tlv_create_sr_pce_capability() + * pcep_tlv_create_path_setup_type_capability() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = true; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 4); + tlv_node = open_obj->tlv_list->head; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + + destroy_pcep_versioning(config.pcep_msg_versioning); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_destroy_pcep_session_null_session() +{ + /* Just testing that it does not core dump */ + destroy_pcep_session(NULL); +} diff --git a/pceplib/test/pcep_session_logic_test.h b/pceplib/test/pcep_session_logic_test.h new file mode 100644 index 0000000000..6cc1963250 --- /dev/null +++ b/pceplib/test/pcep_session_logic_test.h @@ -0,0 +1,43 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_TEST_H_ +#define PCEP_SESSION_LOGIC_TEST_H_ + +int pcep_session_logic_test_suite_setup(void); +int pcep_session_logic_test_suite_teardown(void); +void pcep_session_logic_test_setup(void); +void pcep_session_logic_test_teardown(void); +void test_run_stop_session_logic(void); +void test_run_session_logic_twice(void); +void test_session_logic_without_run(void); +void test_create_pcep_session_null_params(void); +void test_create_destroy_pcep_session(void); +void test_create_destroy_pcep_session_ipv6(void); +void test_create_pcep_session_open_tlvs(void); +void test_destroy_pcep_session_null_session(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_tests.c b/pceplib/test/pcep_session_logic_tests.c new file mode 100644 index 0000000000..67bf6e22ef --- /dev/null +++ b/pceplib/test/pcep_session_logic_tests.c @@ -0,0 +1,201 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_session_logic_loop_test.h" +#include "pcep_session_logic_states_test.h" +#include "pcep_session_logic_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_session_logic_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic Test Suite", + pcep_session_logic_test_suite_setup, // suite setup and + // cleanup function + // pointers + pcep_session_logic_test_suite_teardown, + pcep_session_logic_test_setup, // test case setup + // function pointer + pcep_session_logic_test_teardown); // test case teardown + // function pointer + + CU_add_test(test_session_logic_suite, "test_run_stop_session_logic", + test_run_stop_session_logic); + CU_add_test(test_session_logic_suite, "test_run_session_logic_twice", + test_run_session_logic_twice); + CU_add_test(test_session_logic_suite, "test_session_logic_without_run", + test_session_logic_without_run); + CU_add_test(test_session_logic_suite, + "test_create_pcep_session_null_params", + test_create_pcep_session_null_params); + CU_add_test(test_session_logic_suite, + "test_create_destroy_pcep_session", + test_create_destroy_pcep_session); + CU_add_test(test_session_logic_suite, + "test_create_destroy_pcep_session_ipv6", + test_create_destroy_pcep_session_ipv6); + CU_add_test(test_session_logic_suite, + "test_create_pcep_session_open_tlvs", + test_create_pcep_session_open_tlvs); + CU_add_test(test_session_logic_suite, + "test_destroy_pcep_session_null_session", + test_destroy_pcep_session_null_session); + + CU_pSuite test_session_logic_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic Loop Test Suite", + pcep_session_logic_loop_test_suite_setup, // suite setup + // and cleanup + // function + // pointers + pcep_session_logic_loop_test_suite_teardown, + pcep_session_logic_loop_test_setup, // test case setup + // function pointer + pcep_session_logic_loop_test_teardown); // test case + // teardown + // function + // pointer + + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_loop_null_data", + test_session_logic_loop_null_data); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_loop_inactive", + test_session_logic_loop_inactive); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_msg_ready_handler", + test_session_logic_msg_ready_handler); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_conn_except_notifier", + test_session_logic_conn_except_notifier); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_timer_expire_handler", + test_session_logic_timer_expire_handler); + + CU_pSuite test_session_logic_states_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic States Test Suite", + pcep_session_logic_states_test_suite_setup, // suite + // setup and + // cleanup + // function + // pointers + pcep_session_logic_states_test_suite_teardown, + pcep_session_logic_states_test_setup, // test case setup + // function + // pointer + pcep_session_logic_states_test_teardown); // test case + // teardown + // function + // pointer + + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_dead_timer", + test_handle_timer_event_dead_timer); + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_keep_alive", + test_handle_timer_event_keep_alive); + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_open_keep_wait", + test_handle_timer_event_open_keep_wait); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_null_params", + test_handle_socket_comm_event_null_params); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_close", + test_handle_socket_comm_event_close); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_open", + test_handle_socket_comm_event_open); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_open_error", + test_handle_socket_comm_event_open_error); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_keep_alive", + test_handle_socket_comm_event_keep_alive); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_pcrep", + test_handle_socket_comm_event_pcrep); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_pcreq", + test_handle_socket_comm_event_pcreq); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_report", + test_handle_socket_comm_event_report); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_update", + test_handle_socket_comm_event_update); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_initiate", + test_handle_socket_comm_event_initiate); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_notify", + test_handle_socket_comm_event_notify); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_error", + test_handle_socket_comm_event_error); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_unknown_msg", + test_handle_socket_comm_event_unknown_msg); + CU_add_test(test_session_logic_states_suite, "test_connection_failure", + test_connection_failure); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_session_logic_tests_valgrind.sh b/pceplib/test/pcep_session_logic_tests_valgrind.sh new file mode 100755 index 0000000000..435bb3d5c4 --- /dev/null +++ b/pceplib/test/pcep_session_logic_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_session_logic_tests diff --git a/pceplib/test/pcep_socket_comm_loop_test.c b/pceplib/test/pcep_socket_comm_loop_test.c new file mode 100644 index 0000000000..94f0983ca7 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_loop_test.c @@ -0,0 +1,194 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include + +#include + +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_loop.h" +#include "pcep_socket_comm_loop_test.h" +#include "pcep_socket_comm.h" +#include "pcep_utils_memory.h" + +void test_loop_conn_except_notifier(void *session_data, int socket_fd); + +/* + * Functions to be tested, implemented in pcep_socket_comm_loop.c + */ + +typedef struct ready_to_read_handler_info_ { + bool handler_called; + bool except_handler_called; + void *data; + int socket_fd; + int bytes_read; + +} ready_to_read_handler_info; + +static ready_to_read_handler_info read_handler_info; +static pcep_socket_comm_session *test_comm_session; +static pcep_socket_comm_handle *test_socket_comm_handle = NULL; + +static int test_loop_message_ready_to_read_handler(void *session_data, + int socket_fd) +{ + read_handler_info.handler_called = true; + read_handler_info.data = session_data; + read_handler_info.socket_fd = socket_fd; + + return read_handler_info.bytes_read; +} + + +void test_loop_conn_except_notifier(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + read_handler_info.except_handler_called = true; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ +void pcep_socket_comm_loop_test_setup() +{ + test_socket_comm_handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_handle)); + memset(test_socket_comm_handle, 0, sizeof(pcep_socket_comm_handle)); + test_socket_comm_handle->active = false; + test_socket_comm_handle->read_list = + ordered_list_initialize(socket_fd_node_compare); + test_socket_comm_handle->write_list = + ordered_list_initialize(socket_fd_node_compare); + test_socket_comm_handle->session_list = + ordered_list_initialize(pointer_compare_function); + pthread_mutex_init(&test_socket_comm_handle->socket_comm_mutex, NULL); + test_socket_comm_handle->num_active_sessions = 0; + + test_comm_session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_session)); + memset(test_comm_session, 0, sizeof(pcep_socket_comm_session)); + test_comm_session->message_ready_to_read_handler = + test_loop_message_ready_to_read_handler; + ordered_list_add_node(test_socket_comm_handle->session_list, + test_comm_session); + + read_handler_info.handler_called = false; + read_handler_info.except_handler_called = false; + read_handler_info.data = NULL; + read_handler_info.socket_fd = -1; + read_handler_info.bytes_read = 0; +} + + +void pcep_socket_comm_loop_test_teardown() +{ + pthread_mutex_destroy(&test_socket_comm_handle->socket_comm_mutex); + ordered_list_destroy(test_socket_comm_handle->read_list); + ordered_list_destroy(test_socket_comm_handle->write_list); + ordered_list_destroy(test_socket_comm_handle->session_list); + pceplib_free(PCEPLIB_INFRA, test_socket_comm_handle); + test_socket_comm_handle = NULL; + + if (test_comm_session != NULL) { + pceplib_free(PCEPLIB_INFRA, test_comm_session); + test_comm_session = NULL; + } +} + + +/* + * Test cases + */ + +void test_socket_comm_loop_null_handle() +{ + /* Verify that socket_comm_loop() correctly handles a NULL + * timers_context */ + socket_comm_loop(NULL); +} + + +void test_socket_comm_loop_not_active() +{ + /* Verify that event_loop() correctly handles an inactive flag */ + pcep_socket_comm_handle handle; + handle.active = false; + socket_comm_loop(&handle); +} + + +void test_handle_reads_no_read() +{ + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_FALSE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); +} + + +void test_handle_reads_read_message() +{ + /* Setup the comm session so that it can read. + * It should read 100 bytes, which simulates a successful read */ + test_comm_session->socket_fd = 10; + read_handler_info.bytes_read = 100; + FD_SET(test_comm_session->socket_fd, + &test_socket_comm_handle->read_master_set); + ordered_list_add_node(test_socket_comm_handle->read_list, + test_comm_session); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_TRUE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_EQUAL(test_comm_session->received_bytes, + read_handler_info.bytes_read); +} + + +void test_handle_reads_read_message_close() +{ + /* Setup the comm session so that it can read. + * It should read 0 bytes, which simulates that the socket closed */ + test_comm_session->socket_fd = 11; + read_handler_info.bytes_read = 0; + FD_SET(test_comm_session->socket_fd, + &test_socket_comm_handle->read_master_set); + ordered_list_add_node(test_socket_comm_handle->read_list, + test_comm_session); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_TRUE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_EQUAL(test_comm_session->received_bytes, + read_handler_info.bytes_read); + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); +} diff --git a/pceplib/test/pcep_socket_comm_loop_test.h b/pceplib/test/pcep_socket_comm_loop_test.h new file mode 100644 index 0000000000..d2e3f21ee1 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_loop_test.h @@ -0,0 +1,38 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SOCKET_COMM_LOOP_TEST_H_ +#define PCEP_SOCKET_COMM_LOOP_TEST_H_ + +void pcep_socket_comm_loop_test_setup(void); +void pcep_socket_comm_loop_test_teardown(void); +void test_socket_comm_loop_null_handle(void); +void test_socket_comm_loop_not_active(void); +void test_handle_reads_no_read(void); +void test_handle_reads_read_message(void); +void test_handle_reads_read_message_close(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_socket_comm_test.c b/pceplib/test/pcep_socket_comm_test.c new file mode 100644 index 0000000000..35afbcbb13 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_test.c @@ -0,0 +1,308 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_test.h" + +extern pcep_socket_comm_handle *socket_comm_handle_; + +static pcep_socket_comm_session *test_session = NULL; +static struct in_addr test_host_ip; +static struct in_addr test_src_ip; +static struct in6_addr test_host_ipv6; +static struct in6_addr test_src_ipv6; +static short test_port = 4789; +static short test_src_port = 4999; +static uint32_t connect_timeout_millis = 500; + +/* + * Unit Test Basic pcep_socket_comm API usage. + * Testing sending messages, etc via sockets should be done + * with integration tests, not unit tests. + */ + +/* + * Different socket_comm handler test implementations + */ +static void test_message_received_handler(void *session_data, + const char *message_data, + unsigned int message_length) +{ + (void)session_data; + (void)message_data; + (void)message_length; +} + +static int test_message_ready_to_read_handler(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + return 1; +} + +static void test_message_sent_handler(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + return; +} + +static void test_connection_except_notifier(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ +void pcep_socket_comm_test_setup() +{ + inet_pton(AF_INET, "127.0.0.1", &(test_host_ip)); + inet_pton(AF_INET, "127.0.0.1", &(test_src_ip)); + inet_pton(AF_INET6, "::1", &(test_host_ipv6)); + inet_pton(AF_INET6, "::1", &(test_src_ipv6)); +} + +void pcep_socket_comm_test_teardown() +{ + socket_comm_session_teardown(test_session); + test_session = NULL; +} + + +/* + * Test cases + */ + +void test_pcep_socket_comm_initialize() +{ + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_FALSE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_ipv6() +{ + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_TRUE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_with_src() +{ + /* Test that INADDR_ANY will be used when src_ip is NULL */ + test_session = socket_comm_session_initialize_with_src( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, NULL, 0, &test_host_ip, + test_port, connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr, + INADDR_ANY); + CU_ASSERT_FALSE(test_session->is_ipv6); + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_with_src( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_src_ip, test_src_port, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr, + test_src_ip.s_addr); + CU_ASSERT_EQUAL(test_session->src_sock_addr.src_sock_addr_ipv4.sin_port, + ntohs(test_src_port)); + CU_ASSERT_FALSE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_with_src_ipv6() +{ + /* Test that INADDR6_ANY will be used when src_ip is NULL */ + test_session = socket_comm_session_initialize_with_src_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, NULL, 0, &test_host_ipv6, + test_port, connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(memcmp(&test_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + &in6addr_any, sizeof(struct in6_addr)), + 0); + CU_ASSERT_TRUE(test_session->is_ipv6); + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_with_src_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_src_ipv6, test_src_port, + &test_host_ipv6, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(memcmp(&test_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + &test_src_ipv6, sizeof(struct in6_addr)), + 0); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv6.sin6_port, + ntohs(test_src_port)); + CU_ASSERT_TRUE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_tcpmd5() +{ + char tcp_md5_str[] = "hello"; + int tcp_md5_strlen = strlen(tcp_md5_str); + + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, 1, + tcp_md5_str, true, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_TRUE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); + /* This call does not work, it returns errno=92, Protocol not available + getsockopt(test_session->socket_fd, SOL_SOCKET, TCP_MD5SIG, &sig, + &siglen);*/ + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, 1, + tcp_md5_str, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_FALSE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); +} + + +void test_pcep_socket_comm_initialize_ipv6_tcpmd5() +{ + char tcp_md5_str[] = "hello"; + int tcp_md5_strlen = strlen(tcp_md5_str); + + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, 1, + tcp_md5_str, true, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_TRUE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); + /* This call does not work, it returns errno=92, Protocol not available + getsockopt(test_session->socket_fd, SOL_SOCKET, TCP_MD5SIG, &sig, + &siglen);*/ + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, 1, + tcp_md5_str, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_FALSE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); +} + + +void test_pcep_socket_comm_initialize_handlers() +{ + /* Verify incorrect handler usage is correctly handled */ + + /* Both receive handlers cannot be NULL */ + test_session = socket_comm_session_initialize( + NULL, NULL, NULL, test_connection_except_notifier, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NULL(test_session); + + /* Both receive handlers cannot be set */ + test_session = socket_comm_session_initialize( + test_message_received_handler, + test_message_ready_to_read_handler, test_message_sent_handler, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NULL(test_session); + + /* Only one receive handler can be set */ + test_session = socket_comm_session_initialize( + NULL, test_message_ready_to_read_handler, + test_message_sent_handler, test_connection_except_notifier, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); +} + + +void test_pcep_socket_comm_session_not_initialized() +{ + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(NULL)); + CU_ASSERT_FALSE(socket_comm_session_close_tcp(NULL)); + CU_ASSERT_FALSE(socket_comm_session_close_tcp_after_write(NULL)); + socket_comm_session_send_message(NULL, NULL, 0, true); + CU_ASSERT_FALSE(socket_comm_session_teardown(NULL)); +} + + +void test_pcep_socket_comm_session_destroy() +{ + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, test_message_sent_handler, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + CU_ASSERT_PTR_NOT_NULL(socket_comm_handle_); + CU_ASSERT_EQUAL(socket_comm_handle_->num_active_sessions, 1); + + CU_ASSERT_TRUE(socket_comm_session_teardown(test_session)); + test_session = NULL; + CU_ASSERT_PTR_NOT_NULL(socket_comm_handle_); + + CU_ASSERT_TRUE(destroy_socket_comm_loop()); + CU_ASSERT_PTR_NULL(socket_comm_handle_); +} diff --git a/pceplib/test/pcep_socket_comm_test.h b/pceplib/test/pcep_socket_comm_test.h new file mode 100644 index 0000000000..f857af087a --- /dev/null +++ b/pceplib/test/pcep_socket_comm_test.h @@ -0,0 +1,42 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SOCKET_COMM_TEST_H_ +#define PCEP_SOCKET_COMM_TEST_H_ + +void pcep_socket_comm_test_teardown(void); +void pcep_socket_comm_test_setup(void); +void test_pcep_socket_comm_initialize(void); +void test_pcep_socket_comm_initialize_ipv6(void); +void test_pcep_socket_comm_initialize_with_src(void); +void test_pcep_socket_comm_initialize_with_src_ipv6(void); +void test_pcep_socket_comm_initialize_tcpmd5(void); +void test_pcep_socket_comm_initialize_ipv6_tcpmd5(void); +void test_pcep_socket_comm_initialize_handlers(void); +void test_pcep_socket_comm_session_not_initialized(void); +void test_pcep_socket_comm_session_destroy(void); + +#endif diff --git a/pceplib/test/pcep_socket_comm_tests.c b/pceplib/test/pcep_socket_comm_tests.c new file mode 100644 index 0000000000..293678f1a7 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_tests.c @@ -0,0 +1,128 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_socket_comm_loop_test.h" +#include "pcep_socket_comm_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_socket_comm_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Socket Comm Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + pcep_socket_comm_test_setup, // test case setup function pointer + pcep_socket_comm_test_teardown); // test case teardown function + // pointer + + CU_add_test(test_socket_comm_suite, "test_pcep_socket_comm_initialize", + test_pcep_socket_comm_initialize); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_ipv6", + test_pcep_socket_comm_initialize_ipv6); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_with_src", + test_pcep_socket_comm_initialize_with_src); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_with_src_ipv6", + test_pcep_socket_comm_initialize_with_src_ipv6); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_tcpmd5", + test_pcep_socket_comm_initialize_tcpmd5); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_ipv6_tcpmd5", + test_pcep_socket_comm_initialize_ipv6_tcpmd5); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_handlers", + test_pcep_socket_comm_initialize_handlers); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_session_not_initialized", + test_pcep_socket_comm_session_not_initialized); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_session_destroy", + test_pcep_socket_comm_session_destroy); + + /* + * Tests defined in pcep_socket_comm_loop_test.c + */ + CU_pSuite test_socket_comm_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Socket Comm Loop Test Suite", NULL, NULL, + pcep_socket_comm_loop_test_setup, // suite setup + // function pointer + pcep_socket_comm_loop_test_teardown); // suite cleanup + // function + // pointer + + CU_add_test(test_socket_comm_loop_suite, + "test_socket_comm_loop_null_handle", + test_socket_comm_loop_null_handle); + CU_add_test(test_socket_comm_loop_suite, + "test_socket_comm_loop_not_active", + test_socket_comm_loop_not_active); + CU_add_test(test_socket_comm_loop_suite, "test_handle_reads_no_read", + test_handle_reads_no_read); + CU_add_test(test_socket_comm_loop_suite, + "test_handle_reads_read_message", + test_handle_reads_read_message); + CU_add_test(test_socket_comm_loop_suite, + "test_handle_reads_read_message_close", + test_handle_reads_read_message_close); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_socket_comm_tests_valgrind.sh b/pceplib/test/pcep_socket_comm_tests_valgrind.sh new file mode 100755 index 0000000000..d9e95e4c1c --- /dev/null +++ b/pceplib/test/pcep_socket_comm_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_socket_comm_tests diff --git a/pceplib/test/pcep_tests_valgrind.sh b/pceplib/test/pcep_tests_valgrind.sh new file mode 100755 index 0000000000..ca4772cb67 --- /dev/null +++ b/pceplib/test/pcep_tests_valgrind.sh @@ -0,0 +1,15 @@ +# +# Common function definition for PCEPlib valgrind tests +# + +function valgrind_test() +{ + local test_suite=$1 + [[ -z ${test_suite} ]] && { echo "${FUNCNAME}(): test_suite not specified."; exit 1; } + [[ ! -x "${test_suite}" ]] && { echo "${test_suite} is not an executable file."; exit 1; } + + G_SLICE=always-malloc + G_DEBUG=gc-friendly + VALGRIND="valgrind -v --tool=memcheck --leak-check=full --num-callers=40 --error-exitcode=1" + ${VALGRIND} --log-file=${test_suite}.val.log ./${test_suite} || ({ echo "Valgrind memory check error"; exit 1; }) +} diff --git a/pceplib/test/pcep_timers_event_loop_test.c b/pceplib/test/pcep_timers_event_loop_test.c new file mode 100644 index 0000000000..9fcacaf0f2 --- /dev/null +++ b/pceplib/test/pcep_timers_event_loop_test.c @@ -0,0 +1,160 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_timers.h" +#include "pcep_utils_memory.h" +#include "pcep_timers_event_loop.h" +#include "pcep_timers_event_loop_test.h" + + +typedef struct timer_expire_handler_info_ { + bool handler_called; + void *data; + int timerId; + +} timer_expire_handler_info; + +static pcep_timers_context *test_timers_context = NULL; +static timer_expire_handler_info expire_handler_info; +#define TEST_EVENT_LOOP_TIMER_ID 500 + + +/* Called when a timer expires */ +static void test_timer_expire_handler(void *data, int timerId) +{ + expire_handler_info.handler_called = true; + expire_handler_info.data = data; + expire_handler_info.timerId = timerId; +} + + +/* Test case setup called before each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_event_loop_test_setup() +{ + test_timers_context = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timers_context)); + memset(test_timers_context, 0, sizeof(pcep_timers_context)); + if (pthread_mutex_init(&(test_timers_context->timer_list_lock), NULL) + != 0) { + fprintf(stderr, + "ERROR initializing timers, cannot initialize the mutex\n"); + } + test_timers_context->active = false; + test_timers_context->expire_handler = test_timer_expire_handler; + test_timers_context->timer_list = + ordered_list_initialize(timer_list_node_timer_id_compare); + + expire_handler_info.handler_called = false; + expire_handler_info.data = NULL; + expire_handler_info.timerId = -1; +} + + +/* Test case teardown called after each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_event_loop_test_teardown() +{ + pthread_mutex_unlock(&test_timers_context->timer_list_lock); + pthread_mutex_destroy(&(test_timers_context->timer_list_lock)); + ordered_list_destroy(test_timers_context->timer_list); + pceplib_free(PCEPLIB_INFRA, test_timers_context); + test_timers_context = NULL; +} + + +/* + * Test functions + */ + +void test_walk_and_process_timers_no_timers() +{ + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); + + walk_and_process_timers(test_timers_context); + + CU_ASSERT_FALSE(expire_handler_info.handler_called); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); +} + + +void test_walk_and_process_timers_timer_not_expired() +{ + pcep_timer timer; + timer.data = &timer; + // Set the timer to expire 100 seconds from now + timer.expire_time = time(NULL) + 100; + timer.timer_id = TEST_EVENT_LOOP_TIMER_ID; + ordered_list_add_node(test_timers_context->timer_list, &timer); + + walk_and_process_timers(test_timers_context); + + /* The timer should still be in the list, since it hasnt expired yet */ + CU_ASSERT_FALSE(expire_handler_info.handler_called); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 1); + CU_ASSERT_PTR_NOT_NULL(test_timers_context->timer_list->head); +} + + +void test_walk_and_process_timers_timer_expired() +{ + /* We need to alloc it, since it will be free'd in + * walk_and_process_timers */ + pcep_timer *timer = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timer)); + timer->data = timer; + // Set the timer to expire 10 seconds ago + timer->expire_time = time(NULL) - 10; + timer->timer_id = TEST_EVENT_LOOP_TIMER_ID; + ordered_list_add_node(test_timers_context->timer_list, timer); + + walk_and_process_timers(test_timers_context); + + /* Since the timer expired, the expire_handler should have been called + * and the timer should have been removed from the timer list */ + CU_ASSERT_TRUE(expire_handler_info.handler_called); + CU_ASSERT_PTR_EQUAL(expire_handler_info.data, timer); + CU_ASSERT_EQUAL(expire_handler_info.timerId, TEST_EVENT_LOOP_TIMER_ID); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); +} + +void test_event_loop_null_handle() +{ + /* Verify that event_loop() correctly handles a NULL timers_context */ + event_loop(NULL); +} + + +void test_event_loop_not_active() +{ + /* Verify that event_loop() correctly handles an inactive timers_context + * flag */ + test_timers_context->active = false; + event_loop(test_timers_context); +} diff --git a/pceplib/test/pcep_timers_event_loop_test.h b/pceplib/test/pcep_timers_event_loop_test.h new file mode 100644 index 0000000000..19fd264858 --- /dev/null +++ b/pceplib/test/pcep_timers_event_loop_test.h @@ -0,0 +1,38 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_EVENT_LOOP_TEST_H_ +#define PCEP_TIMERS_EVENT_LOOP_TEST_H_ + +void pcep_timers_event_loop_test_setup(void); +void pcep_timers_event_loop_test_teardown(void); +void test_walk_and_process_timers_no_timers(void); +void test_walk_and_process_timers_timer_not_expired(void); +void test_walk_and_process_timers_timer_expired(void); +void test_event_loop_null_handle(void); +void test_event_loop_not_active(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_timers_test.c b/pceplib/test/pcep_timers_test.c new file mode 100644 index 0000000000..9d9e0f6c1b --- /dev/null +++ b/pceplib/test/pcep_timers_test.c @@ -0,0 +1,109 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include + +#include "pcep_timers.h" +#include "pcep_timers_test.h" + +/* Test case teardown called after each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_test_teardown() +{ + teardown_timers(); +} + +static void test_timer_expire_handler(void *data, int timerId) +{ + (void)data; + (void)timerId; +} + + +void test_double_initialization(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), false); +} + + +void test_initialization_null_callback(void) +{ + CU_ASSERT_EQUAL(initialize_timers(NULL), false); +} + + +void test_not_initialized(void) +{ + /* All of these should fail if initialize_timers() hasnt been called */ + CU_ASSERT_EQUAL(create_timer(5, NULL), -1); + CU_ASSERT_EQUAL(cancel_timer(7), false); + CU_ASSERT_EQUAL(reset_timer(7), false); + CU_ASSERT_EQUAL(teardown_timers(), false); +} + + +void test_create_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(0, NULL); + CU_ASSERT_TRUE(timer_id > -1); +} + + +void test_cancel_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(10, NULL); + CU_ASSERT_TRUE(timer_id > -1); + + CU_ASSERT_EQUAL(cancel_timer(timer_id), true); +} + + +void test_cancel_timer_invalid(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(cancel_timer(1), false); +} + + +void test_reset_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(10, NULL); + CU_ASSERT_TRUE(timer_id > -1); + + CU_ASSERT_EQUAL(reset_timer(timer_id), true); +} + + +void test_reset_timer_invalid(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(reset_timer(1), false); +} diff --git a/pceplib/test/pcep_timers_test.h b/pceplib/test/pcep_timers_test.h new file mode 100644 index 0000000000..6ac9a90e49 --- /dev/null +++ b/pceplib/test/pcep_timers_test.h @@ -0,0 +1,40 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_TEST_H_ +#define PCEP_TIMERS_TEST_H_ + +void pcep_timers_test_teardown(void); +void test_double_initialization(void); +void test_initialization_null_callback(void); +void test_not_initialized(void); +void test_create_timer(void); +void test_cancel_timer(void); +void test_cancel_timer_invalid(void); +void test_reset_timer(void); +void test_reset_timer_invalid(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_timers_tests.c b/pceplib/test/pcep_timers_tests.c new file mode 100644 index 0000000000..adfea17e29 --- /dev/null +++ b/pceplib/test/pcep_timers_tests.c @@ -0,0 +1,113 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include + +#include "pcep_timers_test.h" +#include "pcep_timers_event_loop_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_timers_test.c + */ + CU_pSuite test_timers_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Timers Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + NULL, pcep_timers_test_teardown); // test case setup and + // teardown function pointers + CU_add_test(test_timers_suite, "test_double_initialization", + test_double_initialization); + CU_add_test(test_timers_suite, "test_initialization_null_callback", + test_initialization_null_callback); + CU_add_test(test_timers_suite, "test_not_initialized", + test_not_initialized); + CU_add_test(test_timers_suite, "test_create_timer", test_create_timer); + CU_add_test(test_timers_suite, "test_cancel_timer", test_cancel_timer); + CU_add_test(test_timers_suite, "test_cancel_timer_invalid", + test_cancel_timer_invalid); + CU_add_test(test_timers_suite, "test_reset_timer", test_reset_timer); + CU_add_test(test_timers_suite, "test_reset_timer_invalid", + test_reset_timer_invalid); + + /* + * Tests defined in pcep_timers_event_loop_test.c + */ + CU_pSuite test_timers_event_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Timers Event Loop Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + pcep_timers_event_loop_test_setup, // test case setup + // function pointer + pcep_timers_event_loop_test_teardown); // test case + // teardown + // function + // pointer + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_no_timers", + test_walk_and_process_timers_no_timers); + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_timer_not_expired", + test_walk_and_process_timers_timer_not_expired); + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_timer_expired", + test_walk_and_process_timers_timer_expired); + CU_add_test(test_timers_event_loop_suite, "test_event_loop_null_handle", + test_event_loop_null_handle); + CU_add_test(test_timers_event_loop_suite, "test_event_loop_not_active", + test_event_loop_not_active); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_timers_tests_valgrind.sh b/pceplib/test/pcep_timers_tests_valgrind.sh new file mode 100755 index 0000000000..f9bff3b2a6 --- /dev/null +++ b/pceplib/test/pcep_timers_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_timers_tests diff --git a/pceplib/test/pcep_utils_counters_test.c b/pceplib/test/pcep_utils_counters_test.c new file mode 100644 index 0000000000..6f53e4d400 --- /dev/null +++ b/pceplib/test/pcep_utils_counters_test.c @@ -0,0 +1,254 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include + +#include "pcep_utils_counters.h" +#include "pcep_utils_counters_test.h" + + +void test_create_counters_group() +{ + const char group_name[] = "group"; + uint16_t num_subgroups = 10; + + struct counters_group *group = + create_counters_group(NULL, num_subgroups); + CU_ASSERT_PTR_NULL(group); + + group = create_counters_group(group_name, MAX_COUNTER_GROUPS + 1); + CU_ASSERT_PTR_NULL(group); + + group = create_counters_group(group_name, num_subgroups); + CU_ASSERT_PTR_NOT_NULL(group); + + CU_ASSERT_EQUAL(group->num_subgroups, 0); + CU_ASSERT_EQUAL(group->max_subgroups, num_subgroups); + CU_ASSERT_EQUAL(strcmp(group->counters_group_name, group_name), 0); + + delete_counters_group(group); +} + +void test_create_counters_subgroup() +{ + const char subgroup_name[] = "subgroup"; + uint16_t subgroup_id = 10; + uint16_t num_counters = 20; + + struct counters_subgroup *subgroup = + create_counters_subgroup(NULL, subgroup_id, num_counters); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup( + subgroup_name, MAX_COUNTER_GROUPS + 1, num_counters); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup(subgroup_name, subgroup_id, + MAX_COUNTERS + 1); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup(subgroup_name, subgroup_id, + num_counters); + CU_ASSERT_PTR_NOT_NULL(subgroup); + + CU_ASSERT_EQUAL(subgroup->subgroup_id, subgroup_id); + CU_ASSERT_EQUAL(subgroup->num_counters, 0); + CU_ASSERT_EQUAL(subgroup->max_counters, num_counters); + CU_ASSERT_EQUAL(strcmp(subgroup->counters_subgroup_name, subgroup_name), + 0); + + delete_counters_subgroup(subgroup); +} + +void test_add_counters_subgroup() +{ + struct counters_group *group = create_counters_group("group", 1); + struct counters_subgroup *subgroup1 = + create_counters_subgroup("subgroup", 0, 5); + struct counters_subgroup *subgroup2 = + create_counters_subgroup("subgroup", 1, 5); + + CU_ASSERT_FALSE(add_counters_subgroup(NULL, NULL)); + CU_ASSERT_FALSE(add_counters_subgroup(NULL, subgroup1)); + CU_ASSERT_FALSE(add_counters_subgroup(group, NULL)); + + CU_ASSERT_EQUAL(group->num_subgroups, 0); + CU_ASSERT_TRUE(add_counters_subgroup(group, subgroup1)); + CU_ASSERT_EQUAL(group->num_subgroups, 1); + /* Cant add more than num_subgroups to the group */ + CU_ASSERT_FALSE(add_counters_subgroup(group, subgroup2)); + + CU_ASSERT_PTR_NOT_NULL(find_subgroup(group, 0)); + CU_ASSERT_PTR_NULL(find_subgroup(group, 1)); + + delete_counters_group(group); + delete_counters_subgroup(subgroup2); +} + +void test_create_subgroup_counter() +{ + uint16_t counter_id = 1; + char counter_name[] = "my counter"; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 2); + + CU_ASSERT_FALSE( + create_subgroup_counter(NULL, counter_id, counter_name)); + CU_ASSERT_FALSE(create_subgroup_counter(subgroup, counter_id + 1, + counter_name)); + CU_ASSERT_FALSE(create_subgroup_counter(subgroup, counter_id, NULL)); + CU_ASSERT_EQUAL(subgroup->num_counters, 0); + CU_ASSERT_TRUE( + create_subgroup_counter(subgroup, counter_id, counter_name)); + CU_ASSERT_EQUAL(subgroup->num_counters, 1); + + delete_counters_subgroup(subgroup); +} + +void test_delete_counters_group() +{ + struct counters_group *group = create_counters_group("group", 1); + + CU_ASSERT_FALSE(delete_counters_group(NULL)); + CU_ASSERT_TRUE(delete_counters_group(group)); +} + +void test_delete_counters_subgroup() +{ + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 1); + + CU_ASSERT_FALSE(delete_counters_subgroup(NULL)); + CU_ASSERT_TRUE(delete_counters_subgroup(subgroup)); +} + +void test_reset_group_counters() +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + add_counters_subgroup(group, subgroup); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(reset_group_counters(NULL)); + CU_ASSERT_TRUE(reset_group_counters(group)); + CU_ASSERT_EQUAL(counter->counter_value, 0); + + delete_counters_group(group); +} + +void test_reset_subgroup_counters() +{ + uint16_t counter_id = 1; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(reset_subgroup_counters(NULL)); + CU_ASSERT_TRUE(reset_subgroup_counters(subgroup)); + CU_ASSERT_EQUAL(counter->counter_value, 0); + + delete_counters_subgroup(subgroup); +} + +void test_increment_counter() +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + add_counters_subgroup(group, subgroup); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(increment_counter(NULL, subgroup_id, counter_id)); + CU_ASSERT_FALSE(increment_counter(group, 100, counter_id)); + CU_ASSERT_FALSE(increment_counter(group, subgroup_id, 123)); + CU_ASSERT_TRUE(increment_counter(group, subgroup_id, counter_id)); + CU_ASSERT_EQUAL(counter->counter_value, 101); + CU_ASSERT_EQUAL(subgroup_counters_total(subgroup), 101); + + delete_counters_group(group); +} + +void test_increment_subgroup_counter() +{ + int counter_id = 1; + uint32_t counter_value = 100; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = counter_value; + + CU_ASSERT_FALSE(increment_subgroup_counter(NULL, counter_id)); + CU_ASSERT_FALSE(increment_subgroup_counter(subgroup, counter_id + 1)); + CU_ASSERT_TRUE(increment_subgroup_counter(subgroup, counter_id)); + CU_ASSERT_EQUAL(counter->counter_value, counter_value + 1); + + delete_counters_subgroup(subgroup); +} + +void test_dump_counters_group_to_log() +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + add_counters_subgroup(group, subgroup); + + CU_ASSERT_FALSE(dump_counters_group_to_log(NULL)); + CU_ASSERT_TRUE(dump_counters_group_to_log(group)); + + delete_counters_group(group); +} + +void test_dump_counters_subgroup_to_log() +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter"); + + CU_ASSERT_FALSE(dump_counters_subgroup_to_log(NULL)); + CU_ASSERT_TRUE(dump_counters_subgroup_to_log(subgroup)); + + delete_counters_subgroup(subgroup); +} diff --git a/pceplib/test/pcep_utils_counters_test.h b/pceplib/test/pcep_utils_counters_test.h new file mode 100644 index 0000000000..07236dcb53 --- /dev/null +++ b/pceplib/test/pcep_utils_counters_test.h @@ -0,0 +1,43 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_COUNTERS_TEST_H_ +#define PCEP_UTILS_COUNTERS_TEST_H_ + +void test_create_counters_group(void); +void test_create_counters_subgroup(void); +void test_add_counters_subgroup(void); +void test_create_subgroup_counter(void); +void test_delete_counters_group(void); +void test_delete_counters_subgroup(void); +void test_reset_group_counters(void); +void test_reset_subgroup_counters(void); +void test_increment_counter(void); +void test_increment_subgroup_counter(void); +void test_dump_counters_group_to_log(void); +void test_dump_counters_subgroup_to_log(void); + +#endif diff --git a/pceplib/test/pcep_utils_double_linked_list_test.c b/pceplib/test/pcep_utils_double_linked_list_test.c new file mode 100644 index 0000000000..d2600e66c4 --- /dev/null +++ b/pceplib/test/pcep_utils_double_linked_list_test.c @@ -0,0 +1,297 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_double_linked_list_test.h" + +typedef struct dll_node_data_ { + int int_data; + +} dll_node_data; + +void test_empty_dl_list() +{ + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NULL(dll_delete_first_node(handle)); + CU_ASSERT_PTR_NULL(dll_delete_last_node(handle)); + CU_ASSERT_PTR_NULL(dll_delete_node(handle, NULL)); + + dll_destroy(handle); +} + +void test_null_dl_list_handle() +{ + dll_destroy(NULL); + CU_ASSERT_PTR_NULL(dll_prepend(NULL, NULL)); + CU_ASSERT_PTR_NULL(dll_append(NULL, NULL)); + CU_ASSERT_PTR_NULL(dll_delete_first_node(NULL)); + CU_ASSERT_PTR_NULL(dll_delete_last_node(NULL)); + CU_ASSERT_PTR_NULL(dll_delete_node(NULL, NULL)); +} + +void test_dll_prepend_data() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data3)); + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data2)); + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data1)); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + double_linked_list_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + CU_ASSERT_PTR_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NULL(node->next_node); + CU_ASSERT_PTR_EQUAL(handle->tail, node); + + dll_destroy(handle); +} + + +void test_dll_append_data() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data3)); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + double_linked_list_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + CU_ASSERT_PTR_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NULL(node->next_node); + CU_ASSERT_PTR_EQUAL(handle->tail, node); + + dll_destroy(handle); +} + + +void test_dll_delete_first_node() +{ + dll_node_data data1, data2; + data1.int_data = 1; + data2.int_data = 2; + + double_linked_list *handle = dll_initialize(); + + /* Test deleting with just 1 node in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* Test deleting with 2 nodes in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + deleted_data = dll_delete_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data2); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + + dll_destroy(handle); +} + + +void test_dll_delete_last_node() +{ + dll_node_data data1, data2; + data1.int_data = 1; + data2.int_data = 2; + + double_linked_list *handle = dll_initialize(); + + /* Test deleting with just 1 node in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_last_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* Test deleting with 2 nodes in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + deleted_data = dll_delete_last_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data1); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + + dll_destroy(handle); +} + + +void test_dll_delete_node() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + double_linked_list_node *node1, *node2, *node3; + double_linked_list *handle; + + /* Test deleting with just 1 node in the list */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_node(handle, node1); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* + * Test deleting the head with 2 nodes in the list + */ + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_PTR_NOT_NULL(node2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + /* Delete the head entry */ + deleted_data = dll_delete_node(handle, node1); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data2); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + dll_destroy(handle); + + /* + * Test deleting the tail with 2 nodes in the list + */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_PTR_NOT_NULL(node2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + /* Delete the tail entry */ + deleted_data = dll_delete_node(handle, node2); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data1); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + dll_destroy(handle); + + /* + * Test deleting in the middle with 3 nodes in the list + */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + node3 = dll_append(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_PTR_NOT_NULL(node2); + CU_ASSERT_PTR_NOT_NULL(node3); + CU_ASSERT_EQUAL(handle->num_entries, 3); + + /* Delete the middle entry */ + deleted_data = dll_delete_node(handle, node2); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 2); + CU_ASSERT_PTR_EQUAL(handle->head, node1); + CU_ASSERT_PTR_EQUAL(handle->tail, node3); + CU_ASSERT_PTR_EQUAL(node1->data, &data1); + CU_ASSERT_PTR_EQUAL(node3->data, &data3); + CU_ASSERT_PTR_EQUAL(node1->next_node, node3); + CU_ASSERT_PTR_EQUAL(node3->prev_node, node1); + CU_ASSERT_PTR_NULL(node1->prev_node); + CU_ASSERT_PTR_NULL(node3->next_node); + + dll_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_double_linked_list_test.h b/pceplib/test/pcep_utils_double_linked_list_test.h new file mode 100644 index 0000000000..ddb6467cb6 --- /dev/null +++ b/pceplib/test/pcep_utils_double_linked_list_test.h @@ -0,0 +1,38 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_DOUBLE_LINKED_LIST_TEST_H_ +#define PCEP_UTILS_DOUBLE_LINKED_LIST_TEST_H_ + +void test_empty_dl_list(void); +void test_null_dl_list_handle(void); +void test_dll_prepend_data(void); +void test_dll_append_data(void); +void test_dll_delete_first_node(void); +void test_dll_delete_last_node(void); +void test_dll_delete_node(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_memory_test.c b/pceplib/test/pcep_utils_memory_test.c new file mode 100644 index 0000000000..b0b528f084 --- /dev/null +++ b/pceplib/test/pcep_utils_memory_test.c @@ -0,0 +1,241 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include + +#include + +#include "pcep_utils_memory.h" +#include "pcep_utils_memory_test.h" + +void *test_pceplib_malloc(void *mem_type, size_t size); +void *test_pceplib_calloc(void *mem_type, size_t size); +void *test_pceplib_realloc(void *mem_type, void *ptr, size_t size); +void *test_pceplib_strdup(void *mem_type, const char *str); +void test_pceplib_free(void *mem_type, void *ptr); +void verify_memory_type(struct pceplib_memory_type *mt, uint32_t num_alloc, + uint32_t alloc_bytes, uint32_t num_free, + uint32_t free_bytes); +void verify_ext_memory_type(void *mt, int num_malloc_calls, + int num_calloc_calls, int num_realloc_calls, + int num_strdup_calls, int num_free_calls); + +struct test_memory_type { + int num_malloc_calls; + int num_calloc_calls; + int num_realloc_calls; + int num_strdup_calls; + int num_free_calls; +}; + +void *test_pceplib_malloc(void *mem_type, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_malloc_calls++; + return malloc(size); +} + +void *test_pceplib_calloc(void *mem_type, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_calloc_calls++; + return calloc(1, size); +} + +void *test_pceplib_realloc(void *mem_type, void *ptr, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_realloc_calls++; + return realloc(ptr, size); +} + +void *test_pceplib_strdup(void *mem_type, const char *str) +{ + ((struct test_memory_type *)mem_type)->num_strdup_calls++; + return strdup(str); +} + +void test_pceplib_free(void *mem_type, void *ptr) +{ + ((struct test_memory_type *)mem_type)->num_free_calls++; + free(ptr); +} + +void verify_memory_type(struct pceplib_memory_type *mt, uint32_t num_alloc, + uint32_t alloc_bytes, uint32_t num_free, + uint32_t free_bytes) +{ + CU_ASSERT_EQUAL(num_alloc, mt->num_allocates); + CU_ASSERT_EQUAL(alloc_bytes, mt->total_bytes_allocated); + CU_ASSERT_EQUAL(num_free, mt->num_frees); + CU_ASSERT_EQUAL(free_bytes, mt->total_bytes_freed); +} + +void verify_ext_memory_type(void *mt, int num_malloc_calls, + int num_calloc_calls, int num_realloc_calls, + int num_strdup_calls, int num_free_calls) +{ + struct test_memory_type *mt_ptr = (struct test_memory_type *)mt; + CU_ASSERT_EQUAL(num_malloc_calls, mt_ptr->num_malloc_calls); + CU_ASSERT_EQUAL(num_calloc_calls, mt_ptr->num_calloc_calls); + CU_ASSERT_EQUAL(num_realloc_calls, mt_ptr->num_realloc_calls); + CU_ASSERT_EQUAL(num_strdup_calls, mt_ptr->num_strdup_calls); + CU_ASSERT_EQUAL(num_free_calls, mt_ptr->num_free_calls); +} + +void test_memory_internal_impl() +{ + int alloc_size = 100; + struct pceplib_memory_type *pceplib_infra_ptr = + (struct pceplib_memory_type *)PCEPLIB_INFRA; + struct pceplib_memory_type *pceplib_messages_ptr = + (struct pceplib_memory_type *)PCEPLIB_MESSAGES; + int alloc_counter = 1; + int free_counter = 1; + + /* reset the memory type counters for easier testing */ + pceplib_infra_ptr->num_allocates = + pceplib_infra_ptr->total_bytes_allocated = + pceplib_infra_ptr->num_frees = + pceplib_infra_ptr->total_bytes_freed = 0; + pceplib_messages_ptr->num_allocates = + pceplib_messages_ptr->total_bytes_allocated = + pceplib_messages_ptr->num_frees = + pceplib_messages_ptr->total_bytes_freed = 0; + + /* Make sure nothing crashes when all these are set NULL, since the + * internal default values should still be used. */ + pceplib_memory_initialize(NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + /* Test malloc() */ + void *ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + verify_memory_type(pceplib_infra_ptr, alloc_counter, alloc_size, + free_counter++, 0); + + /* Test calloc() */ + ptr = pceplib_calloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + alloc_counter++; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + alloc_size * alloc_counter, free_counter++, 0); + + /* Test realloc() */ + ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + ptr = pceplib_realloc(PCEPLIB_INFRA, ptr, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + alloc_counter += 2; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + alloc_size * alloc_counter, free_counter++, 0); + + /* Test strdup() */ + ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + /* Make strdup duplicate (alloc_size - 1) bytes */ + memset(ptr, 'a', alloc_size); + ((char *)ptr)[alloc_size - 1] = '\0'; + char *str = pceplib_strdup(PCEPLIB_INFRA, (char *)ptr); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + pceplib_free(PCEPLIB_INFRA, str); + alloc_counter += 2; + free_counter++; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + (alloc_size * alloc_counter) - 1, free_counter, 0); + + /* Make sure only the pceplib_infra_ptr memory counters are incremented + */ + verify_memory_type(pceplib_messages_ptr, 0, 0, 0, 0); +} + +void test_memory_external_impl() +{ + int alloc_size = 100; + struct pceplib_memory_type *pceplib_infra_ptr = + (struct pceplib_memory_type *)PCEPLIB_INFRA; + struct pceplib_memory_type *pceplib_messages_ptr = + (struct pceplib_memory_type *)PCEPLIB_MESSAGES; + + /* reset the internal memory type counters to later verify they are NOT + * incremented since an external impl was provided */ + pceplib_infra_ptr->num_allocates = + pceplib_infra_ptr->total_bytes_allocated = + pceplib_infra_ptr->num_frees = + pceplib_infra_ptr->total_bytes_freed = 0; + pceplib_messages_ptr->num_allocates = + pceplib_messages_ptr->total_bytes_allocated = + pceplib_messages_ptr->num_frees = + pceplib_messages_ptr->total_bytes_freed = 0; + + /* Setup the external memory type */ + struct test_memory_type infra_mt, messages_mt; + void *infra_ptr = &infra_mt; + void *messages_ptr = &messages_mt; + memset(infra_ptr, 0, sizeof(struct test_memory_type)); + memset(messages_ptr, 0, sizeof(struct test_memory_type)); + int free_counter = 1; + + /* Initialize the PCEPlib memory system with an external implementation + */ + pceplib_memory_initialize(infra_ptr, messages_ptr, test_pceplib_malloc, + test_pceplib_calloc, test_pceplib_realloc, + test_pceplib_strdup, test_pceplib_free); + + /* Test malloc() */ + void *ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 1, 0, 0, 0, free_counter++); + + /* Test calloc() */ + ptr = pceplib_calloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 1, 1, 0, 0, free_counter++); + + /* Test realloc() */ + ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + ptr = pceplib_realloc(PCEPLIB_MESSAGES, ptr, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 2, 1, 1, 0, free_counter++); + + /* Test strdup() */ + ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + /* Make strdup duplicate (alloc_size - 1) bytes */ + memset(ptr, 'a', alloc_size); + ((char *)ptr)[alloc_size - 1] = '\0'; + char *str = pceplib_strdup(PCEPLIB_MESSAGES, (char *)ptr); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + pceplib_free(PCEPLIB_MESSAGES, str); + verify_ext_memory_type(messages_ptr, 3, 1, 1, 1, free_counter + 1); + + /* Make sure the internal memory counters are NOT incremented */ + verify_memory_type(pceplib_infra_ptr, 0, 0, 0, 0); + verify_memory_type(pceplib_messages_ptr, 0, 0, 0, 0); + + verify_ext_memory_type(infra_ptr, 0, 0, 0, 0, 0); +} diff --git a/pceplib/test/pcep_utils_memory_test.h b/pceplib/test/pcep_utils_memory_test.h new file mode 100644 index 0000000000..4e0c3fadf1 --- /dev/null +++ b/pceplib/test/pcep_utils_memory_test.h @@ -0,0 +1,33 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MEMORY_TEST_H_ +#define PCEP_MEMORY_TEST_H_ + +void test_memory_internal_impl(void); +void test_memory_external_impl(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_ordered_list_test.c b/pceplib/test/pcep_utils_ordered_list_test.c new file mode 100644 index 0000000000..fe9ee58825 --- /dev/null +++ b/pceplib/test/pcep_utils_ordered_list_test.c @@ -0,0 +1,248 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_ordered_list_test.h" + +typedef struct node_data_ { + int int_data; + +} node_data; + + +int node_data_compare(void *list_entry, void *new_entry) +{ + /* + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ + + return ((node_data *)new_entry)->int_data + - ((node_data *)list_entry)->int_data; +} + + +void test_empty_list() +{ + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + CU_ASSERT_PTR_NOT_NULL(handle); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NOT_NULL(handle->compare_function); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + ordered_list_destroy(handle); +} + + +void test_null_list_handle() +{ + node_data data; + ordered_list_node node_data; + + void *ptr = ordered_list_add_node(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_find(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_first_node(NULL); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_first_node_equals(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_node(NULL, &node_data, &node_data); + CU_ASSERT_PTR_NULL(ptr); +} + + +void test_add_to_list() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data3); + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + ordered_list_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node, NULL); + + ordered_list_destroy(handle); +} + + +void test_find() +{ + node_data data1, data2, data3, data_not_inList; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + data_not_inList.int_data = 5; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data3); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data1); + + ordered_list_node *node = ordered_list_find(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node); + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = ordered_list_find(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node); + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = ordered_list_find(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node); + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = ordered_list_find(handle, &data_not_inList); + CU_ASSERT_PTR_NULL(node); + + ordered_list_destroy(handle); +} + + +void test_remove_first_node() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NULL(node_data); + + ordered_list_destroy(handle); +} + + +void test_remove_first_node_equals() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_first_node_equals(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_first_node_equals(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = ordered_list_remove_first_node_equals(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + node_data = ordered_list_remove_first_node_equals(handle, &data1); + CU_ASSERT_PTR_NULL(node_data); + + ordered_list_destroy(handle); +} + + +void test_remove_node() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_node *node1 = ordered_list_add_node(handle, &data1); + ordered_list_node *node2 = ordered_list_add_node(handle, &data2); + ordered_list_node *node3 = ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_node(handle, node2, node3); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_node(handle, node1, node2); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + ordered_list_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_ordered_list_test.h b/pceplib/test/pcep_utils_ordered_list_test.h new file mode 100644 index 0000000000..3686848b69 --- /dev/null +++ b/pceplib/test/pcep_utils_ordered_list_test.h @@ -0,0 +1,39 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_ORDERED_LIST_TEST_H_ +#define PCEP_UTILS_ORDERED_LIST_TEST_H_ + +void test_empty_list(void); +void test_null_list_handle(void); +void test_add_to_list(void); +void test_find(void); +void test_remove_first_node(void); +void test_remove_first_node_equals(void); +void test_remove_node(void); +int node_data_compare(void *list_entry, void *new_entry); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_queue_test.c b/pceplib/test/pcep_utils_queue_test.c new file mode 100644 index 0000000000..1731457789 --- /dev/null +++ b/pceplib/test/pcep_utils_queue_test.c @@ -0,0 +1,157 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include + +#include "pcep_utils_queue.h" +#include "pcep_utils_queue_test.h" + +typedef struct node_data_ { + int int_data; + +} node_data; + + +void test_empty_queue() +{ + queue_handle *handle = queue_initialize(); + + CU_ASSERT_PTR_NOT_NULL(handle); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + queue_destroy(handle); +} + + +void test_null_queue_handle() +{ + /* test each method handles a NULL handle without crashing */ + node_data data; + queue_destroy(NULL); + void *ptr = queue_enqueue(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = queue_dequeue(NULL); + CU_ASSERT_PTR_NULL(ptr); +} + + +void test_enqueue() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize(); + + queue_enqueue(handle, &data1); + queue_enqueue(handle, &data2); + queue_enqueue(handle, &data3); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + queue_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = node->next_node; + CU_ASSERT_PTR_NULL(node); + + queue_destroy(handle); +} + + +void test_enqueue_with_limit() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize_with_size(2); + + queue_node *node = queue_enqueue(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node); + + node = queue_enqueue(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node); + + node = queue_enqueue(handle, &data3); + CU_ASSERT_PTR_NULL(node); + + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_NULL(node); + + queue_destroy(handle); +} + + +void test_dequeue() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize(); + + /* first test dequeue handles an empty queue */ + void *node_data = queue_dequeue(handle); + CU_ASSERT_PTR_NULL(node_data); + + queue_enqueue(handle, &data1); + queue_enqueue(handle, &data2); + queue_enqueue(handle, &data3); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_NULL(node_data); + + queue_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_queue_test.h b/pceplib/test/pcep_utils_queue_test.h new file mode 100644 index 0000000000..16236d0d9d --- /dev/null +++ b/pceplib/test/pcep_utils_queue_test.h @@ -0,0 +1,36 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_QUEUE_TEST_H_ +#define PCEP_UTILS_QUEUE_TEST_H_ + +void test_empty_queue(void); +void test_null_queue_handle(void); +void test_enqueue(void); +void test_enqueue_with_limit(void); +void test_dequeue(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_tests.c b/pceplib/test/pcep_utils_tests.c new file mode 100644 index 0000000000..452b9fa09c --- /dev/null +++ b/pceplib/test/pcep_utils_tests.c @@ -0,0 +1,136 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Author : Brady Johnson + * + */ + + +#include +#include +#include +#include "pcep_utils_ordered_list_test.h" +#include "pcep_utils_queue_test.h" +#include "pcep_utils_double_linked_list_test.h" +#include "pcep_utils_counters_test.h" +#include "pcep_utils_memory_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + CU_pSuite test_queue_suite = + CU_add_suite("PCEP Utils Queue Test Suite", NULL, NULL); + CU_add_test(test_queue_suite, "test_empty_queue", test_empty_queue); + CU_add_test(test_queue_suite, "test_null_queue_handle", + test_null_queue_handle); + CU_add_test(test_queue_suite, "test_enqueue", test_enqueue); + CU_add_test(test_queue_suite, "test_enqueue_with_limit", + test_enqueue_with_limit); + CU_add_test(test_queue_suite, "test_dequeue", test_dequeue); + + CU_pSuite test_list_suite = + CU_add_suite("PCEP Utils Ordered List Test Suite", NULL, NULL); + CU_add_test(test_list_suite, "test_empty_list", test_empty_list); + CU_add_test(test_list_suite, "test_null_handle", test_null_list_handle); + CU_add_test(test_list_suite, "test_add_toList", test_add_to_list); + CU_add_test(test_list_suite, "test_find", test_find); + CU_add_test(test_list_suite, "test_remove_first_node", + test_remove_first_node); + CU_add_test(test_list_suite, "test_remove_first_node_equals", + test_remove_first_node_equals); + CU_add_test(test_list_suite, "test_remove_node", test_remove_node); + + CU_pSuite test_dl_list_suite = CU_add_suite( + "PCEP Utils Double Linked List Test Suite", NULL, NULL); + CU_add_test(test_dl_list_suite, "test_empty_dl_list", + test_empty_dl_list); + CU_add_test(test_dl_list_suite, "test_null_dl_handle", + test_null_dl_list_handle); + CU_add_test(test_dl_list_suite, "test_dll_prepend_data", + test_dll_prepend_data); + CU_add_test(test_dl_list_suite, "test_dll_append_data", + test_dll_append_data); + CU_add_test(test_dl_list_suite, "test_dll_delete_first_node", + test_dll_delete_first_node); + CU_add_test(test_dl_list_suite, "test_dll_delete_last_node", + test_dll_delete_last_node); + CU_add_test(test_dl_list_suite, "test_dll_delete_node", + test_dll_delete_node); + + CU_pSuite test_counters_suite = + CU_add_suite("PCEP Utils Counters Test Suite", NULL, NULL); + CU_add_test(test_counters_suite, "test_create_counters_group", + test_create_counters_group); + CU_add_test(test_counters_suite, "test_create_counters_subgroup", + test_create_counters_subgroup); + CU_add_test(test_counters_suite, "test_add_counters_subgroup", + test_add_counters_subgroup); + CU_add_test(test_counters_suite, "test_create_subgroup_counter", + test_create_subgroup_counter); + CU_add_test(test_counters_suite, "test_delete_counters_group", + test_delete_counters_group); + CU_add_test(test_counters_suite, "test_delete_counters_subgroup", + test_delete_counters_subgroup); + CU_add_test(test_counters_suite, "test_reset_group_counters", + test_reset_group_counters); + CU_add_test(test_counters_suite, "test_reset_subgroup_counters", + test_reset_subgroup_counters); + CU_add_test(test_counters_suite, "test_increment_counter", + test_increment_counter); + CU_add_test(test_counters_suite, "test_increment_subgroup_counter", + test_increment_subgroup_counter); + CU_add_test(test_counters_suite, "test_dump_counters_group_to_log", + test_dump_counters_group_to_log); + CU_add_test(test_counters_suite, "test_dump_counters_subgroup_to_log", + test_dump_counters_subgroup_to_log); + + CU_pSuite test_memory_suite = + CU_add_suite("PCEP Utils Memory Test Suite", NULL, NULL); + CU_add_test(test_memory_suite, "test_memory_internal_impl", + test_memory_internal_impl); + CU_add_test(test_memory_suite, "test_memory_external_impl", + test_memory_external_impl); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_utils_tests_valgrind.sh b/pceplib/test/pcep_utils_tests_valgrind.sh new file mode 100755 index 0000000000..6348d82708 --- /dev/null +++ b/pceplib/test/pcep_utils_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_utils_tests diff --git a/pceplib/test/subdir.am b/pceplib/test/subdir.am new file mode 100644 index 0000000000..0ae61d1bce --- /dev/null +++ b/pceplib/test/subdir.am @@ -0,0 +1,122 @@ +if PATHD_PCEP +if PATHD_PCEP_TEST + +# The default Automake target is check, add a test target to call check. +# Also make sure the binaries are current before running the tests. +test: pceplib/test/pcep_msg_tests pceplib/test/pcep_pcc_api_tests pceplib/test/pcep_session_logic_tests pceplib/test/pcep_socket_comm_tests pceplib/test/pcep_timers_tests pceplib/test/pcep_utils_tests + +check_SCRIPTS = pceplib/test/pcep_msg_tests pceplib/test/pcep_pcc_api_tests pceplib/test/pcep_session_logic_tests pceplib/test/pcep_socket_comm_tests pceplib/test/pcep_timers_tests pceplib/test/pcep_utils_tests +TESTS = $(check_SCRIPTS) + + +# Definitions to build the Unit Test binaries with CUnit +noinst_PROGRAMS += pceplib/test/pcep_msg_tests \ + pceplib/test/pcep_pcc_api_tests \ + pceplib/test/pcep_session_logic_tests \ + pceplib/test/pcep_socket_comm_tests \ + pceplib/test/pcep_timers_tests \ + pceplib/test/pcep_utils_tests + +noinst_HEADERS += pceplib/test/pcep_msg_messages_test.h \ + pceplib/test/pcep_msg_object_error_types_test.h \ + pceplib/test/pcep_msg_objects_test.h \ + pceplib/test/pcep_msg_tlvs_test.h \ + pceplib/test/pcep_msg_tools_test.h \ + pceplib/test/pcep_pcc_api_test.h \ + pceplib/test/pcep_session_logic_loop_test.h \ + pceplib/test/pcep_session_logic_states_test.h \ + pceplib/test/pcep_session_logic_test.h \ + pceplib/test/pcep_socket_comm_loop_test.h \ + pceplib/test/pcep_socket_comm_test.h \ + pceplib/test/pcep_timers_event_loop_test.h \ + pceplib/test/pcep_timers_test.h \ + pceplib/test/pcep_utils_counters_test.h \ + pceplib/test/pcep_utils_double_linked_list_test.h \ + pceplib/test/pcep_utils_memory_test.h \ + pceplib/test/pcep_utils_ordered_list_test.h \ + pceplib/test/pcep_utils_queue_test.h + +pceplib_test_pcep_msg_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_msg_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_msg_tests_SOURCES = pceplib/test/pcep_msg_messages_test.c \ + pceplib/test/pcep_msg_messages_tests.c \ + pceplib/test/pcep_msg_object_error_types_test.c \ + pceplib/test/pcep_msg_objects_test.c \ + pceplib/test/pcep_msg_tlvs_test.c \ + pceplib/test/pcep_msg_tools_test.c + +# The pcc_api_tests and pcep_session_logic_tests use the +# socket_comm_mock, so the LDADD variable needs to be modified +pceplib_test_pcep_pcc_api_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_pcc_api_tests_LDADD = $(top_builddir)/pceplib/libsocket_comm_mock.la $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_pcc_api_tests_SOURCES = pceplib/test/pcep_pcc_api_test.c pceplib/test/pcep_pcc_api_tests.c + +pceplib_test_pcep_session_logic_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_session_logic_tests_LDADD = $(top_builddir)/pceplib/libsocket_comm_mock.la $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_session_logic_tests_SOURCES = pceplib/test/pcep_session_logic_loop_test.c \ + pceplib/test/pcep_session_logic_states_test.c \ + pceplib/test/pcep_session_logic_test.c \ + pceplib/test/pcep_session_logic_tests.c + +pceplib_test_pcep_socket_comm_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_socket_comm_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_socket_comm_tests_SOURCES = pceplib/test/pcep_socket_comm_loop_test.c \ + pceplib/test/pcep_socket_comm_test.c \ + pceplib/test/pcep_socket_comm_tests.c + +pceplib_test_pcep_timers_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_timers_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_timers_tests_SOURCES = pceplib/test/pcep_timers_event_loop_test.c \ + pceplib/test/pcep_timers_test.c \ + pceplib/test/pcep_timers_tests.c + +pceplib_test_pcep_utils_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_utils_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_utils_tests_SOURCES = pceplib/test/pcep_utils_counters_test.c \ + pceplib/test/pcep_utils_double_linked_list_test.c \ + pceplib/test/pcep_utils_memory_test.c \ + pceplib/test/pcep_utils_ordered_list_test.c \ + pceplib/test/pcep_utils_queue_test.c \ + pceplib/test/pcep_utils_tests.c + +# These test scripts will call the test binaries +# defined above in noinst_PROGRAMS with Valgrind +if HAVE_VALGRIND_PCEP + +dist_noinst_SCRIPTS = pceplib/test/pcep_pcc_api_tests_valgrind.sh \ + pceplib/test/pcep_session_logic_tests_valgrind.sh \ + pceplib/test/pcep_socket_comm_tests_valgrind.sh \ + pceplib/test/pcep_timers_tests_valgrind.sh \ + pceplib/test/pcep_utils_tests_valgrind.sh \ + pceplib/test/pcep_msg_tests_valgrind.sh \ + pceplib/test/pcep_tests_valgrind.sh + +check_SCRIPTS += pceplib/test/pcep_msg_tests_valgrind.sh \ + pceplib/test/pcep_pcc_api_tests_valgrind.sh \ + pceplib/test/pcep_session_logic_tests_valgrind.sh \ + pceplib/test/pcep_socket_comm_tests_valgrind.sh \ + pceplib/test/pcep_timers_tests_valgrind.sh \ + pceplib/test/pcep_utils_tests_valgrind.sh + +TESTS += $(check_SCRIPTS) + + + +pceplib/test/pcep_msg_tests_valgrind.sh: + chmod +x pceplib/test/pcep_msg_tests_valgrind.sh +pceplib/test/pcep_pcc_api_tests_valgrind.sh: + chmod +x pceplib/test/pcep_pcc_api_tests_valgrind.sh +pceplib/test/pcep_session_logic_tests_valgrind.sh: + chmod +x pceplib/test/pcep_session_logic_tests_valgrind.sh +pceplib/test/pcep_socket_comm_tests_valgrind.sh: + chmod +x pceplib/test/pcep_socket_comm_tests_valgrind.sh +pceplib/test/pcep_timers_tests_valgrind.sh: + chmod +x pceplib/test/pcep_timers_tests_valgrind.sh +pceplib/test/pcep_utils_tests_valgrind.sh: + chmod +x pceplib/test/pcep_utils_tests_valgrind.sh + + +endif + +endif +endif diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index 02c272f47c..b6d7ab2416 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -698,6 +698,7 @@ fi %endif %if %{with_pathd} %{_sbindir}/pathd + %{_libdir}/frr/modules/pathd_pcep.so %endif %{_libdir}/libfrr.so* %{_libdir}/libfrrcares* -- 2.39.5