]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/marshal.c
marshal: ensure that two identic structures are serialized in the same way
[thirdparty/lldpd.git] / src / marshal.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
2 /*
3 * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #define _GNU_SOURCE 1
19 #define MARSHAL_EXPORT
20 #include "marshal.h"
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/types.h>
25 #include <sys/queue.h>
26 #include <string.h>
27
28 #include "compat/compat.h"
29 #include "log.h"
30
31 #include "lldpd-structs.h"
32
33 /* A serialized object */
34 struct marshal_serialized {
35 void *orig; /* Original reference. Also enforce alignment. */
36 size_t size;
37 unsigned char object[0];
38 };
39
40 struct marshal_info marshal_info_string = {
41 .name = "null string",
42 .size = 0,
43 .pointers = {MARSHAL_SUBINFO_NULL},
44 };
45 struct marshal_info marshal_info_fstring = {
46 .name = "fixed string",
47 .size = 0,
48 .pointers = {MARSHAL_SUBINFO_NULL},
49 };
50 struct marshal_info marshal_info_ignore = {
51 .name = "ignored",
52 .size = 0,
53 .pointers = {MARSHAL_SUBINFO_NULL},
54 };
55
56 /* List of already seen pointers */
57 struct ref {
58 TAILQ_ENTRY(ref) next;
59 void *pointer;
60 int dummy; /* To renumerate pointers */
61 };
62 TAILQ_HEAD(ref_l, ref);
63
64 /* Serialize the given object. */
65 ssize_t
66 marshal_serialize_(struct marshal_info *mi, void *unserialized, void **input,
67 int skip, void *_refs, int osize)
68 {
69 struct ref_l *refs = _refs;
70 struct ref *cref;
71 int size;
72 size_t len;
73 struct marshal_subinfo *current;
74 struct marshal_serialized *new = NULL, *serialized = NULL;
75 int dummy = 1;
76
77 log_debug("marshal", "start serialization of %s", mi->name);
78
79 /* Check if we have already serialized this one. */
80 if (!refs) {
81 refs = calloc(1, sizeof(struct ref_l));
82 if (!refs) {
83 log_warnx("marshal", "unable to allocate memory for list of references");
84 return -1;
85 }
86 TAILQ_INIT(refs);
87 }
88 TAILQ_FOREACH(cref, refs, next) {
89 if (unserialized == cref->pointer)
90 return 0;
91 /* dummy should be higher than any existing dummy */
92 if (cref->dummy >= dummy) dummy = cref->dummy + 1;
93 }
94
95 /* Handle special cases. */
96 size = mi->size;
97 if (!strcmp(mi->name, "null string"))
98 size = strlen((char *)unserialized) + 1;
99 else if (!strcmp(mi->name, "fixed string"))
100 size = osize;
101
102 /* Allocate serialized structure */
103 len = sizeof(struct marshal_serialized) + (skip?0:size);
104 serialized = calloc(1, len);
105 if (!serialized) {
106 log_warnx("marshal", "unable to allocate memory to serialize structure %s",
107 mi->name);
108 len = -1;
109 goto marshal_error;
110 }
111 /* We don't use the original pointer but a dummy one. */
112 serialized->orig = (unsigned char*)NULL + dummy;
113
114 /* Append the new reference */
115 if (!(cref = calloc(1, sizeof(struct ref)))) {
116 log_warnx("marshal", "unable to allocate memory for list of references");
117 free(serialized);
118 len = -1;
119 goto marshal_error;
120 }
121 cref->pointer = unserialized;
122 cref->dummy = dummy;
123 TAILQ_INSERT_TAIL(refs, cref, next);
124
125 /* First, serialize the main structure */
126 if (!skip)
127 memcpy(serialized->object, unserialized, size);
128
129 /* Then, serialize inner structures */
130 for (current = mi->pointers; current->mi; current++) {
131 size_t sublen;
132 void *source;
133 void *target;
134 if (current->kind == ignore) continue;
135 if (current->kind == pointer) {
136 memcpy(&source,
137 (unsigned char *)unserialized + current->offset,
138 sizeof(void *));
139 if (source == NULL) continue;
140 } else
141 source = (void *)((unsigned char *)unserialized + current->offset);
142 if (current->offset2)
143 memcpy(&osize, (unsigned char*)unserialized + current->offset2, sizeof(int));
144 sublen = marshal_serialize_(current->mi,
145 source, &target,
146 current->kind == substruct, refs, osize);
147 if (sublen == -1) {
148 log_warnx("marshal", "unable to serialize substructure %s for %s",
149 current->mi->name, mi->name);
150 free(serialized);
151 return -1;
152 }
153 /* We want to put the renumerated pointer instead of the real one. */
154 if (current->kind == pointer && !skip) {
155 TAILQ_FOREACH(cref, refs, next) {
156 if (source == cref->pointer) {
157 void *fakepointer = (unsigned char*)NULL + cref->dummy;
158 memcpy((unsigned char *)serialized->object + current->offset,
159 &fakepointer, sizeof(void *));
160 break;
161 }
162 }
163 }
164 if (sublen == 0) continue; /* This was already serialized */
165 /* Append the result */
166 new = realloc(serialized, len + sublen);
167 if (!new) {
168 log_warnx("marshal", "unable to allocate more memory to serialize structure %s",
169 mi->name);
170 free(serialized);
171 free(target);
172 len = -1;
173 goto marshal_error;
174 }
175 memcpy((unsigned char *)new + len, target, sublen);
176 free(target);
177 len += sublen;
178 serialized = (struct marshal_serialized *)new;
179 }
180
181 serialized->size = len;
182 *input = serialized;
183 marshal_error:
184 if (refs && !_refs) {
185 struct ref *cref, *cref_next;
186 for (cref = TAILQ_FIRST(refs);
187 cref != NULL;
188 cref = cref_next) {
189 cref_next = TAILQ_NEXT(cref, next);
190 TAILQ_REMOVE(refs, cref, next);
191 free(cref);
192 }
193 free(refs);
194 }
195 return len;
196 }
197
198 /* This structure is used to track memory allocation when serializing */
199 struct gc {
200 TAILQ_ENTRY(gc) next;
201 void *pointer;
202 void *orig; /* Original reference (not valid anymore !) */
203 };
204 TAILQ_HEAD(gc_l, gc);
205
206 static void*
207 marshal_alloc(struct gc_l *pointers, size_t len, void *orig)
208 {
209 struct gc *gpointer = NULL;
210
211 void *result = calloc(1, len);
212 if (!result) return NULL;
213 if ((gpointer = (struct gc *)calloc(1,
214 sizeof(struct gc))) == NULL) {
215 free(result);
216 return NULL;
217 }
218 gpointer->pointer = result;
219 gpointer->orig = orig;
220 TAILQ_INSERT_TAIL(pointers, gpointer, next);
221 return result;
222 }
223 static void
224 marshal_free(struct gc_l *pointers, int gconly)
225 {
226 struct gc *pointer, *pointer_next;
227 for (pointer = TAILQ_FIRST(pointers);
228 pointer != NULL;
229 pointer = pointer_next) {
230 pointer_next = TAILQ_NEXT(pointer, next);
231 TAILQ_REMOVE(pointers, pointer, next);
232 if (!gconly)
233 free(pointer->pointer);
234 free(pointer);
235 }
236 }
237
238
239 /* Unserialize the given object. */
240 size_t
241 marshal_unserialize_(struct marshal_info *mi, void *buffer, size_t len, void **output,
242 void *_pointers, int skip, int osize)
243 {
244 int total_len = sizeof(struct marshal_serialized) + (skip?0:mi->size);
245 struct marshal_serialized *serialized = buffer;
246 struct gc_l *pointers = _pointers;
247 int size, already, extra = 0;
248 void *new;
249 struct marshal_subinfo *current;
250 struct gc *apointer;
251
252 log_debug("marshal", "start unserialization of %s", mi->name);
253
254 if (len < sizeof(struct marshal_serialized) || len < total_len) {
255 log_warnx("marshal", "data to deserialize is too small (%zu) for structure %s",
256 len, mi->name);
257 return 0;
258 }
259
260 /* Initialize garbage collection */
261 if (!pointers) {
262 pointers = calloc(1, sizeof(struct gc_l));
263 if (!pointers) {
264 log_warnx("marshal", "unable to allocate memory for garbage collection");
265 return 0;
266 }
267 TAILQ_INIT(pointers);
268 }
269
270 /* Special cases */
271 size = mi->size;
272 if (!strcmp(mi->name, "null string") || !strcmp(mi->name, "fixed string")) {
273 switch (mi->name[0]) {
274 case 'n': size = strnlen((char *)serialized->object,
275 len - sizeof(struct marshal_serialized)) + 1; break;
276 case 'f': size = osize; extra=1; break; /* The extra byte is to ensure that
277 the string is null terminated. */
278 }
279 if (size > len - sizeof(struct marshal_serialized)) {
280 log_warnx("marshal", "data to deserialize contains a string too long");
281 total_len = 0;
282 goto unmarshal_error;
283 }
284 total_len += size;
285 }
286
287 /* First, the main structure */
288 if (!skip) {
289 if ((*output = marshal_alloc(pointers, size + extra, serialized->orig)) == NULL) {
290 log_warnx("marshal", "unable to allocate memory to unserialize structure %s",
291 mi->name);
292 total_len = 0;
293 goto unmarshal_error;
294 }
295 memcpy(*output, serialized->object, size);
296 }
297
298 /* Then, each substructure */
299 for (current = mi->pointers; current->mi; current++) {
300 size_t sublen;
301 new = (unsigned char *)*output + current->offset;
302 if (current->kind == ignore) {
303 memset((unsigned char *)*output + current->offset,
304 0, sizeof(void *));
305 continue;
306 }
307 if (current->kind == pointer) {
308 if (*(void **)new == NULL) continue;
309
310 /* Did we already see this reference? */
311 already = 0;
312 TAILQ_FOREACH(apointer, pointers, next)
313 if (apointer->orig == *(void **)new) {
314 memcpy((unsigned char *)*output + current->offset,
315 &apointer->pointer, sizeof(void *));
316 already = 1;
317 break;
318 }
319 if (already) continue;
320 }
321 /* Deserialize */
322 if (current->offset2)
323 memcpy(&osize, (unsigned char *)*output + current->offset2, sizeof(int));
324 sublen = marshal_unserialize_(current->mi,
325 (unsigned char *)buffer + total_len, len - total_len, &new, pointers,
326 current->kind == substruct, osize);
327 if (sublen == 0) {
328 log_warnx("marshal", "unable to serialize substructure %s for %s",
329 current->mi->name, mi->name);
330 total_len = 0;
331 goto unmarshal_error;
332 }
333 /* Link the result */
334 if (current->kind == pointer)
335 memcpy((unsigned char *)*output + current->offset,
336 &new, sizeof(void *));
337 total_len += sublen;
338 }
339
340 unmarshal_error:
341 if (pointers && !_pointers) {
342 marshal_free(pointers, (total_len > 0));
343 free(pointers);
344 }
345 return total_len;
346 }