plugin types FEATURE ietf-inet-types plugins
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0567c34..3445f8d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -138,6 +138,8 @@
     src/plugins_types/leafref.c
     src/plugins_types/string.c
     src/plugins_types/union.c
+    src/plugins_types/ip_address.c
+    src/plugins_types/ip_prefix.c
     src/plugins_exts.c
     src/plugins_exts/metadata.c
     src/plugins_exts/nacm.c
diff --git a/src/plugins.c b/src/plugins.c
index 3b96c3f..dc0bba0 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -51,6 +51,9 @@
 extern const struct lyplg_type_record plugins_string[];
 extern const struct lyplg_type_record plugins_union[];
 
+extern const struct lyplg_type_record plugins_ip_address[];
+extern const struct lyplg_type_record plugins_ip_prefix[];
+
 /*
  * internal extension plugins records
  */
@@ -406,6 +409,9 @@
     LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
     LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
 
+    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ip_address), error);
+    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ip_prefix), error);
+
     /* internal extensions */
     LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
     LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
diff --git a/src/plugins_types/ip_address.c b/src/plugins_types/ip_address.c
new file mode 100644
index 0000000..a5195bd
--- /dev/null
+++ b/src/plugins_types/ip_address.c
@@ -0,0 +1,166 @@
+/**
+ * @file ip_address.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief ietf-inet-types ip-address type plugin.
+ *
+ * Copyright (c) 2019-2021 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include "plugins_types.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "compat.h"
+
+/**
+ * @brief Canonize a valid IPv6 address.
+ *
+ * @param[in] ipv6_addr IPv6 address to canonize.
+ * @param[out] canonical Canonical format of @p ipv6_addr.
+ * @param[out] err Error information on error.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+canonize_ipv6_addr(const char *ipv6_addr, char **canonical, struct ly_err_item **err)
+{
+    char buf[sizeof(struct in6_addr)], *str;
+
+    *canonical = NULL;
+    *err = NULL;
+
+    str = malloc(INET6_ADDRSTRLEN);
+    if (!str) {
+        return LY_EMEM;
+    }
+
+    if (!inet_pton(AF_INET6, ipv6_addr, buf)) {
+        free(str);
+        return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address \"%s\".", ipv6_addr);
+    }
+
+    if (!inet_ntop(AF_INET6, buf, str, INET6_ADDRSTRLEN)) {
+        free(str);
+        return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address (%s).", strerror(errno));
+    }
+
+    *canonical = str;
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Validate, canonize and store value of the ietf-inet-types ip(v4/v6)-address type.
+ * Implementation of the ::lyplg_type_store_clb.
+ */
+static LY_ERR
+lyplg_type_store_ip_address(const struct ly_ctx *ctx, const struct lysc_type *type, const char *value, size_t value_len,
+        uint32_t options, LY_PREFIX_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node,
+        struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err)
+{
+    LY_ERR ret = LY_SUCCESS;
+    const char *ptr;
+    char *ipv6_addr, *result, *tmp;
+
+    /* store as a string */
+    ret = lyplg_type_store_string(ctx, type, value, value_len, options, format, prefix_data, hints, ctx_node,
+            storage, unres, err);
+    if (ret) {
+        return ret;
+    }
+
+    if (strchr(storage->canonical, ':')) {
+        /* canonize IPv6 address */
+        if ((ptr = strchr(storage->canonical, '%'))) {
+            /* there is a zone index */
+            ipv6_addr = strndup(storage->canonical, ptr - storage->canonical);
+        } else {
+            ipv6_addr = (char *)storage->canonical;
+        }
+
+        /* convert to canonical format */
+        ret = canonize_ipv6_addr(ipv6_addr, &result, err);
+        if (ptr) {
+            free(ipv6_addr);
+        }
+        if (ret) {
+            goto cleanup;
+        }
+
+        if (strncmp(storage->canonical, result, strlen(result))) {
+            /* some conversion took place */
+            if (ptr) {
+                /* concatenate the zone, if any */
+                tmp = result;
+                if (asprintf(&result, "%s%s", tmp, ptr) == -1) {
+                    free(tmp);
+                    ret = LY_EMEM;
+                    goto cleanup;
+                }
+                free(tmp);
+            }
+
+            /* update the value */
+            lydict_remove(ctx, storage->canonical);
+            lydict_insert_zc(ctx, result, &storage->canonical);
+        } else {
+            free(result);
+        }
+    }
+
+cleanup:
+    if (ret) {
+        type->plugin->free(ctx, storage);
+    }
+    return ret;
+}
+
+/**
+ * @brief Plugin information for ip-address type implementation.
+ *
+ * Note that external plugins are supposed to use:
+ *
+ *   LYPLG_TYPES = {
+ */
+const struct lyplg_type_record plugins_ip_address[] = {
+    {
+        .module = "ietf-inet-types",
+        .revision = "2013-07-15",
+        .name = "ipv6-address",
+
+        .plugin.id = "libyang 2 - ipv6-address, version 1",
+        .plugin.store = lyplg_type_store_ip_address,
+        .plugin.validate = NULL,
+        .plugin.compare = lyplg_type_compare_simple,
+        .plugin.print = lyplg_type_print_simple,
+        .plugin.duplicate = lyplg_type_dup_simple,
+        .plugin.free = lyplg_type_free_simple
+    },
+    {
+        .module = "ietf-inet-types",
+        .revision = "2013-07-15",
+        .name = "ipv6-address-no-zone",
+
+        .plugin.id = "libyang 2 - ipv6-address-no-zone, version 1",
+        .plugin.store = lyplg_type_store_ip_address,
+        .plugin.validate = NULL,
+        .plugin.compare = lyplg_type_compare_simple,
+        .plugin.print = lyplg_type_print_simple,
+        .plugin.duplicate = lyplg_type_dup_simple,
+        .plugin.free = lyplg_type_free_simple
+    },
+    {0}
+};
diff --git a/src/plugins_types/ip_prefix.c b/src/plugins_types/ip_prefix.c
new file mode 100644
index 0000000..975be51
--- /dev/null
+++ b/src/plugins_types/ip_prefix.c
@@ -0,0 +1,298 @@
+/**
+ * @file ip_prefix.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief ietf-inet-types ip-prefix type plugin.
+ *
+ * Copyright (c) 2019-2021 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include "plugins_types.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libyang.h"
+
+#include "common.h"
+#include "compat.h"
+
+/**
+ * @brief Canonize an ipv4-prefix value.
+ *
+ * @param[in] ipv4_prefix Prefix to canonize.
+ * @param[out] canonical Canonical format of @p ipv4_prefix.
+ * @param[out] err Error structure on error.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+canonize_ipv4_prefix(const char *ipv4_prefix, char **canonical, struct ly_err_item **err)
+{
+    LY_ERR ret;
+    const char *pref_str;
+    char *ptr, *result = NULL;
+    uint32_t pref, addr_bin, i, mask;
+
+    *canonical = NULL;
+    *err = NULL;
+
+    pref_str = strchr(ipv4_prefix, '/');
+    if (!pref_str) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid IPv4 prefix \"%s\".", ipv4_prefix);
+        goto error;
+    }
+
+    /* learn prefix */
+    pref = strtoul(pref_str + 1, &ptr, 10);
+    if (ptr[0] || (pref > 32)) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid IPv4 prefix \"%s\".", ipv4_prefix);
+        goto error;
+    }
+
+    result = malloc(INET_ADDRSTRLEN + 3);
+    if (!result) {
+        ret = LY_EMEM;
+        goto error;
+    }
+
+    /* copy just the network prefix */
+    strncpy(result, ipv4_prefix, pref_str - ipv4_prefix);
+    result[pref_str - ipv4_prefix] = '\0';
+
+    /* convert it to binary form */
+    if (inet_pton(AF_INET, result, (void *)&addr_bin) != 1) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv4 address \"%s\".", result);
+        goto error;
+    }
+
+    /* zero host bits */
+    mask = 0;
+    for (i = 0; i < 32; ++i) {
+        mask <<= 1;
+        if (pref > i) {
+            mask |= 1;
+        }
+    }
+    mask = htonl(mask);
+    addr_bin &= mask;
+
+    /* convert back to string */
+    if (!inet_ntop(AF_INET, (void *)&addr_bin, result, INET_ADDRSTRLEN)) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv4 address (%s).", strerror(errno));
+        goto error;
+    }
+
+    /* add the prefix */
+    strcat(result, pref_str);
+
+    *canonical = result;
+    return LY_SUCCESS;
+
+error:
+    free(result);
+    return ret;
+}
+
+/**
+ * @brief Canonize an ipv6-prefix value.
+ *
+ * @param[in] ipv6_prefix Prefix to canonize.
+ * @param[out] canonical Canonical format of @p ipv6_prefix.
+ * @param[out] err Error structure on error.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+canonize_ipv6_prefix(const char *ipv4_prefix, char **canonical, struct ly_err_item **err)
+{
+    LY_ERR ret;
+    const char *pref_str;
+    char *ptr, *result = NULL;
+    unsigned long int pref, i, j;
+
+    union {
+        struct in6_addr s;
+        uint32_t a[4];
+    } addr_bin;
+    uint32_t mask;
+
+    *canonical = NULL;
+    *err = NULL;
+
+    pref_str = strchr(ipv4_prefix, '/');
+    if (!pref_str) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid IPv6 prefix \"%s\".", ipv4_prefix);
+        goto error;
+    }
+
+    /* learn prefix */
+    pref = strtoul(pref_str + 1, &ptr, 10);
+    if (ptr[0] || (pref > 128)) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid IPv6 prefix \"%s\".", ipv4_prefix);
+        goto error;
+    }
+
+    result = malloc(INET6_ADDRSTRLEN + 4);
+    if (!result) {
+        ret = LY_EMEM;
+        goto error;
+    }
+
+    /* copy just the network prefix */
+    strncpy(result, ipv4_prefix, pref_str - ipv4_prefix);
+    result[pref_str - ipv4_prefix] = '\0';
+
+    /* convert it to binary form */
+    if (inet_pton(AF_INET6, result, (void *)&addr_bin.s) != 1) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address \"%s\".", result);
+        goto error;
+    }
+
+    /* zero host bits */
+    for (i = 0; i < 4; ++i) {
+        mask = 0;
+        for (j = 0; j < 32; ++j) {
+            mask <<= 1;
+            if (pref > (i * 32) + j) {
+                mask |= 1;
+            }
+        }
+        mask = htonl(mask);
+        addr_bin.a[i] &= mask;
+    }
+
+    /* convert back to string */
+    if (!inet_ntop(AF_INET6, (void *)&addr_bin.s, result, INET6_ADDRSTRLEN)) {
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address (%s).", strerror(errno));
+        goto error;
+    }
+
+    /* add the prefix */
+    strcat(result, pref_str);
+
+    *canonical = result;
+    return LY_SUCCESS;
+
+error:
+    free(result);
+    return ret;
+}
+
+/**
+ * @brief Validate, canonize and store value of the ietf-inet-types ipv4-prefix type.
+ * Implementation of the ::lyplg_type_store_clb.
+ */
+static LY_ERR
+lyplg_type_store_ipv4_prefix(const struct ly_ctx *ctx, const struct lysc_type *type, const char *value, size_t value_len,
+        uint32_t options, LY_PREFIX_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node,
+        struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err)
+{
+    LY_ERR ret = LY_SUCCESS;
+    char *canonical;
+
+    /* store as a string */
+    ret = lyplg_type_store_string(ctx, type, value, value_len, options, format, prefix_data, hints, ctx_node,
+            storage, unres, err);
+    LY_CHECK_RET(ret);
+
+    /* canonize */
+    ret = canonize_ipv4_prefix(storage->canonical, &canonical, err);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    if (strcmp(canonical, storage->canonical)) {
+        /* some conversion took place, update the value */
+        lydict_remove(ctx, storage->canonical);
+        lydict_insert_zc(ctx, canonical, &storage->canonical);
+    } else {
+        free(canonical);
+    }
+
+cleanup:
+    if (ret) {
+        type->plugin->free(ctx, storage);
+    }
+    return ret;
+}
+
+/**
+ * @brief Validate, canonize and store value of the ietf-inet-types ipv6-prefix type.
+ * Implementation of the ::lyplg_type_store_clb.
+ */
+static LY_ERR
+lyplg_type_store_ipv6_prefix(const struct ly_ctx *ctx, const struct lysc_type *type, const char *value, size_t value_len,
+        uint32_t options, LY_PREFIX_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node,
+        struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err)
+{
+    LY_ERR ret = LY_SUCCESS;
+    char *canonical;
+
+    /* store as a string */
+    ret = lyplg_type_store_string(ctx, type, value, value_len, options, format, prefix_data, hints, ctx_node,
+            storage, unres, err);
+    LY_CHECK_RET(ret);
+
+    /* canonize */
+    ret = canonize_ipv6_prefix(storage->canonical, &canonical, err);
+    LY_CHECK_GOTO(ret, cleanup);
+
+    if (strcmp(canonical, storage->canonical)) {
+        /* some conversion took place, update the value */
+        lydict_remove(ctx, storage->canonical);
+        lydict_insert_zc(ctx, canonical, &storage->canonical);
+    } else {
+        free(canonical);
+    }
+
+cleanup:
+    if (ret) {
+        type->plugin->free(ctx, storage);
+    }
+    return ret;
+}
+
+/**
+ * @brief Plugin information for ip-prefix type implementation.
+ *
+ * Note that external plugins are supposed to use:
+ *
+ *   LYPLG_TYPES = {
+ */
+const struct lyplg_type_record plugins_ip_prefix[] = {
+    {
+        .module = "ietf-inet-types",
+        .revision = "2013-07-15",
+        .name = "ipv4-prefix",
+
+        .plugin.id = "libyang 2 - ipv4-prefix, version 1",
+        .plugin.store = lyplg_type_store_ipv4_prefix,
+        .plugin.validate = NULL,
+        .plugin.compare = lyplg_type_compare_simple,
+        .plugin.print = lyplg_type_print_simple,
+        .plugin.duplicate = lyplg_type_dup_simple,
+        .plugin.free = lyplg_type_free_simple
+    },
+    {
+        .module = "ietf-inet-types",
+        .revision = "2013-07-15",
+        .name = "ipv6-prefix",
+
+        .plugin.id = "libyang 2 - ipv6-prefix, version 1",
+        .plugin.store = lyplg_type_store_ipv6_prefix,
+        .plugin.validate = NULL,
+        .plugin.compare = lyplg_type_compare_simple,
+        .plugin.print = lyplg_type_print_simple,
+        .plugin.duplicate = lyplg_type_dup_simple,
+        .plugin.free = lyplg_type_free_simple
+    },
+    {0}
+};
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index e6ea88e..b9fad5f 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -17,6 +17,7 @@
 ly_add_utest(NAME int8   SOURCES types/int8.c)
 ly_add_utest(NAME string SOURCES types/string.c)
 ly_add_utest(NAME bits   SOURCES types/bits.c)
+ly_add_utest(NAME inet_types   SOURCES types/inet_types.c)
 
 ly_add_utest(NAME range  SOURCES restriction/test_range.c)
 ly_add_utest(NAME pattern  SOURCES restriction/test_pattern.c)
diff --git a/tests/utests/types/inet_types.c b/tests/utests/types/inet_types.c
new file mode 100644
index 0000000..1b140e0
--- /dev/null
+++ b/tests/utests/types/inet_types.c
@@ -0,0 +1,108 @@
+/**
+ * @file inet_types.c
+ * @author Michal Vaško <mvasko@cesnet.cz>
+ * @brief test for ietf-inet-types values
+ *
+ * Copyright (c) 2021 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+/* INCLUDE UTEST HEADER */
+#define  _UTEST_MAIN_
+#include "../utests.h"
+
+/* LOCAL INCLUDE HEADERS */
+#include "libyang.h"
+
+#define MODULE_CREATE_YIN(MOD_NAME, NODES) \
+    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
+    "<module name=\"" MOD_NAME "\"\n" \
+    "        xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\"\n" \
+    "        xmlns:pref=\"urn:tests:" MOD_NAME "\">\n" \
+    "  <yang-version value=\"1.1\"/>\n" \
+    "  <namespace uri=\"urn:tests:" MOD_NAME "\"/>\n" \
+    "  <prefix value=\"pref\"/>\n" \
+    NODES \
+    "</module>\n"
+
+#define MODULE_CREATE_YANG(MOD_NAME, NODES) \
+    "module " MOD_NAME " {\n" \
+    "  yang-version 1.1;\n" \
+    "  namespace \"urn:tests:" MOD_NAME "\";\n" \
+    "  prefix pref;\n" \
+    "  import ietf-inet-types {\n" \
+    "    prefix inet;\n" \
+    "  }\n" \
+    NODES \
+    "}\n"
+
+#define TEST_SUCCESS_XML(MOD_NAME, NODE_NAME, DATA, TYPE, ...) \
+    { \
+        struct lyd_node *tree; \
+        const char *data = "<" NODE_NAME " xmlns=\"urn:tests:" MOD_NAME "\">" DATA "</" NODE_NAME ">"; \
+        CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); \
+        CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, TYPE, ## __VA_ARGS__); \
+        lyd_free_all(tree); \
+    }
+
+static void
+test_data_xml(void **state)
+{
+    const char *schema;
+
+    /* xml test */
+    schema = MODULE_CREATE_YANG("a",
+            "leaf l {type inet:ip-address;}"
+            "leaf l2 {type inet:ipv6-address;}"
+            "leaf l3 {type inet:ip-address-no-zone;}"
+            "leaf l4 {type inet:ipv6-address-no-zone;}"
+            "leaf l5 {type inet:ip-prefix;}"
+            "leaf l6 {type inet:ipv4-prefix;}"
+            "leaf l7 {type inet:ipv6-prefix;}");
+    UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+    /* ip-address */
+    TEST_SUCCESS_XML("a", "l", "192.168.0.1", UNION, "192.168.0.1", STRING, "192.168.0.1");
+    TEST_SUCCESS_XML("a", "l", "192.168.0.1%12", UNION, "192.168.0.1%12", STRING, "192.168.0.1%12");
+    TEST_SUCCESS_XML("a", "l", "2008:15:0:0:0:0:feAC:1", UNION, "2008:15::feac:1", STRING, "2008:15::feac:1");
+
+    /* ipv6-address */
+    TEST_SUCCESS_XML("a", "l2", "FAAC:21:011:Da85::87:daaF%1", STRING, "faac:21:11:da85::87:daaf%1");
+
+    /* ip-address-no-zone */
+    TEST_SUCCESS_XML("a", "l3", "127.0.0.1", UNION, "127.0.0.1", STRING, "127.0.0.1");
+    TEST_SUCCESS_XML("a", "l3", "0:00:000:0000:000:00:0:1", UNION, "::1", STRING, "::1");
+
+    /* ipv6-address-no-zone */
+    TEST_SUCCESS_XML("a", "l4", "A:B:c:D:e:f:1:0", STRING, "a:b:c:d:e:f:1:0");
+
+    /* ip-prefix */
+    TEST_SUCCESS_XML("a", "l5", "158.1.58.4/1", UNION, "128.0.0.0/1", STRING, "128.0.0.0/1");
+    TEST_SUCCESS_XML("a", "l5", "158.1.58.4/24", UNION, "158.1.58.0/24", STRING, "158.1.58.0/24");
+    TEST_SUCCESS_XML("a", "l5", "2000:A:B:C:D:E:f:a/16", UNION, "2000::/16", STRING, "2000::/16");
+
+    /* ipv4-prefix */
+    TEST_SUCCESS_XML("a", "l6", "0.1.58.4/32", STRING, "0.1.58.4/32");
+    TEST_SUCCESS_XML("a", "l6", "12.1.58.4/8", STRING, "12.0.0.0/8");
+
+    /* ipv6-prefix */
+    TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/112", STRING, "::c:d:e:f:0/112");
+    TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/110", STRING, "::c:d:e:c:0/110");
+    TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/96", STRING, "::c:d:e:0:0/96");
+    TEST_SUCCESS_XML("a", "l7", "::C:D:E:f:a/55", STRING, "::/55");
+}
+
+int
+main(void)
+{
+    const struct CMUnitTest tests[] = {
+        UTEST(test_data_xml),
+    };
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}