/* FastCRC library code is placed under the MIT license
 * Copyright (c) 2014,2015,2016 Frank Bösing
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

//
// Thanks to:
// - Catalogue of parametrised CRC algorithms, CRC RevEng
// http://reveng.sourceforge.net/crc-catalogue/
//
// - Danjel McGougan (CRC-Table-Generator)
//

#include "Arduino.h"
#if !defined(KINETISK)

#include "FastCRC.h"
#include "FastCRC_cpu.h"
#include "FastCRC_tables.h"


// ================= 7-BIT CRC ===================

/** Constructor
 */
FastCRC7::FastCRC7(){}

/** SMBUS CRC
 * aka CRC-8
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint8_t FastCRC7::crc7_upd(const uint8_t *data, uint16_t datalen)
{
	uint8_t crc = seed;
	if (datalen) do {
		crc = pgm_read_byte(&crc_table_crc7[crc ^ *data]);
		data++;
	} while (--datalen);
	seed = crc;
	return crc >> 1;
}

uint8_t FastCRC7::crc7(const uint8_t *data, const uint16_t datalen)
{
  // poly=0x09 init=0x00 refin=false refout=false xorout=0x00 check=0x75
  seed = 0x00;
  return crc7_upd(data, datalen);
}

// ================= 8-BIT CRC ===================

/** Constructor
 */
FastCRC8::FastCRC8(){}

/** SMBUS CRC
 * aka CRC-8
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint8_t FastCRC8::smbus_upd(const uint8_t *data, uint16_t datalen)
{
	uint8_t crc = seed;
	if (datalen) do {
		crc = pgm_read_byte(&crc_table_smbus[crc ^ *data]);
		data++;
	} while (--datalen);
	seed = crc;
	return crc;
}

uint8_t FastCRC8::smbus(const uint8_t *data, const uint16_t datalen)
{
  // poly=0x07 init=0x00 refin=false refout=false xorout=0x00 check=0xf4
  seed = 0x00;
  return smbus_upd(data, datalen);
}

/** MAXIM 8-Bit CRC
 * equivalent to _crc_ibutton_update() in crc16.h from avr_libc
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint8_t FastCRC8::maxim_upd(const uint8_t *data, uint16_t datalen)
{
	uint8_t crc = seed;
	if (datalen) do {
		crc = pgm_read_byte(&crc_table_maxim[crc ^ *data]);
		data++;
	} while (--datalen);
	seed = crc;
	return crc;
}
uint8_t FastCRC8::maxim(const uint8_t *data, const uint16_t datalen)
{
  // poly=0x31 init=0x00 refin=true refout=true xorout=0x00  check=0xa1
  seed = 0x00;
  return maxim_upd(data, datalen);
}

// ================= 16-BIT CRC ===================
/** Constructor
 */
FastCRC16::FastCRC16(){}

#define crc_n4(crc, data, table) crc ^= data; \
	crc = pgm_read_word(&table[(crc & 0xff) + 0x300]) ^		\
	pgm_read_word(&table[((crc >> 8) & 0xff) + 0x200]) ^	\
	pgm_read_word(&table[((data >> 16) & 0xff) + 0x100]) ^	\
	pgm_read_word(&table[data >> 24]);

/** CCITT
 * Alias "false CCITT"
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint16_t FastCRC16::ccitt_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;
	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_ccitt[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_ccitt);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_ccitt);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_ccitt);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_ccitt);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_ccitt[(crc & 0xff) ^ *data++]);
	}
	
	seed = crc;	
	crc = REV16(crc);
	
	return crc;
}
uint16_t FastCRC16::ccitt(const uint8_t *data,const uint16_t datalen)
{
 // poly=0x1021 init=0xffff refin=false refout=false xorout=0x0000 check=0x29b1
  seed = 0xffff;
  return ccitt_upd(data, datalen);
}

/** MCRF4XX
 * equivalent to _crc_ccitt_update() in crc16.h from avr_libc
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */

uint16_t FastCRC16::mcrf4xx_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_mcrf4xx[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_mcrf4xx);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_mcrf4xx);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_mcrf4xx);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_mcrf4xx);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_mcrf4xx[(crc & 0xff) ^ *data++]);
	}

	seed = crc;
	return crc;
}

uint16_t FastCRC16::mcrf4xx(const uint8_t *data,const uint16_t datalen)
{
 // poly=0x1021 init=0xffff refin=true refout=true xorout=0x0000 check=0x6f91
  seed = 0xffff;
  return mcrf4xx_upd(data, datalen);
}

/** MODBUS
 * equivalent to _crc_16_update() in crc16.h from avr_libc
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint16_t FastCRC16::modbus_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_modbus[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_modbus);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_modbus);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_modbus);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_modbus);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_modbus[(crc & 0xff) ^ *data++]);
	}

	seed = crc;
	return crc;
}

uint16_t FastCRC16::modbus(const uint8_t *data, const uint16_t datalen)
{
 // poly=0x8005 init=0xffff refin=true refout=true xorout=0x0000 check=0x4b37
  seed = 0xffff;
  return modbus_upd(data, datalen);
}

/** KERMIT
 * Alias CRC-16/CCITT, CRC-16/CCITT-TRUE, CRC-CCITT
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint16_t FastCRC16::kermit_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_kermit[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_kermit);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_kermit);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_kermit);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_kermit);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_kermit[(crc & 0xff) ^ *data++]);
	}

	seed = crc;
	return crc;
}

uint16_t FastCRC16::kermit(const uint8_t *data, const uint16_t datalen)
{
 // poly=0x1021 init=0x0000 refin=true refout=true xorout=0x0000 check=0x2189
 // sometimes byteswapped presentation of result
  seed = 0x0000;
  return kermit_upd(data, datalen);
}

/** XMODEM
 * Alias ZMODEM, CRC-16/ACORN
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint16_t FastCRC16::xmodem_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_xmodem[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_xmodem);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_xmodem);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_xmodem);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_xmodem);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_xmodem[(crc & 0xff) ^ *data++]);
	}

	seed = crc;
	crc = REV16(crc);	
	return crc;
}

uint16_t FastCRC16::xmodem(const uint8_t *data, const uint16_t datalen)
{
  //width=16 poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 check=0x31c3
  seed = 0x0000;
  return xmodem_upd(data, datalen);
}

/** X25
 * Alias CRC-16/IBM-SDLC, CRC-16/ISO-HDLC, CRC-B
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
uint16_t FastCRC16::x25_upd(const uint8_t *data, uint16_t len)
{

	uint16_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_x25[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		crc_n4(crc, ((uint32_t *)data)[0], crc_table_x25);
		crc_n4(crc, ((uint32_t *)data)[1], crc_table_x25);
		crc_n4(crc, ((uint32_t *)data)[2], crc_table_x25);
		crc_n4(crc, ((uint32_t *)data)[3], crc_table_x25);
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_word(&crc_table_x25[(crc & 0xff) ^ *data++]);
	}

	crc = ~crc;

	seed = crc;
	return crc;
}

uint16_t FastCRC16::x25(const uint8_t *data, const uint16_t datalen)
{
  // poly=0x1021 init=0xffff refin=true refout=true xorout=0xffff check=0x906e
  seed = 0xffff;
  return x25_upd(data, datalen);
}





// ================= 32-BIT CRC ===================
/** Constructor
 */
FastCRC32::FastCRC32(){}

#define crc_n4d(crc, data, table) crc ^= data; \
	crc = pgm_read_dword(&table[(crc & 0xff) + 0x300]) ^	\
	pgm_read_dword(&table[((crc >> 8) & 0xff) + 0x200]) ^	\
	pgm_read_dword(&table[((crc >> 16) & 0xff) + 0x100]) ^	\
	pgm_read_dword(&table[(crc >> 24) & 0xff]);
	
#define crcsm_n4d(crc, data, table) crc ^= data; \
	crc = (crc >> 8) ^ pgm_read_dword(&table[crc & 0xff]); \
	crc = (crc >> 8) ^ pgm_read_dword(&table[crc & 0xff]); \
	crc = (crc >> 8) ^ pgm_read_dword(&table[crc & 0xff]); \
	crc = (crc >> 8) ^ pgm_read_dword(&table[crc & 0xff]);

/** CRC32
 * Alias CRC-32/ADCCP, PKZIP, Ethernet, 802.3
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
#if CRC_BIGTABLES
#define CRC_TABLE_CRC32 crc_table_crc32_big
#else
#define CRC_TABLE_CRC32 crc_table_crc32
#endif

uint32_t FastCRC32::crc32_upd(const uint8_t *data, uint16_t len, bool reflect)
{

	uint32_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_dword(&CRC_TABLE_CRC32[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		#if CRC_BIGTABLES
		crc_n4d(crc, ((uint32_t *)data)[0], CRC_TABLE_CRC32);
		crc_n4d(crc, ((uint32_t *)data)[1], CRC_TABLE_CRC32);
		crc_n4d(crc, ((uint32_t *)data)[2], CRC_TABLE_CRC32);
		crc_n4d(crc, ((uint32_t *)data)[3], CRC_TABLE_CRC32);
		#else
		crcsm_n4d(crc, ((uint32_t *)data)[0], CRC_TABLE_CRC32);
		crcsm_n4d(crc, ((uint32_t *)data)[1], CRC_TABLE_CRC32);
		crcsm_n4d(crc, ((uint32_t *)data)[2], CRC_TABLE_CRC32);
		crcsm_n4d(crc, ((uint32_t *)data)[3], CRC_TABLE_CRC32);
		#endif
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_dword(&CRC_TABLE_CRC32[(crc & 0xff) ^ *data++]);
	}

	if(reflect) { crc = ~crc; }
	

	seed = crc;
	return crc;
}

uint32_t FastCRC32::crc32(const uint8_t *data, const uint16_t datalen, bool reflect)
{
  // poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0xcbf43926
  seed = 0xffffffff;
  return crc32_upd(data, datalen, reflect);
}

/** CKSUM
 * Alias CRC-32/POSIX
 * @param data Pointer to Data
 * @param datalen Length of Data
 * @return CRC value
 */
 #if CRC_BIGTABLES
#define CRC_TABLE_CKSUM crc_table_cksum_big
#else
#define CRC_TABLE_CKSUM crc_table_cksum
#endif
uint32_t FastCRC32::cksum_upd(const uint8_t *data, uint16_t len)
{

	uint32_t crc = seed;

	while (((uintptr_t)data & 3) && len) {
		crc = (crc >> 8) ^ pgm_read_dword(&CRC_TABLE_CKSUM[(crc & 0xff) ^ *data++]);
		len--;
	}

	while (len >= 16) {
		len -= 16;
		#if CRC_BIGTABLES
		crc_n4d(crc, ((uint32_t *)data)[0], CRC_TABLE_CKSUM);
		crc_n4d(crc, ((uint32_t *)data)[1], CRC_TABLE_CKSUM);
		crc_n4d(crc, ((uint32_t *)data)[2], CRC_TABLE_CKSUM);
		crc_n4d(crc, ((uint32_t *)data)[3], CRC_TABLE_CKSUM);
		#else
		crcsm_n4d(crc, ((uint32_t *)data)[0], CRC_TABLE_CKSUM);
		crcsm_n4d(crc, ((uint32_t *)data)[1], CRC_TABLE_CKSUM);
		crcsm_n4d(crc, ((uint32_t *)data)[2], CRC_TABLE_CKSUM);
		crcsm_n4d(crc, ((uint32_t *)data)[3], CRC_TABLE_CKSUM);
		#endif
		data += 16;
	}

	while (len--) {
		crc = (crc >> 8) ^ pgm_read_dword(&CRC_TABLE_CKSUM[(crc & 0xff) ^ *data++]);
	}
	
	seed = crc;
	crc = ~REV32(crc);	
	return crc;
}

uint32_t FastCRC32::cksum(const uint8_t *data, const uint16_t datalen)
{
  // width=32 poly=0x04c11db7 init=0x00000000 refin=false refout=false xorout=0xffffffff check=0x765e7680
  seed = 0x00;
  return cksum_upd(data, datalen);
}

#endif // #if !defined(KINETISK)
