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