user types CHANGE a new installed user type example
It does only validation, so user can work with
all the values just like before.
diff --git a/src/libyang.h.in b/src/libyang.h.in
index 126e769..2fa32fa 100644
--- a/src/libyang.h.in
+++ b/src/libyang.h.in
@@ -481,9 +481,13 @@
* the callback is allowed to store anything in the union. Another example, if there are many strings being created and
* handled, is to store the string length instead having 2 pointers to the same string.
*
+ * Also, it is possible to perform some additional validation of the value except for the standard YANG one. Even if
+ * the value should only be validated, this callback can be defined and used, it will just keep the #lyd_val value
+ * unchanged.
+ *
* @subsection typeplugins User Type Plugins
*
- * There is a simple example user type plugin in `src/user_types` that is not compiled nor installed.
+ * There are simple example user type plugins in `src/user_types`.
*
* - ::lytype_plugin_list - plugin is supposed to provide callbacks for:
* + @link lytype_store_clb storing the value itself @endlink
diff --git a/src/user_types/CMakeLists.txt b/src/user_types/CMakeLists.txt
new file mode 100644
index 0000000..c15d1e9
--- /dev/null
+++ b/src/user_types/CMakeLists.txt
@@ -0,0 +1,8 @@
+macro(USER_TYPE_PLUGIN PLUGIN_NAME SRCS)
+ add_library(${PLUGIN_NAME} SHARED ${SRCS})
+ set_target_properties(${PLUGIN_NAME} PROPERTIES PREFIX "")
+ target_link_libraries(${PLUGIN_NAME} yang)
+ install(TARGETS ${PLUGIN_NAME} DESTINATION ${USER_TYPES_PLUGINS_DIR_MACRO})
+endmacro(USER_TYPE_PLUGIN)
+
+USER_TYPE_PLUGIN(user_date_and_time "user_date_and_time.c")
diff --git a/src/user_types/user_date_and_time.c b/src/user_types/user_date_and_time.c
new file mode 100644
index 0000000..04ac129
--- /dev/null
+++ b/src/user_types/user_date_and_time.c
@@ -0,0 +1,258 @@
+/**
+ * @file user_date_and_time.c
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief Example implementation of an date-and-time as a user type, only validation is performed
+ *
+ * Copyright (c) 2018 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 _XOPEN_SOURCE
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <ctype.h>
+
+#include <libyang/user_types.h>
+
+#ifdef __GNUC__
+# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
+#else
+# define UNUSED(x) UNUSED_ ## x
+#endif
+
+static const char *gmt_offsets[] = {
+ "+00:00",
+ "+00:20",
+ "+00:30",
+ "+01:00",
+ "+01:24",
+ "+01:30",
+ "+02:00",
+ "+02:30",
+ "+03:00",
+ "+03:30",
+ "+04:00",
+ "+04:30",
+ "+04:51",
+ "+05:00",
+ "+05:30",
+ "+05:40",
+ "+05:45",
+ "+06:00",
+ "+06:30",
+ "+07:00",
+ "+07:20",
+ "+07:30",
+ "+08:00",
+ "+08:30",
+ "+08:45",
+ "+09:00",
+ "+09:30",
+ "+09:45",
+ "+10:00",
+ "+10:30",
+ "+11:00",
+ "+11:30",
+ "+12:00",
+ "+12:45",
+ "+13:00",
+ "+13:45",
+ "+14:00",
+ "-00:00",
+ "-00:44",
+ "-01:00",
+ "-02:00",
+ "-02:30",
+ "-03:00",
+ "-03:30",
+ "-04:00",
+ "-04:30",
+ "-05:00",
+ "-06:00",
+ "-07:00",
+ "-08:00",
+ "-08:30",
+ "-09:00",
+ "-09:30",
+ "-10:00",
+ "-10:30",
+ "-11:00",
+ "-12:00",
+};
+
+static int
+date_and_time_store_clb(const char *UNUSED(type_name), const char *value_str, lyd_val *UNUSED(value), char **err_msg)
+{
+ struct tm tm = {0}, tm2;
+ uint32_t i, j, k;
+
+ /* \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})
+ * 2018-03-21T09:11:05(.5)(Z|+02:00) */
+ i = 0;
+
+ /* year */
+ tm.tm_year = atoi(value_str + i);
+ /* if there was some invalid number, it will either be discovered in the loop below or by mktime() */
+ tm.tm_year -= 1900;
+ for (j = i + 4; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if (value_str[i] != '-') {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+
+ /* month */
+ tm.tm_mon = atoi(value_str + i);
+ for (j = i + 2; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if (value_str[i] != '-') {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+
+ /* day */
+ tm.tm_mday = atoi(value_str + i);
+ for (j = i + 2; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if (value_str[i] != 'T') {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", 'T' expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+
+ /* hours */
+ tm.tm_hour = atoi(value_str + i);
+ for (j = i + 2; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if (value_str[i] != ':') {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+
+ /* minutes */
+ tm.tm_min = atoi(value_str + i);
+ for (j = i + 2; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if (value_str[i] != ':') {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+
+ /* seconds */
+ tm.tm_sec = atoi(value_str + i);
+ for (j = i + 2; i < j; ++i) {
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ }
+ if ((value_str[i] != '.') && (value_str[i] != 'Z') && (value_str[i] != '+') && (value_str[i] != '-')) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '.', 'Z', '+', or '-' expected.",
+ value_str[i], i, value_str);
+ return 1;
+ }
+
+ /* validate using mktime() */
+ tm2 = tm;
+ if (mktime(&tm) == -1) {
+ asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", value_str, strerror(errno));
+ return 1;
+ }
+ /* we now have correctly filled the remaining values, use them */
+ memcpy(((char *)&tm2) + (6 * sizeof(int)), ((char *)&tm) + (6 * sizeof(int)), sizeof(struct tm) - (6 * sizeof(int)));
+ /* back it up again */
+ tm = tm2;
+ /* let mktime() correct date & time with having the other values correct now */
+ if (mktime(&tm) == -1) {
+ asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", value_str, strerror(errno));
+ return 1;
+ }
+ /* detect changes in the filled values */
+ if (memcmp(&tm, &tm2, 6 * sizeof(int))) {
+ asprintf(err_msg, "Checking date-and-time value \"%s\" failed, canonical date and time is \"%04d-%02d-%02dT%02d:%02d:%02d\".",
+ value_str, tm.tm_year + 1900, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ return 1;
+ }
+
+ /* tenth of a second */
+ if (value_str[i] == '.') {
+ ++i;
+ if (!isdigit(value_str[i])) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", value_str[i], i, value_str);
+ return 1;
+ }
+ ++i;
+ }
+
+ switch (value_str[i]) {
+ case 'Z':
+ /* done */
+ break;
+ case '+':
+ case '-':
+ /* timezone shift */
+ k = sizeof gmt_offsets / sizeof *gmt_offsets;
+ for (j = 0; j < k ; ++j) {
+ if (!strncmp(value_str + i, gmt_offsets[j], 6)) {
+ break;
+ }
+ }
+ if (j == k) {
+ asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", value_str + i, value_str);
+ return 1;
+ }
+ i += 5;
+ break;
+ default:
+ /* unreachable */
+ return 1;
+ }
+
+ /* no other characters expected */
+ ++i;
+ if (value_str[i]) {
+ asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", no characters expected.", value_str[i], i, value_str);
+ return 1;
+ }
+
+ /* validation succeeded and we do not want to change how it is stored */
+ return 0;
+}
+
+/* Name of this array must match the file name! */
+struct lytype_plugin_list user_date_and_time[] = {
+ {"ietf-yang-types", "2013-07-15", "date-and-time", date_and_time_store_clb, NULL},
+ {NULL, NULL, NULL, NULL, NULL} /* terminating item */
+};
diff --git a/src/user_types/user_ipv4.c b/src/user_types/user_ipv4.c
index 891a6b4..367597f 100644
--- a/src/user_types/user_ipv4.c
+++ b/src/user_types/user_ipv4.c
@@ -1,7 +1,7 @@
/**
- * @file user_ipv4.h
+ * @file user_ipv4.c
* @author Michal Vasko <mvasko@cesnet.cz>
- * @brief example implementation of an ipv4-address as a user type
+ * @brief Example implementation of an ipv4-address as a user type
*
* Copyright (c) 2018 CESNET, z.s.p.o.
*