#define MODULE_LOG_PREFIX "emu"

#include "globals.h"

#ifdef WITH_EMU

#include "cscrypt/des.h"
#include "module-emulator-osemu.h"
#include "oscam-aes.h"
#include "oscam-string.h"

/*************************************************************************************************/

// Shared functions

static uint16_t calculate_checksum(uint8_t *data, uint8_t length)
{
	/*
	 * ECM and EMM checksum calculation
	 * 1. Combine data in 2 byte groups
	 * 2. Add them together
	 * 3. Multiply result by itself (power of 7)
	 * 4. XOR with fixed value 0x17E3
	*/

	uint8_t i;
	uint16_t checksum = 0;

	for (i = 0; i < length; i += 2)
	{
		checksum += (data[i] << 8) | data[i + 1];
	}

	checksum = checksum * checksum * checksum * checksum * checksum * checksum * checksum;
	checksum ^= 0x17E3;

	return checksum;
}

static inline int8_t get_key(uint32_t keyIndex, char *keyName, uint8_t *key, uint32_t keyLength)
{
	/*
	 * keyIndex meaning for:
	 * ecm keys --> entitlementId
	 * emm keys --> aeskeyIndex
	 * aes keys --> keyIndex
	 *
	 * keyName meaning for:
	 * ecm keys --> "01"
	 * emm keys --> "MK" or "MK01"
	 * aes keys --> "AES"
	*/

	return emu_find_key('T', keyIndex, 0, keyName, key, keyLength, 1, 0, 0, NULL);
}

/*************************************************************************************************/

/*
 * Director ECM emulator
 * Supported versions: v4, v5, v6 (not working correctly)
*/

int8_t director_ecm(uint8_t *ecm, uint8_t *dw)
{
	uint8_t nanoType, nanoLength;
	uint8_t *nanoData;
	uint32_t pos = 3;
	uint32_t entitlementId;
	uint32_t ks[32];
	uint8_t ecmKey[8];
	uint16_t ecmLen = SCT_LEN(ecm);

	if (ecmLen < 5)
	{
		return EMU_NOT_SUPPORTED;
	}

	do
	{
		nanoType = ecm[pos];
		nanoLength = ecm[pos + 1];

		if (pos + 2 + nanoLength > ecmLen)
		{
			break;
		}

		nanoData = ecm + pos + 2;

		// ECM validation
		uint16_t payloadChecksum = (nanoData[nanoLength - 2] << 8) | nanoData[nanoLength - 1];
		uint16_t calculatedChecksum = calculate_checksum(nanoData, nanoLength - 2);

		if (calculatedChecksum != payloadChecksum)
		{
			cs_log_dbg(D_READER, "ECM checksum error (%.4X instead of %.4X)", calculatedChecksum, payloadChecksum);
			return EMU_CHECKSUM_ERROR;
		}
		// End of ECM validation

		switch (nanoType)
		{
			case 0xEC: // Director v6 (September 2017)
			{
				if (nanoLength != 0x28)
				{
					cs_log_dbg(D_READER, "WARNING: nanoType EC length (%d) != %d", nanoLength, 0x28);
					break;
				}

				entitlementId = b2i(4, nanoData);
				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);

				if (!get_key(entitlementId, "01", ecmKey, 8))
				{
					return EMU_KEY_NOT_FOUND;
				}

				// Step 1 - Decrypt DES CBC with ecmKey and iv = { 0 } (equal to nanoED)
				uint8_t encryptedData[32] = { 0 };
				memcpy(encryptedData, nanoData + 6, 32);

				uint8_t iv[8] = { 0 };
				des_cbc_decrypt(encryptedData, iv, ecmKey, 32);

				uint8_t nanoMode = nanoData[5];

				if ((nanoMode & 0x20) == 0) // Old algo
				{
					// Step 2 - Create CW (equal to nano ED)
					dw[0] = encryptedData[0x05];
					dw[1] = encryptedData[0x19];
					dw[2] = encryptedData[0x1D];
					dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
					dw[4] = encryptedData[0x0B];
					dw[5] = encryptedData[0x12];
					dw[6] = encryptedData[0x1A];
					dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
					dw[8] = encryptedData[0x16];
					dw[9] = encryptedData[0x03];
					dw[10] = encryptedData[0x11];
					dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
					dw[12] = encryptedData[0x18];
					dw[13] = encryptedData[0x10];
					dw[14] = encryptedData[0x0E];
					dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;

					return EMU_OK;
				}
				else // New algo (overencryption with AES)
				{
					// Step 2 - Prepare data for AES (it is like the creation of CW in nanoED but swapped each 8 bytes)
					uint8_t dataEC[16] = { 0 };

					dataEC[0] = encryptedData[0x02];
					dataEC[1] = encryptedData[0x0E];
					dataEC[2] = encryptedData[0x10];
					dataEC[3] = encryptedData[0x18];
					dataEC[4] = encryptedData[0x09];
					dataEC[5] = encryptedData[0x11];
					dataEC[6] = encryptedData[0x03];
					dataEC[7] = encryptedData[0x16];

					dataEC[8] = encryptedData[0x13];
					dataEC[9] = encryptedData[0x1A];
					dataEC[10] = encryptedData[0x12];
					dataEC[11] = encryptedData[0x0B];
					dataEC[12] = encryptedData[0x04];
					dataEC[13] = encryptedData[0x1D];
					dataEC[14] = encryptedData[0x19];
					dataEC[15] = encryptedData[0x05];

					// Step 3 - Decrypt AES CBC with new aesKey and iv 2EBD816A5E749A708AE45ADDD84333DE
					uint8_t aesKeyIndex = nanoMode & 0x1F; // 32 possible AES keys
					uint8_t aesKey[16] = { 0 };

					char tmpBuffer[33];
					cs_hexdump(0, aesKey, 16, tmpBuffer, sizeof(tmpBuffer));
					cs_log_dbg(D_READER, "INFO: Using AES key index: %02X, value: %s", aesKeyIndex, tmpBuffer);

					if (!get_key(aesKeyIndex, "AES", aesKey, 16))
					{
						return EMU_KEY_NOT_FOUND;
					}

					struct aes_keys aes;
					aes_set_key(&aes, (char *)aesKey);

					uint8_t ivAes[16] = { 0x2E, 0xBD, 0x81, 0x6A, 0x5E, 0x74, 0x9A, 0x70, 0x8A, 0xE4, 0x5A, 0xDD, 0xD8, 0x43, 0x33, 0xDE };
					aes_cbc_decrypt(&aes, dataEC, 16, ivAes);

					// Step 4 - Create CW (a simple swap)
					uint8_t offset;
					for (offset = 0; offset < 16; offset++)
					{
						dw[offset] = dataEC[15 - offset];
					}

					return EMU_OK;
				}
			}

			case 0xED: // Director v5 (September 2016)
			{
				if (nanoLength != 0x26)
				{
					cs_log_dbg(D_READER, "WARNING: nanoType ED length (%d) != %d", nanoLength, 0x26);
					break;
				}

				entitlementId = b2i(4, nanoData);
				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);

				if (!get_key(entitlementId, "01", ecmKey, 8))
				{
					return EMU_KEY_NOT_FOUND;
				}

				uint8_t encryptedData[32] = { 0 };
				memcpy(encryptedData, nanoData + 4, 32);

				uint8_t iv[8] = { 0 };
				des_cbc_decrypt(encryptedData, iv, ecmKey, 32);

				dw[0] = encryptedData[0x05];
				dw[1] = encryptedData[0x19];
				dw[2] = encryptedData[0x1D];
				dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
				dw[4] = encryptedData[0x0B];
				dw[5] = encryptedData[0x12];
				dw[6] = encryptedData[0x1A];
				dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
				dw[8] = encryptedData[0x16];
				dw[9] = encryptedData[0x03];
				dw[10] = encryptedData[0x11];
				dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
				dw[12] = encryptedData[0x18];
				dw[13] = encryptedData[0x10];
				dw[14] = encryptedData[0x0E];
				dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;

				return EMU_OK;
			}

			case 0xEE: // Director v4
			{
				if (nanoLength != 0x16)
				{
					cs_log_dbg(D_READER, "WARNING: nanoType EE length (%d) != %d", nanoLength, 0x16);
					break;
				}

				entitlementId = b2i(4, nanoData);
				cs_log_dbg(D_READER, "INFO: Using entitlement id %.4X", entitlementId);

				if (!get_key(entitlementId, "01", ecmKey, 8))
				{
					return EMU_KEY_NOT_FOUND;
				}

				memcpy(dw, nanoData + 4 + 8, 8); // even
				memcpy(dw + 8, nanoData + 4, 8); // odd

				des_set_key(ecmKey, ks);

				des(dw, ks, 0);
				des(dw + 8, ks, 0);

				dw[3] = (dw[0] + dw[1] + dw[2]) & 0xFF;
				dw[7] = (dw[4] + dw[5] + dw[6]) & 0xFF;
				dw[11] = (dw[8] + dw[9] + dw[10]) & 0xFF;
				dw[15] = (dw[12] + dw[13] + dw[14]) & 0xFF;

				return EMU_OK;
			}

			default:
				cs_log_dbg(D_READER, "WARNING: nanoType %.2X not supported", nanoType);
				return EMU_NOT_SUPPORTED;
		}

		pos += 2 + nanoLength;

	} while (pos < ecmLen);

	return EMU_NOT_SUPPORTED;
}

/*************************************************************************************************/

/*
 * Director EMM emulator
 * Supported versions: v4, v5, v6 (same as v5)
*/

static const uint8_t MixTable[] =
{
	0x12, 0x78, 0x4B, 0x19, 0x13, 0x80, 0x2F, 0x84, 0x86, 0x4C, 0x09, 0x53, 0x15, 0x79, 0x6B, 0x49,
	0x10, 0x4D, 0x33, 0x43, 0x18, 0x37, 0x83, 0x38, 0x82, 0x1B, 0x6E, 0x24, 0x2A, 0x85, 0x3C, 0x3D,
	0x5A, 0x58, 0x55, 0x5D, 0x20, 0x41, 0x65, 0x51, 0x0C, 0x45, 0x63, 0x7F, 0x0F, 0x46, 0x21, 0x7C,
	0x2C, 0x61, 0x7E, 0x0A, 0x42, 0x57, 0x35, 0x16, 0x87, 0x3B, 0x4F, 0x40, 0x34, 0x22, 0x26, 0x74,
	0x32, 0x69, 0x44, 0x7A, 0x6A, 0x6D, 0x0D, 0x56, 0x23, 0x2B, 0x5C, 0x72, 0x76, 0x36, 0x28, 0x25,
	0x2E, 0x52, 0x5B, 0x6C, 0x7D, 0x30, 0x0B, 0x5E, 0x47, 0x1F, 0x7B, 0x31, 0x3E, 0x11, 0x77, 0x1E,
	0x60, 0x75, 0x54, 0x27, 0x50, 0x17, 0x70, 0x59, 0x1A, 0x2D, 0x4A, 0x67, 0x3A, 0x5F, 0x68, 0x08,
	0x4E, 0x3F, 0x29, 0x6F, 0x81, 0x71, 0x39, 0x64, 0x48, 0x66, 0x73, 0x14, 0x0E, 0x1D, 0x62, 0x1C
};

/*
static void rotate_bytes(uint8_t *in, int8_t n)
{
	if (n > 1)
	{
		uint8_t *e = in + n - 1;
		do
		{
			uint8_t temp = *in;
			*in++ = *e;
			*e-- = temp;
		}
		while (in < e);
	}
}
*/

static void decrypt_ecm_key(uint8_t *emmKey, uint8_t *tagData, uint8_t *ecmKey)
{
	uint8_t temp, *e, *payLoad, iv[8] = { 0 };

	//rotate_bytes(emmKey, 8);

	e = emmKey + 8 - 1;
	do
	{
		temp = *emmKey;
		*emmKey++ = *e;
		*e-- = temp;
	}
	while (emmKey < e);

	payLoad = tagData + 4 + 5;
	des_cbc_decrypt(payLoad, iv, emmKey, 16);

	ecmKey[0] = payLoad[0x0F];
	ecmKey[1] = payLoad[0x01];
	ecmKey[2] = payLoad[0x0B];
	ecmKey[3] = payLoad[0x03];
	ecmKey[4] = payLoad[0x0E];
	ecmKey[5] = payLoad[0x04];
	ecmKey[6] = payLoad[0x0A];
	ecmKey[7] = payLoad[0x08];
}

static int8_t parse_emm_nano_tags(uint8_t *data, uint32_t length, uint8_t keyIndex, uint32_t *keysAdded)
{
	uint8_t tagType, tagLength, *tagData, blockIndex, emmKey[8], tagDataDecrypted[16][8];
	uint32_t pos = 0, entitlementId, ks[32];
	int32_t i, k;
	char keyValue[17];

	if (length < 2)
	{
		return EMU_NOT_SUPPORTED;
	}

	while (pos < length)
	{
		tagType = data[pos];
		tagLength = data[pos+1];

		if (pos + 2 + tagLength > length)
		{
			return EMU_CORRUPT_DATA;
		}

		tagData = data + pos + 2;

		switch (tagType)
		{
			case 0xE4: // EMM_TAG_SECURITY_TABLE_DESCRIPTOR (ram emm keys)
			{
				uint8_t tagMode = data[pos + 2];

				switch (tagMode)
				{
					case 0x01: // keySet 01 (MK01)
					{
						if (tagLength != 0x8A)
						{
							cs_log_dbg(D_READER, "WARNING: nanoTag E4 length (%d) != %d", tagLength, 0x8A);
							return EMU_NOT_SUPPORTED;
						}

						if (!get_key(keyIndex, "MK01", emmKey, 8))
						{
							return EMU_KEY_NOT_FOUND;
						}

						uint8_t iv[8] = { 0 };
						uint8_t *tagPayload = tagData + 2;
						des_cbc_decrypt(tagPayload, iv, emmKey, 136);

						for (k = 0; k < 16; k++) // loop 16 keys
						{
							for (i = 0; i < 8; i++) // loop 8 bytes of key
							{
								tagDataDecrypted[k][i] = tagPayload[MixTable[8 * k + i]];
							}
						}

						blockIndex = tagData[1] & 0x03;

						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
						for (i = 0; i < 16; i++)
						{
							emu_set_key('T', (blockIndex << 4) + i, "MK01", tagDataDecrypted[i], 8, 0, NULL, NULL);
						}
						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
					}
					break;

					case 0xFF: // keySet FF (MK)
					{
						if (tagLength != 0x82)
						{
							cs_log_dbg(D_READER, "WARNING: nanoTag E4 length (%d) != %d", tagLength, 0x82);
							return EMU_NOT_SUPPORTED;
						}

						if (!get_key(keyIndex, "MK", emmKey, 8))
						{
							return EMU_KEY_NOT_FOUND;
						}

						des_set_key(emmKey, ks);

						for (i = 0; i < 16; i++)
						{
							des(tagData + 2 + (i * 8), ks, 0);
						}

						blockIndex = tagData[1] & 0x03;

						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
						for (i = 0; i < 16; i++)
						{
							emu_set_key('T', (blockIndex << 4) + i, "MK", tagData + 2 + (i * 8), 8, 0, NULL, NULL);
						}
						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
					}
					break;

					default:
						cs_log_dbg(D_READER, "WARNING: nanoTag E4 mode %.2X not supported", tagMode);
						return EMU_NOT_SUPPORTED;
				}
				break;
			}

			case 0xE1: // EMM_TAG_EVENT_ENTITLEMENT_DESCRIPTOR (ecm keys)
			{
				uint8_t tagMode = data[pos + 2 + 4];

				switch (tagMode)
				{
					case 0x00: // ecm keys from mode FF
					{
						if (tagLength != 0x12)
						{
							cs_log_dbg(D_READER, "WARNING: nanoTag E1 length (%d) != %d", tagLength, 0x12);
							return EMU_NOT_SUPPORTED;
						}

						entitlementId = b2i(4, tagData);

						if (!get_key(keyIndex, "MK", emmKey, 8))
						{
							return EMU_KEY_NOT_FOUND;
						}

						des_set_key(emmKey, ks);
						des(tagData + 4 + 5, ks, 0);
						uint8_t ecmKeyChk[1] = { 0 };
						memcpy(ecmKeyChk, tagData + 4 + 5 + 7, 1);

						if (ecmKeyChk[0] != 0x00) // check if key looks valid (last byte 0x00)
						{
							cs_log_dbg(D_READER, "Key rejected from EMM (looks invalid)");
							return EMU_KEY_REJECTED;
						}

						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
						if (emu_update_key('T', entitlementId, "01", tagData + 4 + 5, 8, 1, NULL))
						{
							(*keysAdded)++;
							cs_hexdump(0, tagData + 4 + 5, 8, keyValue, sizeof(keyValue));
							cs_log("Key found in EMM: T %.8X 01 %s", entitlementId, keyValue);
						}
						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
					}
					break;

					case 0x01: // ecm keys from mode 01
					{
						if (tagLength != 0x1A)
						{
							cs_log_dbg(D_READER, "WARNING: nanoTag E1 length (%d) != %d", tagLength, 0x1A);
							return EMU_NOT_SUPPORTED;
						}

						entitlementId = b2i(4, tagData);

						if (!get_key(keyIndex, "MK01", emmKey, 8))
						{
							return EMU_KEY_NOT_FOUND;
						}

						uint8_t ecmKey[8] = { 0 };
						decrypt_ecm_key(emmKey, tagData, ecmKey);

						if (ecmKey[7] != 0x00) // check if key looks valid (last byte 0x00)
						{
							cs_log_dbg(D_READER, "Key rejected from EMM (looks invalid)");
							return EMU_KEY_REJECTED;
						}

						SAFE_MUTEX_LOCK(&emu_key_data_mutex);
						if (emu_update_key('T', entitlementId, "01", ecmKey, 8, 1, NULL))
						{
							(*keysAdded)++;
							cs_hexdump(0, ecmKey, 8, keyValue, sizeof(keyValue));
							cs_log("Key found in EMM: T %.8X 01 %s", entitlementId, keyValue);
						}
						SAFE_MUTEX_UNLOCK(&emu_key_data_mutex);
					}
					break;

					default:
						cs_log_dbg(D_READER, "WARNING: nanoTag E1 mode %.2X not supported", tagMode);
						return EMU_NOT_SUPPORTED;
				}
				break;
			}

			default:
				cs_log_dbg(D_READER, "WARNING: nanoTag %.2X not supported", tagType);
				return EMU_NOT_SUPPORTED;
		}

		pos += 2 + tagLength;
	}

	return EMU_OK;
}

static int8_t parse_emm_nano_data(uint8_t *data, uint32_t *nanoLength, uint32_t maxLength,
									uint8_t keyIndex, uint32_t *keysAdded)
{
	uint32_t pos = 0;
	uint16_t sectionLength;
	int8_t ret = EMU_OK;

	if (maxLength < 2)
	{
		(*nanoLength) = 0;
		return EMU_NOT_SUPPORTED;
	}

	sectionLength = ((data[pos] << 8) | data[pos + 1]) & 0x0FFF;

	if (pos + 2 + sectionLength > maxLength)
	{
		(*nanoLength) = pos;
		return EMU_CORRUPT_DATA;
	}

	ret = parse_emm_nano_tags(data + pos + 2, sectionLength, keyIndex, keysAdded);

	pos += 2 + sectionLength;

	(*nanoLength) = pos;
	return ret;
}

int8_t director_emm(uint8_t *emm, uint32_t *keysAdded)
{
	uint8_t keyIndex, ret = EMU_OK;
	uint16_t emmLen = SCT_LEN(emm);
	uint32_t pos = 3;
	uint32_t permissionDataType;
	uint32_t nanoLength = 0;

	while (pos < emmLen && !ret)
	{
		permissionDataType = emm[pos];

		switch (permissionDataType)
		{
			case 0x00:
				break;

			case 0x01:
				pos += 0x0A;
				break;

			case 0x02:
				pos += 0x26;
				break;

			default:
				cs_log_dbg(D_READER, "ERROR: unknown permissionDataType %.2X (pos: %d)", permissionDataType, pos);
				return EMU_NOT_SUPPORTED;
		}

		if (pos + 6 >= emmLen)
		{
			return EMU_CORRUPT_DATA;
		}

		keyIndex = emm[pos + 1];

		// EMM validation
		// Copy payload checksum bytes and then set them to zero,
		// so they do not affect the calculated checksum.
		uint16_t payloadChecksum = (emm[pos + 2] << 8) | emm[pos + 3];
		memset(emm + pos + 2, 0, 2);
		uint16_t calculatedChecksum = calculate_checksum(emm + 3, emmLen - 3);

		if (calculatedChecksum != payloadChecksum)
		{
			cs_log_dbg(D_READER, "EMM checksum error (%.4X instead of %.4X)", calculatedChecksum, payloadChecksum);
			return EMU_CHECKSUM_ERROR;
		}
		// End of EMM validation

		pos += 0x04;
		ret = parse_emm_nano_data(emm + pos, &nanoLength, emmLen - pos, keyIndex, keysAdded);
		pos += nanoLength;
	}

	return ret;
}

#endif // WITH_EMU
