view pack.c @ 153:a6a7cd08be72

pack.c: o added two new functions pack_fmtlen, pack_vfmtlen, compute the number of bytes needed for format. o added support for buffer, pack_swrite, pack_vswrite, pack_sread, pack_vsread. Buffer will be automatically allocated for _write functions.
author David Demelier <markand@malikania.fr>
date Sun, 15 Jul 2012 20:40:54 +0200
parents 54976019a5a8
children c7ad101ed4a1
line wrap: on
line source

/*
 * pack.c -- endian dependant binary file reader
 *
 * Copyright (c) 2011, 2012, David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include "pack.h"

/* --------------------------------------------------------
 * structure definitions
 * -------------------------------------------------------- */

typedef void (*ConvertFn)(void *);
typedef void (*WriteFn)(uint64_t, size_t, void *, void *);

struct value {
	char		tok;		/* token found */
	int		nelem;		/* number element for this value */
	size_t		size;		/* size of this element */

	union {
		uint8_t	*array;		/* if (nelem > 1) */
		uint64_t value;		/* if (nelem == 1) */
	} data;
};

/* --------------------------------------------------------
 * prototypes
 * -------------------------------------------------------- */

static size_t		pack_getsize(char);
static ConvertFn	pack_getconvert_by_tok(char);
static ConvertFn	pack_getconvert_by_size(size_t);
static void		pack_convert16(uint16_t *);
static void		pack_convert32(uint32_t *);
static void		pack_convert64(uint64_t *);

static void		pack_writefunc_fp(uint64_t, size_t, FILE *, void *);
static void		pack_writefunc_buffer(uint64_t, size_t, uint8_t *, size_t *);
static void		pack_convert_write(const struct value *, int,
			    WriteFn, void *, void *);

/* --------------------------------------------------------
 * private functions
 * -------------------------------------------------------- */

#define LENGTH(x)	(sizeof (x) / sizeof (x[0]))	

/*
 * Associative structure, the `tok' member is used for the format
 * and define the integer size. The convert function reverse the
 * bytes if it is needed.
 */

static struct integer {
	char		tok;		/* format char */
	size_t		tocopy;		/* size */
	ConvertFn	convert;	/* conversion function */
} sizes[] = {
	{ 'c',		sizeof (uint8_t),	NULL				},
	{ 's',		sizeof (uint16_t),	(ConvertFn)pack_convert16	},
	{ 'i',		sizeof (uint32_t),	(ConvertFn)pack_convert32	},
	{ 'l',		sizeof (uint64_t),	(ConvertFn)pack_convert64	}
};

/*
 * Get the appropriate size associated with the `tok' character. If
 * the token is not found the result is 0.
 */

static size_t
pack_getsize(char tok)
{
	struct integer *s;
	unsigned int i;

	for (s = sizes, i = 0; i < LENGTH(sizes); ++s, ++i)
		if (s->tok == tok)
			return s->tocopy;

	return 0;
}

/*
 * Return the conversion function.
 */

static ConvertFn
pack_getconvert_by_tok(char tok)
{
	struct integer *s;
	unsigned int i;

	for (s = sizes, i = 0; i < LENGTH(sizes); ++s, ++i)
		if (s->tok == tok)
			return s->convert;

	return NULL;
}

/*
 * Same but by size.
 */

static ConvertFn
pack_getconvert_by_size(size_t size)
{
	struct integer *s;
	unsigned int i;

	for (s = sizes, i = 0; i < LENGTH(sizes); ++s, ++i)
		if (s->tocopy == size)
			return s->convert;

	return NULL;
}

/*
 * Conversion functions. They reverse the bytes without any
 * check.
 */

static void
pack_convert16(uint16_t *x)
{
	*x = pack_swap16(*x);
}

static void
pack_convert32(uint32_t *x)
{
	*x = pack_swap32(*x);
}

static void
pack_convert64(uint64_t *x)
{
	*x = pack_swap64(*x);
}

static void
pack_writefunc_fp(uint64_t i, size_t size, FILE *fp, void *unused)
{
	fwrite(&i, size, 1, fp);

	(void)unused;
}

static void
pack_writefunc_buffer(uint64_t i, size_t size, uint8_t *buffer, size_t *offset)
{
	memcpy(&buffer[*offset], &i, size);

	(*offset) += size;
}

/*
 * Convert data and write it using write func function.
 */

static void
pack_convert_write(const struct value *val, int ptype,
		   WriteFn writefunc, void *data1, void *data2)
{
	ConvertFn conv = NULL;
	uint64_t it = 0;
	int i;

	if (ptype != PACK_HOST_BYTEORDER)
		conv = pack_getconvert_by_size(val->size);

	for (i = 0; i < val->nelem; ++i) {
		it = 0;

		if (val->nelem == 1)
			memcpy(&it, &val->data.value, val->size);
		else
			memcpy(&it, &val->data.array[i * val->size], val->size);

		/* Converts and bring back to array */
		if (conv != NULL)
			conv(&it);

		writefunc(it, val->size, data1, data2);
	}
}

/*
 * Convert value for reading, dst pointer will be converted if
 * necessary.
 */

static void
pack_convert_read(const struct value *val, uint8_t *dst, int ptype)
{
	ConvertFn conv = NULL;
	int i;
	uint64_t it = 0;

	if (ptype != PACK_HOST_BYTEORDER)
		conv = pack_getconvert_by_size(val->size);

	for (i = 0; i < val->nelem; ++i) {
		it = 0;
		memcpy(&it, &dst[i * val->size], val->size);

		if (conv != NULL) {
			conv(&it);
			memset(&dst[i * val->size], 0, val->size);
		}

		memcpy(&dst[i * val->size], &it, val->size);
	}
}

/*
 * Store the argument into the corrent .data member.
 */

static void
pack_get_arg(struct value *val, va_list ap, int ptype)
{
	if (val->nelem == 1) {
		switch (val->tok) {
		case 'c':
		case 's':
		case 'i':
			val->data.value = va_arg(ap, unsigned);
			break;
		case 'l':
			val->data.value = va_arg(ap, uint64_t);
		default:
			break;
		}
	} else if (val->nelem > 1)
		val->data.array = va_arg(ap, uint8_t *);
}

/*
 * Get the properties, the token size, the number of element to read.
 */

static void
pack_get_properties(struct value *val, char **p, va_list ap, int ptype)
{
	memset(val, 0, sizeof (struct value));

	if (isspace(**p))
		goto skip;
	if ((val->size = pack_getsize(**p)) == 0)
		goto skip;

	val->tok = **p;

	/*
	 * Now there is valid char, c, s, i or l, we must determine
	 * if the next character is [ (array) or anything else.
	 */
	if ((*p)[1] == '[') {
		if ((*p)[2] != ']')
			val->nelem = (int)strtol((*p) + 2, NULL, 10);
		else if ((*p)[2] == ']')
			val->nelem = va_arg(ap, int);

		/* Go to the end of fmt and find ']' */
		while (**p != ']' && **p != '\0')
			++ (*p);
	} else {
		val->nelem = 1;
		++ (*p);
	}

	return;
skip:
	++ (*p);
}

/* --------------------------------------------------------
 * public functions
 * -------------------------------------------------------- */

/*
 * Returns the number of bytes needed to write the specified format.
 */

size_t
pack_fmtlen(int ptype, const char *fmt, ...)
{
	va_list ap;
	size_t count;

	va_start(ap, fmt);
	count = pack_vfmtlen(ptype, fmt, ap);
	va_end(ap);

	return count;
}

size_t
pack_vfmtlen(int ptype, const char *fmt, va_list ap)
{
	const char *p;
	struct value v;
	size_t count = 0;

	p = fmt;
	while (*p != '\0') {
		pack_get_properties(&v, (char **)&p, ap, ptype);
		count += (v.size * v.nelem);
	}

	return count;
}

/*
 * Functions that write anything to the buffer, the buffer is automatically
 * allocated for you. Argument buflen will be set to the buffer length and
 * must not be NULL.
 */

int
pack_swrite(int ptype, uint8_t **buf, size_t *buflen, const char *fmt, ...)
{
	va_list ap;
	int rv;

	va_start(ap, fmt);
	rv = pack_vswrite(ptype, buf, buflen, fmt, ap);
	va_end(ap);

	return rv;
}

int
pack_vswrite(int ptype, uint8_t **buf, size_t *len, const char *fmt, va_list ap)
{
	const char *p;
	uint8_t *b = NULL;
	size_t offset = 0;
	struct value v;

	p = fmt;
	while (*p != '\0') {
		pack_get_properties(&v, (char **)&p, ap, ptype);
		pack_get_arg(&v, ap, ptype);

		if ((b = realloc(b, offset + (v.nelem * v.size))) == NULL) {
			*buf	= NULL;
			*len	= 0;
			return -1;
		}

		pack_convert_write(&v, ptype,
		    (WriteFn)pack_writefunc_buffer, b, &offset);
	}

	*buf	= b;
	*len	= offset;

	return 0;
}

/*
 * Function that writes everything to the file `path'. These functions
 * does not append to the file, so if you want successive call to append
 * variables use fpack instead.
 * Returns 0 on success or -1 on failure.
 */

int
pack_write(int ptype, const char *path, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vwrite(ptype, path, fmt, ap);
	va_end(ap);
	
	return status;
}

int
pack_vwrite(int ptype, const char *path, const char *fmt, va_list ap)
{
	FILE *fp;
	int status;

	if ((fp = fopen(path, "w+b")) == NULL)
		return -1;

	status = pack_vfwrite(ptype, fp, fmt, ap);
	fclose(fp);

	return status;
}

/*
 * Write to a file that is already open. The function does not call
 * fclose() so you need to do it yourself later.
 */

int
pack_fwrite(int ptype, FILE *fp, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vfwrite(ptype, fp, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vfwrite(int ptype, FILE *fp, const char *fmt, va_list ap)
{
	const char *p;
	struct value v;

	p = fmt;
	while (*p != '\0') {
		pack_get_properties(&v, (char **)&p, ap, ptype);
		pack_get_arg(&v, ap, ptype);
		pack_convert_write(&v, ptype,
		    (WriteFn)pack_writefunc_fp, fp, NULL);
	}

	return 0;
}

int
pack_sread(int ptype, uint8_t *buf, size_t len, const char *fmt, ...)
{
	va_list ap;
	int rv;

	va_start(ap, fmt);
	rv = pack_vsread(ptype, buf, len, fmt, ap);
	va_end(ap);

	return rv;
}

int
pack_vsread(int ptype, uint8_t *buf, size_t len, const char *fmt, va_list ap)
{
	const char *p;
	uint8_t *ptr;
	size_t offset = 0;
	struct value v;

	p = fmt;
	while (*p != '\0' && offset <= len) {
		pack_get_properties(&v, (char **)&p, ap, ptype);

		if ((ptr = va_arg(ap, uint8_t *)) == NULL || v.nelem == 0)
			continue;

		memcpy(ptr, &buf[offset], v.nelem * v.size);
		pack_convert_read(&v, ptr, ptype);

		offset += (v.nelem * v.size);
	}

	return 0;
}

/*
 * Function that read the binary file and restore values to the same format
 * as pack functions. Arguments must be pointer of enough space to store
 * the values.
 */

int
pack_read(int ptype, const char *path, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vread(ptype, path, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vread(int ptype, const char *path, const char *fmt, va_list ap)
{
	FILE *fp;
	int status;

	if ((fp = fopen(path, "rb")) == NULL)
		return -1;

	status = pack_vfread(ptype, fp, fmt, ap);
	fclose(fp);

	return status;
}

int
pack_fread(int ptype, FILE *fp, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vfread(ptype, fp, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vfread(int ptype, FILE *fp, const char *fmt, va_list ap)
{
	const char *p;
	uint8_t *ptr;
	struct value v;

	p = fmt;
	while (*p != '\0') {
		pack_get_properties(&v, (char **)&p, ap, ptype);

		if ((ptr = va_arg(ap, uint8_t *)) == NULL || v.nelem == 0)
			continue;

		fread(ptr, v.size, v.nelem, fp);
		pack_convert_read(&v, ptr, ptype);
	}

	return 0;
}