/*
 * Copyright (c) 2003, 2004, 2005 Nokia
 * Author: tsavola@movial.fi
 *
 * This program is licensed under GPL (see COPYING for details)
 */

#include "protocol.h"
#include "common.h"
#include "mount.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>

#if __BYTE_ORDER == __BIG_ENDIAN
# define htonll(n)  (n)
# define ntohll(n)  (n)
#elif __BYTE_ORDER == __LITTLE_ENDIAN
# define htonll(n)  ((((uint64_t) htonl(n)) << 32LL) | htonl((n) >> 32LL))
# define ntohll(n)  ((((uint64_t) ntohl(n)) << 32LL) | ntohl((n) >> 32LL))
#endif


#if 0

#include <ctype.h>

static FILE *get_log(void)
{
	static FILE *file = NULL;

	if (!file) {
		static char path[PATH_MAX];
		snprintf(path, sizeof (path), "/tmp/protocol.%d", getpid());

		file = fopen(path, "w");
		if (!file)
			abort();
	}

	return file;
}

static ssize_t my_write(int fd, void *buf, size_t len)
{
	static size_t total = 0;

	ssize_t retval;
	size_t i;
	FILE *file;

	retval = write(fd, buf, len);

	if (retval > 0) {
		total += retval;

		file = get_log();
		fprintf(file, "write fd=%2d total=%6d len=%5d buf=", fd, total, retval);

		for (i = 0; i < retval; ++i)
			fprintf(file, "%02x", (unsigned int) ((unsigned char *) buf)[i]);

		fprintf(file, "\n");
		fflush(file);
	}

	return retval;
}

static ssize_t my_read(int fd, void *buf, size_t len)
{
	static size_t total = 0;

	ssize_t retval;
	size_t i;
	FILE *file;

	retval = read(fd, buf, len);

	if (retval > 0) {
		total += retval;

		file = get_log();
		fprintf(file, "read  fd=%2d total=%6d len=%5d buf=", fd, total, retval);

		for (i = 0; i < retval; ++i)
			fprintf(file, "%02x", (unsigned int) ((unsigned char *) buf)[i]);

		fprintf(file, "\n");
		fflush(file);
	}

	return retval;
}

#define write my_write
#define read  my_read

#endif


uint16v_t *uint16v_alloc(uint32_t len)
{
	char *mem = malloc(sizeof (uint16v_t) + len * sizeof (uint16_t));
	uint16v_t *ints = (uint16v_t *) mem;
	if (ints) {
		ints->len = len;
		ints->vec = (uint16_t *) (mem + sizeof (uint16v_t));
	}
	return ints;
}

void uint16v_free(uint16v_t *ints)
{
	free(ints);
}

/**
 * Same as read(2) but retries on EINTR.
 */
ssize_t read_ni(int fd, void *buf, size_t len)
{
	ssize_t retval;
	do {
		retval = read(fd, buf, len);
	} while (retval < 0 && errno == EINTR);
	return retval;
}

/**
 * Same as write(2) but retries on EINTR.
 */
ssize_t write_ni(int fd, void *buf, size_t len)
{
	ssize_t retval;
	do {
		retval = write(fd, buf, len);
	} while (retval < 0 && errno == EINTR);
	return retval;
}

int write_uint16(int fd, uint16_t i)
{
	uint16_t data = htons(i);
	return write_buf(fd, &data, sizeof (data));
}

int write_uint32(int fd, uint32_t i)
{
	uint32_t data = htonl(i);
	return write_buf(fd, &data, sizeof (data));
}

int write_uint64(int fd, uint64_t i)
{
	uint64_t data = htonll(i);
	return write_buf(fd, &data, sizeof (data));
}

int write_uint16v(int fd, uint16v_t *ints)
{
	if (write_uint32(fd, ints->len) < 0)
		return -1;

	if (ints) {
		int i;
		for (i = 0; i < ints->len; ++i)
			if (write_uint16(fd, ints->vec[i]) < 0)
				return -1;
	}

	return 0;
}

/**
 * Writes a string into a file.
 * @param fd the file descriptor
 * @param str the null-terminated string
 * @return 0 on success, -1 on error
 */
int write_str(int fd, const char *str)
{
	uint32_t len;

	if (!str)
		str = "";

	len = strlen(str);

	if (write_uint32(fd, len) < 0 || write_buf(fd, (void *) str, len) < 0)
		return -1;
	return 0;
}

/**
 * Writes a string vector into a file.
 * @param fd the file descriptor
 * @param strv the string vector
 * @return 0 on success, -1 on error
 */
int write_strv(int fd, char **strv)
{
	if (write_uint32(fd, calc_vec_len((void **) strv)) < 0)
		return -1;

	if (strv) {
		char **s;
		for (s = strv; *s; ++s)
			if (write_str(fd, *s) < 0)
				return -1;
	}

	return 0;
}

int write_mount(int fd, const mount_info_t *mi)
{
	if (write_enum  (fd, mi->type      ) < 0 || write_str(fd, mi->point) < 0 ||
	    write_str   (fd, mi->device    ) < 0 || write_str(fd, mi->opts ) < 0 ||
	    write_uint64(fd, mi->device_dev))
		return -1;
	return 0;
}

int write_mountv(int fd, mount_info_t **mounts)
{
	if (write_uint32(fd, calc_vec_len((void **) mounts)) < 0)
		return -1;

	if (mounts) {
		mount_info_t **p;
		for (p = mounts; *p; ++p)
			if (write_mount(fd, *p) < 0)
				return -1;
	}

	return 0;
}

int write_buf(int fd, void *buf, size_t len)
{
	ssize_t i, cnt;

	for (i = 0; i < len; i += cnt) {
		cnt = write(fd, buf + i, len - i);
		if (cnt < 0) {
			if (errno == EAGAIN || errno == EINTR) {
				cnt = 0;
				continue;
			}

			return -1;
		}
	}

	return 0;
}

/**
 * Reads a complete buffer from a file.  errno will be set to EIO at EOF.
 * @param fd a file descriptor
 * @param buf pointer to a buffer large enough
 * @param len the length of the buffer
 * @return 0 on success, -1 on i/o error
 */
int read_buf(int fd, void *buf, size_t len)
{
	ssize_t i, cnt;

	for (i = 0; i < len; i += cnt) {
		cnt = read(fd, buf + i, len - i);
		if (cnt < 0) {
			if (errno == EAGAIN || errno == EINTR) {
				cnt = 0;
				continue;
			}

			return -1;
		}

		if (cnt == 0) {
			errno = EIO;
			return -1;
		}
	}

	return 0;
}

int read_uint16(int fd, uint16_t *iptr)
{
	uint16_t data;
	if (read_buf(fd, &data, sizeof (data)) < 0)
		return -1;
	*iptr = ntohs(data);
	return 0;
}

int read_uint32(int fd, uint32_t *iptr)
{
	uint32_t data;
	if (read_buf(fd, &data, sizeof (data)) < 0)
		return -1;
	*iptr = ntohl(data);
	return 0;
}

int read_uint64(int fd, uint64_t *iptr)
{
	uint64_t data;
	if (read_buf(fd, &data, sizeof (data)) < 0)
		return -1;
	*iptr = ntohll(data);
	return 0;
}

uint16v_t *read_uint16v(int fd)
{
	uint32_t len, i;
	uint16v_t *ints;

	if (read_uint32(fd, &len) < 0)
		return NULL;

	ints = uint16v_alloc(len);
	if (!ints)
		return NULL;

	for (i = 0; i < len; ++i)
		if (read_uint16(fd, &ints->vec[i]) < 0) {
			uint16v_free(ints);
			return NULL;
		}

	return ints;
}

/**
 * Reads a string from a file. The returned string should be free()'d.
 * @param fd a file descriptor
 * @return NULL on error
 */
char *read_str(int fd)
{
	uint32_t len;
	char *str;

	if (read_uint32(fd, &len) < 0)
		return NULL;

	str = calloc(len + 1, 1);
	if (!str)
		return NULL;

	if (read_buf(fd, str, len) < 0) {
		free(str);
		return NULL;
	}
	return str;
}

/**
 * Reads a string vector from a file. The returned strings and the vector
 * should be set free(). The vector is null-terminated.
 * @param fd a file descriptor
 * @return NULL on error
 */
char **read_strv(int fd)
{
	uint32_t len, i;
	char **strv;

	if (read_uint32(fd, &len) < 0)
		return NULL;

	strv = calloc(len + 1, sizeof (char *));
	if (!strv)
		return NULL;

	for (i = 0; i < len; ++i) {
		strv[i] = read_str(fd);
		if (!strv[i]) {
			free_vec((void **) strv, NULL);
			return NULL;
		}
	}

	return strv;
}

mount_info_t *read_mount(int fd)
{
	mount_info_t *mi;

	mi = mntinfo_alloc();
	if (!mi)
		return NULL;

	if ((mi->type   = read_enum(fd)) >= 0 &&
	    (mi->point  = read_str(fd)) &&
	    (mi->device = read_str(fd)) &&
	    (mi->opts   = read_str(fd)) &&
	    read_uint64(fd, &mi->device_dev) >= 0)
		return mi;

	mntinfo_free(mi);
	return NULL;
}

mount_info_t **read_mountv(int fd)
{
	uint32_t len, i;
	mount_info_t **mounts;

	if (read_uint32(fd, &len) < 0)
		return NULL;

	mounts = calloc(len + 1, sizeof (mount_info_t *));
	if (!mounts)
		return NULL;

	for (i = 0; i < len; ++i) {
		mounts[i] = read_mount(fd);
		if (!mounts[i]) {
			free_vec((void **) mounts, (free_func_t *) mntinfo_free);
			return NULL;
		}
	}

	return mounts;
}

int write_winsize(int fd, const struct winsize *ws)
{
	if (write_uint16(fd, ws->ws_row   ) < 0 || write_uint16(fd, ws->ws_col   ) < 0 ||
	    write_uint16(fd, ws->ws_xpixel) < 0 || write_uint16(fd, ws->ws_ypixel) < 0)
		return -1;
	return 0;
}

struct winsize *read_winsize(int fd)
{
	uint16_t row, col, xpixel, ypixel;
	struct winsize *ws;

	if (read_uint16(fd, &row   ) < 0 || read_uint16(fd, &col   ) < 0 ||
	    read_uint16(fd, &xpixel) < 0 || read_uint16(fd, &ypixel) < 0)
		return NULL;

	ws = malloc(sizeof (struct winsize));
	if (!ws)
		return NULL;

	ws->ws_row    = row;
	ws->ws_col    = col;
	ws->ws_xpixel = xpixel;
	ws->ws_ypixel = ypixel;
	return ws;
}

int write_enum(int fd, int type)
{
	return write_uint16(fd, type);
}

int read_enum(int fd)
{
	uint16_t type;
	if (read_uint16(fd, &type) < 0)
		return -1;
	return type;
}

int write_buf_packet(int fd, ptype_t type, size_t size, void *buf)
{
	if (write_enum(fd, type) < 0 || write_uint32(fd, size) < 0 || write_buf(fd, buf, size) < 0)
		return -1;
	return 0;
}

int write_str_packet(int fd, ptype_t type, const char *str)
{
	if (write_enum(fd, type) < 0 || write_str(fd, (char *) str) < 0)
		return -1;
	return 0;
}

int write_uint16_packet(int fd, ptype_t type, uint16_t val)
{
	if (write_enum(fd, type) < 0 || write_uint16(fd, val) < 0)
		return -1;
	return 0;
}

/**
 * Writes the VERSION packet to a file.
 * @param fd a file descriptor
 * @return 0 on success, -1 on i/o error
 */
int send_version(int fd)
{
	if (write_enum(fd, PTYPE_VERSION) < 0 || write_uint16(fd, PROTOCOL_VERSION) < 0)
		return -1;
	return 0;
}

/**
 * Reads the VERSION packet from a file.
 * @param fd a file descriptor
 * @return version on success, -1 on i/o error
 */
int get_version(int fd)
{
	uint16_t ver;
	if (read_enum(fd) != PTYPE_VERSION) {
		errno = 0;
		return -1;
	}
	if (read_uint16(fd, &ver) < 0)
		return -1;
	return ver;
}
