Blame src/marshal.c

Packit e9ba0d
/* -*- mode: c; c-file-style: "openbsd" -*- */
Packit e9ba0d
/*
Packit e9ba0d
 * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
Packit e9ba0d
 *
Packit e9ba0d
 * Permission to use, copy, modify, and/or distribute this software for any
Packit e9ba0d
 * purpose with or without fee is hereby granted, provided that the above
Packit e9ba0d
 * copyright notice and this permission notice appear in all copies.
Packit e9ba0d
 *
Packit e9ba0d
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
Packit e9ba0d
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
Packit e9ba0d
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
Packit e9ba0d
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
Packit e9ba0d
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
Packit e9ba0d
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
Packit e9ba0d
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Packit e9ba0d
 */
Packit e9ba0d
Packit e9ba0d
#define MARSHAL_EXPORT
Packit e9ba0d
#include "marshal.h"
Packit e9ba0d
Packit e9ba0d
#include <stdio.h>
Packit e9ba0d
#include <stdlib.h>
Packit e9ba0d
#include <sys/types.h>
Packit e9ba0d
#include <sys/queue.h>
Packit e9ba0d
#include <string.h>
Packit e9ba0d
Packit e9ba0d
#include "compat/compat.h"
Packit e9ba0d
#include "log.h"
Packit e9ba0d
Packit e9ba0d
#include "lldpd-structs.h"
Packit e9ba0d
Packit e9ba0d
/* Stolen from CCAN */
Packit e9ba0d
#if HAVE_ALIGNOF
Packit e9ba0d
# define ALIGNOF(t) (__alignof__(t))
Packit e9ba0d
#else
Packit e9ba0d
# define ALIGNOF(t) ((sizeof(t) > 1)?((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0):1)
Packit e9ba0d
#endif
Packit e9ba0d
Packit e9ba0d
/* A serialized object */
Packit e9ba0d
struct marshal_serialized {
Packit e9ba0d
	void         *orig;	/* Original reference. Also enforce alignment. */
Packit e9ba0d
	size_t        size;
Packit e9ba0d
	unsigned char object[0];
Packit e9ba0d
};
Packit e9ba0d
Packit e9ba0d
struct marshal_info marshal_info_string = {
Packit e9ba0d
	.name = "null string",
Packit e9ba0d
	.size = 0,
Packit e9ba0d
	.pointers = {MARSHAL_SUBINFO_NULL},
Packit e9ba0d
};
Packit e9ba0d
struct marshal_info marshal_info_fstring = {
Packit e9ba0d
	.name = "fixed string",
Packit e9ba0d
	.size = 0,
Packit e9ba0d
	.pointers = {MARSHAL_SUBINFO_NULL},
Packit e9ba0d
};
Packit e9ba0d
struct marshal_info marshal_info_ignore = {
Packit e9ba0d
	.name = "ignored",
Packit e9ba0d
	.size = 0,
Packit e9ba0d
	.pointers = {MARSHAL_SUBINFO_NULL},
Packit e9ba0d
};
Packit e9ba0d
Packit e9ba0d
/* List of already seen pointers */
Packit e9ba0d
struct ref {
Packit e9ba0d
	TAILQ_ENTRY(ref) next;
Packit e9ba0d
	void *pointer;
Packit e9ba0d
	int dummy;		/* To renumerate pointers */
Packit e9ba0d
};
Packit e9ba0d
TAILQ_HEAD(ref_l, ref);
Packit e9ba0d
Packit e9ba0d
/* Serialize the given object. */
Packit e9ba0d
ssize_t
Packit e9ba0d
marshal_serialize_(struct marshal_info *mi, void *unserialized, void **input,
Packit e9ba0d
    int skip, void *_refs, int osize)
Packit e9ba0d
{
Packit e9ba0d
	struct ref_l *refs = _refs;
Packit e9ba0d
	struct ref *cref;
Packit e9ba0d
	int size;
Packit e9ba0d
	size_t len;
Packit e9ba0d
	struct marshal_subinfo *current;
Packit e9ba0d
	struct marshal_serialized *new = NULL, *serialized = NULL;
Packit e9ba0d
	int dummy = 1;
Packit e9ba0d
Packit e9ba0d
	log_debug("marshal", "start serialization of %s", mi->name);
Packit e9ba0d
Packit e9ba0d
	/* Check if we have already serialized this one. */
Packit e9ba0d
	if (!refs) {
Packit e9ba0d
		refs = calloc(1, sizeof(struct ref_l));
Packit e9ba0d
		if (!refs) {
Packit e9ba0d
			log_warnx("marshal", "unable to allocate memory for list of references");
Packit e9ba0d
			return -1;
Packit e9ba0d
		}
Packit e9ba0d
		TAILQ_INIT(refs);
Packit e9ba0d
	}
Packit e9ba0d
	TAILQ_FOREACH(cref, refs, next) {
Packit e9ba0d
		if (unserialized == cref->pointer)
Packit e9ba0d
			return 0;
Packit e9ba0d
		/* dummy should be higher than any existing dummy */
Packit e9ba0d
		if (cref->dummy >= dummy) dummy = cref->dummy + 1;
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	/* Handle special cases. */
Packit e9ba0d
	size = mi->size;
Packit e9ba0d
	if (!strcmp(mi->name, "null string"))
Packit e9ba0d
		/* We know we can't be called with NULL */
Packit e9ba0d
		size = strlen((char *)unserialized) + 1;
Packit e9ba0d
	else if (!strcmp(mi->name, "fixed string"))
Packit e9ba0d
		size = osize;
Packit e9ba0d
Packit e9ba0d
	/* Allocate serialized structure */
Packit e9ba0d
	len = sizeof(struct marshal_serialized) + (skip?0:size);
Packit e9ba0d
	serialized = calloc(1, len);
Packit e9ba0d
	if (!serialized) {
Packit e9ba0d
		log_warnx("marshal", "unable to allocate memory to serialize structure %s",
Packit e9ba0d
		    mi->name);
Packit e9ba0d
		len = -1;
Packit e9ba0d
		goto marshal_error;
Packit e9ba0d
	}
Packit e9ba0d
	/* We don't use the original pointer but a dummy one. */
Packit e9ba0d
	serialized->orig = (unsigned char*)NULL + dummy;
Packit e9ba0d
Packit e9ba0d
	/* Append the new reference */
Packit e9ba0d
	if (!(cref = calloc(1, sizeof(struct ref)))) {
Packit e9ba0d
		log_warnx("marshal", "unable to allocate memory for list of references");
Packit e9ba0d
		free(serialized);
Packit e9ba0d
		len = -1;
Packit e9ba0d
		goto marshal_error;
Packit e9ba0d
	}
Packit e9ba0d
	cref->pointer = unserialized;
Packit e9ba0d
	cref->dummy = dummy;
Packit e9ba0d
	TAILQ_INSERT_TAIL(refs, cref, next);
Packit e9ba0d
Packit e9ba0d
	/* First, serialize the main structure */
Packit e9ba0d
	if (!skip)
Packit e9ba0d
		memcpy(serialized->object, unserialized, size);
Packit e9ba0d
Packit e9ba0d
	/* Then, serialize inner structures */
Packit e9ba0d
	for (current = mi->pointers; current->mi; current++) {
Packit e9ba0d
		size_t sublen;
Packit e9ba0d
		size_t padlen;
Packit e9ba0d
		void  *source;
Packit e9ba0d
		void  *target = NULL;
Packit e9ba0d
		if (current->kind == ignore) continue;
Packit e9ba0d
		if (current->kind == pointer) {
Packit e9ba0d
			memcpy(&source,
Packit e9ba0d
			    (unsigned char *)unserialized + current->offset,
Packit e9ba0d
			    sizeof(void *));
Packit e9ba0d
			if (source == NULL) continue;
Packit e9ba0d
		} else
Packit e9ba0d
			source = (void *)((unsigned char *)unserialized + current->offset);
Packit e9ba0d
		if (current->offset2)
Packit e9ba0d
			memcpy(&osize, (unsigned char*)unserialized + current->offset2, sizeof(int));
Packit e9ba0d
		target = NULL;
Packit e9ba0d
		sublen = marshal_serialize_(current->mi,
Packit e9ba0d
		    source, &target,
Packit e9ba0d
		    current->kind == substruct, refs, osize);
Packit e9ba0d
		if (sublen == -1) {
Packit e9ba0d
			log_warnx("marshal", "unable to serialize substructure %s for %s",
Packit e9ba0d
			    current->mi->name, mi->name);
Packit e9ba0d
			free(serialized);
Packit e9ba0d
			return -1;
Packit e9ba0d
		}
Packit e9ba0d
		/* We want to put the renumerated pointer instead of the real one. */
Packit e9ba0d
		if (current->kind == pointer && !skip) {
Packit e9ba0d
			TAILQ_FOREACH(cref, refs, next) {
Packit e9ba0d
				if (source == cref->pointer) {
Packit e9ba0d
					void *fakepointer = (unsigned char*)NULL + cref->dummy;
Packit e9ba0d
					memcpy((unsigned char *)serialized->object + current->offset,
Packit e9ba0d
					    &fakepointer, sizeof(void *));
Packit e9ba0d
					break;
Packit e9ba0d
				}
Packit e9ba0d
			}
Packit e9ba0d
		}
Packit e9ba0d
		if (sublen == 0) continue; /* This was already serialized */
Packit e9ba0d
		/* Append the result, force alignment to be able to unserialize it */
Packit e9ba0d
		padlen = ALIGNOF(struct marshal_serialized);
Packit e9ba0d
		padlen = (padlen - (len % padlen)) % padlen;
Packit e9ba0d
		new = realloc(serialized, len + padlen + sublen);
Packit e9ba0d
		if (!new) {
Packit e9ba0d
			log_warnx("marshal", "unable to allocate more memory to serialize structure %s",
Packit e9ba0d
			    mi->name);
Packit e9ba0d
			free(serialized);
Packit e9ba0d
			free(target);
Packit e9ba0d
			len = -1;
Packit e9ba0d
			goto marshal_error;
Packit e9ba0d
		}
Packit e9ba0d
		memset((unsigned char *)new + len, 0, padlen);
Packit e9ba0d
		memcpy((unsigned char *)new + len + padlen, target, sublen);
Packit e9ba0d
		free(target);
Packit e9ba0d
		len += sublen + padlen;
Packit e9ba0d
		serialized = (struct marshal_serialized *)new;
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	serialized->size = len;
Packit e9ba0d
	*input = serialized;
Packit e9ba0d
marshal_error:
Packit e9ba0d
	if (refs && !_refs) {
Packit e9ba0d
		struct ref *cref, *cref_next;
Packit e9ba0d
		for (cref = TAILQ_FIRST(refs);
Packit e9ba0d
		     cref != NULL;
Packit e9ba0d
		     cref = cref_next) {
Packit e9ba0d
			cref_next = TAILQ_NEXT(cref, next);
Packit e9ba0d
			TAILQ_REMOVE(refs, cref, next);
Packit e9ba0d
			free(cref);
Packit e9ba0d
		}
Packit e9ba0d
		free(refs);
Packit e9ba0d
	}
Packit e9ba0d
	return len;
Packit e9ba0d
}
Packit e9ba0d
Packit e9ba0d
/* This structure is used to track memory allocation when serializing */
Packit e9ba0d
struct gc {
Packit e9ba0d
	TAILQ_ENTRY(gc) next;
Packit e9ba0d
	void *pointer;
Packit e9ba0d
	void *orig;		/* Original reference (not valid anymore !) */
Packit e9ba0d
};
Packit e9ba0d
TAILQ_HEAD(gc_l, gc);
Packit e9ba0d
Packit e9ba0d
static void*
Packit e9ba0d
marshal_alloc(struct gc_l *pointers, size_t len, void *orig)
Packit e9ba0d
{
Packit e9ba0d
	struct gc *gpointer = NULL;
Packit e9ba0d
Packit e9ba0d
	void *result = calloc(1, len);
Packit e9ba0d
	if (!result) return NULL;
Packit e9ba0d
	if ((gpointer = (struct gc *)calloc(1,
Packit e9ba0d
		    sizeof(struct gc))) == NULL) {
Packit e9ba0d
		free(result);
Packit e9ba0d
		return NULL;
Packit e9ba0d
	}
Packit e9ba0d
	gpointer->pointer = result;
Packit e9ba0d
	gpointer->orig = orig;
Packit e9ba0d
	TAILQ_INSERT_TAIL(pointers, gpointer, next);
Packit e9ba0d
	return result;
Packit e9ba0d
}
Packit e9ba0d
static void
Packit e9ba0d
marshal_free(struct gc_l *pointers, int gconly)
Packit e9ba0d
{
Packit e9ba0d
	struct gc *pointer, *pointer_next;
Packit e9ba0d
	for (pointer = TAILQ_FIRST(pointers);
Packit e9ba0d
	     pointer != NULL;
Packit e9ba0d
	     pointer = pointer_next) {
Packit e9ba0d
		pointer_next = TAILQ_NEXT(pointer, next);
Packit e9ba0d
		TAILQ_REMOVE(pointers, pointer, next);
Packit e9ba0d
		if (!gconly)
Packit e9ba0d
			free(pointer->pointer);
Packit e9ba0d
		free(pointer);
Packit e9ba0d
	}
Packit e9ba0d
}
Packit e9ba0d
Packit e9ba0d
Packit e9ba0d
/* Unserialize the given object. */
Packit e9ba0d
size_t
Packit e9ba0d
marshal_unserialize_(struct marshal_info *mi, void *buffer, size_t len, void **output,
Packit e9ba0d
    void *_pointers, int skip, int osize)
Packit e9ba0d
{
Packit e9ba0d
	int    total_len = sizeof(struct marshal_serialized) + (skip?0:mi->size);
Packit e9ba0d
	struct marshal_serialized *serialized = buffer;
Packit e9ba0d
	struct gc_l *pointers = _pointers;
Packit e9ba0d
	int size, already, extra = 0;
Packit e9ba0d
	void *new;
Packit e9ba0d
	struct marshal_subinfo *current;
Packit e9ba0d
	struct gc *apointer;
Packit e9ba0d
Packit e9ba0d
	log_debug("marshal", "start unserialization of %s", mi->name);
Packit e9ba0d
Packit e9ba0d
	if (len < sizeof(struct marshal_serialized) || len < total_len) {
Packit e9ba0d
		log_warnx("marshal", "data to deserialize is too small (%zu) for structure %s",
Packit e9ba0d
		    len, mi->name);
Packit e9ba0d
		return 0;
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	/* Initialize garbage collection */
Packit e9ba0d
	if (!pointers) {
Packit e9ba0d
		pointers = calloc(1, sizeof(struct gc_l));
Packit e9ba0d
		if (!pointers) {
Packit e9ba0d
			log_warnx("marshal", "unable to allocate memory for garbage collection");
Packit e9ba0d
			return 0;
Packit e9ba0d
		}
Packit e9ba0d
		TAILQ_INIT(pointers);
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	/* Special cases */
Packit e9ba0d
	size = mi->size;
Packit e9ba0d
	if (!strcmp(mi->name, "null string") || !strcmp(mi->name, "fixed string")) {
Packit e9ba0d
		switch (mi->name[0]) {
Packit e9ba0d
		case 'n': size = strnlen((char *)serialized->object,
Packit e9ba0d
		    len - sizeof(struct marshal_serialized)) + 1; break;
Packit e9ba0d
		case 'f': size = osize; extra=1; break; /* The extra byte is to ensure that
Packit e9ba0d
							   the string is null terminated. */
Packit e9ba0d
		}
Packit e9ba0d
		if (size > len - sizeof(struct marshal_serialized)) {
Packit e9ba0d
			log_warnx("marshal", "data to deserialize contains a string too long");
Packit e9ba0d
			total_len = 0;
Packit e9ba0d
			goto unmarshal_error;
Packit e9ba0d
		}
Packit e9ba0d
		total_len += size;
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	/* First, the main structure */
Packit e9ba0d
	if (!skip) {
Packit e9ba0d
		if ((*output = marshal_alloc(pointers, size + extra, serialized->orig)) == NULL) {
Packit e9ba0d
			log_warnx("marshal", "unable to allocate memory to unserialize structure %s",
Packit e9ba0d
			    mi->name);
Packit e9ba0d
			total_len = 0;
Packit e9ba0d
			goto unmarshal_error;
Packit e9ba0d
		}
Packit e9ba0d
		memcpy(*output, serialized->object, size);
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
	/* Then, each substructure */
Packit e9ba0d
	for (current = mi->pointers; current->mi; current++) {
Packit e9ba0d
		size_t  sublen;
Packit e9ba0d
		size_t  padlen;
Packit e9ba0d
		new = (unsigned char *)*output + current->offset;
Packit e9ba0d
		if (current->kind == ignore) {
Packit e9ba0d
			memset((unsigned char *)*output + current->offset,
Packit e9ba0d
			       0, sizeof(void *));
Packit e9ba0d
			continue;
Packit e9ba0d
		}
Packit e9ba0d
		if (current->kind == pointer) {
Packit e9ba0d
			if (*(void **)new == NULL) continue;
Packit e9ba0d
Packit e9ba0d
			/* Did we already see this reference? */
Packit e9ba0d
			already = 0;
Packit e9ba0d
			TAILQ_FOREACH(apointer, pointers, next)
Packit e9ba0d
				if (apointer->orig == *(void **)new) {
Packit e9ba0d
					memcpy((unsigned char *)*output + current->offset,
Packit e9ba0d
					    &apointer->pointer, sizeof(void *));
Packit e9ba0d
					already = 1;
Packit e9ba0d
					break;
Packit e9ba0d
				}
Packit e9ba0d
			if (already) continue;
Packit e9ba0d
		}
Packit e9ba0d
		/* Deserialize */
Packit e9ba0d
		if (current->offset2)
Packit e9ba0d
			memcpy(&osize, (unsigned char *)*output + current->offset2, sizeof(int));
Packit e9ba0d
		padlen = ALIGNOF(struct marshal_serialized);
Packit e9ba0d
		padlen = (padlen - (total_len % padlen)) % padlen;
Packit e9ba0d
		if (len < total_len + padlen || ((sublen = marshal_unserialize_(current->mi,
Packit e9ba0d
				(unsigned char *)buffer + total_len + padlen,
Packit e9ba0d
				len - total_len - padlen, &new, pointers,
Packit e9ba0d
				current->kind == substruct, osize)) == 0)) {
Packit e9ba0d
			log_warnx("marshal", "unable to serialize substructure %s for %s",
Packit e9ba0d
			    current->mi->name, mi->name);
Packit e9ba0d
			total_len = 0;
Packit e9ba0d
			goto unmarshal_error;
Packit e9ba0d
		}
Packit e9ba0d
		/* Link the result */
Packit e9ba0d
		if (current->kind == pointer)
Packit e9ba0d
			memcpy((unsigned char *)*output + current->offset,
Packit e9ba0d
			    &new, sizeof(void *));
Packit e9ba0d
		total_len += sublen + padlen;
Packit e9ba0d
	}
Packit e9ba0d
Packit e9ba0d
unmarshal_error:
Packit e9ba0d
	if (pointers && !_pointers) {
Packit e9ba0d
		marshal_free(pointers, (total_len > 0));
Packit e9ba0d
		free(pointers);
Packit e9ba0d
	}
Packit e9ba0d
	return total_len;
Packit e9ba0d
}