#include <stdio.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <hfs/hfsplus.h>
#include <openssl/aes.h>
#include "hfs/hfslib.h"
#include "emf.h"

size_t ConvertHexaCFString(CFStringRef s, uint8_t** bytes)
{
	uint32_t len = CFStringGetLength(s);
	uint8_t* hex = malloc(len+1);
	
	if(hex == NULL)
		return 0;
	
	if(!CFStringGetCString(s, hex, len+1, kCFStringEncodingASCII))
	{
		free(hex);
		return 0;
	}
	size_t size = 0;
	hexToBytes(hex, bytes, &size);
	free(hex);
	return size;
}

void grabClassKey(const void *key, const void *value, void *context)
{
	EMFInfo* emf = (EMFInfo*) context;
	uint8_t* class_key = NULL;
	
	if(CFGetTypeID(key) != CFStringGetTypeID() || CFGetTypeID(value) != CFStringGetTypeID())
		return;
	
	SInt32 clas = CFStringGetIntValue((CFStringRef)key);

	if(clas > 0 && clas <= MAX_CLASS_KEYS && CFStringGetLength((CFStringRef) value) == 64)
	{
		if(ConvertHexaCFString(value, &class_key) == 32)
		{
			AES_set_decrypt_key(class_key, 32*8, &(emf->classKeys[clas-1]));
			free(class_key);
			emf->classKeys_bitset |= 1 << clas;
		}
	}
	
}

EMFInfo* EMF_init(Volume* volume, char* imagePath)
{
	uint8_t* emfk = NULL;
	uint8_t* dkey = NULL;
	
	uint64_t volume_id = *((uint64_t*) (&volume->volumeHeader->finderInfo[6]));
	FLIPENDIAN(volume_id);
	
	if(imagePath == NULL)
		imagePath = ".";
		
	printf("Volume identifier : %llx\n", volume_id);
	printf("Searching for %s/%llx.plist\n", imagePath, volume_id);
	
	CFStringRef path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%llx.plist"), imagePath, volume_id);
	
	CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, FALSE);
	CFRelease(path);
	
	CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, fileURL);
	CFRelease(fileURL);
	
	if(stream == NULL)
	{
		return NULL;
	}
	if(!CFReadStreamOpen(stream))
	{
		fprintf(stderr, "Cannot open file\n");
		return NULL;
	}
	CFPropertyListRef dict = CFPropertyListCreateWithStream(NULL, stream, 0, kCFPropertyListImmutable, NULL, NULL);

	CFRelease(stream);
	
	if (dict == NULL || CFGetTypeID(dict) != CFDictionaryGetTypeID())
		return NULL;
	
	CFStringRef emfHex = CFDictionaryGetValue(dict, CFSTR("EMF"));
	CFStringRef dkeyHex = CFDictionaryGetValue(dict, CFSTR("DKey"));
	CFNumberRef dataVolumeOffset = CFDictionaryGetValue (dict, CFSTR("dataVolumeOffset"));
	
	if (emfHex == NULL || CFGetTypeID(emfHex) != CFStringGetTypeID())
		return NULL;
	if (dkeyHex == NULL || CFGetTypeID(dkeyHex) != CFStringGetTypeID())
		return NULL;
	if (dataVolumeOffset == NULL || CFGetTypeID(dataVolumeOffset) != CFNumberGetTypeID())
		return NULL;

	EMFInfo* emf = malloc(sizeof(EMFInfo));
	
	if(emf == NULL)
		return NULL;

	memset(emf, 0, sizeof(EMFInfo));
	
	emf->volume = volume;
	
	CFNumberGetValue(dataVolumeOffset, kCFNumberLongType, &emf->volume_offset);
	
	printf("Data partition offset = %llx\n", emf->volume_offset);
	
	if(ConvertHexaCFString(emfHex, &emfk) != 32)
	{
		fprintf(stderr, "Invalid EMF key\n");
		free(emf);
		return NULL;
	}
	if(ConvertHexaCFString(dkeyHex, &dkey) != 32)
	{
		fprintf(stderr, "Invalid DKey key\n");
		free(emf);
		return NULL;
	}
	
	AES_set_encrypt_key(emfk, 32*8, &(emf->emfkey));
	AES_set_decrypt_key(dkey, 32*8, &(emf->classKeys[CLASS_DKEY-1]));
	emf->classKeys_bitset |= 1 << CLASS_DKEY;
	
	CFDictionaryRef classKeys = CFDictionaryGetValue(dict, CFSTR("classKeys"));
	
	if(classKeys != NULL && CFGetTypeID(classKeys) == CFDictionaryGetTypeID())
	{
		printf("Reading class keys, NSProtectionComplete files should be decrypted OK\n");
		CFDictionaryApplyFunction(classKeys, grabClassKey, (void*) emf);
	}
	else
	{
		printf("Only NSProtectionNone files will be decrypted\n");
	}
	
	free(emfk);
	free(dkey);
	return emf;
}
