Commit 3b078ce1f9 for frr
commit 3b078ce1f9cf830c71514428a9955d7bb220caf0
Author: Grigorii Solovev <gs1571@gmail.com>
Date: Fri Oct 9 11:33:20 2026 +0300
tests: cover SRv6 SID notification decoder boundaries
Exercise the existing decoder with caller-owned locator buffers. Check
long-to-short replacements, NUL termination, independent buffers and
decoded SID fields.
Cover empty names, buffer-size limits, ignored locator output and every
truncation of the mandatory notification fields. Verify complete stream
consumption and successful decoding after an invalid notification.
Signed-off-by: Grigorii Solovev <gs1571@gmail.com>
diff --git a/tests/lib/subdir.am b/tests/lib/subdir.am
index 3b2796e6d2..0a96bf07c0 100644
--- a/tests/lib/subdir.am
+++ b/tests/lib/subdir.am
@@ -403,6 +403,14 @@ tests_lib_test_stream_expand_SOURCES = tests/lib/test_stream_expand.c
EXTRA_DIST += tests/lib/test_stream_expand.py
+check_PROGRAMS += tests/lib/test_srv6_sid_notify
+tests_lib_test_srv6_sid_notify_CFLAGS = $(TESTS_CFLAGS)
+tests_lib_test_srv6_sid_notify_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_lib_test_srv6_sid_notify_LDADD = $(ALL_TESTS_LDADD)
+tests_lib_test_srv6_sid_notify_SOURCES = tests/lib/test_srv6_sid_notify.c
+EXTRA_DIST += tests/lib/test_srv6_sid_notify.py
+
+
check_PROGRAMS += tests/lib/test_table
tests_lib_test_table_CFLAGS = $(TESTS_CFLAGS)
tests_lib_test_table_CPPFLAGS = $(TESTS_CPPFLAGS)
diff --git a/tests/lib/test_srv6_sid_notify.c b/tests/lib/test_srv6_sid_notify.c
new file mode 100644
index 0000000000..71a997561d
--- /dev/null
+++ b/tests/lib/test_srv6_sid_notify.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* SRv6 SID notification decoder: caller buffers and stream boundaries.
+ *
+ * Copyright (C) 2026 Grigorii Solovev
+ */
+
+#include <zebra.h>
+
+#include "stream.h"
+#include "zclient.h"
+
+static struct stream *notification(const char *name, size_t len)
+{
+ struct stream *s = stream_new(1024);
+ struct srv6_sid_ctx ctx = {0};
+ struct in6_addr sid;
+ enum zapi_srv6_sid_notify note = ZAPI_SRV6_SID_ALLOCATED;
+
+ ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_X;
+ ctx.ifindex = 42;
+ assert(inet_pton(AF_INET6, "2001:db8::1", &sid) == 1);
+ stream_put(s, ¬e, sizeof(note));
+ stream_put(s, &ctx, sizeof(ctx));
+ stream_put(s, &sid, sizeof(sid));
+ stream_putl(s, 0xe000);
+ stream_putl(s, 0x12345678);
+ stream_putw(s, len);
+ stream_put(s, name, len);
+ return s;
+}
+
+static bool decode(struct stream *s, char *name, size_t size)
+{
+ struct srv6_sid_ctx ctx;
+ struct in6_addr sid, expected;
+ enum zapi_srv6_sid_notify note;
+ uint32_t func, wide_func;
+ bool success;
+
+ success = zapi_srv6_sid_notify_decode(s, &ctx, &sid, &func,
+ &wide_func, ¬e, name, size);
+ if (success) {
+ assert(ctx.behavior == ZEBRA_SEG6_LOCAL_ACTION_END_X);
+ assert(ctx.ifindex == 42);
+ assert(inet_pton(AF_INET6, "2001:db8::1", &expected) == 1);
+ assert(IPV6_ADDR_SAME(&sid, &expected));
+ assert(func == 0xe000 && wide_func == 0x12345678);
+ assert(note == ZAPI_SRV6_SID_ALLOCATED);
+ assert(STREAM_READABLE(s) == 0);
+ }
+ return success;
+}
+
+static void test_names(void)
+{
+ static const char *const names[] = {"CLASSIC_LONG", "USID", "USID", "CLASSIC_LONG", ""};
+ char name[SRV6_LOCNAME_SIZE], second[SRV6_LOCNAME_SIZE];
+ struct stream *s;
+
+ memset(name, 0xa5, sizeof(name));
+ memset(second, 0xa5, sizeof(second));
+ for (size_t i = 0; i < array_size(names); i++) {
+ s = notification(names[i], strlen(names[i]));
+ assert(decode(s, name, sizeof(name)));
+ assert(!memcmp(name, names[i], strlen(names[i]) + 1));
+ stream_free(s);
+ }
+ printf("Names decode without stale suffixes.\n");
+
+ s = notification("FIRST", 5);
+ assert(decode(s, name, sizeof(name)));
+ stream_free(s);
+ s = notification("SECOND", 6);
+ assert(decode(s, second, sizeof(second)));
+ assert(!strcmp(name, "FIRST") && !strcmp(second, "SECOND"));
+ stream_free(s);
+ printf("Caller buffers retain independent names.\n");
+}
+
+static void test_boundaries(void)
+{
+ struct {
+ uint8_t before;
+ char name[SRV6_LOCNAME_SIZE];
+ uint8_t after;
+ } guarded;
+ char payload[SRV6_LOCNAME_SIZE + 1];
+ struct stream *s;
+
+ memset(payload, 'A', sizeof(payload));
+ memset(&guarded, 0xa5, sizeof(guarded));
+ s = notification(payload, SRV6_LOCNAME_SIZE - 1);
+ assert(decode(s, guarded.name, sizeof(guarded.name)));
+ assert(!memcmp(guarded.name, payload, SRV6_LOCNAME_SIZE - 1));
+ assert(guarded.name[SRV6_LOCNAME_SIZE - 1] == '\0');
+ assert(guarded.before == 0xa5 && guarded.after == 0xa5);
+ stream_free(s);
+
+ for (size_t len = SRV6_LOCNAME_SIZE; len <= sizeof(payload); len++) {
+ s = notification(payload, len);
+ assert(!decode(s, guarded.name, sizeof(guarded.name)));
+ assert(guarded.before == 0xa5 && guarded.after == 0xa5);
+ stream_free(s);
+ }
+ for (size_t size = 0; size <= 4; size += 4) {
+ s = notification("MAIN", 4);
+ assert(!decode(s, guarded.name, size));
+ assert(guarded.before == 0xa5 && guarded.after == 0xa5);
+ stream_free(s);
+ }
+ s = notification("", 0);
+ assert(!decode(s, guarded.name, 0));
+ stream_free(s);
+ s = notification("", 0);
+ assert(decode(s, guarded.name, 1));
+ assert(guarded.name[0] == '\0');
+ stream_free(s);
+ printf("Buffer boundaries reserve space for the terminator.\n");
+}
+
+static void test_skip_and_truncation(void)
+{
+ char name[SRV6_LOCNAME_SIZE];
+ struct stream *s;
+ size_t end;
+
+ for (size_t len = 0; len <= 4; len += 4) {
+ s = notification("MAIN", len);
+ assert(decode(s, NULL, 0));
+ stream_free(s);
+ }
+ printf("Unused names are consumed.\n");
+
+ s = notification("MAIN", 4);
+ end = stream_get_endp(s);
+ stream_free(s);
+ for (size_t len = 0; len < end; len++) {
+ for (unsigned int want_name = 0; want_name < 2; want_name++) {
+ s = notification("MAIN", 4);
+ stream_set_endp(s, len);
+ assert(!decode(s, want_name ? name : NULL,
+ want_name ? sizeof(name) : 0));
+ stream_free(s);
+ }
+ }
+ s = notification("RECOVERED", 9);
+ assert(decode(s, name, sizeof(name)));
+ assert(!strcmp(name, "RECOVERED"));
+ stream_free(s);
+ printf("Truncated notifications fail and do not poison later decodes.\n");
+}
+
+int main(void)
+{
+ test_names();
+ test_boundaries();
+ test_skip_and_truncation();
+ return 0;
+}
diff --git a/tests/lib/test_srv6_sid_notify.py b/tests/lib/test_srv6_sid_notify.py
new file mode 100644
index 0000000000..93835292c5
--- /dev/null
+++ b/tests/lib/test_srv6_sid_notify.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+import frrtest
+
+
+class TestSrv6SidNotify(frrtest.TestMultiOut):
+ program = "./test_srv6_sid_notify"
+
+
+TestSrv6SidNotify.onesimple("Names decode without stale suffixes.")
+TestSrv6SidNotify.onesimple("Caller buffers retain independent names.")
+TestSrv6SidNotify.onesimple("Buffer boundaries reserve space for the terminator.")
+TestSrv6SidNotify.onesimple("Unused names are consumed.")
+TestSrv6SidNotify.onesimple("Truncated notifications fail and do not poison later decodes.")