/*
 * Copyright (c) 2007-2016 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef VFS_HAMMER_CRC_H_
#define VFS_HAMMER_CRC_H_

#include "hammer_disk.h"
#include "hammer_ioctl.h"

#ifndef _KERNEL
/*
 * These are only for userspace.
 * Userspace can't include sys/sys/systm.h.
 */
uint32_t crc32(const void *buf, size_t size);
uint32_t crc32_ext(const void *buf, size_t size, uint32_t ocrc);
uint32_t iscsi_crc32(const void *buf, size_t size);
uint32_t iscsi_crc32_ext(const void *buf, size_t size, uint32_t ocrc);
#endif

/*
 * hammer_datacrc() expects vol_version to be the volume version of the
 * currently mounted HAMMER filesystem to do either of the following.
 * If vol_version is <= 6, do crc32() as it did in the past.
 * If vol_version is >= 7, do iscsi_crc32().
 *
 * hammer_crc_test_xxx() first tests CRC using hammer_datacrc().
 * If vol_version is <= 6, it's likely to match crc32().
 * If vol_version is >= 7 and was created using the current newfs_hammer,
 * it's likely to match iscsi_crc32().  If it's been upgraded, the CRC
 * generated by old version(s) doesn't match iscsi_crc32(), and requires
 * retry.  This means there is overhead until CRCs for majority of data
 * and metadata are upgraded.
 *
 * Once the volume is upgraded, all the newly created data and metadata
 * are initialized with version 7 CRC.  Reblocking the filesystem can
 * upgrade CRCs upon moving data and metadata.
 */

#define hammer_datacrc(vol_version, buf, size)			\
	(((vol_version) >= HAMMER_VOL_VERSION_SEVEN) ?		\
	 iscsi_crc32(buf, size) : crc32(buf, size))

#define hammer_datacrc_ext(vol_version, buf, size, ocrc)	\
	(((vol_version) >= HAMMER_VOL_VERSION_SEVEN) ?		\
	 iscsi_crc32_ext(buf, size, ocrc) : crc32_ext(buf, size, ocrc))

/*
 * Blockmap
 */
static __inline hammer_crc_t
hammer_crc_get_blockmap(uint32_t vol_version, hammer_blockmap_t blockmap)
{
	return(hammer_datacrc(vol_version, blockmap, HAMMER_BLOCKMAP_CRCSIZE));
}

static __inline void
hammer_crc_set_blockmap(uint32_t vol_version, hammer_blockmap_t blockmap)
{
	blockmap->entry_crc = hammer_crc_get_blockmap(vol_version, blockmap);
}

static __inline int
hammer_crc_test_blockmap(uint32_t vol_version, hammer_blockmap_t blockmap)
{
	if (blockmap->entry_crc == hammer_crc_get_blockmap(vol_version, blockmap))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (blockmap->entry_crc == hammer_crc_get_blockmap(
			HAMMER_VOL_VERSION_SIX, blockmap))
			return(1);
	}
	return(0);
}

/*
 * Layer1
 */
static __inline hammer_crc_t
hammer_crc_get_layer1(uint32_t vol_version, hammer_blockmap_layer1_t layer1)
{
	return(hammer_datacrc(vol_version, layer1, HAMMER_LAYER1_CRCSIZE));
}

static __inline void
hammer_crc_set_layer1(uint32_t vol_version, hammer_blockmap_layer1_t layer1)
{
	layer1->layer1_crc = hammer_crc_get_layer1(vol_version, layer1);
}

static __inline int
hammer_crc_test_layer1(uint32_t vol_version, hammer_blockmap_layer1_t layer1)
{
	if (layer1->layer1_crc == hammer_crc_get_layer1(vol_version, layer1))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (layer1->layer1_crc == hammer_crc_get_layer1(
			HAMMER_VOL_VERSION_SIX, layer1))
			return(1);
	}
	return(0);
}

/*
 * Layer2
 */
static __inline hammer_crc_t
hammer_crc_get_layer2(uint32_t vol_version, hammer_blockmap_layer2_t layer2)
{
	return(hammer_datacrc(vol_version, layer2, HAMMER_LAYER2_CRCSIZE));
}

static __inline void
hammer_crc_set_layer2(uint32_t vol_version, hammer_blockmap_layer2_t layer2)
{
	layer2->entry_crc = hammer_crc_get_layer2(vol_version, layer2);
}

static __inline int
hammer_crc_test_layer2(uint32_t vol_version, hammer_blockmap_layer2_t layer2)
{
	if (layer2->entry_crc == hammer_crc_get_layer2(vol_version, layer2))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (layer2->entry_crc == hammer_crc_get_layer2(
			HAMMER_VOL_VERSION_SIX, layer2))
			return(1);
	}
	return(0);
}

/*
 * Volume
 */
static __inline hammer_crc_t
hammer_crc_get_volume(uint32_t vol_version, hammer_volume_ondisk_t ondisk)
{
	return(hammer_datacrc(vol_version, ondisk, HAMMER_VOL_CRCSIZE1) ^
		hammer_datacrc(vol_version, &ondisk->vol_crc + 1, HAMMER_VOL_CRCSIZE2));
}

static __inline void
hammer_crc_set_volume(uint32_t vol_version, hammer_volume_ondisk_t ondisk)
{
	ondisk->vol_crc = hammer_crc_get_volume(vol_version, ondisk);
}

static __inline int
hammer_crc_test_volume(uint32_t vol_version, hammer_volume_ondisk_t ondisk)
{
	if (ondisk->vol_crc == hammer_crc_get_volume(vol_version, ondisk))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (ondisk->vol_crc == hammer_crc_get_volume(
			HAMMER_VOL_VERSION_SIX, ondisk))
			return(1);
	}
	return(0);
}

/*
 * FIFO head
 */
static __inline hammer_crc_t
hammer_crc_get_fifo_head(uint32_t vol_version, hammer_fifo_head_t head, int bytes)
{
	return(hammer_datacrc(vol_version, head, HAMMER_FIFO_HEAD_CRCOFF) ^
		hammer_datacrc(vol_version, head + 1, bytes - sizeof(*head)));
}

static __inline void
hammer_crc_set_fifo_head(uint32_t vol_version, hammer_fifo_head_t head, int bytes)
{
	head->hdr_crc = hammer_crc_get_fifo_head(vol_version, head, bytes);
}

static __inline int
hammer_crc_test_fifo_head(uint32_t vol_version, hammer_fifo_head_t head, int bytes)
{
	if (head->hdr_crc == hammer_crc_get_fifo_head(vol_version, head, bytes))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (head->hdr_crc == hammer_crc_get_fifo_head(
			HAMMER_VOL_VERSION_SIX, head, bytes))
			return(1);
	}
	return(0);
}

/*
 * B-Tree node
 */
static __inline hammer_crc_t
hammer_crc_get_btree(uint32_t vol_version, hammer_node_ondisk_t node)
{
	return(hammer_datacrc(vol_version, &node->crc + 1, HAMMER_BTREE_CRCSIZE));
}

static __inline void
hammer_crc_set_btree(uint32_t vol_version, hammer_node_ondisk_t node)
{
	node->crc = hammer_crc_get_btree(vol_version, node);
}

static __inline int
hammer_crc_test_btree(uint32_t vol_version, hammer_node_ondisk_t node)
{
	if (node->crc == hammer_crc_get_btree(vol_version, node))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (node->crc == hammer_crc_get_btree(
			HAMMER_VOL_VERSION_SIX, node))
			return(1);
	}
	return(0);
}

/*
 * B-Tree leaf elm
 */
/*
 * Get the leaf->data_crc field.  Deal with any special cases given
 * a generic B-Tree leaf element and its data.
 *
 * NOTE: Inode-data: the atime and mtime fields are not CRCd,
 *       allowing them to be updated in-place.
 */
static __inline hammer_crc_t
hammer_crc_get_leaf(uint32_t vol_version, void *data, hammer_btree_leaf_elm_t leaf)
{
	hammer_crc_t crc;

	if (leaf->data_len == 0)
		return(0);

	switch(leaf->base.rec_type) {
	case HAMMER_RECTYPE_INODE:
		if (leaf->data_len != sizeof(struct hammer_inode_data))
			return(0);  /* This shouldn't happen */
		crc = hammer_datacrc(vol_version, data, HAMMER_INODE_CRCSIZE);
		break;
	default:
		crc = hammer_datacrc(vol_version, data, leaf->data_len);
		break;
	}
	return(crc);
}

static __inline void
hammer_crc_set_leaf(uint32_t vol_version, void *data, hammer_btree_leaf_elm_t leaf)
{
#ifdef _KERNEL
#ifdef INVARIANTS
	if (leaf->data_len && leaf->base.rec_type == HAMMER_RECTYPE_INODE)
		KKASSERT(leaf->data_len == sizeof(struct hammer_inode_data));
#endif
#endif
	leaf->data_crc = hammer_crc_get_leaf(vol_version, data, leaf);
}

static __inline int
hammer_crc_test_leaf(uint32_t vol_version, void *data, hammer_btree_leaf_elm_t leaf)
{
	if (leaf->data_crc == hammer_crc_get_leaf(vol_version, data, leaf))
		return(1);
	if (vol_version >= HAMMER_VOL_VERSION_SEVEN) {
		if (leaf->data_crc == hammer_crc_get_leaf(
			HAMMER_VOL_VERSION_SIX, data, leaf))
			return(1);
	}
	return(0);
}

/*
 * Mirror record head
 */
static __inline hammer_crc_t
hammer_crc_get_mrec_head(struct hammer_ioc_mrecord_head *head, int bytes)
{
	return(crc32(&head->rec_size, bytes - HAMMER_MREC_CRCOFF));
}

static __inline void
hammer_crc_set_mrec_head(struct hammer_ioc_mrecord_head *head, int bytes)
{
	head->rec_crc = hammer_crc_get_mrec_head(head, bytes);
}

static __inline int
hammer_crc_test_mrec_head(struct hammer_ioc_mrecord_head *head, int bytes)
{
	return(head->rec_crc == hammer_crc_get_mrec_head(head, bytes));
}

#endif /* !VFS_HAMMER_CRC_H_ */
