#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "../include/ecryptfs.h"

static uint64_t swab64(uint64_t x)
{
	return x<<56 | x>>56 |
		(x & (uint64_t)0x000000000000ff00ULL)<<40 |
		(x & (uint64_t)0x0000000000ff0000ULL)<<24 |
		(x & (uint64_t)0x00000000ff000000ULL)<< 8 |
	        (x & (uint64_t)0x000000ff00000000ULL)>> 8 |
		(x & (uint64_t)0x0000ff0000000000ULL)>>24 |
		(x & (uint64_t)0x00ff000000000000ULL)>>40;
}

static int host_is_big_endian(void)
{
	int i;
	uint32_t tmp_u32;
	char tmp_str[sizeof(uint32_t)];

	tmp_u32 = 0x00000001;
	memcpy(tmp_str, (char *)&tmp_u32, sizeof(uint32_t));
	if (tmp_str[0] == 0x01)
		return 0; /* If the first byte contains 0x01, host is
			     little endian (e.g., x86). Reverse what's
			     read from disk. */
	else
		return 1; /* If the first byte contains 0x00, host is
			   * big endian (e.g., ppc). Just copy from
			   * disk. */
}

/**
 * contains_ecryptfs_marker - check for the ecryptfs marker
 * @data: The data block in which to check
 *
 * Returns one if marker found; zero if not found
 */
static int ecryptfs_contains_ecryptfs_marker(char *data)
{
	uint32_t m_1, m_2;
	int big_endian;

	big_endian = host_is_big_endian();
	memcpy(&m_1, data, 4);
	if (!big_endian)
		m_1 = ntohl(m_1);
	memcpy(&m_2, (data + 4), 4);
	if (!big_endian)
		m_2 = ntohl(m_2);
	if ((m_1 ^ MAGIC_ECRYPTFS_MARKER) == m_2)
		return 1;
	return 0;
}

struct ecryptfs_flag_map_elem {
	uint32_t file_flag;
	uint32_t local_flag;
};

/* Add support for additional flags by adding elements here. */
static struct ecryptfs_flag_map_elem ecryptfs_flag_map[] = {
	{0x00000001, ECRYPTFS_ENABLE_HMAC},
	{0x00000002, ECRYPTFS_ENCRYPTED},
	{0x00000004, ECRYPTFS_METADATA_IN_XATTR}
};

/**
 * ecryptfs_process_flags
 * @crypt_stat: The cryptographic context
 * @page_virt: Source data to be parsed
 * @bytes_read: Updated with the number of bytes read
 *
 * Returns zero on success; non-zero if the flag set is invalid
 */
static int ecryptfs_process_flags(struct ecryptfs_crypt_stat_user *crypt_stat,
				  char *buf, int *bytes_read)
{
	int rc = 0;
	int i;
	uint32_t flags;
	int big_endian;

	big_endian = host_is_big_endian();
	memcpy(&flags, buf, 4);
	if (!big_endian)
		flags = ntohl(flags);
	for (i = 0; i < ((sizeof(ecryptfs_flag_map)
			  / sizeof(struct ecryptfs_flag_map_elem))); i++)
		if (flags & ecryptfs_flag_map[i].file_flag) {
			crypt_stat->flags |= ecryptfs_flag_map[i].local_flag;
		} else
			crypt_stat->flags &= ~(ecryptfs_flag_map[i].local_flag);
	/* Version is in top 8 bits of the 32-bit flag vector */
	crypt_stat->file_version = ((flags >> 24) & 0xFF);
	(*bytes_read) = 4;
	return rc;
}

#define ECRYPTFS_DONT_VALIDATE_HEADER_SIZE 0
#define ECRYPTFS_VALIDATE_HEADER_SIZE 1
static int
ecryptfs_parse_header_metadata(struct ecryptfs_crypt_stat_user *crypt_stat,
			       char *buf, int *bytes_read,
			       int validate_header_size)
{
	int rc = 0;
	uint32_t header_extent_size;
	uint16_t num_header_extents_at_front;
	int big_endian;

	big_endian = host_is_big_endian();
	memcpy(&header_extent_size, buf, sizeof(uint32_t));
	if (!big_endian)
		header_extent_size = ntohl(header_extent_size);
	buf += sizeof(uint32_t);
	memcpy(&num_header_extents_at_front, buf, sizeof(uint16_t));
	if (!big_endian)
		num_header_extents_at_front =
			ntohs(num_header_extents_at_front);
	crypt_stat->num_header_bytes_at_front =
		(((size_t)num_header_extents_at_front
		  * (size_t)header_extent_size));
	(*bytes_read) = (sizeof(uint32_t) + sizeof(uint16_t));
	if ((validate_header_size == ECRYPTFS_VALIDATE_HEADER_SIZE)
	    && (crypt_stat->num_header_bytes_at_front
		< ECRYPTFS_MINIMUM_HEADER_EXTENT_SIZE)) {
		rc = -EINVAL;
		printf("%s Invalid header size: [%d]\n", __FUNCTION__,
		       crypt_stat->num_header_bytes_at_front);
	}
	return rc;
}

static int
ecryptfs_parse_tag_3_packet(struct ecryptfs_crypt_stat_user *crypt_stat,
			    unsigned char *data,
			    struct ecryptfs_auth_tok_list **auth_tok_list_head,
			    struct ecryptfs_auth_tok **new_auth_tok,
			    size_t *packet_size, size_t max_packet_size)
{
	(*new_auth_tok) = NULL;
	return 0;
}

static int
ecryptfs_parse_tag_11_packet(unsigned char *data, unsigned char *contents,
			     size_t max_contents_bytes,
			     size_t *tag_11_contents_size,
			     size_t *packet_size, size_t max_packet_size)
{
	return 0;
}

static int
ecryptfs_parse_tag_1_packet(struct ecryptfs_crypt_stat_user *crypt_stat,
			    unsigned char *data,
			    struct ecryptfs_auth_tok_list **auth_tok_list_head,
			    struct ecryptfs_auth_tok **new_auth_tok,
			    size_t *packet_size, size_t max_packet_size)
{
	(*new_auth_tok) = NULL;
	return 0;
}

/**
 * ecryptfs_parse_packet_set
 * @crypt_stat: The cryptographic context
 * @src: Virtual address of region of memory containing the packets
 *
 * Returns Zero if a valid authentication token was retrieved and
 * processed; negative value for file not encrypted or for error
 * conditions.
 */
static int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat_user *crypt_stat,
				     unsigned char *src)
{
	size_t i = 0;
	struct ecryptfs_auth_tok_list *auth_tok_list;
	size_t packet_size;
	struct ecryptfs_auth_tok *new_auth_tok;
	unsigned char sig_tmp_space[ECRYPTFS_SIG_SIZE];
	size_t tag_11_contents_size;
	size_t tag_11_packet_size;
	int next_packet_is_auth_tok_packet = 1;
	int rc = 0;

	while (next_packet_is_auth_tok_packet) {
		size_t max_packet_size = ((4096 - 8) - i);

		switch (src[i]) {
		case ECRYPTFS_TAG_3_PACKET_TYPE:
			rc = ecryptfs_parse_tag_3_packet(
				crypt_stat,
				(unsigned char *)&src[i],
				&crypt_stat->ptr_to_auth_tok_list_head,
				&new_auth_tok, &packet_size, max_packet_size);
			if (rc) {
				printf("%s: Error parsing tag 3 packet; "
				       "rc = [%d]\n", __FUNCTION__, rc);
				rc = -EINVAL;
				goto out_wipe_list;
			}
			i += packet_size;
			rc = ecryptfs_parse_tag_11_packet(
				(unsigned char *)&src[i],
				sig_tmp_space,
				ECRYPTFS_SIG_SIZE,
				&tag_11_contents_size,
				&tag_11_packet_size,
				max_packet_size);
			if (rc) {
				printf("%s: No valid "
				       "(ecryptfs-specific) literal "
				       "packet containing "
				       "authentication token "
				       "signature found after "
				       "tag 3 packet; rc = [%d]\n", __FUNCTION__,
					rc);
				rc = -EINVAL;
				goto out_wipe_list;
			}
			i += tag_11_packet_size;
			if (ECRYPTFS_SIG_SIZE != tag_11_contents_size) {
				printf("%s: Expected "
				       "signature of size [%d]; "
				       "read size [%d]\n", __FUNCTION__,
				       ECRYPTFS_SIG_SIZE,
				       tag_11_contents_size);
				rc = -EINVAL;
				goto out_wipe_list;
			}
			ecryptfs_to_hex(new_auth_tok->token.password.signature,
					sig_tmp_space, tag_11_contents_size);
			new_auth_tok->token.password.signature[
				ECRYPTFS_PASSWORD_SIG_SIZE] = '\0';
			break;
		case ECRYPTFS_TAG_1_PACKET_TYPE:
			rc = ecryptfs_parse_tag_1_packet(
				crypt_stat,
				(unsigned char *)&src[i],
				&crypt_stat->ptr_to_auth_tok_list_head,
				&new_auth_tok, &packet_size, max_packet_size);
			if (rc) {
				printf("%s: Error parsing "
				       "tag 1 packet; rc = [%d]\n", __FUNCTION__,
					rc);
				rc = -EINVAL;
				goto out_wipe_list;
			}
			i += packet_size;
			break;
		case ECRYPTFS_TAG_11_PACKET_TYPE:
			printf("%s: Invalid packet set "
			       "(Tag 11 not allowed by itself)\n", __FUNCTION__);
			rc = -EINVAL;
			goto out_wipe_list;
			break;
		default:
			printf("%s: No packet at offset "
			       "[%d] of the file header; hex value of "
			       "character is [0x%.2x]\n", __FUNCTION__, i,
			       src[i]);
			next_packet_is_auth_tok_packet = 0;
		}
	}
	if (crypt_stat->ptr_to_auth_tok_list_head == NULL) {
		printf("%s: No no valid key packets found in header\n",
		       __FUNCTION__);
		rc = -EINVAL;
		goto out;
	}
	goto out;
out_wipe_list:
	auth_tok_list = crypt_stat->ptr_to_auth_tok_list_head;
	while (auth_tok_list) {
		struct ecryptfs_auth_tok_list *next_auth_tok_list;

		next_auth_tok_list = auth_tok_list->next;
		free(auth_tok_list->auth_tok);
		free(auth_tok_list);
		auth_tok_list = next_auth_tok_list;
	}
	crypt_stat->ptr_to_auth_tok_list_head = NULL;
out:
	return rc;
}

int ecryptfs_parse_stat(struct ecryptfs_crypt_stat_user *crypt_stat, char *buf,
			size_t buf_size)
{
	uint64_t file_size;
	int bytes_read;
	int big_endian;
	int rc = 0;

	if (buf_size < (ECRYPTFS_FILE_SIZE_BYTES
			+ MAGIC_ECRYPTFS_MARKER_SIZE_BYTES
			+ 4)) {
		printf("%s: Invalid metadata size; must have at least [%d] "
		       "bytes; there are only [%d] bytes\n", __FUNCTION__,
		       (ECRYPTFS_FILE_SIZE_BYTES
			+ MAGIC_ECRYPTFS_MARKER_SIZE_BYTES
			+ 4), buf_size);
		rc = -EINVAL;
		goto out;
	}
	memset(crypt_stat, 0, sizeof(*crypt_stat));
	memcpy(&file_size, buf, ECRYPTFS_FILE_SIZE_BYTES);
	buf += ECRYPTFS_FILE_SIZE_BYTES;
	big_endian = host_is_big_endian();
	if (!big_endian)
		file_size = swab64(file_size);
	crypt_stat->file_size = file_size;
	rc = ecryptfs_contains_ecryptfs_marker(buf);
	if (rc != 1) {
		printf("%s: Magic eCryptfs marker not found in header.\n",
		       __FUNCTION__);
		rc = -EINVAL;
		goto out;
	}
	buf += MAGIC_ECRYPTFS_MARKER_SIZE_BYTES;
	rc = ecryptfs_process_flags(crypt_stat, buf, &bytes_read);
	if (rc) {
		printf("%s: Invalid header content.\n", __FUNCTION__);
		goto out;
	}
	buf += bytes_read;
	rc = ecryptfs_parse_header_metadata(crypt_stat, buf, &bytes_read,
					    ECRYPTFS_VALIDATE_HEADER_SIZE);
	if (rc) {
		printf("%s: Invalid header content.\n", __FUNCTION__);
		goto out;
	}
	buf += bytes_read;
/*	rc = ecryptfs_parse_packet_set(crypt_stat, buf); */
out:
	return rc;
}
