/ external / libder / libder / libder_write.c
libder_write.c
  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
 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
 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
 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
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
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 *
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  }