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 }