xref: /freebsd/contrib/libder/libder/libder_write.c (revision 35c0a8c449fd2b7f75029ebed5e10852240f0865)
1 /*-
2  * Copyright (c) 2024 Kyle Evans <kevans@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <assert.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "libder.h"
12 #include "libder_private.h"
13 
14 struct memory_write_data {
15 	uint8_t		*buf;
16 	size_t		 bufsz;
17 	size_t		 offset;
18 };
19 
20 typedef bool (write_buffer_t)(void *, const uint8_t *, size_t);
21 
22 static bool
libder_write_object_tag(struct libder_ctx * ctx __unused,const struct libder_object * obj,write_buffer_t * write_buffer,void * cookie)23 libder_write_object_tag(struct libder_ctx *ctx __unused,
24     const struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
25 {
26 	const struct libder_tag *type = obj->type;
27 	uint8_t value;
28 
29 	if (!type->tag_encoded) {
30 		value = libder_type_simple(type);
31 		return (write_buffer(cookie, &value, sizeof(value)));
32 	}
33 
34 	/* Write out the tag info first. */
35 	value = BER_TYPE_LONG_MASK;
36 	value |= type->tag_class << 6;
37 	if (type->tag_constructed)
38 		value |= BER_TYPE_CONSTRUCTED_MASK;
39 
40 	if (!write_buffer(cookie, &value, sizeof(value)))
41 		return (false);
42 
43 	/* Write out the encoded tag next. */
44 	return (write_buffer(cookie, type->tag_long, type->tag_size));
45 }
46 
47 static bool
libder_write_object_header(struct libder_ctx * ctx,struct libder_object * obj,write_buffer_t * write_buffer,void * cookie)48 libder_write_object_header(struct libder_ctx *ctx, struct libder_object *obj,
49     write_buffer_t *write_buffer, void *cookie)
50 {
51 	size_t size;
52 	uint8_t sizelen, value;
53 
54 	if (!libder_write_object_tag(ctx, obj, write_buffer, cookie))
55 		return (false);
56 
57 	size = obj->disk_size;
58 	sizelen = libder_size_length(size);
59 
60 	if (sizelen == 1) {
61 		assert((size & ~0x7f) == 0);
62 
63 		value = size;
64 		if (!write_buffer(cookie, &value, sizeof(value)))
65 			return (false);
66 	} else {
67 		/*
68 		 * Protocol supports at most 0x7f size bytes, but we can only
69 		 * do up to a size_t.
70 		 */
71 		uint8_t sizebuf[sizeof(size_t)], *sizep;
72 
73 		sizelen--;	/* Remove the lead byte. */
74 
75 		value = 0x80 | sizelen;
76 		if (!write_buffer(cookie, &value, sizeof(value)))
77 			return (false);
78 
79 		sizep = &sizebuf[0];
80 		for (uint8_t i = 0; i < sizelen; i++)
81 			*sizep++ = (size >> ((sizelen - i - 1) * 8)) & 0xff;
82 
83 		if (!write_buffer(cookie, &sizebuf[0], sizelen))
84 			return (false);
85 	}
86 
87 	return (true);
88 }
89 
90 static bool
libder_write_object_payload(struct libder_ctx * ctx __unused,struct libder_object * obj,write_buffer_t * write_buffer,void * cookie)91 libder_write_object_payload(struct libder_ctx *ctx __unused,
92     struct libder_object *obj, write_buffer_t *write_buffer, void *cookie)
93 {
94 	uint8_t *payload = obj->payload;
95 	size_t length = obj->length;
96 
97 	/* We don't expect `obj->payload` to be valid for a zero-size value. */
98 	if (length == 0)
99 		return (true);
100 
101 	/*
102 	 * We allow a NULL payload with a non-zero length to indicate that an
103 	 * object should write zeroes out, we just didn't waste the memory on
104 	 * these small allocations.  Ideally if it's more than just one or two
105 	 * zeroes we're instead allocating a buffer for it and doing some more
106 	 * efficient copying from there.
107 	 */
108 	if (payload == NULL) {
109 		uint8_t zero = 0;
110 
111 		for (size_t i = 0; i < length; i++) {
112 			if (!write_buffer(cookie, &zero, 1))
113 				return (false);
114 		}
115 
116 		return (true);
117 	}
118 
119 	return (write_buffer(cookie, payload, length));
120 }
121 
122 static bool
libder_write_object(struct libder_ctx * ctx,struct libder_object * obj,write_buffer_t * write_buffer,void * cookie)123 libder_write_object(struct libder_ctx *ctx, struct libder_object *obj,
124     write_buffer_t *write_buffer, void *cookie)
125 {
126 	struct libder_object *child;
127 
128 	if (DER_NORMALIZING(ctx, CONSTRUCTED) && !libder_obj_coalesce_children(obj, ctx))
129 		return (false);
130 
131 	/* Write out this object's header first */
132 	if (!libder_write_object_header(ctx, obj, write_buffer, cookie))
133 		return (false);
134 
135 	/* Write out the payload. */
136 	if (obj->children == NULL)
137 		return (libder_write_object_payload(ctx, obj, write_buffer, cookie));
138 
139 	assert(obj->type->tag_constructed);
140 
141 	/* Recurse on each child. */
142 	DER_FOREACH_CHILD(child, obj) {
143 		if (!libder_write_object(ctx, child, write_buffer, cookie))
144 			return (false);
145 	}
146 
147 	return (true);
148 }
149 
150 static bool
memory_write(void * cookie,const uint8_t * data,size_t datasz)151 memory_write(void *cookie, const uint8_t *data, size_t datasz)
152 {
153 	struct memory_write_data *mwrite = cookie;
154 	uint8_t *dst = &mwrite->buf[mwrite->offset];
155 	size_t left;
156 
157 	/* Small buffers should have been rejected long before now. */
158 	left = mwrite->bufsz - mwrite->offset;
159 	assert(datasz <= left);
160 
161 	memcpy(dst, data, datasz);
162 	mwrite->offset += datasz;
163 	return (true);
164 }
165 
166 /*
167  * Writes the object rooted at `root` to the buffer.  If `buf` == NULL and
168  * `*bufsz` == 0, we'll allocate a buffer just large enough to hold the result
169  * and pass the size back via `*bufsz`.  If a pre-allocated buffer is passed,
170  * we may still update `*bufsz` if normalization made the buffer smaller.
171  *
172  * If the buffer is too small, *bufsz will be set to the size of buffer needed.
173  */
174 uint8_t *
libder_write(struct libder_ctx * ctx,struct libder_object * root,uint8_t * buf,size_t * bufsz)175 libder_write(struct libder_ctx *ctx, struct libder_object *root, uint8_t *buf,
176     size_t *bufsz)
177 {
178 	struct memory_write_data mwrite = { 0 };
179 	size_t needed;
180 
181 	/*
182 	 * We shouldn't really see buf == NULL with *bufsz != 0 or vice-versa.
183 	 * Combined, they mean that we should allocate whatever buffer size we
184 	 * need.
185 	 */
186 	if ((buf == NULL && *bufsz != 0) || (buf != NULL && *bufsz == 0))
187 		return (NULL);	/* XXX Surface error? */
188 
189 	/*
190 	 * If we're doing any normalization beyond our standard size
191 	 * normalization, we apply those rules up front since they may alter our
192 	 * disk size every so slightly.
193 	 */
194 	if (ctx->normalize != 0 && !libder_obj_normalize(root, ctx))
195 		return (NULL);
196 
197 	needed = libder_obj_disk_size(root, true);
198 	if (needed == 0)
199 		return (NULL);	/* Overflow */
200 
201 	/* Allocate if we weren't passed a buffer. */
202 	if (*bufsz == 0) {
203 		*bufsz = needed;
204 		buf = malloc(needed);
205 		if (buf == NULL)
206 			return (NULL);
207 	} else if (needed > *bufsz) {
208 		*bufsz = needed;
209 		return (NULL);	/* Insufficient space */
210 	}
211 
212 	/* Buffer large enough, write into it. */
213 	mwrite.buf = buf;
214 	mwrite.bufsz = *bufsz;
215 	if (!libder_write_object(ctx, root, &memory_write, &mwrite)) {
216 		libder_bzero(mwrite.buf, mwrite.offset);
217 		free(buf);
218 		return (NULL);	/* XXX Error */
219 	}
220 
221 	/*
222 	 * We don't normalize the in-memory representation of the tree, we do
223 	 * that as we're writing into the buffer.  It could be the case that we
224 	 * didn't need the full buffer as a result of normalization.
225 	 */
226 	*bufsz = mwrite.offset;
227 
228 	return (buf);
229 }
230