plugin types FEATURE ietf-inet-types plugins
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}
+};