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