1 /**
2 * @file user_yang_types.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief ietf-yang-types typedef validation and conversion to canonical format
5 *
6 * Copyright (c) 2018 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14 #define _GNU_SOURCE
15
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdint.h>
19 #include <errno.h>
20 #include <time.h>
21 #include <ctype.h>
22
23 #include "compat.h"
24 #include "../user_types.h"
25
26 /**
27 * @brief Storage for ID used to check plugin API version compatibility.
28 */
29 LYTYPE_VERSION_CHECK
30
31 #ifdef __GNUC__
32 # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
33 #else
34 # define UNUSED(x) UNUSED_ ## x
35 #endif
36
37 static int
date_and_time_store_clb(struct ly_ctx * UNUSED (ctx),const char * UNUSED (type_name),const char ** value_str,lyd_val * UNUSED (value),char ** err_msg)38 date_and_time_store_clb(struct ly_ctx *UNUSED(ctx), const char *UNUSED(type_name), const char **value_str,
39 lyd_val *UNUSED(value), char **err_msg)
40 {
41 struct tm tm, tm2;
42 uint32_t i, j;
43 const char *val_str = *value_str;
44 int ret;
45
46 /* \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})
47 * 2018-03-21T09:11:05(.55785...)(Z|+02:00) */
48 memset(&tm, 0, sizeof tm);
49 i = 0;
50
51 /* year */
52 tm.tm_year = atoi(val_str + i);
53 /* if there was some invalid number, it will either be discovered in the loop below or by mktime() */
54 tm.tm_year -= 1900;
55 for (j = i + 4; i < j; ++i) {
56 if (!isdigit(val_str[i])) {
57 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
58 goto error;
59 }
60 }
61 if (val_str[i] != '-') {
62 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", val_str[i], i, val_str);
63 goto error;
64 }
65 ++i;
66
67 /* month */
68 tm.tm_mon = atoi(val_str + i);
69 tm.tm_mon -= 1;
70 for (j = i + 2; i < j; ++i) {
71 if (!isdigit(val_str[i])) {
72 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
73 goto error;
74 }
75 }
76 if (val_str[i] != '-') {
77 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", val_str[i], i, val_str);
78 goto error;
79 }
80 ++i;
81
82 /* day */
83 tm.tm_mday = atoi(val_str + i);
84 for (j = i + 2; i < j; ++i) {
85 if (!isdigit(val_str[i])) {
86 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
87 goto error;
88 }
89 }
90 if (val_str[i] != 'T') {
91 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", 'T' expected.", val_str[i], i, val_str);
92 goto error;
93 }
94 ++i;
95
96 /* hours */
97 tm.tm_hour = atoi(val_str + i);
98 for (j = i + 2; i < j; ++i) {
99 if (!isdigit(val_str[i])) {
100 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
101 goto error;
102 }
103 }
104 if (val_str[i] != ':') {
105 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", val_str[i], i, val_str);
106 goto error;
107 }
108 ++i;
109
110 /* minutes */
111 tm.tm_min = atoi(val_str + i);
112 for (j = i + 2; i < j; ++i) {
113 if (!isdigit(val_str[i])) {
114 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
115 goto error;
116 }
117 }
118 if (val_str[i] != ':') {
119 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", val_str[i], i, val_str);
120 goto error;
121 }
122 ++i;
123
124 /* seconds */
125 tm.tm_sec = atoi(val_str + i);
126 for (j = i + 2; i < j; ++i) {
127 if (!isdigit(val_str[i])) {
128 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
129 goto error;
130 }
131 }
132 if ((val_str[i] != '.') && (val_str[i] != 'Z') && (val_str[i] != '+') && (val_str[i] != '-')) {
133 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '.', 'Z', '+', or '-' expected.",
134 val_str[i], i, val_str);
135 goto error;
136 }
137
138 /* validate using mktime() */
139 tm2 = tm;
140 errno = 0;
141 mktime(&tm);
142 /* ENOENT is set when "/etc/localtime" is missing but the function suceeeds */
143 if (errno && (errno != ENOENT)) {
144 ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", val_str, strerror(errno));
145 goto error;
146 }
147 /* we now have correctly filled the remaining values, use them */
148 memcpy(((char *)&tm2) + (6 * sizeof(int)), ((char *)&tm) + (6 * sizeof(int)), sizeof(struct tm) - (6 * sizeof(int)));
149 /* back it up again */
150 tm = tm2;
151 /* let mktime() correct date & time with having the other values correct now */
152 errno = 0;
153 mktime(&tm);
154 if (errno && (errno != ENOENT)) {
155 ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", val_str, strerror(errno));
156 goto error;
157 }
158 /* detect changes in the filled values */
159 if (memcmp(&tm, &tm2, 6 * sizeof(int))) {
160 ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed, canonical date and time is \"%04d-%02d-%02dT%02d:%02d:%02d\".",
161 val_str, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
162 goto error;
163 }
164
165 /* tenth of a second */
166 if (val_str[i] == '.') {
167 ++i;
168 if (!isdigit(val_str[i])) {
169 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
170 goto error;
171 }
172 do {
173 ++i;
174 } while (isdigit(val_str[i]));
175 }
176
177 switch (val_str[i]) {
178 case 'Z':
179 /* done */
180 break;
181 case '+':
182 case '-':
183 /* timezone shift */
184 if ((val_str[i + 1] < '0') || (val_str[i + 1] > '2')) {
185 ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
186 goto error;
187 }
188 if ((val_str[i + 2] < '0') || ((val_str[i + 1] == '2') && (val_str[i + 2] > '3')) || (val_str[i + 2] > '9')) {
189 ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
190 goto error;
191 }
192
193 if (val_str[i + 3] != ':') {
194 ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
195 goto error;
196 }
197
198 if ((val_str[i + 4] < '0') || (val_str[i + 4] > '5')) {
199 ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
200 goto error;
201 }
202 if ((val_str[i + 5] < '0') || (val_str[i + 5] > '9')) {
203 ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
204 goto error;
205 }
206
207 i += 5;
208 break;
209 default:
210 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", 'Z', '+', or '-' expected.", val_str[i], i, val_str);
211 goto error;
212 }
213
214 /* no other characters expected */
215 ++i;
216 if (val_str[i]) {
217 ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", no characters expected.", val_str[i], i, val_str);
218 goto error;
219 }
220
221 /* validation succeeded and we do not want to change how it is stored */
222 return 0;
223
224 error:
225 if (ret == -1) {
226 *err_msg = NULL;
227 }
228 return 1;
229 }
230
231 static int
hex_string_store_clb(struct ly_ctx * ctx,const char * UNUSED (type_name),const char ** value_str,lyd_val * value,char ** err_msg)232 hex_string_store_clb(struct ly_ctx *ctx, const char *UNUSED(type_name), const char **value_str, lyd_val *value, char **err_msg)
233 {
234 char *str;
235 uint32_t i, len;
236
237 str = strdup(*value_str);
238 if (!str) {
239 /* we can hardly allocate an error message */
240 *err_msg = NULL;
241 return 1;
242 }
243
244 len = strlen(str);
245 for (i = 0; i < len; ++i) {
246 if ((str[i] >= 'A') && (str[i] <= 'Z')) {
247 /* make it lowercase (canonical format) */
248 str[i] += 32;
249 }
250 }
251
252 /* update the value correctly */
253 lydict_remove(ctx, *value_str);
254 *value_str = lydict_insert_zc(ctx, str);
255 value->string = *value_str;
256 return 0;
257 }
258
259 /* Name of this array must match the file name! */
260 struct lytype_plugin_list user_yang_types[] = {
261 {"ietf-yang-types", "2013-07-15", "date-and-time", date_and_time_store_clb, NULL},
262 {"ietf-yang-types", "2013-07-15", "phys-address", hex_string_store_clb, NULL},
263 {"ietf-yang-types", "2013-07-15", "mac-address", hex_string_store_clb, NULL},
264 {"ietf-yang-types", "2013-07-15", "hex-string", hex_string_store_clb, NULL},
265 {"ietf-yang-types", "2013-07-15", "uuid", hex_string_store_clb, NULL},
266 {NULL, NULL, NULL, NULL, NULL} /* terminating item */
267 };
268