/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre - 2005
 *					All rights reserved
 *
 *  This file is part of GPAC / Authoring Tools sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */



#include <gpac/intern/m4_author_dev.h>
#include <gpac/intern/m4_isomedia_dev.h>
#include <gpac/m4_crypt.h>


#ifndef M4_READ_ONLY
/*in ISMA.c*/
void log_message(void (*LogMsg)(void *cbk, const char *szMsg), void *cbk, char *format, ...);
void ismac_progress(void (*prog)(void *cbk, u32 done, u32 total), void *cbk, u32 done, u32 total)
{
	if (prog) prog(cbk, done, total);
	else MP4T_OnProgress("Processing", done, total);
}
#else
void log_message(void (*LogMsg)(void *cbk, const char *szMsg), void *cbk, char *format, ...)
{
}
void ismac_progress(void (*prog)(void *cbk, u32 done, u32 total), void *cbk, u32 done, u32 total)
{
}
#endif

static M4Err load_track_info(XMLParser *parser, M4TrackCryptInfo *tkc, void (*logs)(void *cbk, const char *szMsg), void *cbk)
{
	u32 i;
	char *str;
	memset(tkc, 0, sizeof(M4TrackCryptInfo));

	while (!parser->done) {
		str = xml_get_element(parser);
		if (!str) return M4EOF;
		if (stricmp(str, "ISMACrypTrack")) {
			xml_skip_element(parser, str);
			continue;
		}
		while (xml_has_attributes(parser)) {
			str = xml_get_attribute(parser);
			if (!stricmp(str, "trackID") || !stricmp(str, "ID")) {
				if (!strcmp(parser->value_buffer, "*")) {
					tkc->trackID = 0;
				} else {
					tkc->trackID = atoi(parser->value_buffer);
				}
			}
			else if (!stricmp(str, "key")) {
				char *sKey = parser->value_buffer;
				if (!strnicmp(sKey, "0x", 2)) sKey += 2;
				if (strlen(sKey) == 32) {
					for (i=0; i<32; i+=2) {
						u32 v;
						char szV[5];
						sprintf(szV, "%c%c", sKey[i], sKey[i+1]);
						sscanf(szV, "%x", &v);
						tkc->key[i/2] = v;
					}
				} else {
					log_message(logs, cbk, "Warning: Key %s not 16-bytes long - skipping", sKey);
				}
			}
			else if (!stricmp(str, "salt")) {
				char *sKey = parser->value_buffer;
				if (!strnicmp(sKey, "0x", 2)) sKey += 2;
				if (strlen(sKey) != 8) {
					for (i=0; i<16; i+=2) {
						char szV[5];
						u32 v;
						sprintf(szV, "%c%c", sKey[i], sKey[i+1]);
						sscanf(szV, "%x", &v);
						tkc->salt[i/2] = v;
					}
				} else {
					memcpy(tkc->salt, sKey, 8);
				}
			}
			else if (!stricmp(str, "kms_URI")) strcpy(tkc->KMS_URI, parser->value_buffer);
			else if (!stricmp(str, "scheme_URI")) strcpy(tkc->Scheme_URI, parser->value_buffer);
			else if (!stricmp(str, "selectiveType")) {
				if (!stricmp(parser->value_buffer, "Rap")) tkc->sel_enc_type = M4_SELENC_RAP;
				else if (!stricmp(parser->value_buffer, "Non-Rap")) tkc->sel_enc_type = M4_SELENC_NON_RAP;
				else if (!stricmp(parser->value_buffer, "Rand")) tkc->sel_enc_type = M4_SELENC_RAND;
				else if (!strnicmp(parser->value_buffer, "Rand", 4)) {
					tkc->sel_enc_type = M4_SELENC_RAND_RANGE;
					tkc->sel_enc_range = atoi(&parser->value_buffer[4]);
				}
				else if (sscanf(parser->value_buffer, "%d", &tkc->sel_enc_range)==1) {
					if (tkc->sel_enc_range==1) tkc->sel_enc_range = 0;
					else tkc->sel_enc_type = M4_SELENC_RANGE;
				}
			}
			else if (!stricmp(str, "ipmpType")) {
				if (!stricmp(parser->value_buffer, "None")) tkc->ipmp_type = 0;
				else if (!stricmp(parser->value_buffer, "IPMP")) tkc->sel_enc_type = 1;
				else if (!stricmp(parser->value_buffer, "IPMPX")) tkc->sel_enc_type = 2;
			}
			else if (!stricmp(str, "ipmpDescriptorID")) tkc->ipmp_desc_id = atoi(parser->value_buffer);
		}
		xml_skip_element(parser, "ISMACrypTrack");
		return M4OK;
	}
	return M4EOF;
}

M4Err MP4T_GetISMACrypInfoForGPAC(u32 stream_id, char *drm_file, char *key, char *salt)
{
	M4Err e;
	Bool has_def_keys;
	char def_key[16], def_salt[8];
	M4TrackCryptInfo tci;
	XMLParser parser;

	e = xml_init_parser(&parser, drm_file);
	if (e) return e;
	has_def_keys = 0;

	while (1) {
		e = load_track_info(&parser, &tci, NULL, NULL);
		if (e) break;
		if (tci.trackID != stream_id) {
			if (!tci.trackID) {
				has_def_keys = 1;
				memcpy(def_key, tci.key, sizeof(char)*16);
				memcpy(def_salt, tci.salt, sizeof(char)*8);
			}
			continue;
		}
		memcpy(key, tci.key, sizeof(char)*16);
		memcpy(salt, tci.salt, sizeof(char)*8);
		e = M4OK;
		break;
	}
	if (e==M4EOF) {
		e = M4ChannelNotFound;
		if (has_def_keys) {
			memcpy(key, def_key, sizeof(char)*16);
			memcpy(salt, def_salt, sizeof(char)*8);
			e = M4OK;
		}
	}
	xml_reset_parser(&parser);
	return e;
}

Bool MP4T_GetISMACrypInfoForMPEG4IP(u32 stream_id, char *kms_uri, char *key, char *salt)
{
	char szPath[1024], catKey[24];
	u32 i, x;
	Bool got_it;
	FILE *kms;
	strcpy(szPath, getenv("HOME"));
	strcat(szPath , "/.kms_data"); 
	got_it = 0;
	kms = fopen(szPath, "r");
	while (kms && !feof(kms)) {
		if (!fgets(szPath, 1024, kms)) break;
		szPath[strlen(szPath) - 1] = 0;
		if (stricmp(szPath, kms_uri)) continue;
		for (i=0; i<24; i++) {
			if (!fscanf(kms, "%x", &x)) break;
			catKey[i] = x;
		}
		if (i==24) got_it = 1;
		break;
	}
	if (kms) fclose(kms);
	if (got_it) {
		/*watchout, MPEG4IP stores SALT|KEY, NOT KEY|SALT*/
		memcpy(key, catKey+8, sizeof(char)*16);
		memcpy(salt, catKey, sizeof(char)*8);
		return 1;
	}
	return 0;
}

#ifndef M4_READ_ONLY

static M4INLINE void resync_IV(M4CRYPT mc, u64 BSO, char *salt)
{
	char IV[17];
	u64 count;
	u32 remain;
	BitStream *bs;
	count = BSO / 16;
	remain = (u32) (BSO % 16);

	/*format IV to begin of counter*/
	bs = NewBitStream(IV, 17, BS_WRITE);
	BS_WriteU8(bs, 0);	/*begin of counter*/
	BS_WriteData(bs, salt, 8);
	BS_WriteU64(bs, (s64) count);
	DeleteBitStream(bs);
	m4crypt_set_state(mc, IV, 17);

	/*decrypt remain bytes*/
	if (remain) {
		char dummy[20];
		m4crypt_decrypt(mc, dummy, remain);
	}
}

M4Err MP4T_ISMADecryptTrack(M4File *mp4, M4TrackCryptInfo *tci, void (*logs)(void *cbk, const char *szMsg), void (*progress)(void *cbk, u32 done, u32 total), void *cbk)
{
	M4Err e;
	Bool use_sel_enc;
	u32 track, count, i, j, si;
	M4Sample *samp;
	ISMASample *ismasamp;
	M4CRYPT mc;
	unsigned char IV[17];
	u8 IV_size;
	Bool prev_sample_encrypted;
	ESDescriptor *esd;

	track = M4_GetTrackByID(mp4, tci->trackID);
	e = M4_GetISMACrypInfo(mp4, track, 1, NULL, NULL, NULL, NULL, NULL, &use_sel_enc, &IV_size, NULL);

	mc = m4crypt_open("AES-128", "CTR");
	if (!mc) {
		log_message(logs, cbk, "Cannot open AES-128 CTR cryptography - skipping", tci->trackID);
		return M4IOErr;
	}

	memset(IV, 0, sizeof(char)*16);
	memcpy(IV, tci->salt, sizeof(char)*8);
	e = m4crypt_init(mc, tci->key, 16, IV);
	if (e) {
		m4crypt_close(mc);
		log_message(logs, cbk, "Error %s initializing AES-128 CTR", M4ErrToString(e));
		return M4IOErr;
	}

	log_message(logs, cbk, "Decrypting track ID %d - KMS: %s%s", tci->trackID, tci->KMS_URI, use_sel_enc ? " - Selective Decryption" : "");

	/*start as initialized*/
	prev_sample_encrypted = 1;
	/* decrypt each sample */
	count = M4_GetSampleCount(mp4, track);
	for (i = 0; i < count; i++) {
		samp = M4_GetSample(mp4, track, i+1, &si); 
		ismasamp = M4_GetISMACrypSample(mp4, track, samp, si);

		samp->data = ismasamp->data;
		samp->dataLength = ismasamp->dataLength;
		ismasamp->data = NULL;
		ismasamp->dataLength = 0;

		/* Decrypt payload */
		if (ismasamp->flags & ISMA_IsEncrypted) {
			/*restore IV*/
			if (!prev_sample_encrypted) resync_IV(mc, ismasamp->IV, tci->salt);
			m4crypt_decrypt(mc, samp->data, samp->dataLength);
		}
		prev_sample_encrypted = (ismasamp->flags & ISMA_IsEncrypted);
		ISMA_DeleteSample(ismasamp);

		M4_UpdateSample(mp4, track, i+1, samp, 1);
		M4_DeleteSample(&samp);
		ismac_progress(progress, cbk, i+1, count);
	}

	m4crypt_close(mc);
	/*and remove protection info*/
	e = M4_RemoveISMACrypProtection(mp4, track, 1);
	if (e) log_message(logs, cbk, "Error %s removing ISMACryp signature from trackID %d", M4ErrToString(e), tci->trackID);

	/*remove all IPMP ptrs*/
	esd = M4_GetStreamDescriptor(mp4, track, 1);
	if (esd) {
		while (ChainGetCount(esd->IPMPDescriptorPointers)) {
			Descriptor *d = ChainGetEntry(esd->IPMPDescriptorPointers, 0);
			ChainDeleteEntry(esd->IPMPDescriptorPointers, 0);
			OD_DeleteDescriptor(&d);
		}
		M4_ChangeStreamDescriptor(mp4, track, 1, esd);
		OD_DeleteDescriptor((Descriptor **)&esd);
	}

	/*update OD track if any*/
	track = 0;
	for (i=0; i<M4_GetTrackCount(mp4); i++) {
		LPODCODEC cod;
		if (M4_GetMediaType(mp4, i+1) != M4_ODMediaType) continue;

		/*remove all IPMPUpdate commads...*/
		samp = M4_GetSample(mp4, i+1, 1, &si);
		cod = OD_NewCodec();
		OD_SetBuffer(cod, samp->data, samp->dataLength);
		OD_DecodeAU(cod);
		for (j=0; j<ChainGetCount(cod->CommandList); j++) {
			IPMPDescriptorUpdate *com = (IPMPDescriptorUpdate *)ChainGetEntry(cod->CommandList, j);
			if (com->tag != IPMPDUpdate_Tag) continue;
			ChainDeleteEntry(cod->CommandList, j);
			j--;
			OD_DeleteCommand((ODCommand **)&com);
		}
		free(samp->data);
		samp->data = NULL;
		samp->dataLength = 0;
		OD_EncodeAU(cod);
		OD_GetEncodedAU(cod, &samp->data, &samp->dataLength);
		OD_DeleteCodec(cod);
		M4_UpdateSample(mp4, i+1, 1, samp, 1);
		M4_DeleteSample(&samp);

		/*remove IPMPToolList if any*/	
		if (mp4->moov->iods && (mp4->moov->iods->descriptor->tag == MP4_IOD_Tag) ) {
			M4F_InitialObjectDescriptor *iod = (M4F_InitialObjectDescriptor *)mp4->moov->iods->descriptor;
			if (iod->IPMPToolList) OD_DeleteDescriptor((Descriptor**) &iod->IPMPToolList);
		}
		break;
	}
	return M4OK;
}


M4Err MP4T_ISMADecrypt(M4File *mp4, const char *drm_file, void (*logs)(void *cbk, const char *szMsg), void *cbk)
{
	M4Err e;
	Bool is_ismacryp, decrypt_all;
	u32 track, scheme_type, cur_tk;
	const char *scheme_URI, *KMS_URI;
	M4TrackCryptInfo tci;
	XMLParser parser;

	if (drm_file) {
		e = xml_init_parser(&parser, drm_file);
		if (e) {
			log_message(logs, cbk, "Cannot open or validate xml file %s", drm_file);
			return e;
		}
	}
	decrypt_all = 0;
	cur_tk = 1;
	e = M4OK;
	while (1) {
		if (drm_file && !decrypt_all) {
			e = load_track_info(&parser, &tci, logs, cbk);
			if (e) break;
			if (!tci.trackID) decrypt_all = 1;
		}
		if (!drm_file || decrypt_all) {
			if (cur_tk>M4_GetTrackCount(mp4)) break;
			track = cur_tk;
			cur_tk++;
			tci.trackID = M4_GetTrackID(mp4, track);
		} else {
			track = M4_GetTrackByID(mp4, tci.trackID);
			if (!track) {
				log_message(logs, cbk, "Cannot find TrackID %d in input file - skipping", tci.trackID);
				continue;
			}
		}
		if (!M4_IsCryptedMedia(mp4, track, 1)) {
			if (drm_file) log_message(logs, cbk, "TrackID %d not crypted - skipping", tci.trackID);
			continue;
		}

		is_ismacryp = M4_IsISMACrypMedia(mp4, track, 1);
		e = M4_GetISMACrypInfo(mp4, track, 1, NULL, &scheme_type, NULL, &scheme_URI, &KMS_URI, NULL, NULL, NULL);

		if (!e && (scheme_type != M4_ISMA_EncSchemeType)) {
			char name[5];
			log_message(logs, cbk, "Encrypted track #%d uses an unsupported encryption scheme: %s", tci.trackID, FCC_TO_STR(scheme_type, name));
			if (scheme_URI) log_message(logs, cbk, "scheme defined at %s", scheme_URI);
			continue;
		} else if (!is_ismacryp) {
			log_message(logs, cbk, "TrackID %d not crypted with ISMACrypt - skipping", tci.trackID);
			continue;
		} else if (e) {
			log_message(logs, cbk, "TrackID %d not compliant with ISMACrypt - skipping", tci.trackID);
			continue;
		}
		/*get key and salt from KMS*/
		/*GPAC*/
		if (!strnicmp(KMS_URI, "(key)", 5)) {
			char data[100];
			Base64_dec((unsigned char *) KMS_URI+5, strlen(KMS_URI)-5, data, 100);
			memcpy(tci.key, data, sizeof(char)*16);
			memcpy(tci.salt, data+16, sizeof(char)*8);
		}
		/*MPEG4IP*/
		else if (!stricmp(KMS_URI, "AudioKey") || !stricmp(KMS_URI, "VideoKey")) {
			if (!MP4T_GetISMACrypInfoForMPEG4IP(tci.trackID, (char *) KMS_URI, tci.key, tci.salt)) {
				log_message(logs, cbk, "Couldn't load MPEG4IP ISMACryp keys for TrackID %d", tci.trackID);
				continue;
			}
		} else if (!drm_file) {
			FILE *test = NULL;
			if (!stricmp(scheme_URI, "urn:gpac:isma:encryption_scheme")) test = fopen(KMS_URI, "rt");

			if (!test) {
				log_message(logs, cbk, "TrackID %d does not contain decryption keys - skipping", tci.trackID);
				continue;
			} else {
				fclose(test);
				if (!MP4T_GetISMACrypInfoForGPAC(tci.trackID, (char *) KMS_URI, tci.key, tci.salt)) {
					log_message(logs, cbk, "Couldn't load TrackID %d keys in GPAC DRM file %s", tci.trackID, KMS_URI);
					continue;
				}
			}
		}

		if (strlen(tci.KMS_URI) && strcmp(KMS_URI, tci.KMS_URI) )
			log_message(logs, cbk, "Warning: KMS URI for trackID %d Mismatch", tci.trackID);

		if (drm_file) {
			strcpy(tci.KMS_URI, KMS_URI);
		} else {
			strcpy(tci.KMS_URI, "self-contained");
		}

		e = MP4T_ISMADecryptTrack(mp4, &tci, logs, NULL, cbk);
		if (e) break;
	}
	if (e==M4EOF) e = M4OK;
	if (drm_file) xml_reset_parser(&parser);
	return e;
}

M4Err MP4T_ISMACryptTrack(M4File *mp4, M4TrackCryptInfo *tci, void (*logs)(void *cbk, const char *szMsg), void (*progress)(void *cbk, u32 done, u32 total), void *cbk)
{
	char IV[16];
	M4Sample *samp;
	ISMASample *isamp;
	M4CRYPT mc;
	u32 i, count, di, track, IV_size, rand;
	u64 BSO;
	ESDescriptor *esd;
	IPMP_DescrPointer *ipmpdp;
	IPMP_Descriptor *ipmpd;
	IPMPDescriptorUpdate *ipmpdU;
	IPMPX_ISMACryp *ismac;
	M4Err e;
	Bool prev_sample_encryped, has_crypted_samp;

	track = M4_GetTrackByID(mp4, tci->trackID);
	if (!track) {
		log_message(logs, cbk, "Cannot find TrackID %d in input file - skipping", tci->trackID);
		return M4BadParam;
	}
	esd = M4_GetStreamDescriptor(mp4, track, 1);
	if (esd && (esd->decoderConfig->streamType==M4ST_OD)) {
		OD_DeleteDescriptor((Descriptor **) &esd);
		log_message(logs, cbk, "!! Cannot encrypt OD tracks !! - skipping");
		return M4NotSupported;
	}
	if (esd) OD_DeleteDescriptor((Descriptor**) &esd);

	if (!strlen(tci->Scheme_URI)) strcpy(tci->Scheme_URI, "urn:gpac:isma:encryption_scheme");

	if (!M4_HasSyncPoints(mp4, track) &&
		((tci->sel_enc_type==M4_SELENC_RAP) || (tci->sel_enc_type==M4_SELENC_NON_RAP)) ) {
		log_message(logs, cbk, "Warning: All samples in trackID %d are random access - disabling selective encryption", tci->trackID);
		tci->sel_enc_type = M4_SELENC_NONE;
	}
	else if ((tci->sel_enc_type==M4_SELENC_RAND) || (tci->sel_enc_type==M4_SELENC_RAND_RANGE)) {
		M4_RandInit(1);
	}

	BSO = M4_GetMediaDataSize(mp4, track);
	if (BSO<0xFFFF) IV_size = 2;
	else if (BSO<0xFFFFFFFF) IV_size = 4;
	else IV_size = 8;

	log_message(logs, cbk, "Encrypting track ID %d - KMS: %s%s", tci->trackID, tci->KMS_URI, tci->sel_enc_type ? " - Selective Encryption" : "");

	/*init crypto*/
	memset(IV, 0, sizeof(char)*16);
	memcpy(IV, tci->salt, sizeof(char)*8);
	mc = m4crypt_open("AES-128", "CTR");
	if (!mc) {
		log_message(logs, cbk, "Cannot open AES-128 CTR - skipping");
		return M4IOErr;
	}
	e = m4crypt_init(mc, tci->key, 16, IV);
	if (e) {
		log_message(logs, cbk, "Cannot initialize AES-128 CTR (%s) - skipping", M4ErrToString(e));
		m4crypt_close(mc);
		return M4IOErr;
	}
	if (!stricmp(tci->KMS_URI, "self")) {
		char Data[100], d64[100];
		u32 s64;
		memcpy(Data, tci->key, sizeof(char)*16);
		memcpy(Data+16, tci->salt, sizeof(char)*8);
		s64 = Base64_enc(Data, 24, d64, 100);
		d64[s64] = 0;
		strcpy(tci->KMS_URI, "(key)");
		strcat(tci->KMS_URI, d64);
	}

	/*create ISMA protection*/
	e = M4_SetISMAProtection(mp4, track, 1, M4_ISMA_EncSchemeType, 1, 
		tci->Scheme_URI, tci->KMS_URI, (tci->sel_enc_type!=0) ? 1 : 0, 0, IV_size);	 
	if (e) return e;

	has_crypted_samp = 0;
	BSO = 0;
	prev_sample_encryped = 1;
	if (M4_HasTimeOffsets(mp4, track)) M4_SetCTSPackMode(mp4, track, 1);

	count = M4_GetSampleCount(mp4, track);
	for (i = 0; i < count; i++) {
		samp = M4_GetSample(mp4, track, i+1, &di); 

		isamp = ISMA_NewSample();
		isamp->IV_length = IV_size;
		isamp->KI_length = 0;

		switch (tci->sel_enc_type) {
		case M4_SELENC_RAP:
			if (samp->IsRAP) isamp->flags |= ISMA_IsEncrypted;
			break;
		case M4_SELENC_NON_RAP:
			if (!samp->IsRAP) isamp->flags |= ISMA_IsEncrypted;
			break;
		/*random*/
		case M4_SELENC_RAND:
			rand = M4_Rand();
			if (rand%2) isamp->flags |= ISMA_IsEncrypted;
			break;
		/*random every sel_freq samples*/
		case M4_SELENC_RAND_RANGE:
			if (!(i%tci->sel_enc_range)) has_crypted_samp = 0;
			if (!has_crypted_samp) {
				rand = M4_Rand();
				if (!(rand%tci->sel_enc_range)) isamp->flags |= ISMA_IsEncrypted;

				if (!(isamp->flags & ISMA_IsEncrypted) && !( (1+i)%tci->sel_enc_range)) {
					isamp->flags |= ISMA_IsEncrypted;
				}
				has_crypted_samp = (isamp->flags & ISMA_IsEncrypted);
			}
			break;
		/*every sel_freq samples*/
		case M4_SELENC_RANGE:
			if (!(i%tci->sel_enc_type)) isamp->flags |= ISMA_IsEncrypted;
			break;
		case 0:
			isamp->flags |= ISMA_IsEncrypted;
			break;
		default:
			break;
		}
		if (tci->sel_enc_type) isamp->flags |= ISMA_UseSelEnc;

		if (isamp->flags & ISMA_IsEncrypted) {
			/*resync IV*/
			if (!prev_sample_encryped) resync_IV(mc, BSO, tci->salt);
			m4crypt_encrypt(mc, samp->data, samp->dataLength);
			prev_sample_encryped = 1;
		} else {
			prev_sample_encryped = 0;
		}


		isamp->IV = BSO;
		BSO += samp->dataLength;
		isamp->data = samp->data;
		isamp->dataLength = samp->dataLength;
		samp->data = NULL;
		samp->dataLength = 0;

		M4_ISMASampleToSample(isamp, samp);
		ISMA_DeleteSample(isamp);
		M4_UpdateSample(mp4, track, i+1, samp, 1);
		M4_DeleteSample(&samp);
		ismac_progress(progress, cbk, i+1, count);
	}
	M4_SetCTSPackMode(mp4, track, 0);
	m4crypt_close(mc);

	/*format as IPMP(X) - note that the ISMACryp spec is broken since it always uses IPMPPointers to a 
	single desc which would assume the same protection (eg key & salt) for all streams using it...*/
	if (!tci->ipmp_type) return M4OK;

	ipmpdp = (IPMP_DescrPointer*)OD_NewDescriptor(IPMP_DescriptorPointer_Tag);
	if (!tci->ipmp_desc_id) tci->ipmp_desc_id = track;
	if (tci->ipmp_type==2) {
		ipmpdp->IPMP_DescriptorID = 0xFF;
		ipmpdp->IPMP_DescriptorIDEx = tci->ipmp_desc_id;
	} else {
		ipmpdp->IPMP_DescriptorID = tci->ipmp_desc_id;
	}
	M4_AddDescriptorToESD(mp4, track, 1, (Descriptor *)ipmpdp);
	OD_DeleteDescriptor((Descriptor**)&ipmpdp);

	ipmpdU = (IPMPDescriptorUpdate*)OD_NewCommand(IPMPDUpdate_Tag);
	/*format IPMPD*/
	ipmpd = (IPMP_Descriptor*)OD_NewDescriptor(IPMP_Descriptor_Tag);
	if (tci->ipmp_type==2) {
		ipmpd->IPMP_DescriptorID = 0xFF;
		ipmpd->IPMP_DescriptorIDEx = tci->ipmp_desc_id;
		ipmpd->IPMPS_Type = 0xFFFF;
		ipmpd->IPMP_ToolID[14] = 0x49; ipmpd->IPMP_ToolID[15] = 0x53;
		ipmpd->control_point = 1;
		ipmpd->cp_sequence_code = 0x80;
		/*format IPMPXData*/
		ismac = (IPMPX_ISMACryp *) IPMPX_NewData(IPMPX_ISMACryp_Tag);
		ismac->cryptoSuite = 1;	/*default ISMA AESCTR128*/
		ismac->IV_length = IV_size;
		ismac->key_indicator_length = 0;
		ismac->use_selective_encryption = (tci->sel_enc_type!=0)? 1 : 0;
		ChainAddEntry(ipmpd->ipmpx_data, ismac);
	} else {
		ipmpd->IPMP_DescriptorID = tci->ipmp_desc_id;
	}
	ChainAddEntry(ipmpdU->IPMPDescList, ipmpd);

	for (i=0; i<M4_GetTrackCount(mp4); i++) {
		LPODCODEC cod;
		if (M4_GetMediaType(mp4, i+1) != M4_ODMediaType) continue;

		/*add com*/
		samp = M4_GetSample(mp4, i+1, 1, &di);
		cod = OD_NewCodec();
		OD_SetBuffer(cod, samp->data, samp->dataLength);
		OD_DecodeAU(cod);
		OD_AddCommand(cod, (ODCommand *) ipmpdU);
		free(samp->data);
		samp->data = NULL;
		samp->dataLength = 0;
		OD_EncodeAU(cod);
		OD_GetEncodedAU(cod, &samp->data, &samp->dataLength);
		ipmpdU = NULL;
		OD_DeleteCodec(cod);
		M4_UpdateSample(mp4, i+1, 1, samp, 1);
		M4_DeleteSample(&samp);

		if (tci->ipmp_type==2) {
			IPMP_ToolListDescriptor*ipmptl = (IPMP_ToolListDescriptor*)OD_NewDescriptor(IPMP_ToolList_Tag);
			IPMP_Tool *ipmpt = (IPMP_Tool*)OD_NewDescriptor(IPMP_Tool_Tag);
			ChainAddEntry(ipmptl->ipmp_tools, ipmpt);
			ipmpt->IPMP_ToolID[14] = 0x49; ipmpt->IPMP_ToolID[15] = 0x53;
			M4_AddDescriptorToRootOD(mp4, (Descriptor *)ipmptl);
			OD_DeleteDescriptor((Descriptor **)&ipmptl);
		}
		break;
	}
	return e;
}

M4Err MP4T_ISMACrypt(M4File *mp4, const char *drm_file, void (*logs)(void *cbk, const char *szMsg), void *cbk)
{
	M4Err e;
	M4TrackCryptInfo tci;
	XMLParser parser;

	e = xml_init_parser(&parser, drm_file);
	if (e) {
		log_message(logs, cbk, "Cannot open or validate xml file %s", drm_file);
		return e;
	}

	while (1) {
		e = load_track_info(&parser, &tci, logs, cbk);
		if (e) break;

		/*default to FILE uri*/
		if (!strlen(tci.KMS_URI)) strcpy(tci.KMS_URI, drm_file);

		/*crypt all tracks*/
		if (!tci.trackID) {
			u32 i;
			for (i=0; i<M4_GetTrackCount(mp4); i++) {
				tci.trackID = M4_GetTrackID(mp4, i+1);
				e = MP4T_ISMACryptTrack(mp4, &tci, logs, NULL, cbk);
				if (e) break;
			}
			break;
		} else {
			e = MP4T_ISMACryptTrack(mp4, &tci, logs, NULL, cbk);
			if (e) break;
		}
	}

	if (e==M4EOF) e = M4OK;
	if (e) goto err_exit;
	
err_exit:
	xml_reset_parser(&parser);
	return e;
}

#endif

