From 140dfe054d35fb20c929ea1404d20d3354be70a3 Mon Sep 17 00:00:00 2001 From: Yuqing Zhao Date: Thu, 26 Mar 2026 17:01:01 +0800 Subject: [PATCH 1/2] [rules][src]: Add sonic-fib for nexthopgroup deb Update compiling files to build libnexthopgroup debian for sonic-fib. Signed-off-by: Eddie Ruan Signed-off-by: Yuqing Zhao --- rules/frr.mk | 4 +-- rules/sonic-fib.dep | 12 ++++++++ rules/sonic-fib.mk | 28 +++++++++++++++++++ rules/swss.mk | 4 +-- .../dplane_fpm_sonic/dplane_fpm_sonic.c | 1 + ...C-ONLY-build-dplane-fpm-sonic-module.patch | 4 +-- 6 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 rules/sonic-fib.dep create mode 100644 rules/sonic-fib.mk diff --git a/rules/frr.mk b/rules/frr.mk index f2974604c87..93aa311cd55 100644 --- a/rules/frr.mk +++ b/rules/frr.mk @@ -6,8 +6,8 @@ FRR_TAG = frr-$(FRR_VERSION) export FRR_VERSION FRR_SUBVERSION FRR_TAG FRR = frr_$(FRR_VERSION)-sonic-$(FRR_SUBVERSION)_$(CONFIGURED_ARCH).deb -$(FRR)_DEPENDS += $(LIBSNMP_DEV) $(LIBYANG3_DEV) -$(FRR)_RDEPENDS += $(LIBYANG3) +$(FRR)_DEPENDS += $(LIBSNMP_DEV) $(LIBYANG3_DEV) $(LIBFIB_DEV) +$(FRR)_RDEPENDS += $(LIBYANG3) $(LIBFIB) $(FRR)_UNINSTALLS = $(LIBYANG3_DEV) $(FRR)_SRC_PATH = $(SRC_PATH)/sonic-frr SONIC_MAKE_DEBS += $(FRR) diff --git a/rules/sonic-fib.dep b/rules/sonic-fib.dep new file mode 100644 index 00000000000..b857d56996e --- /dev/null +++ b/rules/sonic-fib.dep @@ -0,0 +1,12 @@ + +SPATH := $($(LIBFIB)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/sonic-fib.mk rules/sonic-fib.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +SMDEP_FILES := $(addprefix $(SPATH)/,$(shell cd $(SPATH) && git ls-files)) + +$(LIBFIB)_CACHE_MODE := GIT_CONTENT_SHA +$(LIBFIB)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(LIBFIB)_DEP_FILES := $(DEP_FILES) +$(LIBFIB)_SMDEP_FILES := $(SMDEP_FILES) +$(LIBFIB)_SMDEP_PATHS := $(SPATH) + diff --git a/rules/sonic-fib.mk b/rules/sonic-fib.mk new file mode 100644 index 00000000000..9fb43d923ac --- /dev/null +++ b/rules/sonic-fib.mk @@ -0,0 +1,28 @@ +# libFIB package + +LIBFIB_VERSION = 1.0.0 +LIBFIB_NAME = libnexthopgroup + +LIBFIB = $(LIBFIB_NAME)_$(LIBFIB_VERSION)_$(CONFIGURED_ARCH).deb +$(LIBFIB)_SRC_PATH = $(SRC_PATH)/sonic-fib +$(LIBFIB)_VERSION = $(LIBFIB_VERSION) +$(LIBFIB)_NAME = $(LIBFIB_NAME) +$(LIBFIB)_DEPENDS += $(LIBNL3_DEV) $(LIBNL_GENL3_DEV) \ + $(LIBNL_ROUTE3_DEV) $(LIBNL_NF3_DEV) \ + $(LIBNL_CLI_DEV) +$(LIBFIB)_RDEPENDS += $(LIBNL3) $(LIBNL_GENL3) \ + $(LIBNL_ROUTE3) $(LIBNL_NF3) $(LIBNL_CLI) +SONIC_DPKG_DEBS += $(LIBFIB) + +LIBFIB_DEV = $(LIBFIB_NAME)-dev_$(LIBFIB_VERSION)_$(CONFIGURED_ARCH).deb +$(eval $(call add_derived_package,$(LIBFIB),$(LIBFIB_DEV))) + + + + + +# The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list} +# are archived into debug one image to facilitate debugging. +# +DBG_SRC_ARCHIVE += sonic-fib + diff --git a/rules/swss.mk b/rules/swss.mk index b68804887c1..bca03d71991 100644 --- a/rules/swss.mk +++ b/rules/swss.mk @@ -3,13 +3,13 @@ SWSS = swss_1.0.0_$(CONFIGURED_ARCH).deb $(SWSS)_SRC_PATH = $(SRC_PATH)/sonic-swss $(SWSS)_DEPENDS += $(LIBSAIREDIS_DEV) $(LIBSAIMETADATA_DEV) $(LIBTEAM_DEV) \ - $(LIBTEAMDCTL) $(LIBTEAM_UTILS) $(LIBSWSSCOMMON_DEV) \ + $(LIBTEAMDCTL) $(LIBTEAM_UTILS) $(LIBSWSSCOMMON_DEV) $(LIBFIB_DEV) \ $(LIBSAIVS) $(LIBSAIVS_DEV) $(STP)\ $(PROTOBUF) $(PROTOBUF_LITE) $(PROTOBUF_DEV) $(LIB_SONIC_DASH_API) $(SWSS)_UNINSTALLS = $(LIBSAIVS_DEV) $(SWSS)_RDEPENDS += $(LIBSAIREDIS) $(LIBSAIMETADATA) $(LIBTEAM) \ - $(LIBTEAMDCTL) $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON) \ + $(LIBTEAMDCTL) $(LIBSWSSCOMMON) $(PYTHON3_SWSSCOMMON) $(LIBFIB) \ $(PROTOBUF) $(PROTOBUF_LITE) $(PYTHON3_PROTOBUF) $(LIB_SONIC_DASH_API) SONIC_DPKG_DEBS += $(SWSS) diff --git a/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c b/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c index 324cef3e5dd..c6d963ea063 100644 --- a/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c +++ b/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c @@ -58,6 +58,7 @@ #include "zebra/zebra_srv6.h" #include "fpm/fpm.h" #include "lib/srv6.h" +#include #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK #define SOUTHBOUND_DEFAULT_PORT 2620 diff --git a/src/sonic-frr/patch/0012-SONiC-ONLY-build-dplane-fpm-sonic-module.patch b/src/sonic-frr/patch/0012-SONiC-ONLY-build-dplane-fpm-sonic-module.patch index c23de09f4b2..d6cf2bb6e04 100644 --- a/src/sonic-frr/patch/0012-SONiC-ONLY-build-dplane-fpm-sonic-module.patch +++ b/src/sonic-frr/patch/0012-SONiC-ONLY-build-dplane-fpm-sonic-module.patch @@ -30,12 +30,12 @@ index f76744736..c222984ae 100644 @@ -252,6 +252,12 @@ zebra_dplane_fpm_nl_la_LDFLAGS = $(MODULE_LDFLAGS) zebra_dplane_fpm_nl_la_LIBADD = endif - + +module_LTLIBRARIES += zebra/dplane_fpm_sonic.la + +zebra_dplane_fpm_sonic_la_SOURCES = zebra/dplane_fpm_sonic.c +zebra_dplane_fpm_sonic_la_LDFLAGS = $(MODULE_LDFLAGS) -+zebra_dplane_fpm_sonic_la_LIBADD = ++zebra_dplane_fpm_sonic_la_LIBADD = -lnexthopgroup + if NETLINK_DEBUG zebra_zebra_SOURCES += \ From e74192110ba6ba8f8e4756b5488fc447b8e73b87 Mon Sep 17 00:00:00 2001 From: Yuqing Zhao Date: Thu, 26 Mar 2026 17:04:56 +0800 Subject: [PATCH 2/2] [src/libraries/sonic-fib]: Implementation for sonic-fib Introduce sonic-fib, a C++ library that provides encoding and decoding capabilities for NextHopGroupFull objects: - FRR uses the encoding function to generate a JSON string embedded in messages sent to fpmsyncd - fpmsyncd uses the decoding function to reconstruct the NHG object from the incoming JSON string To minimize human error, the library uses a JSON Schema as the single source of truth and Jinja2 templates to auto-generate the required code. Key components: - Data schema: NextHopGroupFull.json drives auto-generation of C++ classes, JSON codecs, and C struct wrappers via render_schema.py + Jinja2. - C-API: C-callable interface for FRR to encode NHG objects into JSON. - Logging: pluggable FIB_LOG macro with per-process callback registration (fib_frr_register_callback for FRR, registerLogCallback for SWSS). - Unit tests: covers C++ class behavior, JSON round-trip, C-API conversion, and logging callback using Google Test. Signed-off-by: Yuqing Zhao Signed-off-by: Eddie Ruan --- rules/sonic-fib.mk | 10 +- src/libraries/sonic-fib/.gitignore | 52 ++ src/libraries/sonic-fib/Makefile.am | 18 + src/libraries/sonic-fib/README.md | 25 + src/libraries/sonic-fib/autogen.sh | 6 + src/libraries/sonic-fib/configure.ac | 82 +++ src/libraries/sonic-fib/debian/changelog | 5 + src/libraries/sonic-fib/debian/compat | 1 + src/libraries/sonic-fib/debian/control | 24 + .../sonic-fib/debian/libnexthopgroup-dev.dirs | 2 + .../debian/libnexthopgroup-dev.install | 3 + .../sonic-fib/debian/libnexthopgroup.dirs | 1 + .../sonic-fib/debian/libnexthopgroup.install | 2 + src/libraries/sonic-fib/debian/rules | 19 + src/libraries/sonic-fib/m4/python3.m4 | 195 +++++ .../sonic-fib/schema/NextHopGroupFull.json | 271 +++++++ .../sonic-fib/scripts/render_schema.py | 375 ++++++++++ src/libraries/sonic-fib/src/Makefile.am | 90 +++ .../sonic-fib/src/c-api/nexthopgroup_capi.cpp | 233 ++++++ .../sonic-fib/src/c-api/nexthopgroup_capi.h | 60 ++ .../sonic-fib/src/nexthopgroup_debug.cpp | 100 +++ .../sonic-fib/src/nexthopgroup_debug.h | 52 ++ .../templates/c_nexthopgroupfull.h.j2 | 114 +++ .../templates/nexthopgroupfull.cpp.j2 | 362 +++++++++ .../sonic-fib/templates/nexthopgroupfull.h.j2 | 130 ++++ .../templates/nexthopgroupfull_json.h.j2 | 225 ++++++ src/libraries/sonic-fib/tests/Makefile.am | 19 + src/libraries/sonic-fib/tests/c_api_ut.cpp | 283 +++++++ src/libraries/sonic-fib/tests/main.cpp | 17 + .../sonic-fib/tests/nexthopgroup_debug_ut.cpp | 117 +++ .../tests/nexthopgroupfull_json_ut.cpp | 690 ++++++++++++++++++ .../sonic-fib/tests/nexthopgroupfull_ut.cpp | 484 ++++++++++++ src/libraries/sonic-fib/use_json_schema.md | 21 + 33 files changed, 4081 insertions(+), 7 deletions(-) create mode 100644 src/libraries/sonic-fib/.gitignore create mode 100644 src/libraries/sonic-fib/Makefile.am create mode 100644 src/libraries/sonic-fib/README.md create mode 100755 src/libraries/sonic-fib/autogen.sh create mode 100644 src/libraries/sonic-fib/configure.ac create mode 100644 src/libraries/sonic-fib/debian/changelog create mode 100644 src/libraries/sonic-fib/debian/compat create mode 100644 src/libraries/sonic-fib/debian/control create mode 100644 src/libraries/sonic-fib/debian/libnexthopgroup-dev.dirs create mode 100644 src/libraries/sonic-fib/debian/libnexthopgroup-dev.install create mode 100644 src/libraries/sonic-fib/debian/libnexthopgroup.dirs create mode 100644 src/libraries/sonic-fib/debian/libnexthopgroup.install create mode 100755 src/libraries/sonic-fib/debian/rules create mode 100644 src/libraries/sonic-fib/m4/python3.m4 create mode 100644 src/libraries/sonic-fib/schema/NextHopGroupFull.json create mode 100755 src/libraries/sonic-fib/scripts/render_schema.py create mode 100644 src/libraries/sonic-fib/src/Makefile.am create mode 100644 src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.cpp create mode 100644 src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.h create mode 100644 src/libraries/sonic-fib/src/nexthopgroup_debug.cpp create mode 100644 src/libraries/sonic-fib/src/nexthopgroup_debug.h create mode 100644 src/libraries/sonic-fib/templates/c_nexthopgroupfull.h.j2 create mode 100644 src/libraries/sonic-fib/templates/nexthopgroupfull.cpp.j2 create mode 100644 src/libraries/sonic-fib/templates/nexthopgroupfull.h.j2 create mode 100644 src/libraries/sonic-fib/templates/nexthopgroupfull_json.h.j2 create mode 100644 src/libraries/sonic-fib/tests/Makefile.am create mode 100644 src/libraries/sonic-fib/tests/c_api_ut.cpp create mode 100644 src/libraries/sonic-fib/tests/main.cpp create mode 100644 src/libraries/sonic-fib/tests/nexthopgroup_debug_ut.cpp create mode 100644 src/libraries/sonic-fib/tests/nexthopgroupfull_json_ut.cpp create mode 100644 src/libraries/sonic-fib/tests/nexthopgroupfull_ut.cpp create mode 100644 src/libraries/sonic-fib/use_json_schema.md diff --git a/rules/sonic-fib.mk b/rules/sonic-fib.mk index 9fb43d923ac..452565c913f 100644 --- a/rules/sonic-fib.mk +++ b/rules/sonic-fib.mk @@ -4,14 +4,10 @@ LIBFIB_VERSION = 1.0.0 LIBFIB_NAME = libnexthopgroup LIBFIB = $(LIBFIB_NAME)_$(LIBFIB_VERSION)_$(CONFIGURED_ARCH).deb -$(LIBFIB)_SRC_PATH = $(SRC_PATH)/sonic-fib +$(LIBFIB)_SRC_PATH = $(SRC_PATH)/libraries/sonic-fib $(LIBFIB)_VERSION = $(LIBFIB_VERSION) $(LIBFIB)_NAME = $(LIBFIB_NAME) -$(LIBFIB)_DEPENDS += $(LIBNL3_DEV) $(LIBNL_GENL3_DEV) \ - $(LIBNL_ROUTE3_DEV) $(LIBNL_NF3_DEV) \ - $(LIBNL_CLI_DEV) -$(LIBFIB)_RDEPENDS += $(LIBNL3) $(LIBNL_GENL3) \ - $(LIBNL_ROUTE3) $(LIBNL_NF3) $(LIBNL_CLI) + SONIC_DPKG_DEBS += $(LIBFIB) LIBFIB_DEV = $(LIBFIB_NAME)-dev_$(LIBFIB_VERSION)_$(CONFIGURED_ARCH).deb @@ -24,5 +20,5 @@ $(eval $(call add_derived_package,$(LIBFIB),$(LIBFIB_DEV))) # The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list} # are archived into debug one image to facilitate debugging. # -DBG_SRC_ARCHIVE += sonic-fib +DBG_SRC_ARCHIVE += libraries/sonic-fib diff --git a/src/libraries/sonic-fib/.gitignore b/src/libraries/sonic-fib/.gitignore new file mode 100644 index 00000000000..531c2fa7294 --- /dev/null +++ b/src/libraries/sonic-fib/.gitignore @@ -0,0 +1,52 @@ +# Compiled Source # +################### +*.la +*.lo +*.o +*.swp +*.gcda +*.gcno +*.gcov +*.deb + + + +# Packaging Files # +################### +debian/.debhelper/ +debian/autoreconf.after +debian/autoreconf.before +debian/debhelper-build-stamp +debian/files +debian/*.debhelper.log +debian/*.substvars +debian/tmp/ + +aclocal.m4 +autom4te.cache/ +config +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +libtool +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +Makefile.in +stamp-h1 +.dirstamp +**/.deps/ +**/.libs/ +**/Makefile + +# Executables # +############### +tests/tests + +# Bazel Build System # +/bazel-* diff --git a/src/libraries/sonic-fib/Makefile.am b/src/libraries/sonic-fib/Makefile.am new file mode 100644 index 00000000000..f7cc81dd04d --- /dev/null +++ b/src/libraries/sonic-fib/Makefile.am @@ -0,0 +1,18 @@ +lib_LTLIBRARIES = +bin_PROGRAMS = + +if DEBUG +DBGFLAGS = -ggdb -DDEBUG -gdwarf-5 +else +DBGFLAGS = -g -DNDEBUG +endif + +CLEANFILES = + +include src/Makefile.am +include tests/Makefile.am + +ACLOCAL_AMFLAGS = -I m4 + + +EXTRA_DIST += scripts/render_schema.py diff --git a/src/libraries/sonic-fib/README.md b/src/libraries/sonic-fib/README.md new file mode 100644 index 00000000000..7ab579015f2 --- /dev/null +++ b/src/libraries/sonic-fib/README.md @@ -0,0 +1,25 @@ +# sonic-fib + +## Cheating Sheet +### 1. Clean stale build artifacts +make distclean 2>/dev/null || true +rm -rf autom4te.cache config.log config.status + +### 2. Regenerate build system (critical after Makefile.am changes) +autoreconf -fiv + +### 3. Reconfigure +./configure + +### 4. Build (includes tests) +make -j$(nproc) + +### 5. Run tests (uses pre-built binary) +``` +make check +make check V=1 # Verbose output +``` + +Note: +1. Step 1 and Step 2 are used for installing test_drivers. Use the following command to check ls -la config/test-driver +2. test log is at tests/tests.log diff --git a/src/libraries/sonic-fib/autogen.sh b/src/libraries/sonic-fib/autogen.sh new file mode 100755 index 00000000000..c8d0bbe4a25 --- /dev/null +++ b/src/libraries/sonic-fib/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +libtoolize --force --copy && +autoreconf --force --install -I m4 +rm -Rf autom4te.cache + diff --git a/src/libraries/sonic-fib/configure.ac b/src/libraries/sonic-fib/configure.ac new file mode 100644 index 00000000000..43d572cf021 --- /dev/null +++ b/src/libraries/sonic-fib/configure.ac @@ -0,0 +1,82 @@ +AC_INIT([fib],[1.0.0]) +AC_CONFIG_SRCDIR([]) +AC_CONFIG_AUX_DIR(config) +AM_CONFIG_HEADER(config.h) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign subdir-objects parallel-tests]) +AC_LANG_C +AC_LANG([C++]) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_LIBTOOL +AC_HEADER_STDC +AM_PATH_PYTHON +AM_PATH_PYTHON3 +AX_ADD_AM_MACRO_STATIC([]) + +AC_ARG_ENABLE(debug, +[ --enable-debug Compile with debugging flags], +[case "${enableval}" in + yes) debug=true ;; + no) debug=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; +esac],[debug=false]) +AM_CONDITIONAL(DEBUG, test x$debug = xtrue) +if test x$CONFIGURED_ARCH = xarmhf && test x$CROSS_BUILD_ENVIRON = xy; then + AM_CONDITIONAL(ARCH64, false) +else + AM_CONDITIONAL(ARCH64, test `getconf LONG_BIT` = "64") +fi + + +CFLAGS_COMMON="" +CFLAGS_COMMON+=" -ansi" +CFLAGS_COMMON+=" -fPIC" +CFLAGS_COMMON+=" -std=c++14" +CFLAGS_COMMON+=" -Wall" +CFLAGS_COMMON+=" -Wcast-align" +CFLAGS_COMMON+=" -Wcast-qual" +CFLAGS_COMMON+=" -Wconversion" +CFLAGS_COMMON+=" -Wdisabled-optimization" +CFLAGS_COMMON+=" -Werror" +CFLAGS_COMMON+=" -Wextra" +CFLAGS_COMMON+=" -Wfloat-equal" +CFLAGS_COMMON+=" -Wformat=2" +CFLAGS_COMMON+=" -Wformat-nonliteral" +CFLAGS_COMMON+=" -Wformat-security" +CFLAGS_COMMON+=" -Wformat-y2k" +CFLAGS_COMMON+=" -Wimport" +CFLAGS_COMMON+=" -Winit-self" +CFLAGS_COMMON+=" -Winvalid-pch" +CFLAGS_COMMON+=" -Wlong-long" +CFLAGS_COMMON+=" -Wmissing-field-initializers" +CFLAGS_COMMON+=" -Wmissing-format-attribute" +CFLAGS_COMMON+=" -Wmissing-include-dirs" +CFLAGS_COMMON+=" -Wmissing-noreturn" +CFLAGS_COMMON+=" -Wno-aggregate-return" +CFLAGS_COMMON+=" -Wno-padded" +CFLAGS_COMMON+=" -Wno-switch-enum" +CFLAGS_COMMON+=" -Wno-unused-parameter" +CFLAGS_COMMON+=" -Wpacked" +CFLAGS_COMMON+=" -Wpointer-arith" +CFLAGS_COMMON+=" -Wredundant-decls" +CFLAGS_COMMON+=" -Wshadow" +CFLAGS_COMMON+=" -Wstack-protector" +CFLAGS_COMMON+=" -Wstrict-aliasing=3" +CFLAGS_COMMON+=" -Wswitch" +CFLAGS_COMMON+=" -Wswitch-default" +CFLAGS_COMMON+=" -Wunreachable-code" +CFLAGS_COMMON+=" -Wunused" +CFLAGS_COMMON+=" -Wvariadic-macros" +CFLAGS_COMMON+=" -Wno-write-strings" +CFLAGS_COMMON+=" -Wno-missing-format-attribute" +CFLAGS_COMMON+=" -Wno-long-long" +CFLAGS_COMMON+=" -fstack-protector-strong" + +AC_SUBST(CFLAGS_COMMON) + +AC_CONFIG_FILES([ + Makefile +]) + +AC_OUTPUT diff --git a/src/libraries/sonic-fib/debian/changelog b/src/libraries/sonic-fib/debian/changelog new file mode 100644 index 00000000000..83e568e4437 --- /dev/null +++ b/src/libraries/sonic-fib/debian/changelog @@ -0,0 +1,5 @@ +sonic-fib (1.0.0) stable; urgency=medium + + * Initial release. + + diff --git a/src/libraries/sonic-fib/debian/compat b/src/libraries/sonic-fib/debian/compat new file mode 100644 index 00000000000..48082f72f08 --- /dev/null +++ b/src/libraries/sonic-fib/debian/compat @@ -0,0 +1 @@ +12 diff --git a/src/libraries/sonic-fib/debian/control b/src/libraries/sonic-fib/debian/control new file mode 100644 index 00000000000..58dc0dcce87 --- /dev/null +++ b/src/libraries/sonic-fib/debian/control @@ -0,0 +1,24 @@ +Source: sonic-fib +Section: libs +Maintainer: Eddie Ruan +Priority: optional +Build-Depends: debhelper (>= 12), + autotools-dev, + python3, + nlohmann-json3-dev, + libgtest-dev, + libgmock-dev +Standards-Version: 1.0.0 + +Package: libnexthopgroup +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Pre-Depends} +Section: libs +Description: NextHopGroup serialization library for SONiC FIB This library provides JSON-based serialization for NextHopGroup data structures used between FRR (zebra) and fpmsyncd. + +Package: libnexthopgroup-dev +Architecture: any +Depends: libnexthopgroup (= ${binary:Version}), nlohmann-json3-dev, ${misc:Depends} +Section: libdevel +Description: This package contains development files for nexthopgroup + diff --git a/src/libraries/sonic-fib/debian/libnexthopgroup-dev.dirs b/src/libraries/sonic-fib/debian/libnexthopgroup-dev.dirs new file mode 100644 index 00000000000..740ffa13508 --- /dev/null +++ b/src/libraries/sonic-fib/debian/libnexthopgroup-dev.dirs @@ -0,0 +1,2 @@ +usr/lib +usr/include/nexthopgroup diff --git a/src/libraries/sonic-fib/debian/libnexthopgroup-dev.install b/src/libraries/sonic-fib/debian/libnexthopgroup-dev.install new file mode 100644 index 00000000000..a0bb1dcc4b7 --- /dev/null +++ b/src/libraries/sonic-fib/debian/libnexthopgroup-dev.install @@ -0,0 +1,3 @@ +usr/include/nexthopgroup/ +usr/include/nexthopgroup/c-api/ +usr/lib/*/libnexthopgroup.so diff --git a/src/libraries/sonic-fib/debian/libnexthopgroup.dirs b/src/libraries/sonic-fib/debian/libnexthopgroup.dirs new file mode 100644 index 00000000000..68457717bd8 --- /dev/null +++ b/src/libraries/sonic-fib/debian/libnexthopgroup.dirs @@ -0,0 +1 @@ +usr/lib diff --git a/src/libraries/sonic-fib/debian/libnexthopgroup.install b/src/libraries/sonic-fib/debian/libnexthopgroup.install new file mode 100644 index 00000000000..8ad0c1d9f42 --- /dev/null +++ b/src/libraries/sonic-fib/debian/libnexthopgroup.install @@ -0,0 +1,2 @@ +usr/lib/*/libnexthopgroup.so.* + diff --git a/src/libraries/sonic-fib/debian/rules b/src/libraries/sonic-fib/debian/rules new file mode 100755 index 00000000000..b3fe6142377 --- /dev/null +++ b/src/libraries/sonic-fib/debian/rules @@ -0,0 +1,19 @@ +#!/usr/bin/make -f + +# Enable security hardening +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# Import default build flags (CFLAGS, LDFLAGS, etc.) +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# Configure arguments: disable static lib, enable shared +export CONFIGURE_ARGS = --disable-static --enable-shared + +# Use autoreconf to ensure configure is up to date +%: + dh $@ --with autoreconf + +# Optional: override configure if you need extra flags later +override_dh_auto_configure: + dh_auto_configure -- $(CONFIGURE_ARGS) diff --git a/src/libraries/sonic-fib/m4/python3.m4 b/src/libraries/sonic-fib/m4/python3.m4 new file mode 100644 index 00000000000..f1cd6079aba --- /dev/null +++ b/src/libraries/sonic-fib/m4/python3.m4 @@ -0,0 +1,195 @@ +## ------------------------ -*- Autoconf -*- +## Python 3 file handling, adapted from: +## Python file handling +## From Andrew Dalke +## Updated by James Henstridge +## ------------------------ +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009 +# Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PATH_PYTHON3([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# --------------------------------------------------------------------------- +# Adds support for distributing Python modules and packages. To +# install modules, copy them to $(pythondir), using the python_PYTHON +# automake variable. To install a package with the same name as the +# automake package, install to $(pkgpythondir), or use the +# pkgpython_PYTHON automake variable. +# +# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as +# locations to install python extension modules (shared libraries). +# Another macro is required to find the appropriate flags to compile +# extension modules. +# +# If your package is configured with a different prefix to python, +# users will have to add the install directory to the PYTHONPATH +# environment variable, or create a .pth file (see the python +# documentation for details). +# +# If the MINIMUM-VERSION argument is passed, AM_PATH_PYTHON will +# cause an error if the version of python installed on the system +# doesn't meet the requirement. MINIMUM-VERSION should consist of +# numbers and dots only. +AC_DEFUN([AM_PATH_PYTHON3], + [ + dnl Find a Python 3 interpreter + m4_define_default([_AM_PYTHON3_INTERPRETER_LIST], + [python3 python3.0 python3.1]) + + m4_if([$1],[],[ + dnl No version check is needed. + # Find any Python 3 interpreter. + if test -z "$PYTHON3"; then + AC_PATH_PROGS([PYTHON3], _AM_PYTHON3_INTERPRETER_LIST, :) + fi + am_display_PYTHON3=python3 + ], [ + dnl A version check is needed. + if test -n "$PYTHON3"; then + # If the user set $PYTHON3, use it and don't search something else. + AC_MSG_CHECKING([whether $PYTHON3 version >= $1]) + AM_PYTHON3_CHECK_VERSION([$PYTHON3], [$1], + [AC_MSG_RESULT(yes)], + [AC_MSG_ERROR(too old)]) + am_display_PYTHON3=$PYTHON3 + else + # Otherwise, try each interpreter until we find one that satisfies + # VERSION. + AC_CACHE_CHECK([for a Python3 interpreter with version >= $1], + [am_cv_pathless_PYTHON3],[ + for am_cv_pathless_PYTHON3 in _AM_PYTHON3_INTERPRETER_LIST none; do + test "$am_cv_pathless_PYTHON3" = none && break + AM_PYTHON3_CHECK_VERSION([$am_cv_pathless_PYTHON3], [$1], [break]) + done]) + # Set $PYTHON3 to the absolute path of $am_cv_pathless_PYTHON3. + if test "$am_cv_pathless_PYTHON3" = none; then + PYTHON3=: + else + AC_PATH_PROG([PYTHON3], [$am_cv_pathless_PYTHON3]) + fi + am_display_PYTHON3=$am_cv_pathless_PYTHON3 + fi + ]) + + if test "$PYTHON3" = :; then + dnl Run any user-specified action, or abort. + m4_default([$3], [AC_MSG_ERROR([no suitable Python3 interpreter found])]) + else + + dnl Query Python3 for its version number. Getting [:3] seems to be + dnl the best way to do this; it's what "site.py" does in the standard + dnl library. + + AC_CACHE_CHECK([for $am_display_PYTHON3 version], [am_cv_python3_version], + [am_cv_python3_version=`$PYTHON3 -c "import sys; print ('%u.%u' % sys.version_info[[:2]])"`]) + AC_SUBST([PYTHON3_VERSION], [$am_cv_python3_version]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of PYTHON3_PREFIX and PYTHON3_EXEC_PREFIX. These are made + dnl distinct variables so they can be overridden if need be. However, + dnl general consensus is that you shouldn't need this ability. + + AC_SUBST([PYTHON3_PREFIX], ['${prefix}']) + AC_SUBST([PYTHON3_EXEC_PREFIX], ['${exec_prefix}']) + + dnl At times (like when building shared libraries) you may want + dnl to know which OS platform Python3 thinks this is. + + AC_CACHE_CHECK([for $am_display_PYTHON3 platform], [am_cv_python3_platform], + [am_cv_python3_platform=`$PYTHON3 -c "import sys; sys.stdout.write(sys.platform)"`]) + AC_SUBST([PYTHON3_PLATFORM], [$am_cv_python3_platform]) + + AC_CACHE_CHECK([for $am_display_PYTHON3 bldlibrary], [am_cv_python3_bldlibrary], + [am_cv_python3_bldlibrary=`$PYTHON3 -c "import sys; import sysconfig; sys.stdout.write(sysconfig.get_config_var('BLDLIBRARY'))"`]) + AC_SUBST([PYTHON3_BLDLIBRARY], [$am_cv_python3_bldlibrary]) + + dnl Set up 4 directories: + + dnl python3dir -- where to install python3 scripts. This is the + dnl site-packages directory, not the python3 standard library + dnl directory like in previous automake betas. This behavior + dnl is more consistent with lispdir.m4 for example. + dnl Query distutils for this directory. distutils does not exist in + dnl Python3 1.5, so we fall back to the hardcoded directory if it + dnl doesn't work. + AC_CACHE_CHECK([for $am_display_PYTHON3 script directory], + [am_cv_python3_python3dir], + [if test "x$prefix" = xNONE + then + am_py_prefix=$ac_default_prefix + else + am_py_prefix=$prefix + fi + am_cv_python3_python3dir=`$PYTHON3 -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(0,0,prefix='$am_py_prefix'))" 2>/dev/null || + echo "$PYTHON3_PREFIX/lib/python$PYTHON3_VERSION/site-packages"` + case $am_cv_python3_python3dir in + $am_py_prefix*) + am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` + am_cv_python3_python3dir=`echo "$am_cv_python3_python3dir" | sed "s,^$am__strip_prefix,$PYTHON3_PREFIX,"` + ;; + esac + ]) + AC_SUBST([python3dir], [$am_cv_python3_python3dir]) + + dnl pkgpython3dir -- $PACKAGE directory under python3dir. Was + dnl PYTHON3_SITE_PACKAGE in previous betas, but this naming is + dnl more consistent with the rest of automake. + + AC_SUBST([pkgpython3dir], [\${python3dir}/$PACKAGE]) + + dnl pyexecdir -- directory for installing python3 extension modules + dnl (shared libraries) + dnl Query distutils for this directory. distutils does not exist in + dnl Python3 1.5, so we fall back to the hardcoded directory if it + dnl doesn't work. + AC_CACHE_CHECK([for $am_display_PYTHON3 extension module directory], + [am_cv_python3_pyexecdir], + [if test "x$exec_prefix" = xNONE + then + am_py_exec_prefix=$am_py_prefix + else + am_py_exec_prefix=$exec_prefix + fi + am_cv_python3_pyexecdir=`$PYTHON3 -c "import sys; from distutils import sysconfig; sys.stdout.write(sysconfig.get_python_lib(1,0,prefix='$am_py_exec_prefix'))" 2>/dev/null || + echo "$PYTHON3_EXEC_PREFIX/lib/python$PYTHON3_VERSION/site-packages"` + case $am_cv_python3_pyexecdir in + $am_py_exec_prefix*) + am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` + am_cv_python3_pyexecdir=`echo "$am_cv_python3_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON3_EXEC_PREFIX,"` + ;; + esac + ]) + AC_SUBST([py3execdir], [$am_cv_python3_pyexecdir]) + + dnl pkgpy3execdir -- $(py3execdir)/$(PACKAGE) + + AC_SUBST([pkgpy3execdir], [\${py3execdir}/$PACKAGE]) + + dnl Run any user-specified action. + $2 + fi + +]) + + +# AM_PYTHON3_CHECK_VERSION(PROG, VERSION, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# --------------------------------------------------------------------------- +# Run ACTION-IF-TRUE if the Python3 interpreter PROG has version >= VERSION. +# Run ACTION-IF-FALSE otherwise. +# This test uses sys.hexversion instead of the string equivalent (first +# word of sys.version), in order to cope with versions such as 2.2c1. +# This supports Python3 2.0 or higher. (2.0 was released on October 16, 2000). +AC_DEFUN([AM_PYTHON3_CHECK_VERSION], + [prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python3 3.0 and a list in 2.x +minver = list(map(int, '$2'.split('.'))) + [[0, 0, 0]] +minverhex = 0 +# xrange is not present in Python3yy 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[[i]] +sys.exit(sys.hexversion < minverhex)" + AS_IF([AM_RUN_LOG([$1 -c "$prog"])], [$3], [$4])]) diff --git a/src/libraries/sonic-fib/schema/NextHopGroupFull.json b/src/libraries/sonic-fib/schema/NextHopGroupFull.json new file mode 100644 index 00000000000..bec01f5e310 --- /dev/null +++ b/src/libraries/sonic-fib/schema/NextHopGroupFull.json @@ -0,0 +1,271 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "NextHopGroupFull.json", + "title": "NextHopGroupFull", + "description": "Full nexthop group representation for serialization", + "type": "object", + "properties": { + "id": { + "type": "integer", + "position" : 1, + "minimum": 0, + "default_value" : "0", + "description": "Nexthop group ID" + }, + "key": { + "type": "integer", + "position" : 1, + "minimum": 0, + "default_value" : "0", + "description": "Hash key from Zebra" + }, + "weight": { + "type": "integer", + "position" : 1, + "minimum": 0, + "maximum": 65535, + "default_value" : "0", + "description": "Weight for unequal-cost ECMP" + }, + "flags": { + "type": "integer", + "position" : 1, + "minimum": 0, + "maximum": 255, + "default_value" : "0", + "description": "Bit flags (e.g., NEXTHOP_FLAG_ONLINK = 8)" + }, + "nhg_flags": { + "type": "integer", + "position" : 1, + "minimum": 0, + "default_value" : "0", + "description": "Bit flags for NHG (e.g., NEXTHOP_GROUP_RECEIVED = 1024)" + }, + "ifname": { + "type": "string", + "position" : 2, + "default_value" : "\"\"", + "description": "Interface name" + }, + "nh_grp_full_list": { + "position" : 2, + "type": "array", + "items": { "$ref": "#/$defs/nh_grp_full" }, + "C_len": "(MULTIPATH_NUM * MAX_NHG_RECURSION) + 1" + }, + "depends": { + "position" : 2, + "type": "array", + "items": { "type": "integer", "minimum": 0 }, + "C_len": "MULTIPATH_NUM + 1" + }, + "dependents": { + "position" : 2, + "type": "array", + "items": { "type": "integer", "minimum": 0 }, + "C_len": "MULTIPATH_NUM + 1" + }, + "type": { + "position" : 3, + "default_value" : "NEXTHOP_TYPE_INVALID", + "$ref": "#/$defs/nexthop_types_t" + }, + "vrf_id": { + "type": "integer", + "position" : 3, + "minimum": 0, + "default_value" : "0", + "description": "VRF ID" + }, + "ifindex": { + "type": "integer", + "position" : 3, + "default_value" : "0", + "description": "Interface index (can be negative)" + }, + "nh_label_type": { + "position" : 3, + "default_value" : "ZEBRA_LSP_NONE", + "$ref": "#/$defs/lsp_types_t" + }, + "gate": { + "position" : 4, + "$ref": "#/$defs/ip_address", + "description": "Next-hop gateway address (IPv4 or IPv6)" + }, + "bh_type": { + "$ref": "#/$defs/blackhole_type", + "position" : 4, + "description": "Blackhole type (only present if type is BLACKHOLE)" + }, + "src": { + "$ref": "#/$defs/ip_address", + "position" : 5, + "description": "Source address" + }, + "rmap_src": { + "$ref": "#/$defs/ip_address", + "position" : 5, + "description": "Route-map source address" + }, + + "nh_srv6": { + "position" : 6, + "default_value" : "nullptr", + "data_prefix" : "*", + "$ref": "#/$defs/nexthop_srv6" , + "description": "SRv6 information (optional)" + } + }, + "required": [ + "id", "key", "weight", "flags", "ifname", + "depends", "dependents", + "type", "vrf_id", "ifindex", "nh_label_type", + "gate", "src", "rmap_src" + ], + "additionalProperties": false, + + "$defs": { + "nh_grp_full": { + "type": "object", + "properties": { + "id": { "type": "integer", "minimum": 0 }, + "weight": { "type": "integer", "minimum": 0, "maximum": 65535 }, + "num_direct": { "type": "integer", "minimum": 0 } + }, + "required": ["id", "weight", "num_direct"], + "additionalProperties": false + }, + "nexthop_types_t": { + "type": "string", + "enum": [ + "NEXTHOP_TYPE_INVALID", + "NEXTHOP_TYPE_IFINDEX", + "NEXTHOP_TYPE_IPV4", + "NEXTHOP_TYPE_IPV4_IFINDEX", + "NEXTHOP_TYPE_IPV6", + "NEXTHOP_TYPE_IPV6_IFINDEX", + "NEXTHOP_TYPE_BLACKHOLE" + ] + }, + "lsp_types_t": { + "type": "string", + "enum": [ + "ZEBRA_LSP_NONE", + "ZEBRA_LSP_STATIC", + "ZEBRA_LSP_LDP", + "ZEBRA_LSP_BGP", + "ZEBRA_LSP_OSPF_SR", + "ZEBRA_LSP_ISIS_SR", + "ZEBRA_LSP_SHARP", + "ZEBRA_LSP_SRTE", + "ZEBRA_LSP_EVPN" + ] + }, + "blackhole_type": { + "type": "string", + "enum": [ + "BLACKHOLE_UNSPEC", + "BLACKHOLE_NULL", + "BLACKHOLE_REJECT", + "BLACKHOLE_ADMINPROHIB" + ] + }, + "ip_address": { + "type": "string", + "oneOf": [ + { "format": "ipv4" }, + { "format": "ipv6" } + ], + "description": "IPv4 or IPv6 address in standard string notation" + }, + "seg6local_action_t": { + "type": "string", + "enum": [ + "SEG6_LOCAL_ACTION_UNSPEC", + "SEG6_LOCAL_ACTION_END", + "SEG6_LOCAL_ACTION_END_X", + "SEG6_LOCAL_ACTION_END_T", + "SEG6_LOCAL_ACTION_END_DX2", + "SEG6_LOCAL_ACTION_END_DX6", + "SEG6_LOCAL_ACTION_END_DX4", + "SEG6_LOCAL_ACTION_END_DT6", + "SEG6_LOCAL_ACTION_END_DT4", + "SEG6_LOCAL_ACTION_END_B6", + "SEG6_LOCAL_ACTION_END_B6_ENCAP", + "SEG6_LOCAL_ACTION_END_BM", + "SEG6_LOCAL_ACTION_END_S", + "SEG6_LOCAL_ACTION_END_AS", + "SEG6_LOCAL_ACTION_END_AM", + "SEG6_LOCAL_ACTION_END_BPF", + "SEG6_LOCAL_ACTION_END_DT46" + ] + }, + "seg6local_flavors_info": { + "type": "object", + "properties": { + "flv_ops": { "type": "integer", "minimum": 0 }, + "lcblock_len": { "type": "integer", "minimum": 0, "maximum": 255 }, + "lcnode_func_len": { "type": "integer", "minimum": 0, "maximum": 255 } + }, + "required": ["flv_ops", "lcblock_len", "lcnode_func_len"], + "additionalProperties": false + }, + "seg6local_context": { + "type": "object", + "properties": { + "nh4": { "$ref": "#/$defs/in_address" }, + "nh6": { "$ref": "#/$defs/in6_address" }, + "table": { "type": "integer", "minimum": 0 }, + "flv": { "$ref": "#/$defs/seg6local_flavors_info" }, + "block_len": { "type": "integer", "minimum": 0, "maximum": 255 }, + "node_len": { "type": "integer", "minimum": 0, "maximum": 255 }, + "function_len": { "type": "integer", "minimum": 0, "maximum": 255 }, + "argument_len": { "type": "integer", "minimum": 0, "maximum": 255 } + }, + "required": ["nh4", "nh6", "table", "flv", "block_len", "node_len", "function_len", "argument_len"], + "additionalProperties": false + }, + "srv6_headend_behavior": { + "type": "string", + "enum": [ + "SRV6_HEADEND_BEHAVIOR_H_INSERT", + "SRV6_HEADEND_BEHAVIOR_H_ENCAPS", + "SRV6_HEADEND_BEHAVIOR_H_ENCAPS_RED", + "SRV6_HEADEND_BEHAVIOR_H_ENCAPS_L2", + "SRV6_HEADEND_BEHAVIOR_H_ENCAPS_L2_RED" + ] + }, + "seg6_seg_stack": { + "type": "object", + "properties": { + "encap_behavior": { "$ref": "#/$defs/srv6_headend_behavior" }, + "num_segs": { "$ref": "#/$defs/uint8" }, + "seg": { + "type": "array", + "items": { "$ref": "#/$defs/in6_address" }, + "minItems": 0 + } + }, + "required": ["encap_behavior", "seg"], + "additionalProperties": false + }, + "nexthop_srv6": { + "type": "object", + "properties": { + "seg6local_action": { "$ref": "#/$defs/seg6local_action_t" }, + "seg6local_ctx": { "$ref": "#/$defs/seg6local_context" }, + "seg6_src": { "$ref": "#/$defs/in6_address" }, + "seg6_segs": { + "anyOf": [ + { "type": "null" }, + { "$ref": "#/$defs/seg6_seg_stack" } + ] + } + }, + "required": ["seg6local_action", "seg6local_ctx", "seg6_src", "seg6_segs"], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/scripts/render_schema.py b/src/libraries/sonic-fib/scripts/render_schema.py new file mode 100755 index 00000000000..229949fc10f --- /dev/null +++ b/src/libraries/sonic-fib/scripts/render_schema.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 + +import json +import sys +import os +from jinja2 import Environment, FileSystemLoader + + +def json_type_to_c(prop, defs): + """Map JSON Schema prtperty to C type.""" + if "$ref" in prop: + ref = prop["$ref"] + if ref == "#/$defs/ip_address": + return "union C_g_addr" + elif ref == "#/$defs/uint8": + return "uint8_t" + elif ref == "#/$defs/in_address": + return "struct in_addr" + elif ref == "#/$defs/in6_address": + return "struct in6_addr" + elif ref.startswith("#/$defs/"): + typename = ref.split("/")[-1] + target = defs.get(typename, {}) + if target.get("type") == "string" and "enum" in target: + return f"enum C_{typename}" + else: + return f"struct C_{typename}" + else: + return "void*" + + typ = prop.get("type") + if typ == "integer": + maximum = prop.get("maximum", None) + if maximum is None: + return "uint32_t" if prop.get("minimum", 0) >= 0 else "int32_t" + elif maximum <= 255: + return "uint8_t" if prop.get("minimum", 0) >= 0 else "int8_t" + elif maximum <= 65535: + return "uint16_t" if prop.get("minimum", 0) >= 0 else "int16_t" + elif typ == "string": + return "char*" + elif typ == "array": + item_type = json_type_to_c(prop.get("items", {}), defs) + return item_type + elif typ == "boolean": + return "bool" + elif typ == "null": + return "void *" + return "void" + + +def json_type_to_cpp(prop, defs): + """Map JSON Schema property to C++ type.""" + if "$ref" in prop: + ref = prop["$ref"] + if ref == "#/$defs/ip_address": + return "union g_addr" + elif ref == "#/$defs/uint8": + return "std::uint8_t" + elif ref == "#/$defs/in_address": + return "struct in_addr" + elif ref == "#/$defs/in6_address": + return "struct in6_addr" + elif ref.startswith("#/$defs/"): + typename = ref.split("/")[-1] + target = defs.get(typename, {}) + if target.get("type") == "string" and "enum" in target: + return f"enum {typename}" + else: + return f"struct {typename}" + else: + return "void*" + + typ = prop.get("type") + if typ == "integer": + maximum = prop.get("maximum", None) + if maximum is None: + return "std::uint32_t" if prop.get("minimum", 0) >= 0 else "std::int32_t" + elif maximum <= 255: + return "std::uint8_t" if prop.get("minimum", 0) >= 0 else "std::int8_t" + elif maximum <= 65535: + return "std::uint16_t" if prop.get("minimum", 0) >= 0 else "std::int16_t" + elif typ == "string": + return "std::string" + elif typ == "array": + item_type = json_type_to_cpp(prop.get("items", {}), defs) + if "minItems" in prop and prop.get("minItems", 0) == 0: + return item_type + return f"std::vector<{item_type}>" + elif typ == "boolean": + return "bool" + elif typ == "null": + return "std::nullptr_t" + return "void" + + +def extract_c_enums(defs): + """Extract C enums from $defs.""" + c_enums = {} + for name, schema in defs.items(): + if schema.get("type") == "string" and "enum" in schema: + c_enums[f"C_{name}"] = schema["enum"] + return c_enums + + +def extract_enums(defs): + """Extract enums from $defs.""" + enums = {} + for name, schema in defs.items(): + if schema.get("type") == "string" and "enum" in schema: + enums[name] = schema["enum"] + return enums + + +def build_c_root_struct(schema, defs): + """Build C root struct from top-level properties.""" + fields = [] + for name, prop in schema.get("properties", {}).items(): + c_type = json_type_to_c(prop, defs) + data = {"name": name, "c_type": c_type} + position = prop.get("position", 0) + data["position"] = position + + # array type + if prop.get("type") == "array": + min_items = prop.get("minItems") + if min_items == 0: + # min_items is 0 indicates it's a flexible array + data["flexible_array"] = True + elif "C_len" in prop: + # static array with fixed length + data["fixed_array"] = True + data["array_size"] = prop["C_len"] + + if "default_value" in prop: + dvalue = prop.get("default_value") + data["default_value"] = dvalue + if "data_prefix" in prop: + dvalue = prop.get("data_prefix") + data["data_prefix"] = dvalue + fields.append(data) + name = "C_NextHopGroupFull" + return {"name": name, "fields": fields} + + +def build_root_struct(schema, defs): + """Build root struct from top-level properties.""" + fields = [] + for name, prop in schema.get("properties", {}).items(): + cpp_type = json_type_to_cpp(prop, defs) + data = {"name": name, "cpp_type": cpp_type} + position = prop.get("position", 0) + data["position"] = position + if "default_value" in prop: + dvalue = prop.get("default_value") + data["default_value"] = dvalue + if "data_prefix" in prop: + dvalue = prop.get("data_prefix") + data["data_prefix"] = dvalue + fields.append(data) + name = schema.get("title", "NextHopGroupFull") + return {"name": name, "fields": fields} + + +def build_c_def_structs(defs): + """Build structs from $defs.""" + structs = {} + for name, schema in defs.items(): + if schema.get("type") == "object": + fields = [] + for fname, fprop in schema.get("properties", {}).items(): + c_type = json_type_to_c(fprop, defs) + + # nexthop_srv6's seg6_segs + if name == "nexthop_srv6" and fname == "seg6_segs": + c_type = "struct C_seg6_seg_stack*" + + field_data = {"name": fname, "c_type": c_type} + + # array type + if fprop.get("type") == "array": + min_items = fprop.get("minItems") + if min_items == 0: + # flexible array + field_data["flexible_array"] = True + elif "C_len" in fprop: + # fixed array + field_data["fixed_array"] = True + field_data["array_size"] = fprop["C_len"] + + fields.append(field_data) + structs[f"C_{name}"] = {"name": f"C_{name}", "fields": fields} + + # Build the deps + deps = {} + for name, struct_info in structs.items(): + deps[name] = set() + for field in struct_info.get("fields", []): + field_type = field.get("c_type", "") + for other_name in structs.keys(): + if other_name != name and other_name in field_type: + deps[name].add(other_name) + + # Sort the dict by DFS topo + ordered = {} + visited = set() + + def visit(name): + if name in visited: + return + visited.add(name) + for dep in deps[name]: + visit(dep) + ordered[name] = structs[name] + + for name in structs.keys(): + visit(name) + + return ordered + + +def build_def_structs(defs): + """Build structs from $defs.""" + structs = {} + for name, schema in defs.items(): + if schema.get("type") == "object": + fields = [] + for fname, fprop in schema.get("properties", {}).items(): + cpp_type = json_type_to_cpp(fprop, defs) + if name == "nexthop_srv6" and fname == "seg6_segs": + cpp_type = "struct seg6_seg_stack*" + if fprop.get("type") == "array" and fprop.get("minItems", 0) == 0: + fields.append({"name": fname, "cpp_type": cpp_type, "zeroarray": True}) + else: + fields.append({"name": fname, "cpp_type": cpp_type}) + structs[name] = {"name": name, "fields": fields} + # return structs + + # Now we have already build the structs dict, + # but more we need to do is to sort it. + # When we are using this dict to construct to/from_json in nexthopgroupfull_json.h.j2, + # an dependency issue among the structs will occur. + # Therefore, we sort it from non-depending ones to the complicated. + + # Build the deps + deps = {} + for name, struct_info in structs.items(): + deps[name] = set() + for field in struct_info.get("fields", []): + field_type = field.get("cpp_type", "") + # One's type is another struct indicating it's has dependency + for other_name in structs.keys(): + if other_name != name and other_name in field_type: + deps[name].add(other_name) + + # Sort the dict by DFS topo + ordered = {} + visited = set() + + def visit(name): + if name in visited: + return + visited.add(name) + # We access the deps first + for dep in deps[name]: + visit(dep) + # Then add self in + ordered[name] = structs[name] + + for name in structs.keys(): + visit(name) + + return ordered + + +def main(): + if len(sys.argv) != 5: + print("Usage: ./render_schema.py ") + print(" mode: 'header', 'source', 'json_bindings', or 'c_header'") + sys.exit(1) + + schema_path = sys.argv[1] + template_dir = sys.argv[2] + output_path = sys.argv[3] + mode = sys.argv[4] + + if mode not in ("header", "source", "json_bindings", "c_header"): + print("Error: mode must be 'header', 'source', 'json_bindings', or 'c_header'") + sys.exit(1) + + # Load and parse schema + with open(schema_path, 'r') as f: + schema = json.load(f) + + defs = schema.get("$defs", {}) + enums = extract_enums(defs) + c_enums = extract_c_enums(defs) + + # cpp header + root_struct = build_root_struct(schema, defs) + def_structs = build_def_structs(defs) + + all_structs = def_structs.copy() + all_structs[root_struct["name"]] = root_struct + + root_struct_name = root_struct["name"] + + special_structs = {"nexthop_srv6", "seg6_seg_stack", root_struct_name} + + # c header + c_root_struct = build_c_root_struct(schema, defs) + c_def_structs = build_c_def_structs(defs) + + c_all_structs = c_def_structs.copy() + c_all_structs[c_root_struct["name"]] = c_root_struct + + c_root_struct_name = c_root_struct["name"] + + c_special_structs = {"C_nexthop_srv6", "C_seg6_seg_stack", c_root_struct_name} + + # Jinja setup + # Note: This script generates C/C++ code, not HTML. + # XSS is not applicable in this context. + # nosem: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + env = Environment(loader=FileSystemLoader(template_dir)) + template_name = None + + if mode == "header": + template_name = "nexthopgroupfull.h.j2" + context = { + "enums": enums, + "structs": all_structs, + "special_structs": special_structs, + "root_struct_name": root_struct_name + } + elif mode == "source": + template_name = "nexthopgroupfull.cpp.j2" + context = { + "root_struct_name": root_struct_name + } + elif mode == "json_bindings": + template_name = "nexthopgroupfull_json.h.j2" + context = { + "enums": enums, # dict: name -> list of strings (e.g., ["NEXTHOP_TYPE_INVALID", ...]) + "root_struct_name": root_struct_name, + "root_struct": root_struct, + "special_structs": special_structs, + "all_structs": all_structs + } + elif mode == "c_header": + template_name = "c_nexthopgroupfull.h.j2" + context = { + "c_enums": c_enums, + "structs": c_all_structs, + "special_structs": c_special_structs, + "root_struct": c_root_struct, + "root_struct_name": c_root_struct_name + } + + # Render + # nosem: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + template = env.get_template(template_name) + # nosem: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2 + output = template.render(**context) + + # Write + os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else ".", exist_ok=True) + with open(output_path, 'w') as f: + f.write(output) + + print(f"✅ Generated {output_path} (mode: {mode})") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/libraries/sonic-fib/src/Makefile.am b/src/libraries/sonic-fib/src/Makefile.am new file mode 100644 index 00000000000..d5d5d718d66 --- /dev/null +++ b/src/libraries/sonic-fib/src/Makefile.am @@ -0,0 +1,90 @@ +# --- Jinja2-generated files: header, implementation, and JSON bindings --- + +BUILT_SOURCES = src/nexthopgroupfull.h \ + src/nexthopgroupfull.cpp \ + src/nexthopgroupfull_json.h \ + src/c_nexthopgroupfull.h + +# Common dependencies +schema_file = $(top_srcdir)/schema/NextHopGroupFull.json +render_script = $(top_srcdir)/scripts/render_schema.py + +# Header file for nexthopgroupfull.h +src/nexthopgroupfull.h: $(top_srcdir)/templates/nexthopgroupfull.h.j2 \ + $(schema_file) \ + $(render_script) + $(AM_V_GEN)$(PYTHON) $(render_script) \ + $(schema_file) \ + $(top_srcdir)/templates \ + $@ \ + header + +# Implementation file for nexthopgroupfull.cpp +src/nexthopgroupfull.cpp: $(top_srcdir)/templates/nexthopgroupfull.cpp.j2 \ + $(schema_file) \ + $(render_script) + $(AM_V_GEN)$(PYTHON) $(render_script) \ + $(schema_file) \ + $(top_srcdir)/templates \ + $@ \ + source + +# JSON bindings inline header file for to_json / from_json handling +src/nexthopgroupfull_json.h: $(top_srcdir)/templates/nexthopgroupfull_json.h.j2 \ + $(schema_file) \ + $(render_script) + $(AM_V_GEN)$(PYTHON) $(render_script) \ + $(schema_file) \ + $(top_srcdir)/templates \ + $@ \ + json_bindings + +# C header file for c_nexthopgroupfull.h +src/c_nexthopgroupfull.h: $(top_srcdir)/templates/c_nexthopgroupfull.h.j2 \ + $(schema_file) \ + $(render_script) + $(AM_V_GEN)$(PYTHON) $(render_script) \ + $(schema_file) \ + $(top_srcdir)/templates \ + $@ \ + c_header + +CLEANFILES = src/nexthopgroupfull.h \ + src/nexthopgroupfull.cpp \ + src/nexthopgroupfull_json.h \ + src/c_nexthopgroupfull.h + +EXTRA_DIST = \ + templates/nexthopgroup/nexthopgroupfull.h.j2 \ + templates/nexthopgroup/nexthopgroupfull.cpp.j2 \ + templates/nexthopgroup/nexthopgroupfull_json.h.j2 \ + templates/nexthopgroup/c_nexthopgroupfull.h.j2 \ + schema/nexthopgroup/NextHopGroupFull.json + +# End of Jinja2 generated part + +lib_LTLIBRARIES += src/libnexthopgroup.la + +src_libnexthopgroup_la_SOURCES = \ + src/nexthopgroupfull.cpp \ + src/nexthopgroup_debug.cpp \ + src/c-api/nexthopgroup_capi.cpp + +# Install headers to a dedicated subdir +nexthopgroup_headerdir = $(includedir)/nexthopgroup +nexthopgroup_header_HEADERS = \ + src/nexthopgroupfull.h \ + src/nexthopgroupfull_json.h \ + src/nexthopgroup_debug.h \ + src/c_nexthopgroupfull.h + +# Install C-API headers +nexthopgroup_capi_headerdir = $(includedir)/nexthopgroup/c-api +nexthopgroup_capi_header_HEADERS = \ + src/c-api/nexthopgroup_capi.h + +src_libnexthopgroup_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CODE_COVERAGE_CXXFLAGS) +src_libnexthopgroup_la_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CODE_COVERAGE_CPPFLAGS) +src_libnexthopgroup_la_LIBADD = -lpthread +src_libnexthopgroup_la_LDFLAGS = -Wl,-z,now $(LDFLAGS) + diff --git a/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.cpp b/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.cpp new file mode 100644 index 00000000000..62e0d129b49 --- /dev/null +++ b/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.cpp @@ -0,0 +1,233 @@ +// nexthopgroup_capi.cpp + +#include "src/nexthopgroupfull.h" +#include "src/nexthopgroupfull_json.h" +#include "src/c_nexthopgroupfull.h" +#include "src/nexthopgroup_debug.h" +#include "nexthopgroup_capi.h" +#include +#include +#include +#include +#include + +using namespace std; + +extern "C" { + +const char* nexthopgroup_version(void) { + return LIBNEXTHOPGROUP_VERSION; +} + +char* nexthopgroupfull_json_from_c_nhg_multi(const struct C_NextHopGroupFull* c_nhg, uint32_t nh_grp_full_count, + uint32_t depends_count, uint32_t dependents_count, bool is_recurisve) +{ + if (!c_nhg) { + FIB_LOG(fib::LogLevel::ERROR, "Do NOT pass in an empty C_NextHopGroupFull *"); + return nullptr; + } + + try { + FIB_LOG(fib::LogLevel::DEBUG, "nh_grp_full_count %d, depends_count %d, dependents_count %d, is_recurisve 0x%x", + nh_grp_full_count, depends_count, dependents_count, (uint8_t)is_recurisve); + + /* Defensive bounds check on array count parameters */ + if (nh_grp_full_count > (MULTIPATH_NUM * MAX_NHG_RECURSION) + 1 || + depends_count > MULTIPATH_NUM + 1 || + dependents_count > MULTIPATH_NUM + 1) { + FIB_LOG(fib::LogLevel::ERROR, "Count exceeds array bounds: nh_grp_full_count=%u, depends_count=%u, dependents_count=%u", + nh_grp_full_count, depends_count, dependents_count); + return nullptr; + } + + /* Convert C array to C++ vector */ + vector cpp_nh_grp_full_list; + for (uint32_t i = 0; i < nh_grp_full_count; i++) { + /* convert C nh_grp_full to C++ fib::nh_grp_full explicitly */ + fib::nh_grp_full cpp_nh = { + c_nhg->nh_grp_full_list[i].id, + c_nhg->nh_grp_full_list[i].weight, + c_nhg->nh_grp_full_list[i].num_direct + }; + cpp_nh_grp_full_list.push_back(cpp_nh); + } + vector cpp_depends; + for (uint32_t i = 0; i < depends_count; i++) { + cpp_depends.push_back(c_nhg->depends[i]); + } + vector cpp_dependents; + for (uint32_t i = 0; i < dependents_count; i++) { + cpp_dependents.push_back(c_nhg->dependents[i]); + } + + fib::NextHopGroupFull* cpp_nhg = nullptr; + + if (is_recurisve) { + // Recursive case: includes cpp_gate and type + fib::g_addr cpp_gate = reinterpret_cast(c_nhg->gate); + cpp_nhg = new fib::NextHopGroupFull(c_nhg->id, c_nhg->key, c_nhg->nhg_flags, cpp_gate, + static_cast(c_nhg->type), + cpp_nh_grp_full_list, cpp_depends, cpp_dependents); + } else { + // Non-recursive, multipath case + cpp_nhg = new fib::NextHopGroupFull(c_nhg->id, c_nhg->key, c_nhg->nhg_flags, + cpp_nh_grp_full_list, cpp_depends, cpp_dependents); + } + + /* Convert C++ Obj to JSON stirng */ + char* json_str = nexthopgroup_to_json(cpp_nhg); + FIB_LOG(fib::LogLevel::DEBUG, "json_str length %zu, str: %s", + json_str ? strlen(json_str) : 0, json_str ? json_str : "null"); + + nexthopgroup_free(cpp_nhg); + return json_str; + + } catch (const std::exception& e) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroupfull_json_from_c_nhg_multi::Converting failed: %s", e.what()); + return nullptr; + } catch (...) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroupfull_json_from_c_nhg_multi::Converting failed with unknown exception"); + return nullptr; + } +} + +char* nexthopgroupfull_json_from_c_nhg_singleton(const struct C_NextHopGroupFull* c_nhg, uint32_t depends_count, uint32_t dependents_count) +{ + if (!c_nhg) { + FIB_LOG(fib::LogLevel::ERROR, "Do NOT pass in an empty C_NextHopGroupFull *"); + return nullptr; + } + + try { + /* Convert C array to C++ vector */ + vector cpp_depends; + for (uint32_t i = 0; i < depends_count; i++) { + cpp_depends.push_back(c_nhg->depends[i]); + } + vector cpp_dependents; + for (uint32_t i = 0; i < dependents_count; i++) { + cpp_dependents.push_back(c_nhg->dependents[i]); + } + + /* Almostly we do NOT have ifname in zebra, so set it as empty string */ + std::string cpp_ifname = ""; + + /* Convert seg6_segs flexible array to C++ vector */ + vector cpp_nh_segs; + if (c_nhg->nh_srv6 && c_nhg->nh_srv6->seg6_segs) { + for (uint8_t i = 0; i < c_nhg->nh_srv6->seg6_segs->num_segs; i++) { + cpp_nh_segs.push_back(c_nhg->nh_srv6->seg6_segs->seg[i]); + } + } + + /* Convert g_addr from C type to C++ type */ + fib::g_addr cpp_gate = reinterpret_cast(c_nhg->gate); + fib::g_addr cpp_src = reinterpret_cast(c_nhg->src); + fib::g_addr cpp_rmap_src = reinterpret_cast(c_nhg->rmap_src); + + /* Call NextHopGroupFull constructor(singleton) to create NextHopGroupFull object */ + /* Convert C types to C++ fib types by force */ + fib::NextHopGroupFull* cpp_nhg = new fib::NextHopGroupFull(c_nhg->id, c_nhg->key, + static_cast(c_nhg->type), + static_cast(c_nhg->vrf_id), + static_cast(c_nhg->ifindex), + cpp_ifname, cpp_depends, cpp_dependents, + static_cast(c_nhg->nh_label_type), + static_cast(c_nhg->bh_type), + cpp_gate, cpp_src, cpp_rmap_src,c_nhg->weight, + c_nhg->flags, c_nhg->nhg_flags, + c_nhg->nh_srv6 != nullptr, + c_nhg->nh_srv6 && c_nhg->nh_srv6->seg6_segs != nullptr, + reinterpret_cast(c_nhg->nh_srv6), + reinterpret_cast(c_nhg->nh_srv6 ? c_nhg->nh_srv6->seg6_segs : nullptr), + cpp_nh_segs); + + /* Convert C++ Obj to JSON string */ + char* json_str = nexthopgroup_to_json(cpp_nhg); + FIB_LOG(fib::LogLevel::DEBUG, "json_str length %zu, str: %s", + json_str ? strlen(json_str) : 0, json_str ? json_str : "null"); + + nexthopgroup_free(cpp_nhg); + + return json_str; + + } catch (const std::exception& e) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroupfull_json_from_c_nhg_singleton::Converting failed: %s", e.what()); + return nullptr; + } catch (...) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroupfull_json_from_c_nhg_singleton::Converting failed with unknown exception"); + return nullptr; + } +} + +void nexthopgroup_free(fib::NextHopGroupFull* obj) +{ + delete obj; +} + +char* nexthopgroup_to_json(fib::NextHopGroupFull* obj) +{ + if (!obj) { + return nullptr; + } + + try { + std::string json_str = fib::to_json_string(*obj); + char* c_str = static_cast(std::malloc(json_str.size() + 1)); + if (c_str) { + std::memcpy(c_str, json_str.c_str(), json_str.size() + 1); + } + return c_str; + } catch (const std::exception& e) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroup_to_json failed: %s", e.what()); + return nullptr; + } catch (...) { + FIB_LOG(fib::LogLevel::ERROR, "nexthopgroup_to_json failed with unknown exception"); + return nullptr; + } +} + +// Global C callback pointer (set by FRR) +/* C callback signature matching FRR's needs */ +typedef void (*fib_frr_log_fn)(int level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args); + +static fib_frr_log_fn g_frr_cb = nullptr; + +// C++ wrapper that forwards to C callback +static void frr_cpp_callback(fib::LogLevel level, + const char* file, + int line, + const char* func, + const char* format, + va_list args) { + if (!g_frr_cb) return; + + // Forward directly to C callback (no copying needed – va_list is consumed once) + g_frr_cb(static_cast(level), file, line, func, format, args); +} +void fib_frr_register_callback(fib_frr_log_fn cb) { + g_frr_cb = cb; + if (cb) { + // Bridge C callback → C++ API + fib::registerLogCallback(frr_cpp_callback); + } else { + fib::registerLogCallback(nullptr); + } +} + +void fib_frr_set_log_level(int level) { + // Map (0-3) to fib::LogLevel + if (level >= 0 && level <= 3) { + fib::setLogLevel(static_cast(level)); + } +} + +int fib_frr_get_log_level() { + return static_cast(fib::getLogLevel()); +} +} // extern "C" \ No newline at end of file diff --git a/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.h b/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.h new file mode 100644 index 00000000000..4432dd3ab00 --- /dev/null +++ b/src/libraries/sonic-fib/src/c-api/nexthopgroup_capi.h @@ -0,0 +1,60 @@ +#ifndef NEXTHOPGROUP_CAPI_H +#define NEXTHOPGROUP_CAPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Library version */ +#define LIBNEXTHOPGROUP_VERSION "1.0.0" + +/* Returns the runtime version string of the loaded library */ +const char* nexthopgroup_version(void); + +typedef struct NextHopGroupFull NextHopGroupFull; + +#ifdef __cplusplus +namespace fib { class NextHopGroupFull; } +// C++ APIs +char* nexthopgroupfull_json_from_c_nhg_multi(const struct C_NextHopGroupFull* c_nhg, + uint32_t nh_grp_full_count, + uint32_t depends_count, + uint32_t dependents_count, + bool is_recurisve); +char* nexthopgroupfull_json_from_c_nhg_singleton(const struct C_NextHopGroupFull* c_nhg, + uint32_t depends_count, + uint32_t dependents_count); +void nexthopgroup_free(fib::NextHopGroupFull* obj); +char* nexthopgroup_to_json(fib::NextHopGroupFull* obj); +#else +/* C APIs */ +char* nexthopgroupfull_json_from_c_nhg_multi(const struct C_NextHopGroupFull* c_nhg, + uint32_t nh_grp_full_count, + uint32_t depends_count, + uint32_t dependents_count, + bool is_recurisve); +char* nexthopgroupfull_json_from_c_nhg_singleton(const struct C_NextHopGroupFull* c_nhg, + uint32_t depends_count, + uint32_t dependents_count); +void nexthopgroup_free(NextHopGroupFull* obj); +char* nexthopgroup_to_json(NextHopGroupFull* obj); +#endif + +/* C callback signature matching FRR's needs */ +typedef void (*fib_frr_log_fn)(int level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args); + +/* Register FRR-compatible callback from C code */ +void fib_frr_register_callback(fib_frr_log_fn cb); +void fib_frr_set_log_level(int level); +int fib_frr_get_log_level(); + +#ifdef __cplusplus +} +#endif + +#endif // NEXTHOPGROUP_CAPI_H \ No newline at end of file diff --git a/src/libraries/sonic-fib/src/nexthopgroup_debug.cpp b/src/libraries/sonic-fib/src/nexthopgroup_debug.cpp new file mode 100644 index 00000000000..de6a77aa1cc --- /dev/null +++ b/src/libraries/sonic-fib/src/nexthopgroup_debug.cpp @@ -0,0 +1,100 @@ +#include "nexthopgroup_debug.h" +#include +#include +#include +#include // for std::array +#include // for std::vector + +using namespace std; +using namespace fib; + +namespace { // Anonymous namespace +struct LoggerState { + fib::LogCallback callback; + std::mutex mutex; + fib::LogLevel level = fib::LogLevel::DEBUG; +}; +LoggerState& getState() { + static LoggerState state; + return state; +} + +// Default fallback: print to stderr +void defaultLog(LogLevel level, const char* file, int line, + const char* func, const char* format, va_list args) { + const char* level_str = "DEBUG"; + switch (level) { + case LogLevel::INFO: level_str = "INFO"; break; + case LogLevel::WARN: level_str = "WARN"; break; + case LogLevel::ERROR: level_str = "ERROR"; break; + default: break; + } + // Format the variadic arguments into a buffer + std::array buf; + int written = std::vsnprintf(buf.data(), buf.size(), format, args); + + if (written < 0) { + // Formatting error – log minimal fallback + std::fprintf(stderr, "[%s] %s:%d %s: \n", + level_str, file, line, func); + return; + } + // Handle truncation gracefully (optional but recommended) + if (static_cast(written) >= buf.size()) { + // Truncated – indicate with ellipsis + constexpr size_t ellipsis_len = 3; + if (buf.size() > ellipsis_len) { + std::fill_n(buf.end() - ellipsis_len - 1, ellipsis_len, '.'); + } + } + + // Print the final formatted message + std::fprintf(stderr, "[%s] %s:%d %s: %s\n", + level_str, file, line, func, buf.data()); +} + +} // anonymous namespace + +// Public API implementations +// Don't use FIB_LOG here to avoid recursion mutex +void fib::registerLogCallback(LogCallback cb) { + auto& state = getState(); + std::lock_guard lock(state.mutex); + state.callback = std::move(cb); +} + +void fib::setLogLevel(LogLevel level) { + auto& state = getState(); + std::lock_guard lock(state.mutex); + state.level = level; +} + +fib::LogLevel fib::getLogLevel() { + auto& state = getState(); + std::lock_guard lock(state.mutex); + return state.level; +} + +// Internal logging implementation +void fib::internalLog(LogLevel level, const char* file, int line, + const char* func, const char* format, ...) { + auto& state = getState(); + fib::LogCallback cb; + va_list args; + va_start(args, format); + { + std::lock_guard lock(state.mutex); + if (static_cast(level) < static_cast(state.level) ) { + va_end(args); + return; + } + if (!state.callback) { + // Use default logger + cb = defaultLog; + } else { + cb = state.callback; + } + } + if (cb) cb(level, file, line, func, format, args); // Forward va_list directly + va_end(args); +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/src/nexthopgroup_debug.h b/src/libraries/sonic-fib/src/nexthopgroup_debug.h new file mode 100644 index 00000000000..88630d2e429 --- /dev/null +++ b/src/libraries/sonic-fib/src/nexthopgroup_debug.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace fib { + +// Log levels for filtering messages +enum class LogLevel : uint32_t { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3 +}; + +// C++ interface for Define log callback function +using LogCallback = std::function; + +// Internal logging macro (used inside library implementation) +#define FIB_LOG(level, fmt, ...) \ + do { \ + if (static_cast(level) >= static_cast(fib::getLogLevel())) { \ + fib::internalLog(level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +// Internal helpers to log messages +void internalLog(LogLevel level, const char* file, int line, + const char* func, const char* format, ...); + + +/* + * Public APIs to register a log callback from C++ code + */ +// Register callback function +void registerLogCallback(LogCallback cb); + +// Set and get log level +void setLogLevel(LogLevel level); +LogLevel getLogLevel(); + +} // namespace fib \ No newline at end of file diff --git a/src/libraries/sonic-fib/templates/c_nexthopgroupfull.h.j2 b/src/libraries/sonic-fib/templates/c_nexthopgroupfull.h.j2 new file mode 100644 index 00000000000..b3dec341a57 --- /dev/null +++ b/src/libraries/sonic-fib/templates/c_nexthopgroupfull.h.j2 @@ -0,0 +1,114 @@ +#ifndef C_NEXTHOPGROUPFULL_H +#define C_NEXTHOPGROUPFULL_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Manual definitions */ +typedef uint32_t vrf_id_t; +typedef signed int ifindex_t; + +#define MULTIPATH_NUM 514 +#define MAX_NHG_RECURSION 2 + +union C_g_addr { + struct in_addr ipv4; + struct in6_addr ipv6; +}; + +/* Auto-generated C Enums */ +{%- for enum_name, values in c_enums.items() %} +enum {{ enum_name }} { + {%- for value in values %} + C_{{ value | replace(' ', '') }}, + {%- endfor %} +}; + +{%- endfor %} + +/* Auto-generated Structs */ +{%- for name, s in structs.items() if name not in special_structs %} +struct {{ name }} { +{%- for field in s.fields %} +{%- if field.flexible_array %} + {{ field.c_type }} {{ field.name }}[0]; +{%- elif field.fixed_array %} + {{ field.c_type }} {{ field.name }}[{{ field.array_size }}]; +{%- else %} + {{ field.c_type }} {{ field.name }}; +{%- endif %} +{%- endfor %} +}; + +{%- endfor %} + +/* --- Special Struct: nexthop_srv6 --- */ +struct C_nexthop_srv6 { + enum C_seg6local_action_t seg6local_action; + struct C_seg6local_context seg6local_ctx; + struct in6_addr seg6_src; + struct C_seg6_seg_stack *seg6_segs; +}; + +/* --- Other C-specific structs (not in schema as objects) --- */ +struct C_seg6_seg_stack { +{%- for name, s in structs.items() if name == "C_seg6_seg_stack" %} + {%- for field in s.fields %} + {{ field.c_type }} {{ field.name }}{% if field.flexible_array %}[0]{% endif %}; + {%- endfor %} +{%- endfor %} +}; + +/* --- Root Struct: C_NextHopGroupFull --- */ +struct C_NextHopGroupFull { +{%- for field in root_struct.fields if field.position == 1 %} + {{ field.c_type }} {{ field.name }}; +{%- endfor %} + +{%- for field in root_struct.fields if field.position == 2 and field.name != "ifname" %} + {%- if field.flexible_array %} + {{ field.c_type }} {{ field.name }}[0]; + {%- elif field.fixed_array %} + {{ field.c_type }} {{ field.name }}[{{ field.array_size }}]; + {%- else %} + {{ field.c_type }} {{ field.name }}; + {%- endif %} +{%- endfor %} + + char _hash_begin[0]; +{%- for field in root_struct.fields if field.position == 3 %} + {{ field.c_type }} {{ field.name }}; +{%- endfor %} + union { + union C_g_addr gate; + enum C_blackhole_type bh_type; + }; +{%- for field in root_struct.fields if field.position == 5 %} + {{ field.c_type }} {{ field.name }}; +{%- endfor %} + char _hash_end[0]; +{%- for field in root_struct.fields if field.position == 6 and field.name != "ifname" %} + {%- if field.flexible_array %} + {{ field.c_type }} {{ field.name }}[0]; + {%- elif field.fixed_array %} + {{ field.c_type }} {{ field.name }}[{{ field.array_size }}]; + {%- elif field.data_prefix %} + {{ field.c_type }} {{ field.data_prefix }}{{ field.name }}; + {%- else %} + {{ field.c_type }} {{ field.name }}; + {%- endif %} +{%- endfor %} +}; + +#ifdef __cplusplus +} +#endif + +#endif /* C_NEXTHOPGROUPFULL_H */ diff --git a/src/libraries/sonic-fib/templates/nexthopgroupfull.cpp.j2 b/src/libraries/sonic-fib/templates/nexthopgroupfull.cpp.j2 new file mode 100644 index 00000000000..92ff44a80ef --- /dev/null +++ b/src/libraries/sonic-fib/templates/nexthopgroupfull.cpp.j2 @@ -0,0 +1,362 @@ +#include "nexthopgroupfull.h" + +#include +#include +#include +#include +#include +#include "nexthopgroup_debug.h" + +using namespace std; +using namespace fib; + +/* Constructor for recursive NH {{ root_struct_name }} */ +{{ root_struct_name }}::{{ root_struct_name }}(std::uint32_t id_in, std::uint32_t key_in, std::uint32_t nhg_flags_in, + union g_addr gateway_in, enum nexthop_types_t type_in, + const std::vector& nh_grp_full_list_in, + const std::vector& depends_in, + const std::vector& dependents_in) + : id(id_in), key(key_in), nhg_flags(nhg_flags_in), nh_grp_full_list(nh_grp_full_list_in), depends(depends_in), dependents(dependents_in) +{ + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction started (multi-nexthop) with id: %d, key: %d, nhg_flags: %d", id_in, key_in, nhg_flags_in); + for (size_t i = 0; i < nh_grp_full_list_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] nh_grp_full_list item %d: id= %d weight %d num_direct %d", i, nh_grp_full_list_in[i].id, static_cast(nh_grp_full_list_in[i].weight), nh_grp_full_list_in[i].num_direct); + } + for (size_t i = 0; i < depends_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] depends item %d : %d ", i, depends_in[i]); + } + for (size_t i = 0; i < dependents_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] dependents item %d : %d", i, dependents_in[i]); + } + + // For recursive case, set gate and type + this->gate = gateway_in; + this->type = type_in; + memset(&src, 0, sizeof(src)); + memset(&rmap_src, 0, sizeof(rmap_src)); + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction finished (multi-nexthop) for id: %d", id_in); +} + +/* Constructor for multi-path {{ root_struct_name }} */ +{{ root_struct_name }}::{{ root_struct_name }}(std::uint32_t id_in, std::uint32_t key_in, std::uint32_t nhg_flags_in, + const std::vector& nh_grp_full_list_in, + const std::vector& depends_in, + const std::vector& dependents_in) + : id(id_in), key(key_in), nhg_flags(nhg_flags_in), nh_grp_full_list(nh_grp_full_list_in), depends(depends_in), dependents(dependents_in) +{ + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction started (multi-nexthop) with id: %d, key: %d, nhg_flags: %d", id_in, key_in, nhg_flags_in); + for (size_t i = 0; i < nh_grp_full_list_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] nh_grp_full_list item %d: id= %d weight %d num_direct %d", i, nh_grp_full_list_in[i].id, static_cast(nh_grp_full_list_in[i].weight), nh_grp_full_list_in[i].num_direct); + } + for (size_t i = 0; i < depends_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] depends item %d : %d ", i, depends_in[i]); + } + for (size_t i = 0; i < dependents_in.size(); i++) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] dependents item %d : %d", i, dependents_in[i]); + } + + memset(&gate, 0, sizeof(gate)); + bh_type = BLACKHOLE_UNSPEC; + memset(&src, 0, sizeof(src)); + memset(&rmap_src, 0, sizeof(rmap_src)); + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction finished (multi-nexthop) for id: %d", id_in); +} + +/* Constructor for singleton {{ root_struct_name }} */ +{{ root_struct_name }}::{{ root_struct_name }}(std::uint32_t id_in, std::uint32_t key_in, + enum nexthop_types_t type_in, vrf_id_t vrf_id_in, ifindex_t ifindex_in, + std::string ifname_in, const std::vector& depends_in, + const std::vector& dependents_in, + enum lsp_types_t label_type_in, enum blackhole_type bh_type_in, + union g_addr gateway_in, union g_addr src_in, union g_addr rmap_src_in, + std::uint16_t weight_in, std::uint8_t flags_in, std::uint32_t nhg_flags_in, + bool has_srv6, bool has_seg6_segs, + const struct nexthop_srv6* nh_srv6_in, + const struct seg6_seg_stack* nh_seg6_segs_in, + const std::vector& nh_segs_in) + : id(id_in), key(key_in), weight(weight_in), flags(flags_in), nhg_flags(nhg_flags_in), ifname(ifname_in), + depends(depends_in), dependents(dependents_in), type(type_in), vrf_id(vrf_id_in), + ifindex(ifindex_in), nh_label_type(label_type_in), src(src_in), rmap_src(rmap_src_in), + nh_srv6(nullptr) +{ + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction started (singleton) for id: %d, key: %d, nhg_flags: %d", id_in, key_in, nhg_flags_in); + + /* Initialize union member */ + this->bh_type = bh_type_in; + this->gate = gateway_in; + + /* Check if need to allocate the nexthop_srv6 structure */ + if (has_srv6 && nh_srv6_in != nullptr) + { + nh_srv6 = (struct nexthop_srv6 *)malloc(sizeof(struct nexthop_srv6)); + if (!nh_srv6) { + FIB_LOG(fib::LogLevel::ERROR, "nh_srv6 allocation failed in {{ root_struct_name }} constructor, abort"); + return; + } + + memcpy(nh_srv6, nh_srv6_in, sizeof(struct nexthop_srv6)); + FIB_LOG(fib::LogLevel::DEBUG, "{{ root_struct_name }} constructor finished nh_srv6 initialization"); + } + else + FIB_LOG(fib::LogLevel::DEBUG, "{{ root_struct_name }} does not have srv6 info"); + + /* Check if need to allocate the seg6_seg_stack structure */ + if (has_seg6_segs && nh_seg6_segs_in != nullptr) + { + FIB_LOG(fib::LogLevel::DEBUG, "{{ root_struct_name }} has seg6_segs, allocating..."); + size_t total_size = sizeof(struct seg6_seg_stack) + + nh_seg6_segs_in->num_segs * sizeof(struct in6_addr); + nh_srv6->seg6_segs = (struct seg6_seg_stack *)malloc(total_size); + if (!nh_srv6->seg6_segs) { + FIB_LOG(fib::LogLevel::ERROR, "seg6_segs allocation failed in {{ root_struct_name }} constructor, abort"); + free(nh_srv6); + nh_srv6 = nullptr; + return; + } + // Copy the whole "seg6_segs" structure (including flexible array) + memcpy(nh_srv6->seg6_segs, nh_seg6_segs_in, total_size); + } + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} construction finished (singleton) for id: %d", id_in); +} + +/* Copy Constructor */ +{{ root_struct_name }}::{{ root_struct_name }}(const NextHopGroupFull& other) + : id(other.id), key(other.key), weight(other.weight), flags(other.flags), nhg_flags(other.nhg_flags), + ifname(other.ifname), nh_grp_full_list(other.nh_grp_full_list), + depends(other.depends), dependents(other.dependents), + type(other.type), vrf_id(other.vrf_id), ifindex(other.ifindex), + nh_label_type(other.nh_label_type), bh_type(other.bh_type) +{ + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NextHopGroupFull copy constructor started for id: %d, key: %d, nhg_flags: %d", other.id, other.key, other.nhg_flags); + + memcpy(&gate, &other.gate, sizeof(g_addr)); + memcpy(&src, &other.src, sizeof(g_addr)); + memcpy(&rmap_src, &other.rmap_src, sizeof(g_addr)); + + /* Deep copy for nh_srv6 information */ + if (other.nh_srv6 != nullptr) { + // allocate "nh_srv6" structure + nh_srv6 = static_cast(malloc(sizeof(struct nexthop_srv6))); + if (!nh_srv6) { + FIB_LOG(fib::LogLevel::ERROR, "Failed to allocate nh_srv6 in copy constructor"); + return; + } + + // copy plain attributes + nh_srv6->seg6local_action = other.nh_srv6->seg6local_action; + nh_srv6->seg6local_ctx = other.nh_srv6->seg6local_ctx; + nh_srv6->seg6_src = other.nh_srv6->seg6_src; + + // Deep copy "seg6_segs" structure + if (other.nh_srv6->seg6_segs != nullptr) { + size_t size = sizeof(seg6_seg_stack) + + other.nh_srv6->seg6_segs->num_segs * sizeof(in6_addr); + nh_srv6->seg6_segs = static_cast(malloc(size)); + if (!nh_srv6->seg6_segs) { + FIB_LOG(fib::LogLevel::ERROR, "Failed to allocate seg6_segs in copy constructor"); + free(nh_srv6); // free the allocated nh_srv6 earlier + nh_srv6 = nullptr; + return; + } + // Copy the whole "seg6_segs" structure (including flexible array) + memcpy(nh_srv6->seg6_segs, other.nh_srv6->seg6_segs, size); + } else { + nh_srv6->seg6_segs = nullptr; + } + + FIB_LOG(fib::LogLevel::DEBUG, "NextHopGroupFull copy constructor finished"); + } +} + +/* Copy Assignment Operator */ +{{ root_struct_name }}& {{ root_struct_name }}::operator = (const NextHopGroupFull &other) +{ + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NextHopGroupFull copy assignment operator started for id: %d, key: 0x%x, nhg_flags: 0x%x", other.id, other.key, other.nhg_flags); + + if (this == &other) return *this; + + id = other.id; + key = other.key; + weight = other.weight; + flags = other.flags; + nhg_flags = other.nhg_flags; + + ifname = other.ifname; + + nh_grp_full_list = other.nh_grp_full_list; + depends = other.depends; + dependents = other.dependents; + + type = other.type; + vrf_id = other.vrf_id; + ifindex = other.ifindex; + nh_label_type = other.nh_label_type; + bh_type = other.bh_type; + + memcpy(&gate, &other.gate, sizeof(g_addr)); + memcpy(&src, &other.src, sizeof(g_addr)); + memcpy(&rmap_src, &other.rmap_src, sizeof(g_addr)); + + /* Deep copy for nh_srv6 information */ + if (nh_srv6 != nullptr) { + if (nh_srv6->seg6_segs != nullptr) { + free(nh_srv6->seg6_segs); + } + free(nh_srv6); + nh_srv6 = nullptr; + } + if (other.nh_srv6 != nullptr) { + nh_srv6 = static_cast(malloc(sizeof(nexthop_srv6))); + *nh_srv6 = nexthop_srv6(); + + nh_srv6->seg6local_action = other.nh_srv6->seg6local_action; + nh_srv6->seg6local_ctx = other.nh_srv6->seg6local_ctx; + nh_srv6->seg6_src = other.nh_srv6->seg6_src; + + /* Deep copy the flexible array in seg6_segs */ + if (other.nh_srv6->seg6_segs != nullptr) { + size_t size = sizeof(seg6_seg_stack) + + other.nh_srv6->seg6_segs->num_segs * sizeof(in6_addr); + nh_srv6->seg6_segs = static_cast(malloc(size)); + memcpy(nh_srv6->seg6_segs, other.nh_srv6->seg6_segs, size); + } else { + nh_srv6->seg6_segs = nullptr; + } + } else { + nh_srv6 = nullptr; + } + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NextHopGroupFull copy assignment operator finished for id: %d", other.id); + + return *this; +} + +/* operator == */ +bool {{ root_struct_name }}::operator==(const {{ root_struct_name }}& other) const +{ + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} operator == started for id: %d vs id: %d", id, other.id); + + /* Compare plain values */ + if (id != other.id || + key != other.key || + weight != other.weight || + flags != other.flags || + nhg_flags != other.nhg_flags || + ifname != other.ifname || + depends != other.depends || + dependents != other.dependents || + type != other.type || + vrf_id != other.vrf_id || + ifindex != other.ifindex || + nh_label_type != other.nh_label_type) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME plain values!"); + return false; + } + /* Compare nh_grp_full_list */ + if (nh_grp_full_list.size() != other.nh_grp_full_list.size()) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME size of nh_grp_full_list!"); + return false; + } + for (size_t i = 0; i < nh_grp_full_list.size(); ++i) { + if (nh_grp_full_list[i].id != other.nh_grp_full_list[i].id || + nh_grp_full_list[i].weight != other.nh_grp_full_list[i].weight || + nh_grp_full_list[i].num_direct != other.nh_grp_full_list[i].num_direct) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME values of nh_grp_full_list!"); + return false; + } + } + /* Compare gate/bh_type, depending on the nexthop type */ + if (type == NEXTHOP_TYPE_BLACKHOLE) { + if (bh_type != other.bh_type) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME bh_type!"); + return false; + } + } else { + if (memcmp(&gate, &other.gate, sizeof(union g_addr)) != 0) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME gate address!"); + return false; + } + } + /* Compare src and rmap_src */ + if (memcmp(&src, &other.src, sizeof(union g_addr)) != 0 || + memcmp(&rmap_src, &other.rmap_src, sizeof(union g_addr)) != 0) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME src/rmap_src!"); + return false; + } + /* Compare nh_srv6 */ + if ((nh_srv6 == nullptr) != (other.nh_srv6 == nullptr)) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6 pointer state!"); + return false; + } + if (nh_srv6 != nullptr) { + if (nh_srv6->seg6local_action != other.nh_srv6->seg6local_action) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6local_action!"); + return false; + } + // Compare seg6local_ctx + if (memcmp(&nh_srv6->seg6local_ctx, &other.nh_srv6->seg6local_ctx, + sizeof(struct seg6local_context)) != 0) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6local_ctx!"); + return false; + } + + if (memcmp(&nh_srv6->seg6_src, &other.nh_srv6->seg6_src, + sizeof(struct in6_addr)) != 0) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6_src!"); + return false; + } + + // Compare seg6_segs + if ((nh_srv6->seg6_segs == nullptr) != (other.nh_srv6->seg6_segs == nullptr)) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6_segs pointer state!"); + return false; + } + if (nh_srv6->seg6_segs != nullptr) { + if (nh_srv6->seg6_segs->encap_behavior != other.nh_srv6->seg6_segs->encap_behavior || + nh_srv6->seg6_segs->num_segs != other.nh_srv6->seg6_segs->num_segs) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6_segs plain values!"); + return false; + } + // Compare seg6_segs list + for (int i = 0; i < nh_srv6->seg6_segs->num_segs; ++i) { + if (memcmp(&nh_srv6->seg6_segs->seg[i], &other.nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)) != 0) { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] NOT SAME nh_srv6->seg6_segs list!"); + return false; + } + } + } + } + + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] The two NextHopGroupFull are the same!"); + + return true; +} + +/* operator != */ +bool {{ root_struct_name }}::operator!=(const {{ root_struct_name }}& other) const { + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} operator != started for id: %d vs id: %d, calling operator ==", id, other.id); + return !(*this == other); +} + +/* Destructor of {{ root_struct_name }} */ +{{ root_struct_name }}::~{{ root_struct_name }}() +{ + FIB_LOG(fib::LogLevel::DEBUG, "[CPP DEBUG] {{ root_struct_name }} destructor started for id: %d", id); + if (nh_srv6 != nullptr) + { + if (nh_srv6->seg6_segs != nullptr) + { + FIB_LOG(fib::LogLevel::DEBUG, "Free seg6_segs in {{ root_struct_name }} Destructor"); + free(nh_srv6->seg6_segs); + } + FIB_LOG(fib::LogLevel::DEBUG, "Free nh_srv6 in {{ root_struct_name }} Destructor"); + free(nh_srv6); + } + + FIB_LOG(fib::LogLevel::DEBUG, "{{ root_struct_name }} has been destroyed successfully."); +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/templates/nexthopgroupfull.h.j2 b/src/libraries/sonic-fib/templates/nexthopgroupfull.h.j2 new file mode 100644 index 00000000000..85630baef3e --- /dev/null +++ b/src/libraries/sonic-fib/templates/nexthopgroupfull.h.j2 @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include + +namespace fib { + + /* Can't be generated defintions */ + typedef std::uint32_t vrf_id_t; + typedef signed int ifindex_t; + + union g_addr { + struct in_addr ipv4; + struct in6_addr ipv6; + }; + + /* Auto generated Enums */ + {%- for enum_name, values in enums.items() %} + enum {{ enum_name }} { + {{ values | join(',\n ') }} + }; + {%- endfor %} + + /* Auto-generated simple structs */ + {%- for name, s in structs.items() if name not in special_structs %} + struct {{ name }} { + {%- for field in s.fields %} + {{ field.cpp_type }} {{ field.name }}; + {%- endfor %} + }; + {%- endfor %} + + /* --- Special Struct: nexthop_srv6 --- */ + struct nexthop_srv6 { + enum seg6local_action_t seg6local_action = {}; + struct seg6local_context seg6local_ctx = {}; + struct in6_addr seg6_src = {}; + struct seg6_seg_stack *seg6_segs = nullptr; + }; + + /* --- Other C-specific structs (not in schema as objects) --- */ + struct seg6_seg_stack { +{%- for name, s in structs.items() if name == "seg6_seg_stack" %} + {%- for field in s.fields %} + {{ field.cpp_type }} {{ field.name }}{%- if field.zeroarray -%}[0]{%- endif -%}; + {%- endfor %} +{%- endfor %} + }; + + /* --- Special Struct: NextHopGroupFull --- */ + struct NextHopGroupFull { +{%- for name, s in structs[root_struct_name].items()%} + {%- for field in s if field.position == 1 %} + {{ field.cpp_type }} {{ field.name }} {% if field.default_value %} = {{field.default_value }} {%- endif -%}; + {%- endfor %} +{%- endfor %} + + +{%- for name, s in structs[root_struct_name].items()%} + {%- for field in s if field.position == 2 %} + {{ field.cpp_type }} {{ field.name }} {% if field.default_value %} = {{field.default_value }} {%- endif -%}; + {%- endfor %} +{%- endfor %} + + char _hash_begin[0]; +{%- for name, s in structs[root_struct_name].items()%} + {%- for field in s if field.position == 3 %} + {{ field.cpp_type }} {{ field.name }} {% if field.default_value %} = {{field.default_value }} {%- endif -%}; + {%- endfor %} +{%- endfor %} + union { + union g_addr gate; + enum blackhole_type bh_type; + }; +{%- for name, s in structs[root_struct_name].items()%} + {%- for field in s if field.position == 5 %} + {{ field.cpp_type }} {{ field.name }} {% if field.default_value %} = {{field.default_value }} {%- endif -%}; + {%- endfor %} +{%- endfor %} + char _hash_end[0]; +{%- for name, s in structs[root_struct_name].items()%} + {%- for field in s if field.position == 6 %} + {{ field.cpp_type }} {% if field.data_prefix -%} {{field.data_prefix}}{%- endif %}{{ field.name }} {% if field.default_value -%} = {{field.default_value }} {%- endif -%}; + {%- endfor %} +{%- endfor %} + + // Add default constructor + NextHopGroupFull() = default; + + // Constructors for recursive NH + NextHopGroupFull(std::uint32_t id_in, std::uint32_t key_in, std::uint32_t nhg_flags_in, union g_addr gateway_in, enum nexthop_types_t type_in, + const std::vector& nh_grp_full_list_in, + const std::vector& depends_in, + const std::vector& dependents_in); + + // Constructors for ECMP + NextHopGroupFull(std::uint32_t id_in, std::uint32_t key_in, std::uint32_t nhg_flags_in, + const std::vector& nh_grp_full_list_in, + const std::vector& depends_in, + const std::vector& dependents_in); + + // Constructors for regular nexthop + NextHopGroupFull(std::uint32_t id_in, std::uint32_t key_in, enum nexthop_types_t type_in, + vrf_id_t vrf_id_in, ifindex_t ifindex_in, std::string ifname_in, + const std::vector& depends_in, + const std::vector& dependents_in, + enum lsp_types_t label_type_in, enum blackhole_type bh_type_in, + union g_addr gateway_in, union g_addr src_in, union g_addr rmap_src_in, + std::uint16_t weight_in, std::uint8_t flags_in, std::uint32_t nhg_flags_in, + bool has_srv6, bool has_seg6_segs, + const struct nexthop_srv6* nh_srv6_in, + const struct seg6_seg_stack* nh_seg6_segs_in, + const std::vector& nh_segs_in); + + // Copy assignment operator + NextHopGroupFull& operator = (const NextHopGroupFull &other); + + // Copy constructor + NextHopGroupFull(const NextHopGroupFull& other); + + // operator == and != + bool operator==(const NextHopGroupFull& other) const; + bool operator!=(const NextHopGroupFull& other) const; + + ~NextHopGroupFull(); + }; + +} // namespace fib \ No newline at end of file diff --git a/src/libraries/sonic-fib/templates/nexthopgroupfull_json.h.j2 b/src/libraries/sonic-fib/templates/nexthopgroupfull_json.h.j2 new file mode 100644 index 00000000000..e9aa0dc2585 --- /dev/null +++ b/src/libraries/sonic-fib/templates/nexthopgroupfull_json.h.j2 @@ -0,0 +1,225 @@ +// Auto-generated from JSON Schema. DO NOT EDIT. +#pragma once + +#include "nexthopgroupfull.h" +#include + +#include +#include // for inet_ntop / inet_pton + +namespace fib { + +// --- IP Conversion Helpers --- +inline std::string ipv4_to_string(const struct in_addr& addr) { + char buf[INET_ADDRSTRLEN]; + return inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN) ? std::string(buf) : "0.0.0.0"; +} +inline std::string ipv6_to_string(const struct in6_addr& addr) { + char buf[INET6_ADDRSTRLEN]; + return inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN) ? std::string(buf) : "::"; +} +inline bool string_to_ipv4(const std::string& ip, struct in_addr& out) { + return inet_pton(AF_INET, ip.c_str(), &out) == 1; +} +inline bool string_to_ipv6(const std::string& ip, struct in6_addr& out) { + return inet_pton(AF_INET6, ip.c_str(), &out) == 1; +} +inline std::string gaddr_to_string(const union g_addr& g, nexthop_types_t type) { + if (type == NEXTHOP_TYPE_IPV4 || type == NEXTHOP_TYPE_IPV4_IFINDEX) { + return ipv4_to_string(g.ipv4); + } + return ipv6_to_string(g.ipv6); +} +inline bool string_to_gaddr(const std::string& ip, union g_addr& g, nexthop_types_t type) { + if (type == NEXTHOP_TYPE_IPV4 || type == NEXTHOP_TYPE_IPV4_IFINDEX) { + return string_to_ipv4(ip, g.ipv4); + } + return string_to_ipv6(ip, g.ipv6); +} + +// --- Enum to_json / from_json --- +{%- for enum_name, values in enums.items() %} +inline void to_json(nlohmann::ordered_json& j, const {{ enum_name }}& e) { + static const char* names[] = { + {%- for v in values %} + "{{ v }}", + {%- endfor %} + }; + if (static_cast(e) >= 0 && static_cast(e) < static_cast(sizeof(names)/sizeof(names[0]))) { + j = names[static_cast(e)]; + } else { + j = "UNKNOWN"; + } +} +inline void from_json(const nlohmann::ordered_json& j, {{ enum_name }}& e) { + const std::string s = j; + {%- for v in values %} + if (s == "{{ v }}") { e = static_cast<{{ enum_name }}>({{ loop.index0 }}); return; } + {%- endfor %} + e = static_cast<{{ enum_name }}>(0); // default to first (e.g., *_INVALID or *_UNSPEC) +} +{%- endfor %} + +/* ======== AUTO-GEN each sub structs ======== */ +// Add declaration for each generating func + +{%- for struct_name, struct_info in all_structs.items() %} +{%- if struct_name not in special_structs %} + +// --- {{struct_name}} --- +inline void to_json(nlohmann::ordered_json& j, const {{struct_name}}& obj) { + j = nlohmann::ordered_json{ + {%- for field in struct_info.fields %} + {%- if field.name == "nh4" %} + {"{{field.name}}", ipv4_to_string(obj.nh4)}, + {%- elif field.name == "nh6" %} + {"{{field.name}}", ipv6_to_string(obj.nh6)}, + {%- else %} + {"{{field.name}}", obj.{{field.name}}}, + {%- endif %} + {%- endfor %} + }; +} +inline void from_json(const nlohmann::ordered_json& j, {{struct_name}}& obj) { + {%- for field in struct_info.fields %} + {%- if field.name == "nh4" %} + std::string nh4_str = j.at("nh4"); + string_to_ipv4(nh4_str, obj.nh4); + {%- elif field.name == "nh6" %} + std::string nh6_str = j.at("nh6"); + string_to_ipv6(nh6_str, obj.nh6); + {%- else %} + j.at("{{field.name}}").get_to(obj.{{field.name}}); + {%- endif %} + {%- endfor %} +} + +{%- endif %} +{%- endfor %} + + +// --- seg6_seg_stack --- +inline void to_json(nlohmann::ordered_json& j, const seg6_seg_stack* stack) { + if (!stack) { + j = nullptr; + return; + } + nlohmann::ordered_json segs = nlohmann::ordered_json::array(); + for (int i = 0; i < stack->num_segs; ++i) { + segs.push_back(ipv6_to_string(stack->seg[i])); + } + j = nlohmann::ordered_json{ + {"encap_behavior", stack->encap_behavior}, + {"num_segs", stack->num_segs}, + {"seg", segs} + }; +} +inline void from_json(const nlohmann::ordered_json& j, seg6_seg_stack*& stack) { + if (j.is_null()) { + stack = nullptr; + return; + } + auto behavior = j.at("encap_behavior").get(); + auto segs = j.at("seg").get>(); + size_t total = sizeof(seg6_seg_stack) + segs.size() * sizeof(in6_addr); + stack = static_cast(malloc(total)); + if (!stack) return; + stack->encap_behavior = behavior; + stack->num_segs = static_cast(segs.size()); + for (size_t i = 0; i < segs.size(); ++i) { + string_to_ipv6(segs[i], stack->seg[i]); + } +} + +// --- nexthop_srv6 --- +inline void to_json(nlohmann::ordered_json& j, const nexthop_srv6* srv6) { + if (!srv6) { + j = nullptr; + return; + } + j = nlohmann::ordered_json{ + {"seg6local_action", srv6->seg6local_action}, + {"seg6local_ctx", srv6->seg6local_ctx}, + {"seg6_src", ipv6_to_string(srv6->seg6_src)}, + {"seg6_segs", srv6->seg6_segs} + }; +} +inline void from_json(const nlohmann::ordered_json& j, nexthop_srv6*& srv6) { + if (j.is_null()) { + srv6 = nullptr; + return; + } + srv6 = static_cast(malloc(sizeof(nexthop_srv6))); + *srv6 = nexthop_srv6(); + j.at("seg6local_action").get_to(srv6->seg6local_action); + j.at("seg6local_ctx").get_to(srv6->seg6local_ctx); + j.at("seg6_segs").get_to(srv6->seg6_segs); + std::string nh6_str = j.at("seg6_src"); + string_to_ipv6(nh6_str, srv6->seg6_src); +} + +// --- {{root_struct_name}} --- +inline void to_json(nlohmann::ordered_json& j, const {{root_struct_name}}& nh) { + j = nlohmann::ordered_json{ + {%- set sorted_fields = root_struct.fields | sort(attribute='position') %} + {%- for field in sorted_fields %} + {%- if field.name == "bh_type" %} + {%- elif field.name == "nh_srv6" %} + {%- elif field.name in ["gate", "src", "rmap_src"] %} + {"{{ field.name }}", gaddr_to_string(nh.{{ field.name }}, nh.type)}, + {%- else %} + {"{{ field.name }}", nh.{{ field.name }}}, + {%- endif %} + {%- endfor %} + }; + + // deal with the bh_type + if (nh.type == NEXTHOP_TYPE_BLACKHOLE) { + j["bh_type"] = nh.bh_type; + } + + // deal with the nh_srv6 + if (nh.nh_srv6) { + j["nh_srv6"] = nh.nh_srv6; + } else { + j["nh_srv6"] = nullptr; + } +} +inline void from_json(const nlohmann::ordered_json& j, {{ root_struct_name }}& nh) { + {%- set sorted_fields = root_struct.fields | sort(attribute='position') %} + {%- for field in sorted_fields %} + {%- if field.name in ["gate", "src", "rmap_src"] %} + std::string {{ field.name }}_str = j.at("{{ field.name }}"); + string_to_gaddr({{ field.name }}_str, nh.{{ field.name }}, nh.type); + {%- elif field.name == "bh_type" %} + if (j.contains("bh_type")) { + j.at("bh_type").get_to(nh.bh_type); + } + {%- elif field.name == "nh_srv6" %} + if (j.contains("nh_srv6")) { + j.at("nh_srv6").get_to(nh.nh_srv6); + } else { + nh.nh_srv6 = nullptr; + } + {%- else %} + j.at("{{ field.name }}").get_to(nh.{{ field.name }}); + {%- endif %} + {%- endfor %} +} + +// --- Top-level string helpers --- +inline std::string to_json_string({{ root_struct_name }}& obj) { + return nlohmann::ordered_json(obj).dump(); +} + +inline bool from_json_string(std::string& json_str, {{ root_struct_name }}& out_obj) { + try { + auto j = nlohmann::ordered_json::parse(json_str); + j.get_to(out_obj); + return true; + } catch (const std::exception&) { + return false; + } +} + +} // namespace fib \ No newline at end of file diff --git a/src/libraries/sonic-fib/tests/Makefile.am b/src/libraries/sonic-fib/tests/Makefile.am new file mode 100644 index 00000000000..6c8dba824cc --- /dev/null +++ b/src/libraries/sonic-fib/tests/Makefile.am @@ -0,0 +1,19 @@ +# Build test binary during 'make', but never install it +noinst_PROGRAMS = tests/tests + +# Register test binary to be executed by 'make check' +TESTS = tests/tests + +CFLAGS_GTEST = +LDADD_GTEST = -L/usr/src/gtest -lgtest -lgtest_main -lgmock -lgmock_main + +tests_tests_SOURCES = tests/c_api_ut.cpp \ + tests/nexthopgroupfull_ut.cpp \ + tests/nexthopgroupfull_json_ut.cpp \ + tests/nexthopgroup_debug_ut.cpp \ + tests/main.cpp + +tests_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) +tests_tests_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) -fno-access-control +tests_tests_LDADD = $(LDADD_GTEST) -lpthread src/libnexthopgroup.la $(CODE_COVERAGE_LIBS) -lzmq -luuid -lboost_serialization + diff --git a/src/libraries/sonic-fib/tests/c_api_ut.cpp b/src/libraries/sonic-fib/tests/c_api_ut.cpp new file mode 100644 index 00000000000..9337fe4636d --- /dev/null +++ b/src/libraries/sonic-fib/tests/c_api_ut.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "src/c-api/nexthopgroup_capi.h" +#include "src/nexthopgroupfull.h" +#include "src/nexthopgroupfull_json.h" +#include "src/c_nexthopgroupfull.h" + +using namespace std; +using fib::from_json; +using fib::to_json; + + +TEST(NextHopGroupFull_CAPI, multi_nexthop) { + cout << "TEST_NextHopGroupFull_CAPI::multi_nexthop started: " << endl; + cout << "[DEBUG] Constructing values ..." << endl; + + /* Prepare the parameters */ + C_NextHopGroupFull c_nhg = {}; + + c_nhg.id = 100; + c_nhg.key = 1234567; + c_nhg.nhg_flags = 1024; + + // set up nh_grp_full_list + struct C_nh_grp_full nh1 = {}; + nh1.id = 200; + nh1.weight = 1; + nh1.num_direct = 0; + c_nhg.nh_grp_full_list[0] = nh1; + + struct C_nh_grp_full nh2 = {}; + nh2.id = 300; + nh2.weight = 1; + nh2.num_direct = 2; + c_nhg.nh_grp_full_list[1] = nh2; + + struct C_nh_grp_full nh3 = {}; + nh3.id = 310; + nh3.weight = 2; + nh3.num_direct = 0; + c_nhg.nh_grp_full_list[2] = nh3; + + struct C_nh_grp_full nh4 = {}; + nh4.id = 320; + nh4.weight = 2; + nh4.num_direct = 0; + c_nhg.nh_grp_full_list[3] = nh4; + + struct C_nh_grp_full nh5 = {}; + nh5.id = 400; + nh5.weight = 1; + nh5.num_direct = 0; + c_nhg.nh_grp_full_list[4] = nh5; + + // terminate the list with id = 0 + c_nhg.nh_grp_full_list[5].id = 0; + + // set up depends + c_nhg.depends[0] = 200; + c_nhg.depends[1] = 300; + c_nhg.depends[2] = 400; + c_nhg.depends[3] = 0; + + // set up dependents + c_nhg.dependents[0] = 500; + c_nhg.dependents[1] = 600; + c_nhg.dependents[2] = 0; + + /* Call c-api to convert C_NextHopGroupFull to C++ NextHopGroupFull and return JSON string */ + cout << "[DEBUG] Calling nexthopgroupfull_json_from_c_nhg_multi ..." << endl; + char* json_str = nexthopgroupfull_json_from_c_nhg_multi(&c_nhg, 5, 3, 2, false); + + ASSERT_NE(json_str, nullptr) << "[ERROR] C-API returned nullptr"; + + /* Output the generated JSON string */ + cout << " [DEBUG] The generated JSON string is:" << endl; + cout << " " << json_str << endl; + + /* Parse JSON string and deserialize to C++ object */ + cout << "[DEBUG] Parsing JSON string and deserializing to C++ object ..." << endl; + nlohmann::ordered_json j = nlohmann::json::parse(json_str); + fib::NextHopGroupFull cpp_nhg; + from_json(j, cpp_nhg); + + /* Verify round-trip conversion by comparing C structure and C++ object */ + cout << "[DEBUG] Verifying C_NextHopGroupFull vs C++ NextHopGroupFull ..." << endl; + // verify basic fields + EXPECT_EQ(cpp_nhg.id, c_nhg.id); + EXPECT_EQ(cpp_nhg.key, c_nhg.key); + EXPECT_EQ(cpp_nhg.nhg_flags, c_nhg.nhg_flags); + // verify nh_grp_full_list + EXPECT_EQ(cpp_nhg.nh_grp_full_list.size(), 5); + for (int i = 0; i < 5; i++) { + EXPECT_EQ(cpp_nhg.nh_grp_full_list[i].id, c_nhg.nh_grp_full_list[i].id); + EXPECT_EQ(cpp_nhg.nh_grp_full_list[i].weight, c_nhg.nh_grp_full_list[i].weight); + EXPECT_EQ(cpp_nhg.nh_grp_full_list[i].num_direct, c_nhg.nh_grp_full_list[i].num_direct); + } + // verify depends + EXPECT_EQ(cpp_nhg.depends.size(), 3); + for (int i = 0; i < 3; i++) { + EXPECT_EQ(cpp_nhg.depends[i], c_nhg.depends[i]); + } + // verify dependents + EXPECT_EQ(cpp_nhg.dependents.size(), 2); + for (int i = 0; i < 2; i++) { + EXPECT_EQ(cpp_nhg.dependents[i], c_nhg.dependents[i]); + } + + /* Clean up */ + free(json_str); + + cout << "TEST_NextHopGroupFull_CAPI::multi_nexthop finished." << endl; +} + +TEST(NextHopGroupFull_CAPI, singleton) { + cout << "TEST_NextHopGroupFull_CAPI::singleton started:" << endl; + cout << "[DEBUG] Constructing C_NextHopGroupFull type singleton values ..." << endl; + + /* Prepare the parameters */ + C_NextHopGroupFull c_nhg = {}; + + c_nhg.id = 100; + c_nhg.key = 1234567; + c_nhg.type = C_NEXTHOP_TYPE_IPV6; + c_nhg.vrf_id = 101; + c_nhg.ifindex = 101; + c_nhg.nh_label_type = C_ZEBRA_LSP_NONE; + c_nhg.bh_type = C_BLACKHOLE_NULL; + c_nhg.weight = 8; + c_nhg.flags = 8; + c_nhg.nhg_flags = 1024; + + // set up addresses + inet_pton(AF_INET6, "2001:db8::1", &c_nhg.gate.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::2", &c_nhg.src.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::3", &c_nhg.rmap_src.ipv6.s6_addr); + + // set up depends and dependents + c_nhg.depends[0] = 200; + c_nhg.depends[1] = 300; + c_nhg.depends[2] = 400; + c_nhg.depends[3] = 0; + + c_nhg.dependents[0] = 500; + c_nhg.dependents[1] = 600; + c_nhg.dependents[2] = 0; + + // prepare segment list + vector test_nh_segs(3); + inet_pton(AF_INET6, "2001:db8:1::1", test_nh_segs[0].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::2", test_nh_segs[1].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::3", test_nh_segs[2].s6_addr); + + // prepare seg6_segs + size_t total = sizeof(struct C_seg6_seg_stack) + + test_nh_segs.size() * sizeof(struct in6_addr); + struct C_seg6_seg_stack* test_nh_seg6_segs = + (struct C_seg6_seg_stack*)malloc(total); + test_nh_seg6_segs->encap_behavior = C_SRV6_HEADEND_BEHAVIOR_H_ENCAPS; + test_nh_seg6_segs->num_segs = 3; + memcpy(test_nh_seg6_segs->seg, test_nh_segs.data(), + test_nh_segs.size() * sizeof(in6_addr)); + + // Prepare seg6local_flavors_info + struct C_seg6local_flavors_info test_flv = { + .flv_ops = 100, + .lcblock_len = 20, + .lcnode_func_len = 16 + }; + + // Prepare seg6local_ctx + struct C_seg6local_context test_seg6local_ctx = {}; + inet_pton(AF_INET, "192.168.10.1", &test_seg6local_ctx.nh4.s_addr); + inet_pton(AF_INET6, "2001:db8::a", &test_seg6local_ctx.nh6.s6_addr); + test_seg6local_ctx.table = 100; + test_seg6local_ctx.block_len = 36; + test_seg6local_ctx.node_len = 12; + test_seg6local_ctx.function_len = 20; + test_seg6local_ctx.argument_len = 16; + memcpy(&test_seg6local_ctx.flv, &test_flv, sizeof(struct C_seg6local_flavors_info)); + + // Prepare nh_srv6 + struct C_nexthop_srv6* test_nh_srv6 = + (struct C_nexthop_srv6*)malloc(sizeof(struct C_nexthop_srv6)); + memset(test_nh_srv6, 0, sizeof(struct C_nexthop_srv6)); + test_nh_srv6->seg6local_action = C_SEG6_LOCAL_ACTION_END_DT6; + memcpy(&test_nh_srv6->seg6local_ctx, &test_seg6local_ctx, + sizeof(struct C_seg6local_context)); + test_nh_srv6->seg6_segs = test_nh_seg6_segs; + + c_nhg.nh_srv6 = test_nh_srv6; + + /* Call c-api to convert C_NextHopGroupFull to C++ NextHopGroupFull and return JSON string */ + cout << "[DEBUG] Calling nexthopgroupfull_json_from_c_nhg_singleton ..." << endl; + char* json_str = nexthopgroupfull_json_from_c_nhg_singleton(&c_nhg, 3, 2); + + ASSERT_NE(json_str, nullptr) << "[ERROR] C-API returned nullptr"; + + /* Output the generated JSON string */ + cout << " [DEBUG] The generated JSON string is:" << endl; + cout << " " << json_str << endl; + + /* Parse JSON string and deserialize to C++ object */ + cout << "[DEBUG] Parsing JSON string and deserializing to C++ object ..." << endl; + nlohmann::ordered_json j = nlohmann::json::parse(json_str); + fib::NextHopGroupFull cpp_nhg; + from_json(j, cpp_nhg); + + /* Verify round-trip conversion by comparing C structure and C++ object */ + cout << "[DEBUG] Verifying C_NextHopGroupFull vs C++ NextHopGroupFull ..." << endl; + // verify basic fields + EXPECT_EQ(cpp_nhg.id, c_nhg.id); + EXPECT_EQ(cpp_nhg.key, c_nhg.key); + EXPECT_EQ(cpp_nhg.type, c_nhg.type); + EXPECT_EQ(cpp_nhg.vrf_id, c_nhg.vrf_id); + EXPECT_EQ(cpp_nhg.ifindex, c_nhg.ifindex); + EXPECT_EQ(cpp_nhg.weight, c_nhg.weight); + EXPECT_EQ(cpp_nhg.flags, c_nhg.flags); + EXPECT_EQ(cpp_nhg.nhg_flags, c_nhg.nhg_flags); + EXPECT_EQ(cpp_nhg.nh_label_type, c_nhg.nh_label_type); + EXPECT_EQ(cpp_nhg.bh_type, c_nhg.bh_type); + + // verify addresses + EXPECT_EQ(memcmp(&cpp_nhg.gate.ipv6, &c_nhg.gate.ipv6, sizeof(struct in6_addr)), 0); + EXPECT_EQ(memcmp(&cpp_nhg.src.ipv6, &c_nhg.src.ipv6, sizeof(struct in6_addr)), 0); + EXPECT_EQ(memcmp(&cpp_nhg.rmap_src.ipv6, &c_nhg.rmap_src.ipv6, sizeof(struct in6_addr)), 0); + + // verify depends + EXPECT_EQ(cpp_nhg.depends.size(), 3); + EXPECT_EQ(cpp_nhg.depends[0], c_nhg.depends[0]); + EXPECT_EQ(cpp_nhg.depends[1], c_nhg.depends[1]); + EXPECT_EQ(cpp_nhg.depends[2], c_nhg.depends[2]); + + // verify dependents + EXPECT_EQ(cpp_nhg.dependents.size(), 2); + EXPECT_EQ(cpp_nhg.dependents[0], c_nhg.dependents[0]); + EXPECT_EQ(cpp_nhg.dependents[1], c_nhg.dependents[1]); + + // verify SRv6 basic information + ASSERT_NE(cpp_nhg.nh_srv6, nullptr) << "nh_srv6 should not be null"; + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_action, c_nhg.nh_srv6->seg6local_action); + // verify seg6local_ctx + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.table, c_nhg.nh_srv6->seg6local_ctx.table); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.block_len, c_nhg.nh_srv6->seg6local_ctx.block_len); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.node_len, c_nhg.nh_srv6->seg6local_ctx.node_len); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.function_len, c_nhg.nh_srv6->seg6local_ctx.function_len); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.argument_len, c_nhg.nh_srv6->seg6local_ctx.argument_len); + // verify seg6local_ctx.flv + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.flv.flv_ops, c_nhg.nh_srv6->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.flv.lcblock_len, c_nhg.nh_srv6->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len, c_nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len); + // verify seg6local_ctx nh4 and nh6 + EXPECT_EQ(memcmp(&cpp_nhg.nh_srv6->seg6local_ctx.nh4, &c_nhg.nh_srv6->seg6local_ctx.nh4, + sizeof(struct in_addr)), 0); + EXPECT_EQ(memcmp(&cpp_nhg.nh_srv6->seg6local_ctx.nh6, &c_nhg.nh_srv6->seg6local_ctx.nh6, + sizeof(struct in6_addr)), 0); + // verify seg6_segs + ASSERT_NE(cpp_nhg.nh_srv6->seg6_segs, nullptr) << "seg6_segs should not be null"; + EXPECT_EQ(cpp_nhg.nh_srv6->seg6_segs->encap_behavior, c_nhg.nh_srv6->seg6_segs->encap_behavior); + EXPECT_EQ(cpp_nhg.nh_srv6->seg6_segs->num_segs, c_nhg.nh_srv6->seg6_segs->num_segs); + // verify segment list + for (size_t i = 0; i < cpp_nhg.nh_srv6->seg6_segs->num_segs; i++) { + EXPECT_EQ(memcmp(&cpp_nhg.nh_srv6->seg6_segs->seg[i], &c_nhg.nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)), 0); + } + + /* Clean up */ + free(json_str); + free(test_nh_srv6->seg6_segs); + free(test_nh_srv6); + + cout << "TEST_NextHopGroupFull_CAPI::singleton finished." << endl; +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/tests/main.cpp b/src/libraries/sonic-fib/tests/main.cpp new file mode 100644 index 00000000000..b11b22f59ba --- /dev/null +++ b/src/libraries/sonic-fib/tests/main.cpp @@ -0,0 +1,17 @@ +#include "gtest/gtest.h" +#include + +class FibEnvironment : public ::testing::Environment { +public: + // Override this to define how to set up the environment. + void SetUp() override { + } +}; + +int main(int argc, char* argv[]) +{ + testing::InitGoogleTest(&argc, argv); + FibEnvironment* const env = new FibEnvironment; + testing::AddGlobalTestEnvironment(env); + return RUN_ALL_TESTS(); +} diff --git a/src/libraries/sonic-fib/tests/nexthopgroup_debug_ut.cpp b/src/libraries/sonic-fib/tests/nexthopgroup_debug_ut.cpp new file mode 100644 index 00000000000..3dcb59ea2f6 --- /dev/null +++ b/src/libraries/sonic-fib/tests/nexthopgroup_debug_ut.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "src/c-api/nexthopgroup_capi.h" +#include "src/nexthopgroup_debug.h" +#include // for std::array +#include + +using namespace std; +using namespace fib; + +// Test C++ API registration and logging +TEST(NextHopGroupDEBUG_API, register) { + + setLogLevel(fib::LogLevel::DEBUG); + FIB_LOG(fib::LogLevel::DEBUG, "Current log level: %d", static_cast(getLogLevel())); + ASSERT_EQ(getLogLevel(), fib::LogLevel::DEBUG); // Default is DEBUG + + setLogLevel(fib::LogLevel::INFO); + FIB_LOG(fib::LogLevel::INFO, "Current log level after setting: %d", static_cast(getLogLevel())); + ASSERT_EQ(getLogLevel(), fib::LogLevel::INFO); // Change to INFO + registerLogCallback([](fib::LogLevel lvl, const char* file, int line, + const char* func, const char* format, va_list args) { + const char* level_str = "DEBUG"; + switch (lvl) { + case fib::LogLevel::INFO: level_str = "INFO"; break; + case fib::LogLevel::WARN: level_str = "WARN"; break; + case fib::LogLevel::ERROR: level_str = "ERROR"; break; + default: break; + } + + if (static_cast(lvl) < static_cast(getLogLevel())) { + return; // Skip messages below current log level + } + // Format the variadic arguments into a buffer + std::array buf; + va_list args_copy; + va_copy(args_copy, args); + int len = vsnprintf(buf.data(), buf.size(), format, args_copy); + va_end(args_copy); + if (len > 0 && len < static_cast(buf.size())) { + cout << "[" << level_str << "] " << file << ":" << line << " " << func << " - " << buf.data() << endl; + } + }); + + FIB_LOG(fib::LogLevel::DEBUG, "Test log with registered callback - DEBUG level"); + FIB_LOG(fib::LogLevel::INFO, "Test log with registered callback - INFO level"); +} + +// Ensure C linkage for the callback function to match C ABI expectations +extern "C" { + +/* Map fib-sonic levels to syslog priorities used by FRR */ +static int fib_level_to_syslog(int level) +{ + switch (level) { + case 0: return LOG_DEBUG; // DEBUG + case 1: return LOG_INFO; // INFO + case 2: return LOG_WARNING; // WARN + case 3: return LOG_ERR; // ERROR + default: return LOG_DEBUG; + } +} +static void frr_log_forwarder(int level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + int syslog_prio = fib_level_to_syslog(level); + + int current_log_level = -1; + try { + current_log_level = fib_frr_get_log_level(); + } catch (...) { + current_log_level = -1; // fallback on error + } + if (level < current_log_level) { + return; // Skip messages below current log level + } + fprintf(stderr, "[LVL=%d %s:%d %s] ", syslog_prio, file, line, func); + // Core: print formatted message to stderr using va_list + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); // Add newline (vfprintf does not add one automatically) +} + +} // extern "C" +// Test C API registration and logging +TEST(NextHopGroupDEBUG_CAPI, register_c) { + fib_frr_set_log_level(0); // Set back to DEBUG + LogLevel level = getLogLevel(); + FIB_LOG(fib::LogLevel::DEBUG, "Current log level: %d", static_cast(level)); + ASSERT_EQ(getLogLevel(), fib::LogLevel::DEBUG); // Default is DEBUG + fib_frr_register_callback(nullptr); // Unregister callback to test default logging + FIB_LOG(fib::LogLevel::DEBUG, "Test log with default logger - DEBUG level"); + FIB_LOG(fib::LogLevel::INFO, "Test log with default logger - INFO level"); + + fib_frr_set_log_level(1); // INFO + level = getLogLevel(); + FIB_LOG(fib::LogLevel::INFO, "Current log level after setting: %d", static_cast(level)); + ASSERT_EQ(getLogLevel(), fib::LogLevel::INFO); // Change to INFO + + fib_frr_register_callback(frr_log_forwarder); // Register callback to test default logging + FIB_LOG(fib::LogLevel::DEBUG, "Test log with C callback logger - DEBUG level"); + FIB_LOG(fib::LogLevel::INFO, "Test log with C callback logger - INFO level"); +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/tests/nexthopgroupfull_json_ut.cpp b/src/libraries/sonic-fib/tests/nexthopgroupfull_json_ut.cpp new file mode 100644 index 00000000000..938b8b5e6ce --- /dev/null +++ b/src/libraries/sonic-fib/tests/nexthopgroupfull_json_ut.cpp @@ -0,0 +1,690 @@ +#include +#include + +#include +#include + +#include + +#include "src/nexthopgroupfull.h" +#include "src/nexthopgroupfull_json.h" + +using namespace std; +using namespace fib; + +static nh_grp_full make_nh_grp_full(uint32_t id, uint16_t weight, uint32_t num_direct) { + return {id, weight, num_direct}; +} + +TEST(EnumToJson, nexthop_type) +{ + cout << "TEST_EnumToJson::nexthop_type started: " << endl; + cout << "[DEBUG] Preparing values ..." << endl; + /* Prepare the value */ + fib::nexthop_types_t test_val = NEXTHOP_TYPE_IPV4_IFINDEX; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json function for enum nexthop_types_t ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << std::endl; + /* Check the value of constructed JSON */ + cout << "[DEBUG] Checking JSON string values ..." << endl; + EXPECT_EQ(j, "NEXTHOP_TYPE_IPV4_IFINDEX"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json function for enum nexthop_types_t ..." << endl; + fib::nexthop_types_t parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val, test_val); + + cout << "TEST_EnumToJson::nexthop_type finished." << endl; +} + +TEST(EnumToJson, blackhole_type) +{ + cout << "TEST_EnumToJson::blackhole_type started: " << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::blackhole_type test_val = BLACKHOLE_REJECT; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json function for enum blackhole_type ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the value of constructed JSON */ + EXPECT_EQ(j, "BLACKHOLE_REJECT"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json function for enum blackhole_type ..." << endl; + fib::blackhole_type parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val, test_val); + + cout << "TEST_EnumToJson::blackhole_type finished." << endl; +} + +TEST(EnumToJson, lsp_type) +{ + cout << "Test_EnumToJson::lsp_type started: " << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::lsp_types_t test_val = ZEBRA_LSP_BGP; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json function for enum lsp_types_t ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the value of constructed JSON */ + EXPECT_EQ(j, "ZEBRA_LSP_BGP"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json function for enum lsp_types_t ..." << endl; + fib::lsp_types_t parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val, test_val); + + cout << "TEST_EnumToJson::lsp_types_t finished." << endl; +} + +TEST(EnumToJson, seg6local_action) +{ + cout << "Test_EnumToJson::seg6local_action started: " << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::seg6local_action_t test_val = SEG6_LOCAL_ACTION_END_DT46; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json function for enum seg6local_action_t ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the value of constructed JSON */ + EXPECT_EQ(j, "SEG6_LOCAL_ACTION_END_DT46"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json function for enum seg6local_action_t ..." << endl; + fib::seg6local_action_t parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val, test_val); + + cout << "TEST_EnumToJson::seg6local_action_t finished." << endl; +} + +TEST(EnumToJson, srv6_headend_behavior) +{ + cout << "Test_EnumToJson::srv6_headend_behavior started: " << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing value ..." << endl; + fib::srv6_headend_behavior test_val = SRV6_HEADEND_BEHAVIOR_H_INSERT; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json function for enum srv6_headend_behavior ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the value of constructed JSON */ + EXPECT_EQ(j, "SRV6_HEADEND_BEHAVIOR_H_INSERT"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json function for enum sev6_headend_behavior ..." << endl; + fib::srv6_headend_behavior parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val, test_val); + + cout << "TEST_EnumToJson::srv6_headend_behavior finishd." << endl; +} + + +// --- Test: nh_grp_full --- +TEST(StructToFromJson, nh_grp_full) +{ + cout << "TEST_StructToFromJson::nh_grp_full started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::nh_grp_full test_val{1001, 5, 3}; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for nh_grp_full ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["id"], 1001); + EXPECT_EQ(j["weight"], 5); + EXPECT_EQ(j["num_direct"], 3); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for nh_grp_full ..." << endl; + fib::nh_grp_full parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of nh_grp_full struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val.id, test_val.id); + EXPECT_EQ(parsed_val.weight, test_val.weight); + EXPECT_EQ(parsed_val.num_direct, test_val.num_direct); + + cout << "TEST_StructToFromJson::nh_grp_full finished." << endl; +} + +// --- Test: seg6local_flavors_info --- +TEST(StructToFromJson, seg6local_flavors_info) +{ + cout << "TEST_StructToFromJson::seg6local_flavors_info started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::seg6local_flavors_info test_val{1000, 32, 16}; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for seg6local_flavors_info ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["flv_ops"], test_val.flv_ops); + EXPECT_EQ(j["lcblock_len"], test_val.lcblock_len); + EXPECT_EQ(j["lcnode_func_len"], test_val.lcnode_func_len); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for seg6local_flavors_info ..." << endl; + fib::seg6local_flavors_info parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of seg6local_flavors_info struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + EXPECT_EQ(parsed_val.flv_ops, test_val.flv_ops); + EXPECT_EQ(parsed_val.lcblock_len, test_val.lcblock_len); + EXPECT_EQ(parsed_val.lcnode_func_len, test_val.lcnode_func_len); + + cout << "TEST_StructToFromJson::seg6local_flavors_info finished." << endl; +} + +// --- Helper for IP conversion in tests --- +static void set_ipv4(struct in_addr& addr, const char* ip_str) { + ASSERT_EQ(inet_pton(AF_INET, ip_str, &addr), 1); +} +static void set_ipv6(struct in6_addr& addr, const char* ip_str) { + ASSERT_EQ(inet_pton(AF_INET6, ip_str, &addr), 1); +} + +// --- Test: seg6local_context --- +TEST(StructToFromJson, seg6local_context) +{ + cout << "TEST_StructToFromJson::seg6local_context started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::seg6local_context test_val{}; + char* test_nh4 = "192.168.10.1"; + char* test_nh6 = "2001:db8::a"; + set_ipv4(test_val.nh4, test_nh4); + set_ipv6(test_val.nh6, test_nh6); + test_val.table = 100; + test_val.flv = {1000, 32,16}; + test_val.block_len = 32; + test_val.node_len = 16; + test_val.function_len = 16; + test_val.argument_len = 0; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for seg6local_context ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["nh4"], test_nh4); + EXPECT_EQ(j["nh6"], test_nh6); + EXPECT_EQ(j["table"], test_val.table); + EXPECT_EQ(j["flv"]["flv_ops"], test_val.flv.flv_ops); + EXPECT_EQ(j["flv"]["lcblock_len"], test_val.flv.lcblock_len); + EXPECT_EQ(j["flv"]["lcnode_func_len"], test_val.flv.lcnode_func_len); + EXPECT_EQ(j["block_len"], test_val.block_len); + EXPECT_EQ(j["node_len"], test_val.node_len); + EXPECT_EQ(j["function_len"], test_val.function_len); + EXPECT_EQ(j["argument_len"], test_val.argument_len); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for seg6local_context ..." << endl; + fib::seg6local_context parsed_val; + fib::from_json(j, parsed_val); + + /* Check the value of seg6local_context struct parsed from JSON */ + cout << "[DEBUG] Checking STRUCT value parsed from JSON string ..." << endl; + char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET, &parsed_val.nh4, buf4, sizeof(buf4)); + inet_ntop(AF_INET6, &parsed_val.nh6, buf6, sizeof(buf6)); + EXPECT_STREQ(buf4, test_nh4); + EXPECT_STREQ(buf6, test_nh6); + EXPECT_EQ(parsed_val.table, test_val.table); + EXPECT_EQ(parsed_val.flv.flv_ops, test_val.flv.flv_ops); + EXPECT_EQ(parsed_val.flv.lcblock_len, test_val.flv.lcblock_len); + EXPECT_EQ(parsed_val.flv.lcnode_func_len, test_val.flv.lcnode_func_len); + EXPECT_EQ(parsed_val.block_len, test_val.block_len); + EXPECT_EQ(parsed_val.node_len, test_val.node_len); + EXPECT_EQ(parsed_val.function_len, test_val.function_len); + EXPECT_EQ(parsed_val.argument_len, test_val.argument_len); + + cout << "TEST_StructToFromJson::seg6local_context finished." << endl; +} + +// --- Test: seg6_seg_stack* (pointer) --- +TEST(StructToFromJson, seg6_seg_stack_ptr) +{ + cout << "TEST_StructToFromJson::seg6_seg_stack_ptr started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + std::vector test_segs = {"2001:db8::1", "2001:db8::2"}; + size_t total = sizeof(fib::seg6_seg_stack) + test_segs.size() * sizeof(in6_addr); + fib::seg6_seg_stack* test_val = static_cast(malloc(total)); + ASSERT_NE(test_val, nullptr); + test_val->encap_behavior = fib::SRV6_HEADEND_BEHAVIOR_H_ENCAPS; + test_val->num_segs = 2; + for (size_t i = 0; i < test_segs.size(); ++i) { + set_ipv6(test_val->seg[i], test_segs[i].c_str()); + } + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for seg6_seg_stack_ptr ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["encap_behavior"], "SRV6_HEADEND_BEHAVIOR_H_ENCAPS"); + auto j_segs_out = j["seg"].get>(); + EXPECT_EQ(j["num_segs"], test_val->num_segs); + EXPECT_EQ(j_segs_out.size(), 2); + EXPECT_EQ(j_segs_out[0], "2001:db8::1"); + EXPECT_EQ(j_segs_out[1], "2001:db8::2"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for seg6_seg_stack_ptr ..." << endl; + fib::seg6_seg_stack* parsed_val = nullptr; + fib::from_json(j, parsed_val); + ASSERT_NE(parsed_val, nullptr); + + cout << "[DEBUG] Checking parsed values ..." << endl; + EXPECT_EQ(parsed_val->encap_behavior, test_val->encap_behavior); + EXPECT_EQ(parsed_val->num_segs, test_val->num_segs); + for (int i = 0; i < parsed_val->num_segs; ++i) { + char test_buf[INET6_ADDRSTRLEN], parsed_buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &test_val->seg[i], test_buf, sizeof(test_buf)); + inet_ntop(AF_INET6, &parsed_val->seg[i], parsed_buf, sizeof(parsed_buf)); + EXPECT_STREQ(test_buf, parsed_buf); + } + + // Cleanup + free(test_val); + test_val = nullptr; + if (parsed_val) { + free(parsed_val); + parsed_val = nullptr; + } + + cout << "TEST_StructToJson::seg6_seg_stack_ptr finished." << endl; +} + +// --- Test: nexthop_srv6* (pointer) --- +TEST(StructToFromJson, nexthop_srv6_ptr) +{ + cout << "TEST_StructToFromJson::nexthop_srv6_ptr started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + fib::nexthop_srv6* test_val = (fib::nexthop_srv6*)malloc(sizeof(fib::nexthop_srv6)); + if (!test_val) { + FAIL() << "Failed to allocate test_val"; + return; + } + test_val->seg6local_action = fib::SEG6_LOCAL_ACTION_END_DT6; + char* test_nh4 = "192.168.10.1"; + char* test_nh6 = "2001:db8::a"; + set_ipv4(test_val->seg6local_ctx.nh4, test_nh4); + set_ipv6(test_val->seg6local_ctx.nh6, test_nh6); + test_val->seg6local_ctx.table = 200; + test_val->seg6local_ctx.flv = {1000, 32, 16}; + test_val->seg6local_ctx.block_len = 48; + test_val->seg6local_ctx.node_len = 16; + test_val->seg6local_ctx.function_len = 16; + test_val->seg6local_ctx.argument_len = 0; + // Add seg6_segs + std::vector test_segs = {"2001:db8::1", "2001:db8::2"}; + size_t total = sizeof(fib::seg6_seg_stack) + test_segs.size() * sizeof(in6_addr); + test_val->seg6_segs = static_cast(malloc(total)); + test_val->seg6_segs->encap_behavior = fib::SRV6_HEADEND_BEHAVIOR_H_INSERT; + test_val->seg6_segs->num_segs = 2; + for (size_t i = 0; i < test_segs.size(); ++i) { + set_ipv6(test_val->seg6_segs->seg[i], test_segs[i].c_str()); + } + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for nexthop_srv6 ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["seg6local_action"], "SEG6_LOCAL_ACTION_END_DT6"); + // seg6local_context + EXPECT_EQ(j["seg6local_ctx"]["nh4"], "192.168.10.1"); + EXPECT_EQ(j["seg6local_ctx"]["nh6"], "2001:db8::a"); + EXPECT_EQ(j["seg6local_ctx"]["table"], test_val->seg6local_ctx.table); + // seg6local_context.seg6local_flavors_info + EXPECT_EQ(j["seg6local_ctx"]["flv"]["flv_ops"], test_val->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(j["seg6local_ctx"]["flv"]["lcblock_len"], test_val->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(j["seg6local_ctx"]["flv"]["lcnode_func_len"],test_val->seg6local_ctx.flv.lcnode_func_len); + EXPECT_EQ(j["seg6local_ctx"]["block_len"], test_val->seg6local_ctx.block_len); + EXPECT_EQ(j["seg6local_ctx"]["node_len"], test_val->seg6local_ctx.node_len); + EXPECT_EQ(j["seg6local_ctx"]["function_len"], test_val->seg6local_ctx.function_len); + EXPECT_EQ(j["seg6local_ctx"]["argument_len"], test_val->seg6local_ctx.argument_len); + // seg6_seg_stack * + EXPECT_EQ(j["seg6_segs"]["encap_behavior"], "SRV6_HEADEND_BEHAVIOR_H_INSERT"); + EXPECT_EQ(j["seg6_segs"]["num_segs"], 2); + auto j_segs_out = j["seg6_segs"]["seg"].get>(); + EXPECT_EQ(j_segs_out.size(), 2); + EXPECT_EQ(j_segs_out[0], "2001:db8::1"); + EXPECT_EQ(j_segs_out[1], "2001:db8::2"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for nexthop_srv6 * ..." << endl; + fib::nexthop_srv6 *parsed_val = nullptr; + fib::from_json(j, parsed_val); + ASSERT_NE(parsed_val, nullptr); + + /* Check values of STRUCT parsed from JSON */ + cout << "[DEBUG] Checking parsed values from constructed JSON ..." << endl; + EXPECT_EQ(parsed_val->seg6local_action, test_val->seg6local_action); + // seg6local_context + char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET, &parsed_val->seg6local_ctx.nh4, buf4, sizeof(buf4)); + inet_ntop(AF_INET6, &parsed_val->seg6local_ctx.nh6, buf6, sizeof(buf6)); + EXPECT_STREQ(buf4, test_nh4); + EXPECT_STREQ(buf6, test_nh6); + EXPECT_EQ(parsed_val->seg6local_ctx.table, test_val->seg6local_ctx.table); + // seg6local_context.seg6local_flavors_info + EXPECT_EQ(parsed_val->seg6local_ctx.flv.flv_ops, test_val->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(parsed_val->seg6local_ctx.flv.lcblock_len, test_val->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(parsed_val->seg6local_ctx.flv.lcnode_func_len, test_val->seg6local_ctx.flv.lcnode_func_len); + EXPECT_EQ(parsed_val->seg6local_ctx.block_len, test_val->seg6local_ctx.block_len); + EXPECT_EQ(parsed_val->seg6local_ctx.node_len, test_val->seg6local_ctx.node_len); + EXPECT_EQ(parsed_val->seg6local_ctx.function_len, test_val->seg6local_ctx.function_len); + EXPECT_EQ(parsed_val->seg6local_ctx.argument_len, test_val->seg6local_ctx.argument_len); + // seg6_seg_stack * + EXPECT_EQ(parsed_val->seg6_segs->encap_behavior, test_val->seg6_segs->encap_behavior); + EXPECT_EQ(parsed_val->seg6_segs->num_segs, test_val->seg6_segs->num_segs); + for (int i = 0; i < parsed_val->seg6_segs->num_segs; ++i) { + char test_buf[INET6_ADDRSTRLEN], parsed_buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &test_val->seg6_segs->seg[i], test_buf, sizeof(test_buf)); + inet_ntop(AF_INET6, &parsed_val->seg6_segs->seg[i], parsed_buf, sizeof(parsed_buf)); + EXPECT_STREQ(test_buf, parsed_buf); + } + + /* Clean up */ + if (test_val) { + if (test_val->seg6_segs) { + free(test_val->seg6_segs); + test_val->seg6_segs = nullptr; + } + free(test_val); + test_val = nullptr; + } + if (parsed_val) { + if (parsed_val->seg6_segs) { + free(parsed_val->seg6_segs); + parsed_val->seg6_segs = nullptr; + } + free(parsed_val); + parsed_val = nullptr; + } + + cout << "TEST_StructToFromJson::nexthop_srv6_ptr finished." << endl; +} + +TEST(StructToFromJson, NextHopGroupFull_multi_nexthop) +{ + cout << "TEST_StructToFromJson::NextHopGroupFull started:" << endl; + /* Prepare the value */ + cout << "[DEBUG] Preparing values ..." << endl; + uint32_t test_id = 100; + uint32_t test_key = 1234567; + uint32_t test_nhg_flags = 1024; + vector test_nh_grp_full_list = { + make_nh_grp_full(200, 1, 0), + make_nh_grp_full(300, 1, 2), + make_nh_grp_full(310, 2, 0), + make_nh_grp_full(320, 2, 0), + make_nh_grp_full(400, 1, 0) + }; + vector test_depends = {200, 300, 400}; + vector test_dependents = {500, 600}; + + /* Call constructor function */ + cout << "[DEBUG] Calling NextHopGroupFull Constructor ..." << endl; + NextHopGroupFull test_val(test_id, test_key, test_nhg_flags, + test_nh_grp_full_list, test_depends, test_dependents); + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for NextHopGroupFull in multi-nexthop case ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + EXPECT_EQ(j["id"], 100); + EXPECT_EQ(j["key"], 1234567); + EXPECT_EQ(j["nhg_flags"], 1024); + for (size_t i = 0; i < test_val.nh_grp_full_list.size(); i++) { + EXPECT_EQ(j["nh_grp_full_list"][i]["id"], test_val.nh_grp_full_list[i].id); + EXPECT_EQ(j["nh_grp_full_list"][i]["weight"], test_val.nh_grp_full_list[i].weight); + EXPECT_EQ(j["nh_grp_full_list"][i]["num_direct"], test_val.nh_grp_full_list[i].num_direct); + } + for (size_t i = 0; i < test_val.depends.size(); i++) { + EXPECT_EQ(j["depends"][i], test_val.depends[i]); + } + for (size_t i = 0; i < test_val.dependents.size(); i++) { + EXPECT_EQ(j["dependents"][i], test_val.dependents[i]); + } + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for NextHopGroupFull in multi-nexthop case ..." << endl; + fib::NextHopGroupFull parsed_val; + fib::from_json(j, parsed_val); + + /* Check values of STRUCT parsed from JSON */ + cout << "[DEBUG] Checking parsed values from constructed JSON ..." << endl; + EXPECT_TRUE(parsed_val == test_val); + + cout << "TEST_StructToFromJson::NextHopGroupFull_multi_nexthop finished." << endl; +} + +TEST(StructToFromJson, NextHopGroupFull_singleton) +{ + cout << "TEST_StructToFromJson::NextHopGroupFull_singleton started:" << endl; + /* Prepare the values */ + uint32_t test_id = 100; + uint32_t test_key = 1234567; + enum nexthop_types_t test_type = NEXTHOP_TYPE_IPV6; + vrf_id_t test_vrf_id = 101; + ifindex_t test_ifindex = 101; + string test_ifname = "eth101"; + vector test_depends = {200, 300, 400}; + vector test_dependents = {500, 600}; + enum lsp_types_t test_nh_label_type = ZEBRA_LSP_NONE; + enum blackhole_type test_bh_type = BLACKHOLE_NULL; + + union g_addr test_gateway = {}; + union g_addr test_src = {}; + union g_addr test_rmap_src = {}; + inet_pton(AF_INET6, "2001:db8::1", &test_gateway.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::2", &test_src.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::3", &test_rmap_src.ipv6.s6_addr); + + uint16_t test_weight = 8; + uint8_t test_flags = 8; + uint32_t test_nhg_flags = 1024; + bool test_has_srv6 = true; + bool test_has_seg6_segs = true; + + // Prepare the segment list + vector test_nh_segs { + {}, {}, {} + }; + inet_pton(AF_INET6, "2001:db8:1::1", test_nh_segs[0].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::2", test_nh_segs[1].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::3", test_nh_segs[2].s6_addr); + + // Prepare seg6_segs + size_t seg6_segs_size = sizeof(struct seg6_seg_stack) + + test_nh_segs.size() * sizeof(struct in6_addr); + struct seg6_seg_stack* test_nh_seg6_segs = + (struct seg6_seg_stack*)malloc(seg6_segs_size); + test_nh_seg6_segs->encap_behavior = SRV6_HEADEND_BEHAVIOR_H_ENCAPS; + test_nh_seg6_segs->num_segs = 3; + memcpy(test_nh_seg6_segs->seg, test_nh_segs.data(), test_nh_segs.size() * sizeof(in6_addr)); + + //Prepare seg6local_flavors_info + struct seg6local_flavors_info test_flv = { + .flv_ops = 100, + .lcblock_len = 20, + .lcnode_func_len = 16 + }; + + // Prepare seg6local_ctx + struct seg6local_context test_seg6local_ctx = {}; + char* test_nh4 = "192.168.10.1"; + char* test_nh6 = "2001:db8::a"; + set_ipv4(test_seg6local_ctx.nh4, test_nh4); + set_ipv6(test_seg6local_ctx.nh6, test_nh6); + test_seg6local_ctx.table = 100; + test_seg6local_ctx.block_len = 36; + test_seg6local_ctx.node_len = 12; + test_seg6local_ctx.function_len = 20; + test_seg6local_ctx.argument_len = 16; + memcpy(&test_seg6local_ctx.flv, &test_flv, sizeof(struct seg6local_flavors_info)); + + // Prepare nh_srv6 + struct nexthop_srv6 test_nh_srv6 = {}; + test_nh_srv6.seg6local_action = SEG6_LOCAL_ACTION_END_DT6; + memcpy(&test_nh_srv6.seg6local_ctx, &test_seg6local_ctx, sizeof(struct seg6local_context)); + + /* Call constructor function */ + cout << "[DEBUG] Calling NextHopGroupFull Constructor ..." << endl; + NextHopGroupFull test_val(test_id, test_key, test_type, test_vrf_id, test_ifindex, + test_ifname, test_depends, test_dependents, test_nh_label_type, + test_bh_type, test_gateway, test_src, test_rmap_src, + test_weight, test_flags, test_nhg_flags, test_has_srv6, test_has_seg6_segs, + &test_nh_srv6, test_nh_seg6_segs, test_nh_segs); + + // Free the memory allocated dynamically + free(test_nh_seg6_segs); + test_nh_seg6_segs = nullptr; + + /* Call to_json function */ + cout << "[DEBUG] Calling to_json for NextHopGroupFull in singleton case ..." << endl; + nlohmann::ordered_json j; + fib::to_json(j, test_val); + + /* Output the constructed JSON string */ + cout << " [DEBUG] The constructed JSON string is:" << endl; + cout << " " << j.dump(4) << endl; + /* Check the values of constructed JSON */ + cout << "[DEBUG] Checking JSON values ..." << endl; + EXPECT_EQ(j["id"], test_val.id); + EXPECT_EQ(j["key"], test_val.key); + EXPECT_EQ(j["type"], test_val.type); + EXPECT_EQ(j["vrf_id"], test_val.vrf_id); + EXPECT_EQ(j["ifindex"], test_val.ifindex); + for (size_t i = 0; i < test_val.depends.size(); i++) { + EXPECT_EQ(j["depends"][i], test_val.depends[i]); + } + for (size_t i = 0; i < test_val.dependents.size(); i++) { + EXPECT_EQ(j["dependents"][i], test_val.dependents[i]); + } + EXPECT_EQ(j["nh_label_type"], "ZEBRA_LSP_NONE"); + EXPECT_EQ(j["weight"], test_val.weight); + EXPECT_EQ(j["flags"], test_val.flags); + EXPECT_EQ(j["nhg_flags"], test_val.nhg_flags); + EXPECT_EQ(j["gate"], "2001:db8::1"); + EXPECT_EQ(j["src"], "2001:db8::2"); + EXPECT_EQ(j["rmap_src"], "2001:db8::3"); + // Check SRv6 info + // Check nh_srv6's seg6local_action + EXPECT_EQ(j["nh_srv6"]["seg6local_action"], "SEG6_LOCAL_ACTION_END_DT6"); + // Check nh_srv6->seg6local_ctx + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["table"], test_val.nh_srv6->seg6local_ctx.table); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["nh4"], test_nh4); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["nh6"], test_nh6); + // Check nh_srv6->seg6local_ctx.flv + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["flv"]["flv_ops"], test_val.nh_srv6->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["flv"]["lcblock_len"], test_val.nh_srv6->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["flv"]["lcnode_func_len"], test_val.nh_srv6->seg6local_ctx.flv.lcnode_func_len); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["block_len"], test_val.nh_srv6->seg6local_ctx.block_len); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["node_len"], test_val.nh_srv6->seg6local_ctx.node_len); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["function_len"], test_val.nh_srv6->seg6local_ctx.function_len); + EXPECT_EQ(j["nh_srv6"]["seg6local_ctx"]["argument_len"], test_val.nh_srv6->seg6local_ctx.argument_len); + // Check nh_srv6->seg6_segs + EXPECT_EQ(j["nh_srv6"]["seg6_segs"]["encap_behavior"], "SRV6_HEADEND_BEHAVIOR_H_ENCAPS"); + auto j_segs_out = j["nh_srv6"]["seg6_segs"]["seg"].get>(); + EXPECT_EQ(j["nh_srv6"]["seg6_segs"]["num_segs"], test_val.nh_srv6->seg6_segs->num_segs); + EXPECT_EQ(j_segs_out.size(), 3); + EXPECT_EQ(j_segs_out[0], "2001:db8:1::1"); + EXPECT_EQ(j_segs_out[1], "2001:db8:1::2"); + EXPECT_EQ(j_segs_out[2], "2001:db8:1::3"); + + /* Call from_json function */ + cout << "[DEBUG] Calling from_json for NextHopGroupFull in singleton case ..." << endl; + fib::NextHopGroupFull parsed_val; + fib::from_json(j, parsed_val); + + /* Cehck values of STRUCT parsed from JSON */ + cout << "[DEBUG] Checking parsed values from constructed JSON ..." << endl; + EXPECT_TRUE(parsed_val == test_val); + + cout << "TEST_StructToFromJson::NextHopGroupFull_singleton finished." << endl; +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/tests/nexthopgroupfull_ut.cpp b/src/libraries/sonic-fib/tests/nexthopgroupfull_ut.cpp new file mode 100644 index 00000000000..30d3109c0f6 --- /dev/null +++ b/src/libraries/sonic-fib/tests/nexthopgroupfull_ut.cpp @@ -0,0 +1,484 @@ +#include +#include + +#include +#include + +#include + +#include "src/nexthopgroupfull.h" +#include "src/nexthopgroupfull_json.h" + +using namespace std; +using namespace fib; + +static nh_grp_full make_nh_grp_full(uint32_t id, uint16_t weight, uint32_t num_direct) { + return {id, weight, num_direct}; +} + +TEST(NextHopGroupFull, multi_nexthop) +{ + cout << "TEST_NextHopGroupFull::multi_nexthop started: " << endl; + cout << "[DEBUG] Constructing values ..." << endl; + /* Prepare the parameters */ + uint32_t test_id = 100; + uint32_t test_key = 1234567; + uint32_t test_nhg_flags = 1024; + vector test_nh_grp_full_list = { + make_nh_grp_full(200, 1, 0), + make_nh_grp_full(300, 1, 2), + make_nh_grp_full(310, 2, 0), + make_nh_grp_full(320, 2, 0), + make_nh_grp_full(400, 1, 0) + }; + vector test_depends = {200, 300, 400}; + vector test_dependents = {500, 600}; + + /* Call constructor function */ + cout << "[DEBUG] Calling NextHopGroupFull Constructor ..." << endl; + NextHopGroupFull nhg(test_id, test_key, test_nhg_flags, + test_nh_grp_full_list, test_depends, test_dependents); + + /* Check the value of the constructed NextHopGroupFull */ + cout << "[DEBUG] Checking constructed values ..." << endl; + // Check plain values + EXPECT_EQ(nhg.id, test_id); + EXPECT_EQ(nhg.key, test_key); + EXPECT_EQ(nhg.nhg_flags, test_nhg_flags); + // Check nh_grp_full_list status + EXPECT_EQ(nhg.nh_grp_full_list.size(), test_nh_grp_full_list.size()); + for (size_t i = 0; i < test_nh_grp_full_list.size(); i++) { + EXPECT_EQ(nhg.nh_grp_full_list[i].id, test_nh_grp_full_list[i].id); + EXPECT_EQ(nhg.nh_grp_full_list[i].weight, test_nh_grp_full_list[i].weight); + EXPECT_EQ(nhg.nh_grp_full_list[i].num_direct, test_nh_grp_full_list[i].num_direct); + } + // Check depends status + EXPECT_EQ(nhg.depends.size(), test_depends.size()); + for (size_t i = 0; i < test_depends.size(); i++) { + EXPECT_EQ(nhg.depends[i], test_depends[i]); + } + // Check dependents status + EXPECT_EQ(nhg.dependents.size(), test_dependents.size()); + for (size_t i = 0; i < test_dependents.size(); i++) { + EXPECT_EQ(nhg.dependents[i], test_dependents[i]); + } + // Check other unused values + cout << "[DEBUG] Checking default values ..." << endl; + EXPECT_EQ(nhg.weight, 0); + EXPECT_EQ(nhg.flags, 0); + EXPECT_TRUE(nhg.ifname.empty()); + EXPECT_EQ(nhg.type, NEXTHOP_TYPE_INVALID); + EXPECT_EQ(nhg.vrf_id, 0); + EXPECT_EQ(nhg.ifindex, 0); + EXPECT_EQ(nhg.nh_label_type, ZEBRA_LSP_NONE); + + union g_addr zero_g_addr = {}; + EXPECT_TRUE(memcmp(&nhg.gate, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(nhg.bh_type, BLACKHOLE_UNSPEC); + EXPECT_TRUE(memcmp(&nhg.src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&nhg.rmap_src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(nhg.nh_srv6, nullptr); + + cout << "TEST_NextHopGroupFull::multi_nexthop finished." << endl; + + /* Add test for operator = in multi_nexthop case */ + cout << "TEST_NextHopGroupFull::operator = for multi_nexthop started:" << endl; + cout << "[DEBUG] Declare a NextHopGroupFull then assign it with the constructed nhg ..." << endl; + NextHopGroupFull assigned_nhg; + assigned_nhg = nhg; + + /* Check the value of the assigned NextHopGroupFull */ + cout << "[DEBUG] Checking assigned constructed values ..." << endl; + // Check plain values + EXPECT_EQ(assigned_nhg.id, nhg.id); + EXPECT_EQ(assigned_nhg.key, nhg.key); + EXPECT_EQ(assigned_nhg.nhg_flags, nhg.nhg_flags); + // Check nh_grp_full_list status + EXPECT_EQ(assigned_nhg.nh_grp_full_list.size(), nhg.nh_grp_full_list.size()); + for (size_t i = 0; i < nhg.nh_grp_full_list.size(); i++) { + EXPECT_EQ(assigned_nhg.nh_grp_full_list[i].id, nhg.nh_grp_full_list[i].id); + EXPECT_EQ(assigned_nhg.nh_grp_full_list[i].weight, nhg.nh_grp_full_list[i].weight); + EXPECT_EQ(assigned_nhg.nh_grp_full_list[i].num_direct, assigned_nhg.nh_grp_full_list[i].num_direct); + } + // Check depends status + EXPECT_EQ(assigned_nhg.depends.size(), nhg.depends.size()); + for (size_t i = 0; i < nhg.depends.size(); i++) { + EXPECT_EQ(assigned_nhg.depends[i], nhg.depends[i]); + } + // Check dependents status + EXPECT_EQ(assigned_nhg.dependents.size(), nhg.dependents.size()); + for (size_t i = 0; i < nhg.dependents.size(); i++) { + EXPECT_EQ(assigned_nhg.dependents[i], nhg.dependents[i]); + } + // Check other unused values + cout << "[DEBUG] Checking assigned default values ..." << endl; + EXPECT_EQ(assigned_nhg.weight, nhg.weight); + EXPECT_EQ(assigned_nhg.flags, nhg.flags); + EXPECT_TRUE(assigned_nhg.ifname.empty()); + EXPECT_EQ(assigned_nhg.type, NEXTHOP_TYPE_INVALID); + EXPECT_EQ(assigned_nhg.vrf_id, 0); + EXPECT_EQ(assigned_nhg.ifindex, 0); + EXPECT_EQ(assigned_nhg.nh_label_type, ZEBRA_LSP_NONE); + + EXPECT_TRUE(memcmp(&assigned_nhg.gate, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(assigned_nhg.bh_type, BLACKHOLE_UNSPEC); + EXPECT_TRUE(memcmp(&assigned_nhg.src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&assigned_nhg.rmap_src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(assigned_nhg.nh_srv6, nullptr); + + /* Add test for copy constructor in multi-nexthop case */ + cout << "TEST_NextHopGroupFull::copy_constructor for multi_nexthop started:" << endl; + cout << "[DEBUG] Use the declared NextHopGroupFull nhg to trigger the copy constructor ..." << endl; + NextHopGroupFull copy_nhg = nhg; + + /* Check the value of the copied NextHopGroupFull */ + cout << "[DEBUG] Checking copied constructed values ..." << endl; + // Check plain values + EXPECT_EQ(copy_nhg.id, nhg.id); + EXPECT_EQ(copy_nhg.key, nhg.key); + EXPECT_EQ(copy_nhg.nhg_flags, nhg.nhg_flags); + // Check nh_grp_full_list status + EXPECT_EQ(copy_nhg.nh_grp_full_list.size(), nhg.nh_grp_full_list.size()); + for (size_t i = 0; i < nhg.nh_grp_full_list.size(); i++) { + EXPECT_EQ(copy_nhg.nh_grp_full_list[i].id, nhg.nh_grp_full_list[i].id); + EXPECT_EQ(copy_nhg.nh_grp_full_list[i].weight, nhg.nh_grp_full_list[i].weight); + EXPECT_EQ(copy_nhg.nh_grp_full_list[i].num_direct, nhg.nh_grp_full_list[i].num_direct); + } + // Check depends status + EXPECT_EQ(copy_nhg.depends.size(), nhg.depends.size()); + for (size_t i = 0; i < nhg.depends.size(); i++) { + EXPECT_EQ(copy_nhg.depends[i], nhg.depends[i]); + } + // Check dependents status + EXPECT_EQ(copy_nhg.dependents.size(), nhg.dependents.size()); + for (size_t i = 0; i < nhg.dependents.size(); i++) { + EXPECT_EQ(copy_nhg.dependents[i], nhg.dependents[i]); + } + // Check other unused values + cout << "[DEBUG] Checking copied default values ..." << endl; + EXPECT_EQ(copy_nhg.weight, nhg.weight); + EXPECT_EQ(copy_nhg.flags, nhg.flags); + EXPECT_TRUE(copy_nhg.ifname.empty()); + EXPECT_EQ(copy_nhg.type, nhg.type); + EXPECT_EQ(copy_nhg.vrf_id, nhg.vrf_id); + EXPECT_EQ(copy_nhg.ifindex, nhg.ifindex); + EXPECT_EQ(copy_nhg.nh_label_type, nhg.nh_label_type); + + EXPECT_TRUE(memcmp(©_nhg.gate, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(copy_nhg.bh_type, nhg.bh_type); + EXPECT_TRUE(memcmp(©_nhg.src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(©_nhg.rmap_src, &zero_g_addr, sizeof(g_addr)) == 0); + EXPECT_EQ(copy_nhg.nh_srv6, nullptr); + + cout << "TEST_NextHopGroupFull::copy_constructor for multi_nexthop finished." << endl; + + /* Add case for operator == and != */ + cout << "TEST_NextHopGroupFull::operator == started:" << endl; + EXPECT_TRUE(copy_nhg == nhg); + cout << "TEST_NextHopGroupFull::operator == finished." << endl; + cout << "TEST_NextHopGroupFull::operator != started:" << endl; + EXPECT_FALSE(copy_nhg != nhg); + cout << "TEST_NextHopGroupFull::operator != finished." << endl; + + // Test Serializing API + string json_str = to_json_string(nhg); + std::cout << "Serialized NHG to JSON str: " << json_str << std::endl; + + NextHopGroupFull out_obj; + bool ret = from_json_string(json_str, out_obj); + std::cout << "Deserialized NHG from JSON str: " << ret << std::endl; + EXPECT_EQ(ret, true); + + cout << "TEST_NextHopGroupFull::multi_nexthop finished." << endl; +} + + +TEST(NextHopGroupFull, singleton) +{ + cout << "TEST_NextHopGroupFull::singleton started: " << endl; + cout << "[DEBUG] Constructing values ..." << endl; + + /* Prepare the parameters */ + uint32_t test_id = 100; + uint32_t test_key = 1234567; + enum nexthop_types_t test_type = NEXTHOP_TYPE_IPV6; + vrf_id_t test_vrf_id = 101; + ifindex_t test_ifindex = 101; + string test_ifname = "eth101"; + vector test_depends = {200, 300, 400}; + vector test_dependents = {500, 600}; + enum lsp_types_t test_nh_label_type = ZEBRA_LSP_NONE; + enum blackhole_type test_bh_type = BLACKHOLE_NULL; + + union g_addr test_gateway = {}; + union g_addr test_src = {}; + union g_addr test_rmap_src = {}; + inet_pton(AF_INET6, "2001:db8::1", &test_gateway.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::2", &test_src.ipv6.s6_addr); + inet_pton(AF_INET6, "2001:db8::3", &test_rmap_src.ipv6.s6_addr); + + uint8_t test_weight = 8; + uint8_t test_flags = 8; + uint32_t test_nhg_flags = 1024; + bool test_has_srv6 = true; + bool test_has_seg6_segs = true; + + // Prepare the segment list + vector test_nh_segs { + {}, {}, {} + }; + inet_pton(AF_INET6, "2001:db8:1::1", test_nh_segs[0].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::2", test_nh_segs[1].s6_addr); + inet_pton(AF_INET6, "2001:db8:1::3", test_nh_segs[2].s6_addr); + + // Prepare seg6_segs + size_t seg6_segs_size = sizeof(struct seg6_seg_stack) + + test_nh_segs.size() * sizeof(struct in6_addr); + struct seg6_seg_stack* test_nh_seg6_segs = + (struct seg6_seg_stack*)malloc(seg6_segs_size); + test_nh_seg6_segs->encap_behavior = SRV6_HEADEND_BEHAVIOR_H_ENCAPS; + test_nh_seg6_segs->num_segs = 3; + memcpy(test_nh_seg6_segs->seg, test_nh_segs.data(), test_nh_segs.size() * sizeof(in6_addr)); + + //Prepare seg6local_flavors_info + struct seg6local_flavors_info test_flv = { + .flv_ops = 100, + .lcblock_len = 20, + .lcnode_func_len = 16 + }; + + // Prepare seg6local_ctx + struct seg6local_context test_seg6local_ctx = {}; + test_seg6local_ctx.table = 100; + test_seg6local_ctx.block_len = 36; + test_seg6local_ctx.node_len = 12; + test_seg6local_ctx.function_len = 20; + test_seg6local_ctx.argument_len = 16; + inet_pton(AF_INET6, "2001:db8:2::1", test_seg6local_ctx.nh6.s6_addr); + memcpy(&test_seg6local_ctx.flv, &test_flv, sizeof(struct seg6local_flavors_info)); + + // Prepare nh_srv6 + struct nexthop_srv6 test_nh_srv6 = {}; + test_nh_srv6.seg6local_action = SEG6_LOCAL_ACTION_END_DT6; + memcpy(&test_nh_srv6.seg6local_ctx, &test_seg6local_ctx, sizeof(struct seg6local_context)); + + /* Call constructor function */ + cout << "[DEBUG] Calling NextHopGroupFull Constructor ..." << endl; + NextHopGroupFull nhg(test_id, test_key, test_type, test_vrf_id, test_ifindex, + test_ifname, test_depends, test_dependents, test_nh_label_type, + test_bh_type, test_gateway, test_src, test_rmap_src, + test_weight, test_flags, test_nhg_flags, test_has_srv6, test_has_seg6_segs, + &test_nh_srv6, test_nh_seg6_segs, test_nh_segs); + + /* Check the value of the constructed NextHopGroupFull */ + cout << "[DEBUG] Checking constructed values ..." << endl; + // Check plain values + EXPECT_EQ(nhg.id, test_id); + EXPECT_EQ(nhg.key, test_key); + EXPECT_EQ(nhg.type, test_type); + EXPECT_EQ(nhg.vrf_id, test_vrf_id); + EXPECT_EQ(nhg.ifindex, test_ifindex); + EXPECT_EQ(nhg.ifname, test_ifname); + EXPECT_EQ(nhg.depends.size(), test_depends.size()); + for (size_t i = 0; i < test_depends.size(); i++) { + EXPECT_EQ(nhg.depends[i], test_depends[i]); + } + EXPECT_EQ(nhg.dependents.size(), test_dependents.size()); + for (size_t i = 0; i < test_dependents.size(); i++) { + EXPECT_EQ(nhg.dependents[i], test_dependents[i]); + } + EXPECT_EQ(nhg.nh_label_type, test_nh_label_type); + EXPECT_EQ(nhg.weight, test_weight); + EXPECT_EQ(nhg.flags, test_flags); + EXPECT_EQ(nhg.nhg_flags, test_nhg_flags); + + // Check address values + EXPECT_TRUE(memcmp(&nhg.gate, &test_gateway, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&nhg.src, &test_src, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&nhg.rmap_src, &test_rmap_src, sizeof(g_addr)) == 0); + + // Check SRv6 info + EXPECT_NE(nhg.nh_srv6, nullptr); + // Check nh_srv6's seg6local_action + EXPECT_EQ(nhg.nh_srv6->seg6local_action, test_nh_srv6.seg6local_action); + // Check nh_srv6's seg6local_context + EXPECT_TRUE(memcmp(&nhg.nh_srv6->seg6local_ctx.nh6, &test_seg6local_ctx.nh6, sizeof(in6_addr)) == 0); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.table, test_nh_srv6.seg6local_ctx.table); + // Check nh_srv6's seg6local_ctx's flv + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.flv.flv_ops, test_nh_srv6.seg6local_ctx.flv.flv_ops); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.flv.lcblock_len, test_nh_srv6.seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len, test_nh_srv6.seg6local_ctx.flv.lcnode_func_len); + // flv end + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.block_len, test_nh_srv6.seg6local_ctx.block_len); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.node_len, test_nh_srv6.seg6local_ctx.node_len); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.function_len, test_nh_srv6.seg6local_ctx.function_len); + EXPECT_EQ(nhg.nh_srv6->seg6local_ctx.argument_len, test_nh_srv6.seg6local_ctx.argument_len); + // Check nh_srv6's seg6_seg_stack + EXPECT_NE(nhg.nh_srv6->seg6_segs, nullptr); + EXPECT_EQ(nhg.nh_srv6->seg6_segs->encap_behavior, test_nh_seg6_segs->encap_behavior); + EXPECT_EQ(nhg.nh_srv6->seg6_segs->num_segs, test_nh_seg6_segs->num_segs); + // Check nh_srv6's seg6_seg_stack's seg list + for (size_t i = 0; i < test_nh_seg6_segs->num_segs; i++) { + if (i < test_nh_segs.size()) { + EXPECT_TRUE(memcmp(&nhg.nh_srv6->seg6_segs->seg[i], &test_nh_segs[i], sizeof(in6_addr)) == 0) + << "Mismatch in segment " << i << endl; + } else { + in6_addr zero = {}; + memset(&zero, 0, sizeof(zero)); + EXPECT_TRUE(memcmp(&nhg.nh_srv6->seg6_segs->seg[i], &zero, sizeof(in6_addr)) == 0); + } + } + // Check other unused values + EXPECT_TRUE(nhg.nh_grp_full_list.empty()); + + // Free the memory allocated dynamically + free(test_nh_seg6_segs); + test_nh_seg6_segs = nullptr; + + cout << "TEST_NextHopGroupFull::singleton finished." << endl; + + /* Add test for operator = in singleton case */ + cout << "TEST_NextHopGroupFull::operator = for singleton started:" << endl; + cout << "[DEBUG] Declare a NextHopGroupFull then assign it with the constructed nhg ..." << endl; + NextHopGroupFull assigned_nhg; + assigned_nhg = nhg; + + /* Check the value of the assigned NextHopGroupFull */ + cout << "[DEBUG] Checking assigned constructed values ..." << endl; + // Check plain values + EXPECT_EQ(assigned_nhg.id, nhg.id); + EXPECT_EQ(assigned_nhg.key, nhg.key); + EXPECT_EQ(assigned_nhg.type, nhg.type); + EXPECT_EQ(assigned_nhg.vrf_id, nhg.vrf_id); + EXPECT_EQ(assigned_nhg.ifindex, nhg.ifindex); + EXPECT_EQ(assigned_nhg.ifname, nhg.ifname); + EXPECT_EQ(assigned_nhg.nh_label_type, nhg.nh_label_type); + EXPECT_EQ(assigned_nhg.flags, nhg.flags); + EXPECT_EQ(assigned_nhg.nhg_flags, nhg.nhg_flags); + + // Check address values + EXPECT_TRUE(memcmp(&assigned_nhg.gate, &nhg.gate, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&assigned_nhg.src, &nhg.src, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(&assigned_nhg.rmap_src, &nhg.rmap_src, sizeof(g_addr)) == 0); + + // Check SRv6 info + EXPECT_NE(assigned_nhg.nh_srv6, nullptr); + // Check nh_srv6's seg6local_action + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_action, nhg.nh_srv6->seg6local_action); + // Check nh_srv6's seg6local_context + EXPECT_TRUE(memcmp(&assigned_nhg.nh_srv6->seg6local_ctx.nh6, + &nhg.nh_srv6->seg6local_ctx.nh6, sizeof(in6_addr)) == 0); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.table, nhg.nh_srv6->seg6local_ctx.table); + // Check nh_srv6's seg6local_ctx's flv + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.flv.flv_ops, nhg.nh_srv6->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.flv.lcblock_len, + nhg.nh_srv6->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len, + nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len); + // flv end + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.block_len, nhg.nh_srv6->seg6local_ctx.block_len); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.node_len, nhg.nh_srv6->seg6local_ctx.node_len); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.function_len, nhg.nh_srv6->seg6local_ctx.function_len); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6local_ctx.argument_len, nhg.nh_srv6->seg6local_ctx.argument_len); + // Check nh_srv6's seg6_seg_stack + EXPECT_NE(assigned_nhg.nh_srv6->seg6_segs, nullptr); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6_segs->encap_behavior, nhg.nh_srv6->seg6_segs->encap_behavior); + EXPECT_EQ(assigned_nhg.nh_srv6->seg6_segs->num_segs, nhg.nh_srv6->seg6_segs->num_segs); + // Check nh_srv6's seg6_seg_stack's seg list + for (size_t i = 0; i < nhg.nh_srv6->seg6_segs->num_segs; i++) { + if (i < nhg.nh_srv6->seg6_segs->num_segs) { + EXPECT_TRUE(memcmp(&assigned_nhg.nh_srv6->seg6_segs->seg[i], + &nhg.nh_srv6->seg6_segs->seg[i], sizeof(in6_addr)) == 0) + << "Mismatch in segment " << i << endl; + } else { + in6_addr zero = {}; + memset(&zero, 0, sizeof(zero)); + EXPECT_TRUE(memcmp(&assigned_nhg.nh_srv6->seg6_segs->seg[i], &zero, sizeof(in6_addr)) == 0); + } + } + // Check other unused values + EXPECT_TRUE(assigned_nhg.nh_grp_full_list.empty()); + + cout << "TEST_NextHopGroupFull::operator = for singleton finished." << endl; + + /* Add test for copy constructor in singleton case */ + cout << "TEST_NextHopGroupFull::copy_constructor for singleton started:" << endl; + cout << "[DEBUG] Use a constructed nhg to trigger the copy constructor ..." << endl; + NextHopGroupFull copy_nhg = nhg; + + /* Check the value of the copied NextHopGroupFull */ + cout << "[DEBUG] Checking copied constructed values ..." << endl; + // Check plain values + EXPECT_EQ(copy_nhg.id, nhg.id); + EXPECT_EQ(copy_nhg.key, nhg.key); + EXPECT_EQ(copy_nhg.type, nhg.type); + EXPECT_EQ(copy_nhg.vrf_id, nhg.vrf_id); + EXPECT_EQ(copy_nhg.ifindex, nhg.ifindex); + EXPECT_EQ(copy_nhg.ifname, nhg.ifname); + EXPECT_EQ(copy_nhg.nh_label_type, nhg.nh_label_type); + EXPECT_EQ(copy_nhg.flags, nhg.flags); + EXPECT_EQ(copy_nhg.nhg_flags, nhg.nhg_flags); + + // Check address values + EXPECT_TRUE(memcmp(©_nhg.gate, &nhg.gate, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(©_nhg.src, &nhg.src, sizeof(g_addr)) == 0); + EXPECT_TRUE(memcmp(©_nhg.rmap_src, &nhg.rmap_src, sizeof(g_addr)) == 0); + + // Check SRv6 info + EXPECT_NE(copy_nhg.nh_srv6, nullptr); + // Check nh_srv6's seg6local_action + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_action, nhg.nh_srv6->seg6local_action); + // Check nh_srv6's seg6local_context + EXPECT_TRUE(memcmp(©_nhg.nh_srv6->seg6local_ctx.nh6, + &nhg.nh_srv6->seg6local_ctx.nh6, sizeof(in6_addr)) == 0); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.table, nhg.nh_srv6->seg6local_ctx.table); + // Check nh_srv6's seg6local_ctx's flv + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.flv.flv_ops, nhg.nh_srv6->seg6local_ctx.flv.flv_ops); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.flv.lcblock_len, + nhg.nh_srv6->seg6local_ctx.flv.lcblock_len); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len, + nhg.nh_srv6->seg6local_ctx.flv.lcnode_func_len); + // flv end + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.block_len, nhg.nh_srv6->seg6local_ctx.block_len); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.node_len, nhg.nh_srv6->seg6local_ctx.node_len); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.function_len, nhg.nh_srv6->seg6local_ctx.function_len); + EXPECT_EQ(copy_nhg.nh_srv6->seg6local_ctx.argument_len, nhg.nh_srv6->seg6local_ctx.argument_len); + // Check nh_srv6's seg6_seg_stack + EXPECT_NE(copy_nhg.nh_srv6->seg6_segs, nullptr); + EXPECT_EQ(copy_nhg.nh_srv6->seg6_segs->encap_behavior, nhg.nh_srv6->seg6_segs->encap_behavior); + EXPECT_EQ(copy_nhg.nh_srv6->seg6_segs->num_segs, nhg.nh_srv6->seg6_segs->num_segs); + // Check nh_srv6's seg6_seg_stack's seg list + for (size_t i = 0; i < nhg.nh_srv6->seg6_segs->num_segs; i++) { + if (i < nhg.nh_srv6->seg6_segs->num_segs) { + EXPECT_TRUE(memcmp(©_nhg.nh_srv6->seg6_segs->seg[i], + &nhg.nh_srv6->seg6_segs->seg[i], sizeof(in6_addr)) == 0) + << "Mismatch in segment " << i << endl; + } else { + in6_addr zero = {}; + memset(&zero, 0, sizeof(zero)); + EXPECT_TRUE(memcmp(©_nhg.nh_srv6->seg6_segs->seg[i], &zero, sizeof(in6_addr)) == 0); + } + } + // Check other unused values + EXPECT_TRUE(copy_nhg.nh_grp_full_list.empty()); + + /* We should confirm the copy_nhg is copied deeply */ + if (copy_nhg.nh_srv6 && nhg.nh_srv6) { + EXPECT_NE(copy_nhg.nh_srv6, nhg.nh_srv6); + if (copy_nhg.nh_srv6->seg6_segs && nhg.nh_srv6->seg6_segs) { + EXPECT_NE(copy_nhg.nh_srv6->seg6_segs, nhg.nh_srv6->seg6_segs); + } + } + + cout << "TEST_NextHopGroupFull::copy_constructor for singleton finished." << endl; + + /* Add test of operator == and != for singleton case */ + cout << "TEST_NextHopGroupFull::operator == for singleton started:" << endl; + EXPECT_TRUE(copy_nhg == nhg); + cout << "TEST_NextHopGroupFull::operator == for singleton finished." << endl; + cout << "TEST_NextHopGroupFull::operator != for singleton started:" << endl; + EXPECT_FALSE(copy_nhg != nhg); + cout << "TEST_NextHopGroupFull::operator != for singleton finished." << endl; + + cout << "TEST_NextHopGroupFull::singleton finished." << endl; +} \ No newline at end of file diff --git a/src/libraries/sonic-fib/use_json_schema.md b/src/libraries/sonic-fib/use_json_schema.md new file mode 100644 index 00000000000..f454473e285 --- /dev/null +++ b/src/libraries/sonic-fib/use_json_schema.md @@ -0,0 +1,21 @@ +# **Using JSON Schema to Define NexthopGroup Information** + +# Purpose of Adopting Data Schema +The primary objective of adopting a data schema is to unambiguously define the data format for the nexthop group used in both Zebra and fpmsyncd. By leveraging this schema, we aim to automatically generate the corresponding C++ class shared by both processes, along with serialization and deserialization logic, ensuring consistent and synchronized data exchange. This approach minimizes errors typically introduced through manual coding and enforces system-wide consistency. + +# Why JSON Schema? +JSON Schema is chosen because it offers a standardized, human-readable, and language-agnostic format for describing data structure, types, and validation rules. This facilitates reliable data validation and seamless code generation across heterogeneous components—specifically Zebra ( C-based) and fpmsyncd (C++), while promoting interoperability. Furthermore, JSON Schema works well with modern development tooling, enabling automated code generation, runtime data validation, and clear, self-documenting interfaces between Zebra and fpmsyncd. + +# Design Philosophy +The JSON schema defines the logical data model and serves as the single source of truth for the message format. However, C++-specific implementation details such as constructors, memory management, unions, and logging—are either explicitly handled in handwritten code or generated via customized render script logic. + +# Code Layout +To support this approach, three new directories have been introduced: + +* schema/ – Contains the JSON schemas that define the data exchanged between Zebra and fpmsyncd. +* templates/ – Holds code generation templates used to produce C++ serialization/deserialization logic from the schemas. + * templates/references - Contains generated codes for debugging and reference only. They would NOT be used in compile. +* scripts/ – Includes render_schema.py, a script that processes the JSON schemas using the templates to generate the final C++ source files. +* c-api/ - existing folder for holding c-api header file and its C wrapping function + * nexthopgroup_capi.h + * nexthopgroup_capi.cpp \ No newline at end of file