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