1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/cdefs.h>
31 #include <sys/param.h>
32 
33 #ifdef _KERNEL
34 
35 #include <sys/systm.h>
36 
37 #else /* !_KERNEL */
38 
39 #include <errno.h>
40 #include <string.h>
41 
42 #endif /* _KERNEL */
43 
44 #include "bhnd_nvram_private.h"
45 #include "bhnd_nvram_valuevar.h"
46 
47 /**
48  * Validate the alignment of a value of @p type.
49  *
50  * @param	inp	The value data.
51  * @param	ilen	The value length, in bytes.
52  * @param	itype	The value type.
53  *
54  * @retval 0		success
55  * @retval EFTYPE	if @p type is not an array type, and @p len is not
56  *			equal to the size of a single element of @p type.
57  * @retval EFAULT	if @p data is not correctly aligned to the required
58  *			host alignment.
59  * @retval EFAULT	if @p len is not aligned to the @p type width.
60  */
61 int
62 bhnd_nvram_value_check_aligned(const void *inp, size_t ilen,
63     bhnd_nvram_type itype)
64 {
65 	size_t align, width;
66 
67 	/* As a special case, NULL values have no alignment, but must
68 	 * always have a length of zero */
69 	if (itype == BHND_NVRAM_TYPE_NULL) {
70 		if (ilen != 0)
71 			return (EFAULT);
72 
73 		return (0);
74 	}
75 
76 	/* Check pointer alignment against the required host alignment */
77 	align = bhnd_nvram_type_host_align(itype);
78 	BHND_NV_ASSERT(align != 0, ("invalid zero alignment"));
79 	if ((uintptr_t)inp % align != 0)
80 		return (EFAULT);
81 
82 	/* If type is not fixed width, nothing else to check */
83 	width = bhnd_nvram_type_width(itype);
84 	if (width == 0)
85 		return (0);
86 
87 	/* Length must be aligned to the element width */
88 	if (ilen % width != 0)
89 		return (EFAULT);
90 
91 	/* If the type is not an array type, the length must be equal to the
92 	 * size of a single element of @p type. */
93 	if (!bhnd_nvram_is_array_type(itype) && ilen != width)
94 			return (EFTYPE);
95 
96 	return (0);
97 }
98 
99 /**
100  * Calculate the number of elements represented by a value of @p ilen bytes
101  * with @p itype.
102  *
103  * @param	inp	The value data.
104  * @param	ilen	The value length.
105  * @param	itype	The value type.
106  * @param[out]	nelem	On success, the number of elements.
107  *
108  * @retval 0		success
109  * @retval EINVAL	if @p inp is NULL and the element count of @p itype
110  *			cannot be determined without parsing the value data.
111  * @retval EFTYPE	if @p itype is not an array type, and @p ilen is not
112  *			equal to the size of a single element of @p itype.
113  * @retval EFAULT	if @p ilen is not correctly aligned for elements of
114  *			@p itype.
115  */
116 int
117 bhnd_nvram_value_nelem(const void *inp, size_t ilen, bhnd_nvram_type itype,
118     size_t *nelem)
119 {
120 	int	error;
121 
122 	BHND_NV_ASSERT(inp != NULL, ("NULL inp"));
123 
124 	/* Check alignment */
125 	if ((error = bhnd_nvram_value_check_aligned(inp, ilen, itype)))
126 		return (error);
127 
128 	switch (itype) {
129 	case BHND_NVRAM_TYPE_DATA:
130 		/* Always exactly one element */
131 		*nelem = 1;
132 		return (0);
133 
134 	case BHND_NVRAM_TYPE_NULL:
135 		/* Must be zero length */
136 		if (ilen != 0)
137 			return (EFAULT);
138 
139 		/* Always exactly one element */
140 		*nelem = 1;
141 		return (0);
142 
143 	case BHND_NVRAM_TYPE_STRING:
144 		/* Always exactly one element */
145 		*nelem = 1;
146 		return (0);
147 
148 	case BHND_NVRAM_TYPE_STRING_ARRAY: {
149 		const char	*p;
150 		size_t		 nleft;
151 
152 		/* Iterate over the NUL-terminated strings to calculate
153 		 * total element count */
154 		p = inp;
155 		nleft = ilen;
156 		*nelem = 0;
157 		while (nleft > 0) {
158 			size_t slen;
159 
160 			/* Increment element count */
161 			(*nelem)++;
162 
163 			/* Determine string length */
164 			slen = strnlen(p, nleft);
165 			nleft -= slen;
166 
167 			/* Advance input */
168 			p += slen;
169 
170 			/* Account for trailing NUL, if we haven't hit the end
171 			 * of the input */
172 			if (nleft > 0) {
173 				nleft--;
174 				p++;
175 			}
176 		}
177 
178 		return (0);
179 	}
180 
181 	case BHND_NVRAM_TYPE_UINT8_ARRAY:
182 	case BHND_NVRAM_TYPE_UINT16_ARRAY:
183 	case BHND_NVRAM_TYPE_UINT32_ARRAY:
184 	case BHND_NVRAM_TYPE_UINT64_ARRAY:
185 	case BHND_NVRAM_TYPE_INT8_ARRAY:
186 	case BHND_NVRAM_TYPE_INT16_ARRAY:
187 	case BHND_NVRAM_TYPE_INT32_ARRAY:
188 	case BHND_NVRAM_TYPE_INT64_ARRAY:
189 	case BHND_NVRAM_TYPE_CHAR_ARRAY:
190 	case BHND_NVRAM_TYPE_BOOL_ARRAY: {
191 		size_t width = bhnd_nvram_type_width(itype);
192 		BHND_NV_ASSERT(width != 0, ("invalid width"));
193 
194 		*nelem = ilen / width;
195 		return (0);
196 	}
197 
198 	case BHND_NVRAM_TYPE_INT8:
199 	case BHND_NVRAM_TYPE_UINT8:
200 	case BHND_NVRAM_TYPE_CHAR:
201 	case BHND_NVRAM_TYPE_INT16:
202 	case BHND_NVRAM_TYPE_UINT16:
203 	case BHND_NVRAM_TYPE_INT32:
204 	case BHND_NVRAM_TYPE_UINT32:
205 	case BHND_NVRAM_TYPE_INT64:
206 	case BHND_NVRAM_TYPE_UINT64:
207 	case BHND_NVRAM_TYPE_BOOL:
208 		/* Length must be equal to the size of exactly one
209 		 * element (arrays can represent zero elements -- non-array
210 		 * types cannot) */
211 		if (ilen != bhnd_nvram_type_width(itype))
212 			return (EFTYPE);
213 		*nelem = 1;
214 		return (0);
215 	}
216 
217 	/* Quiesce gcc4.2 */
218 	BHND_NV_PANIC("bhnd nvram type %u unknown", itype);
219 }
220 
221 /**
222  * Return the size, in bytes, of a value of @p itype with @p nelem elements.
223  *
224  * @param	inp	The actual data to be queried, or NULL if unknown. If
225  *			NULL and the base type is not a fixed width type
226  *			(e.g. BHND_NVRAM_TYPE_STRING), 0 will be returned.
227  * @param	ilen	The size of @p inp, in bytes, or 0 if @p inp is NULL.
228  * @param	itype	The value type.
229  * @param	nelem	The number of elements. If @p itype is not an array
230  *			type, this value must be 1.
231  *
232  * @retval 0		If @p itype has a variable width, and @p inp is NULL.
233  * @retval 0		If a @p nelem value greater than 1 is provided for a
234  *			non-array @p itype.
235  * @retval 0		If a @p nelem value of 0 is provided.
236  * @retval 0		If the result would exceed the maximum value
237  *			representable by size_t.
238  * @retval 0		If @p itype is BHND_NVRAM_TYPE_NULL.
239  * @retval non-zero	The size, in bytes, of @p itype with @p nelem elements.
240  */
241 size_t
242 bhnd_nvram_value_size(const void *inp, size_t ilen, bhnd_nvram_type itype,
243     size_t nelem)
244 {
245 	/* If nelem 0, nothing to do */
246 	if (nelem == 0)
247 		return (0);
248 
249 	/* Non-array types must have an nelem value of 1 */
250 	if (!bhnd_nvram_is_array_type(itype) && nelem != 1)
251 		return (0);
252 
253 	switch (itype) {
254 	case BHND_NVRAM_TYPE_UINT8_ARRAY:
255 	case BHND_NVRAM_TYPE_UINT16_ARRAY:
256 	case BHND_NVRAM_TYPE_UINT32_ARRAY:
257 	case BHND_NVRAM_TYPE_UINT64_ARRAY:
258 	case BHND_NVRAM_TYPE_INT8_ARRAY:
259 	case BHND_NVRAM_TYPE_INT16_ARRAY:
260 	case BHND_NVRAM_TYPE_INT32_ARRAY:
261 	case BHND_NVRAM_TYPE_INT64_ARRAY:
262 	case BHND_NVRAM_TYPE_CHAR_ARRAY:
263 	case BHND_NVRAM_TYPE_BOOL_ARRAY:{
264 		size_t width;
265 
266 		width = bhnd_nvram_type_width(itype);
267 
268 		/* Would nelem * width overflow? */
269 		if (SIZE_MAX / nelem < width) {
270 			BHND_NV_LOG("cannot represent size %s[%zu]\n",
271 			    bhnd_nvram_type_name(bhnd_nvram_base_type(itype)),
272 			    nelem);
273 			return (0);
274 		}
275 
276 		return (nelem * width);
277 	}
278 
279 	case BHND_NVRAM_TYPE_STRING_ARRAY: {
280 		const char	*p;
281 		size_t		 total_size;
282 
283 		if (inp == NULL)
284 			return (0);
285 
286 		/* Iterate over the NUL-terminated strings to calculate
287 		 * total byte length */
288 		p = inp;
289 		total_size = 0;
290 		for (size_t i = 0; i < nelem; i++) {
291 			size_t	elem_size;
292 
293 			elem_size = strnlen(p, ilen - total_size);
294 			p += elem_size;
295 
296 			/* Check for (and skip) terminating NUL */
297 			if (total_size < ilen && *p == '\0') {
298 				elem_size++;
299 				p++;
300 			}
301 
302 			/* Would total_size + elem_size overflow?
303 			 *
304 			 * A memory range larger than SIZE_MAX shouldn't be,
305 			 * possible, but include the check for completeness */
306 			if (SIZE_MAX - total_size < elem_size)
307 				return (0);
308 
309 			total_size += elem_size;
310 		}
311 
312 		return (total_size);
313 	}
314 
315 	case BHND_NVRAM_TYPE_STRING: {
316 		size_t size;
317 
318 		if (inp == NULL)
319 			return (0);
320 
321 		/* Find length */
322 		size = strnlen(inp, ilen);
323 
324 		/* Is there a terminating NUL, or did we just hit the
325 		 * end of the string input */
326 		if (size < ilen)
327 			size++;
328 
329 		return (size);
330 	}
331 
332 	case BHND_NVRAM_TYPE_NULL:
333 		return (0);
334 
335 	case BHND_NVRAM_TYPE_DATA:
336 		if (inp == NULL)
337 			return (0);
338 
339 		return (ilen);
340 
341 	case BHND_NVRAM_TYPE_BOOL:
342 		return (sizeof(bhnd_nvram_bool_t));
343 
344 	case BHND_NVRAM_TYPE_INT8:
345 	case BHND_NVRAM_TYPE_UINT8:
346 	case BHND_NVRAM_TYPE_CHAR:
347 		return (sizeof(uint8_t));
348 
349 	case BHND_NVRAM_TYPE_INT16:
350 	case BHND_NVRAM_TYPE_UINT16:
351 		return (sizeof(uint16_t));
352 
353 	case BHND_NVRAM_TYPE_INT32:
354 	case BHND_NVRAM_TYPE_UINT32:
355 		return (sizeof(uint32_t));
356 
357 	case BHND_NVRAM_TYPE_UINT64:
358 	case BHND_NVRAM_TYPE_INT64:
359 		return (sizeof(uint64_t));
360 	}
361 
362 	/* Quiesce gcc4.2 */
363 	BHND_NV_PANIC("bhnd nvram type %u unknown", itype);
364 }
365 
366 /**
367  * Format a string representation of @p inp using @p fmt, with, writing the
368  * result to @p outp.
369  *
370  * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
371  *
372  * @param		fmt	The format string.
373  * @param		inp	The value to be formatted.
374  * @param		ilen	The size of @p inp, in bytes.
375  * @param		itype	The type of @p inp.
376  * @param[out]		outp	On success, the string value will be written to
377  *				this buffer. This argment may be NULL if the
378  *				value is not desired.
379  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
380  *				to the actual size of the formatted string.
381  *
382  * @retval 0		success
383  * @retval EINVAL	If @p fmt contains unrecognized format string
384  *			specifiers.
385  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
386  *			is too small to hold the encoded value.
387  * @retval EFTYPE	If value coercion from @p inp to a string value via
388  *			@p fmt is unsupported.
389  * @retval ERANGE	If value coercion of @p value would overflow (or
390  *			underflow) the representation defined by @p fmt.
391  */
392 int
393 bhnd_nvram_value_printf(const char *fmt, const void *inp, size_t ilen,
394     bhnd_nvram_type itype, char *outp, size_t *olen, ...)
395 {
396 	va_list	ap;
397 	int	error;
398 
399 	va_start(ap, olen);
400 	error = bhnd_nvram_value_vprintf(fmt, inp, ilen, itype, outp, olen, ap);
401 	va_end(ap);
402 
403 	return (error);
404 }
405 
406 /**
407  * Format a string representation of @p inp using @p fmt, with, writing the
408  * result to @p outp.
409  *
410  * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
411  *
412  * @param		fmt	The format string.
413  * @param		inp	The value to be formatted.
414  * @param		ilen	The size of @p inp, in bytes.
415  * @param		itype	The type of @p inp.
416  * @param[out]		outp	On success, the string value will be written to
417  *				this buffer. This argment may be NULL if the
418  *				value is not desired.
419  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
420  *				to the actual size of the formatted string.
421  * @param		ap	Argument list.
422  *
423  * @retval 0		success
424  * @retval EINVAL	If @p fmt contains unrecognized format string
425  *			specifiers.
426  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
427  *			is too small to hold the encoded value.
428  * @retval EFTYPE	If value coercion from @p inp to a string value via
429  *			@p fmt is unsupported.
430  * @retval ERANGE	If value coercion of @p value would overflow (or
431  *			underflow) the representation defined by @p fmt.
432  */
433 int
434 bhnd_nvram_value_vprintf(const char *fmt, const void *inp, size_t ilen,
435     bhnd_nvram_type itype, char *outp, size_t *olen, va_list ap)
436 {
437 	bhnd_nvram_val	val;
438 	int		error;
439 
440 	/* Map input buffer as a value instance */
441 	error = bhnd_nvram_val_init(&val, NULL, inp, ilen, itype,
442 	    BHND_NVRAM_VAL_BORROW_DATA);
443 	if (error)
444 		return (error);
445 
446 	/* Attempt to format the value */
447 	error = bhnd_nvram_val_vprintf(&val, fmt, outp, olen, ap);
448 
449 	/* Clean up */
450 	bhnd_nvram_val_release(&val);
451 	return (error);
452 }
453 
454 /**
455  * Iterate over all elements in @p inp.
456  *
457  * @param		inp	The value to be iterated.
458  * @param		ilen	The size, in bytes, of @p inp.
459  * @param		itype	The data type of @p inp.
460  * @param		prev	The value previously returned by
461  *				bhnd_nvram_value_array_next(), or NULL to begin
462  *				iteration.
463  * @param[in,out]	olen	If @p prev is non-NULL, @p olen must be a
464  *				pointer to the length previously returned by
465  *				bhnd_nvram_value_array_next(). On success, will
466  *				be set to the next element's length, in bytes.
467  *
468  * @retval non-NULL	A borrowed reference to the next element of @p inp.
469  * @retval NULL		If the end of the array is reached.
470  */
471 const void *
472 bhnd_nvram_value_array_next(const void *inp, size_t ilen, bhnd_nvram_type itype,
473     const void *prev, size_t *olen)
474 {
475 	const u_char	*next;
476 	size_t		 offset;
477 
478 	/* Handle first element */
479 	if (prev == NULL) {
480 		/* Zero-length array? */
481 		if (ilen == 0)
482 			return (NULL);
483 
484 		*olen = bhnd_nvram_value_size(inp, ilen, itype, 1);
485 		return (inp);
486 	}
487 
488 	/* Advance to next element */
489 	BHND_NV_ASSERT(prev >= (const void *)inp, ("invalid cookiep"));
490 	next = (const u_char *)prev + *olen;
491 	offset = (size_t)(next - (const u_char *)inp);
492 
493 	if (offset >= ilen) {
494 		/* Hit end of the array */
495 		return (NULL);
496 	}
497 
498 	/* Determine element size */
499 	*olen = bhnd_nvram_value_size(next, ilen - offset, itype, 1);
500 	if (ilen - offset < *olen) {
501 		BHND_NV_LOG("short element of type %s -- misaligned "
502 		    "representation", bhnd_nvram_type_name(itype));
503 		return (NULL);
504 	}
505 
506 	return (next);
507 }
508 
509 /**
510  * Coerce value @p inp of type @p itype to @p otype, writing the
511  * result to @p outp.
512  *
513  * @param		inp	The value to be coerced.
514  * @param		ilen	The size of @p inp, in bytes.
515  * @param		itype	The base data type of @p inp.
516  * @param[out]		outp	On success, the value will be written to this
517  *				buffer. This argment may be NULL if the value
518  *				is not desired.
519  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
520  *				to the actual size of the requested value.
521  * @param		otype	The data type to be written to @p outp.
522  *
523  * @retval 0		success
524  * @retval ENOMEM	If @p outp is non-NULL and a buffer of @p olen is too
525  *			small to hold the requested value.
526  * @retval EFTYPE	If the variable data cannot be coerced to @p otype.
527  * @retval ERANGE	If value coercion would overflow @p otype.
528  */
529 int
530 bhnd_nvram_value_coerce(const void *inp, size_t ilen, bhnd_nvram_type itype,
531     void *outp, size_t *olen, bhnd_nvram_type otype)
532 {
533 	bhnd_nvram_val	val;
534 	int		error;
535 
536 	/* Wrap input buffer in a value instance */
537 	error = bhnd_nvram_val_init(&val, NULL, inp, ilen,
538 	    itype, BHND_NVRAM_VAL_BORROW_DATA|BHND_NVRAM_VAL_FIXED);
539 	if (error)
540 		return (error);
541 
542 	/* Try to encode as requested type */
543 	error = bhnd_nvram_val_encode(&val, outp, olen, otype);
544 
545 	/* Clean up and return error */
546 	bhnd_nvram_val_release(&val);
547 	return (error);
548 }
549