/*
 * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef __FSERVER_H
#define __FSERVER_H

#include <errno.h>
#include <string.h>
#include <netdb.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <openssl/hmac.h>
#include <openssl/evp.h>

#include <asm/byteorder.h>

#include <fs/pohmelfs/netfs.h>

#include "list.h"
#include "rbtree.h"
#include "jhash.h"

#define FSERVER_NAME_SIZE		64
#define FSERVER_MAX_KEY_SIZE		128
#define FSERVER_INET_BUFFER_SIZE	128

struct fserver_crypto_engine
{
	HMAC_CTX		hmac_ctx;
	EVP_MD_CTX 		mdctx;

	const EVP_MD		*evp_md;

	int			hmac;
	char			hash[FSERVER_NAME_SIZE];
	unsigned int		hash_keysize;
	unsigned char		hash_key[FSERVER_MAX_KEY_SIZE];

	int			(*hash_init)(struct fserver_crypto_engine *e);
	int			(*hash_update)(struct fserver_crypto_engine *e, void *buf, unsigned int size);
	int			(*hash_final)(struct fserver_crypto_engine *e);

	unsigned char		hash_output[EVP_MAX_MD_SIZE];
	unsigned int		hash_output_size;
	
	const EVP_CIPHER	*evp_cipher;
	EVP_CIPHER_CTX		cipher_ctx;

	char			cipher[FSERVER_NAME_SIZE];
	unsigned int		cipher_keysize;
	unsigned char		cipher_key[FSERVER_MAX_KEY_SIZE];
	
	int			(*cipher_init)(struct fserver_crypto_engine *e, void *iv, int enc);
	int			(*cipher_update)(struct fserver_crypto_engine *e, void *dst, unsigned int *dsize,
							void *src, unsigned int ssize);
	int			(*cipher_final)(struct fserver_crypto_engine *e, void *dst, unsigned int *size);

	unsigned long long	gen_iv;
	int			perform_crypto;
};

int fserver_init_hash(struct fserver_crypto_engine *e, struct fserver_crypto_engine *meng);
int fserver_init_cipher(struct fserver_crypto_engine *e, struct fserver_crypto_engine *meng);

static inline unsigned int fserver_crypto_pad(struct fserver_crypto_engine *e, unsigned int size)
{
	if (e->evp_cipher) {
		unsigned int bsize = EVP_CIPHER_block_size(e->evp_cipher);

		return ((size + bsize - 1) & ~(bsize - 1)) - size;
	}

	return 0;
}

struct fserver_client
{
	struct list_head	client_entry;

	int 			cs, users;
	size_t			len;
	struct sockaddr_in	sa;
	struct sockaddr_in6	sa6;

	pthread_mutex_t		lock;
	struct fserver		*f;

	__u64			ops;

	struct list_head	worker_list;
	pthread_mutex_t		worker_lock;
};

struct fserver_worker
{
	struct list_head	worker_entry;

	pthread_t		id;
	int			need_exit;
	
	struct fserver_crypto_engine	eng;

	struct fserver_client	*client;
};

struct fserver
{
	struct list_head	client_list;
	pthread_mutex_t		client_lock;

	struct rb_root		coherency_root;
	pthread_mutex_t		coherency_lock;

	int			workers_per_client;
	unsigned int		flags;

	void			*notify_root;

	int			dirfd;
	int			s;

	unsigned long long	export_size;
	
	struct fserver_crypto_engine	eng;
};

#define NIP6(addr) \
        ntohs((addr).s6_addr16[0]), \
        ntohs((addr).s6_addr16[1]), \
        ntohs((addr).s6_addr16[2]), \
        ntohs((addr).s6_addr16[3]), \
        ntohs((addr).s6_addr16[4]), \
        ntohs((addr).s6_addr16[5]), \
        ntohs((addr).s6_addr16[6]), \
        ntohs((addr).s6_addr16[7])
#define NIP6_FMT "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"

static inline char *fserver_addr_convert(struct fserver_client *c)
{
	static char inet_addr[FSERVER_INET_BUFFER_SIZE];
	struct in6_addr *in6;

	memset(&inet_addr, 0, sizeof(inet_addr));
	if (c->len == sizeof(struct sockaddr_in)) {
		sprintf(inet_addr, "%s", inet_ntoa(c->sa.sin_addr));
	 } else {
		in6 = &c->sa6.sin6_addr;
		sprintf(inet_addr,  NIP6_FMT , NIP6(*in6));
	}
	return inet_addr;
}

static inline int fserver_port_convert(struct fserver_client *c)
{
	if (c->len == sizeof(struct sockaddr_in))
		return ntohs(c->sa.sin_port);
	else
		return ntohs(c->sa6.sin6_port);
}

int fserver_put_client_data_direct(struct fserver_crypto_engine *e, struct fserver_client *c, struct netfs_cmd *old);
int fserver_fill_stat_inode(int fd, char *name, struct netfs_inode_info *info);

#define __unused		 __attribute__ ((unused))

void ulog(const char *f, ...) __attribute__ ((format (printf, 1, 2)));
void uloga(const char *f, ...) __attribute__ ((format (printf, 1, 2)));

int ulog_init(char *log);

#define ulog_err(f, a...) uloga(f ": %s [%d].\n", ##a, strerror(errno), errno)

#ifndef NOTIFY
static inline void *fserver_notify_init(struct fserver * f)
{
	return f;
}
static inline void fserver_notify_exit(void *root __unused)
{
}
static inline int fserver_notify_update(struct fserver *f __unused)
{
	return 0;
}
static inline int fserver_notify_add(struct fserver *f __unused, char *path __unused, unsigned int len __unused,
		__u64 id __unused, __u64 ino __unused)
{
	return 0;
}
static inline int fserver_notify_remove(struct fserver *f __unused, char *path __unused, unsigned int len __unused)
{
	return 0;
}
#else
void *fserver_notify_init(struct fserver * f);
void fserver_notify_exit(void *root);
int fserver_notify_update(struct fserver *f);
int fserver_notify_add(struct fserver *f, char *path, unsigned int len, __u64 id, __u64 ino);
int fserver_notify_remove(struct fserver *f, char *path, unsigned int len);
#endif

struct ac_stat
{
	__u64		nr_files;
	__u64		used, avail;
};

int fserver_stat_root(int rootfd, char *path, struct ac_stat *st);

#endif /* __FSERVER_H */
