/*****************************************************************
|
|    AP4 - Marlin File Format Support
|
|    Copyright 2002-2008 Axiomatic Systems, LLC
|
|
|    This file is part of Bento4/AP4 (MP4 Atom Processing Library).
|
|    Unless you have obtained Bento4 under a difference license,
|    this version of Bento4 is Bento4|GPL.
|    Bento4|GPL 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.
|
|    Bento4|GPL 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 Bento4|GPL; see the file COPYING.  If not, write to the
|    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|    02111-1307, USA.
|
****************************************************************/

/*----------------------------------------------------------------------
|   includes
+---------------------------------------------------------------------*/
#include "Ap4SchmAtom.h"
#include "Ap4StsdAtom.h"
#include "Ap4Sample.h"
#include "Ap4StreamCipher.h"
#include "Ap4FrmaAtom.h"
#include "Ap4Utils.h"
#include "Ap4TrakAtom.h"
#include "Ap4FtypAtom.h"
#include "Ap4IodsAtom.h"
#include "Ap4MoovAtom.h"
#include "Ap4Track.h"
#include "Ap4DescriptorFactory.h"
#include "Ap4CommandFactory.h"
#include "Ap4Marlin.h"
#include "Ap4FileByteStream.h"
#include "Ap4Ipmp.h"
#include "Ap4AesBlockCipher.h"
#include "Ap4SyntheticSampleTable.h"
#include "Ap4HdlrAtom.h"
#include "Ap4Hmac.h"
#include "Ap4KeyWrap.h"
#include "Ap4ByteStream.h"

/*----------------------------------------------------------------------
|   dynamic cast support
+---------------------------------------------------------------------*/
AP4_DEFINE_DYNAMIC_CAST_ANCHOR(AP4_MkidAtom)

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpAtomTypeHandler
+---------------------------------------------------------------------*/
class AP4_MarlinIpmpAtomTypeHandler : public AP4_AtomFactory::TypeHandler
{
public:
    // constructor
    AP4_MarlinIpmpAtomTypeHandler(AP4_AtomFactory* atom_factory) :
      m_AtomFactory(atom_factory) {}
      virtual AP4_Result CreateAtom(AP4_Atom::Type  type,
          AP4_UI32        size,
          AP4_ByteStream& stream,
          AP4_Atom::Type  context,
          AP4_Atom*&      atom);

private:
    // members
    AP4_AtomFactory* m_AtomFactory;
};

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpAtomFactory
+---------------------------------------------------------------------*/
class AP4_MarlinIpmpAtomFactory : public AP4_DefaultAtomFactory
{
public:
    // class members
    static AP4_MarlinIpmpAtomFactory Instance;

    // constructor
    AP4_MarlinIpmpAtomFactory() {
        AddTypeHandler(new AP4_MarlinIpmpAtomTypeHandler(this));
    }
};

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpAtomFactory::Instance
+---------------------------------------------------------------------*/
AP4_MarlinIpmpAtomFactory AP4_MarlinIpmpAtomFactory::Instance;

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpAtomTypeHandler::CreateAtom
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpAtomTypeHandler::CreateAtom(AP4_Atom::Type  type,
                                          AP4_UI32        size,
                                          AP4_ByteStream& stream,
                                          AP4_Atom::Type  /*context*/,
                                          AP4_Atom*&      atom)
{
    switch (type) {
        case AP4_ATOM_TYPE_SATR:
            atom = AP4_ContainerAtom::Create(type, size, false, false, stream, *m_AtomFactory);
            break;
            
        case AP4_ATOM_TYPE_STYP:
            atom = new AP4_NullTerminatedStringAtom(type, size, stream);
            break;

        default:
            atom = NULL;
    }

    return atom ? AP4_SUCCESS : AP4_FAILURE;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpParser:Parse
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpParser::Parse(AP4_AtomParent&      top_level, 
                            AP4_ByteStream&      stream,
                            AP4_List<SinfEntry>& sinf_entries,
                            bool                 remove_od_data)
{
    // check the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp == NULL ||
        (ftyp->GetMajorBrand() != AP4_MARLIN_BRAND_MGSV && !ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV))) {
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // check the initial object descriptor and get the OD Track ID
    AP4_IodsAtom* iods = AP4_DYNAMIC_CAST(AP4_IodsAtom, top_level.FindChild("moov/iods"));
    AP4_UI32      od_track_id = 0;
    if (iods == NULL) return AP4_ERROR_INVALID_FORMAT;
    const AP4_ObjectDescriptor* od = iods->GetObjectDescriptor();
    if (od == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_EsIdIncDescriptor* es_id_inc = AP4_DYNAMIC_CAST(AP4_EsIdIncDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_INC));
    if (es_id_inc == NULL) return AP4_ERROR_INVALID_FORMAT;
    od_track_id = es_id_inc->GetTrackId();
    
    // find the track pointed to by the descriptor
    AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_TrakAtom* od_trak = NULL;
    for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
                                       trak_item;
                                       trak_item = trak_item->GetNext()) {
        AP4_TrakAtom* trak = trak_item->GetData();
        if (trak) {
            if (trak->GetId() == od_track_id) {
                od_trak = trak;
            } else {
                sinf_entries.Add(new SinfEntry(trak->GetId(), NULL));
            }
        }
    }

    // check that we have found the OD track 
    if (od_trak == NULL) return AP4_ERROR_INVALID_FORMAT;

    // look for the 'mpod' trak references
    AP4_TrefTypeAtom* track_references;
    track_references = AP4_DYNAMIC_CAST(AP4_TrefTypeAtom, od_trak->FindChild("tref/mpod"));
    if (track_references == NULL) return AP4_ERROR_INVALID_FORMAT;

    // create an AP4_Track object from the trak atom and check that it has samples
    AP4_Track* od_track = new AP4_Track(*od_trak, stream, 0);
    if (od_track->GetSampleCount() < 1) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // get the first sample (in this version, we only look at a single OD command)
    AP4_Sample od_sample;
    AP4_Result result = od_track->GetSample(0, od_sample);
    if (AP4_FAILED(result)) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // adapt the sample data into a byte stream for parsing
    AP4_DataBuffer sample_data;
    od_sample.ReadData(sample_data);
    AP4_MemoryByteStream* sample_stream = new AP4_MemoryByteStream(sample_data);
    
    // look for one ObjectDescriptorUpdate command and 
    // one IPMP_DescriptorUpdate command
    AP4_DescriptorUpdateCommand* od_update = NULL;
    AP4_DescriptorUpdateCommand* ipmp_update = NULL;
    do {
        AP4_Command* command = NULL;
        result = AP4_CommandFactory::CreateCommandFromStream(*sample_stream, command);
        if (AP4_SUCCEEDED(result)) {
            // found a command in the sample, check the type
            switch (command->GetTag()) {
              case AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE:
                if (od_update == NULL) {
                    od_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
                }
                break;
                
              case AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE:
                if (ipmp_update == NULL) {
                    ipmp_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
                }
                break;

              default:
                break;
            }
        }
    } while (AP4_SUCCEEDED(result));
    sample_stream->Release();
    sample_stream = NULL;
    
    // check that we have what we need
    if (od_update == NULL || ipmp_update == NULL) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
        
    // process all the object descriptors in the od update
    for (AP4_List<AP4_Descriptor>::Item* od_item = od_update->GetDescriptors().FirstItem();
                                         od_item;
                                         od_item = od_item->GetNext()) {
        od = AP4_DYNAMIC_CAST(AP4_ObjectDescriptor, od_item->GetData());
        if (od == NULL) continue;

        // find which track this od references
        AP4_EsIdRefDescriptor* es_id_ref;
        es_id_ref = AP4_DYNAMIC_CAST(AP4_EsIdRefDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_REF));
        if (es_id_ref == NULL || 
            es_id_ref->GetRefIndex() > track_references->GetTrackIds().ItemCount() ||
            es_id_ref->GetRefIndex() == 0) {
            continue;
        }
        AP4_UI32 track_id = track_references->GetTrackIds()[es_id_ref->GetRefIndex()-1];
        SinfEntry* sinf_entry = NULL;
        for (AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem();
                                        sinf_entry_item;
                                        sinf_entry_item = sinf_entry_item->GetNext()) {
            sinf_entry = sinf_entry_item->GetData();
            if (sinf_entry->m_TrackId == track_id) {
                break; // match
            } else {
                sinf_entry = NULL; // no match
            }
        }
        if (sinf_entry == NULL) continue; // no matching entry
        if (sinf_entry->m_Sinf != NULL) continue; // entry already populated
        
        // see what ipmp descriptor this od points to
        AP4_IpmpDescriptorPointer* ipmpd_pointer;
        ipmpd_pointer = AP4_DYNAMIC_CAST(AP4_IpmpDescriptorPointer, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR_POINTER));
        if (ipmpd_pointer == NULL) continue; // no pointer

        // find the ipmp descriptor referenced by the pointer
        AP4_IpmpDescriptor* ipmpd = NULL;
        for (AP4_List<AP4_Descriptor>::Item* ipmpd_item = ipmp_update->GetDescriptors().FirstItem();
                                             ipmpd_item;
                                             ipmpd_item = ipmpd_item->GetNext()) {
            // check that this descriptor is of the right type
            ipmpd = AP4_DYNAMIC_CAST(AP4_IpmpDescriptor, ipmpd_item->GetData());
            if (ipmpd == NULL || ipmpd->GetIpmpsType() != AP4_MARLIN_IPMPS_TYPE_MGSV) continue;
            
            // check the descriptor id
            if (ipmpd->GetDescriptorId() == ipmpd_pointer->GetDescriptorId()) {
                break; // match
            } else {
                ipmpd = NULL; // no match
            }
        }
        if (ipmpd == NULL) continue; // no matching entry
        
        // parse the ipmp data into one or more 'sinf' atoms, and keep the one with the
        // right type
        AP4_MemoryByteStream* data = new AP4_MemoryByteStream(ipmpd->GetData().GetData(),
                                                              ipmpd->GetData().GetDataSize());
        AP4_LargeSize bytes_available = ipmpd->GetData().GetDataSize();
        do {
            AP4_Atom* atom = NULL;
            
            // setup the factory with a context so we can instantiate a 'schm'
            // atom with a slightly different format than the standard 'schm'
            AP4_AtomFactory* factory = &AP4_MarlinIpmpAtomFactory::Instance;
            factory->PushContext(AP4_ATOM_TYPE('m','r','l','n'));
            
            // parse the next atom in the stream 
            result = factory->CreateAtomFromStream(*data, bytes_available, atom);
            factory->PopContext();
            if (AP4_FAILED(result) || atom == NULL) break;
            
            // check that what we have parsed is indeed an 'sinf' of the right type
            if (atom->GetType() == AP4_ATOM_TYPE_SINF) {
                AP4_ContainerAtom* sinf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->FindChild("schm"));
                if ((schm->GetSchemeType()    == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC && 
                     schm->GetSchemeVersion() == 0x0100) ||
                    (schm->GetSchemeType()    == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK &&
                     schm->GetSchemeVersion() == 0x0100)) {
                    // store the sinf in the entry for that track
                    sinf_entry->m_Sinf = sinf;
                    break;
                }
            }
            delete atom;
        } while (AP4_SUCCEEDED(result));
        data->Release();        
    }
    
    // get rid of entries that have no SINF
    AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem();
    while (sinf_entry_item) {
        SinfEntry* sinf_entry = sinf_entry_item->GetData();
        sinf_entry_item = sinf_entry_item->GetNext();
        if (sinf_entry->m_Sinf == NULL) {
            sinf_entries.Remove(sinf_entry);
        }
    }
    
    // remove the iods atom and the OD track if required
    if (remove_od_data) {
        od_trak->Detach();
        delete od_trak;
        iods->Detach();
        delete iods;
    }
    
    // cleanup
    delete od_track;
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpSampleDecrypter::Create
+---------------------------------------------------------------------*/
AP4_Result
AP4_MarlinIpmpSampleDecrypter::Create(AP4_AtomParent&                 /*top_level*/,
                                      const AP4_UI08*                 key,
                                      AP4_Size                        key_size,
                                      AP4_BlockCipherFactory*         block_cipher_factory,
                                      AP4_MarlinIpmpSampleDecrypter*& sample_decrypter)
{
    // TODO: need to parse group key info
    return Create(key, key_size, block_cipher_factory, sample_decrypter);
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpSampleDecrypter::Create
+---------------------------------------------------------------------*/
AP4_Result
AP4_MarlinIpmpSampleDecrypter::Create(const AP4_UI08*                 key,
                                      AP4_Size                        key_size,
                                      AP4_BlockCipherFactory*         block_cipher_factory,
                                      AP4_MarlinIpmpSampleDecrypter*& sample_decrypter)
{
    // default value
    sample_decrypter = NULL;
    if (block_cipher_factory == NULL) block_cipher_factory = &AP4_DefaultBlockCipherFactory::Instance;
    
    // create a block cipher for the decrypter
    AP4_BlockCipher* block_cipher = NULL;
    AP4_Result result = block_cipher_factory->CreateCipher(AP4_BlockCipher::AES_128,
                                                           AP4_BlockCipher::DECRYPT,
                                                           AP4_BlockCipher::CBC,
                                                           NULL,
                                                           key,
                                                           key_size,
                                                           block_cipher);
    if (AP4_FAILED(result)) return result;
    
    // create a CBC cipher
    AP4_CbcStreamCipher* cbc_cipher = new AP4_CbcStreamCipher(block_cipher);
    sample_decrypter = new AP4_MarlinIpmpSampleDecrypter(cbc_cipher);
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpSampleDecrypter::~AP4_MarlinIpmpSampleDecrypter
+---------------------------------------------------------------------*/
AP4_MarlinIpmpSampleDecrypter::~AP4_MarlinIpmpSampleDecrypter()
{
    delete m_Cipher;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpSampleDecrypter::GetDecryptedSampleSize
+---------------------------------------------------------------------*/
AP4_Size   
AP4_MarlinIpmpSampleDecrypter::GetDecryptedSampleSize(AP4_Sample& sample)
{
    // with CBC, we need to decrypt the last block to know what the padding was
    AP4_Size       encrypted_size = sample.GetSize()-AP4_AES_BLOCK_SIZE;
    AP4_DataBuffer encrypted;
    AP4_DataBuffer decrypted;
    AP4_Size       decrypted_size = AP4_CIPHER_BLOCK_SIZE;
    if (sample.GetSize() < 2*AP4_CIPHER_BLOCK_SIZE) {
        return 0;
    }
    AP4_Size offset = sample.GetSize()-2*AP4_CIPHER_BLOCK_SIZE;
    if (AP4_FAILED(sample.ReadData(encrypted, 2*AP4_CIPHER_BLOCK_SIZE, offset))) {
        return 0;
    }
    decrypted.Reserve(decrypted_size);
    m_Cipher->SetIV(encrypted.GetData());
    if (AP4_FAILED(m_Cipher->ProcessBuffer(encrypted.GetData()+AP4_CIPHER_BLOCK_SIZE, 
                                           AP4_CIPHER_BLOCK_SIZE,
                                           decrypted.UseData(), 
                                           &decrypted_size, 
                                           true))) {
        return 0;
    }
    unsigned int padding_size = AP4_CIPHER_BLOCK_SIZE-decrypted_size;
    return encrypted_size-padding_size;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpSampleDecrypter::DecryptSampleData
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpSampleDecrypter::DecryptSampleData(AP4_DataBuffer&    data_in,
                                                 AP4_DataBuffer&    data_out,
                                                 const AP4_UI08*    /*iv*/)
{
    AP4_Result result;
    
    const AP4_UI08* in = data_in.GetData();
    AP4_Size        in_size = data_in.GetDataSize();

    // default to 0 output 
    data_out.SetDataSize(0);

    // check that we have at least the minimum size
    if (in_size < 2*AP4_AES_BLOCK_SIZE) return AP4_ERROR_INVALID_FORMAT;

    // process the sample data
    AP4_Size out_size = in_size-AP4_AES_BLOCK_SIZE; // worst case
    data_out.SetDataSize(out_size);
    AP4_UI08* out = data_out.UseData();

    // decrypt the data
    m_Cipher->SetIV(in);
    result = m_Cipher->ProcessBuffer(in+AP4_AES_BLOCK_SIZE, 
                                     in_size-AP4_AES_BLOCK_SIZE, 
                                     out,
                                     &out_size,
                                     true);
    if (AP4_FAILED(result)) return result;
    
    // update the payload size
    data_out.SetDataSize(out_size);
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpDecryptingProcessor:AP4_MarlinIpmpDecryptingProcessor
+---------------------------------------------------------------------*/
AP4_MarlinIpmpDecryptingProcessor::AP4_MarlinIpmpDecryptingProcessor(
    const AP4_ProtectionKeyMap* key_map,             /* = NULL */
    AP4_BlockCipherFactory*     block_cipher_factory /* = NULL */){
    if (key_map) {
        // copy the keys
        m_KeyMap.SetKeys(*key_map);
    }
    
    if (block_cipher_factory == NULL) {
        m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance;
    } else {
        m_BlockCipherFactory = block_cipher_factory;
    }
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpDecryptingProcessor::~AP4_MarlinIpmpDecryptingProcessor
+---------------------------------------------------------------------*/
AP4_MarlinIpmpDecryptingProcessor::~AP4_MarlinIpmpDecryptingProcessor()
{
    m_SinfEntries.DeleteReferences();
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpDecryptingProcessor:Initialize
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpDecryptingProcessor::Initialize(AP4_AtomParent&   top_level, 
                                              AP4_ByteStream&   stream,
                                              ProgressListener* /*listener*/)
{
    AP4_Result result = AP4_MarlinIpmpParser::Parse(top_level, stream, m_SinfEntries, true);
    if (AP4_FAILED(result)) return result;

    // update the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp) {
        ftyp->SetMajorBrandAndVersion(AP4_FTYP_BRAND_MP42, 1);
        for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
            if (ftyp->GetCompatibleBrands()[i] == AP4_MARLIN_BRAND_MGSV) {
                ftyp->GetCompatibleBrands()[i] = AP4_FTYP_BRAND_MP42;
            }
        }
    }    
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpDecryptingProcessor:CreateTrackHandler
+---------------------------------------------------------------------*/
AP4_Processor::TrackHandler* 
AP4_MarlinIpmpDecryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak)
{
    // look for this track in the list of entries
    AP4_MarlinIpmpParser::SinfEntry* sinf_entry = NULL;
    for (AP4_List<AP4_MarlinIpmpParser::SinfEntry>::Item* sinf_entry_item = m_SinfEntries.FirstItem();
                                                          sinf_entry_item;
                                                          sinf_entry_item = sinf_entry_item->GetNext()) {
        sinf_entry = sinf_entry_item->GetData();
        if (sinf_entry->m_TrackId == trak->GetId()) {
            break; // match
        } else {
            sinf_entry = NULL; // no match
        }
    }
    if (sinf_entry == NULL) return NULL; // no matching entry
    AP4_ContainerAtom* sinf = sinf_entry->m_Sinf;
    
    // check the scheme
    bool use_group_key;
    AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHM));
    if (schm == NULL) return NULL; // no schm
    if (schm->GetSchemeType()    == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC && 
        schm->GetSchemeVersion() == 0x0100) {
        use_group_key = false;
    } else if (schm->GetSchemeType()    == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK &&
               schm->GetSchemeVersion() == 0x0100) {
        use_group_key = true;
    } else {
        // unsupported scheme
        return NULL;
    }

    // find the key
    const AP4_DataBuffer* key = NULL;
    AP4_DataBuffer        unwrapped_key;
    if (use_group_key) {
        const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0);
        if (group_key == NULL) return NULL; // no group key
        AP4_ContainerAtom* schi = AP4_DYNAMIC_CAST(AP4_ContainerAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHI));
        if (schi == NULL) return NULL; // no schi
        AP4_Atom* gkey = schi->GetChild(AP4_ATOM_TYPE_GKEY);
        if (gkey == NULL) return NULL; // no gkey
        AP4_MemoryByteStream* gkey_data = new AP4_MemoryByteStream();
        gkey->WriteFields(*gkey_data);
        AP4_AesKeyUnwrap(group_key->GetData(), gkey_data->GetData(), gkey_data->GetDataSize(), unwrapped_key);
        key = &unwrapped_key;
        gkey_data->Release();        
    } else {
        key = m_KeyMap.GetKey(sinf_entry->m_TrackId);
    }
    if (key == NULL) return NULL;

    // create the decrypter
    AP4_MarlinIpmpTrackDecrypter* decrypter = NULL;
    AP4_Result result = AP4_MarlinIpmpTrackDecrypter::Create(*m_BlockCipherFactory,
                                                             key->GetData(), 
                                                             key->GetDataSize(),
                                                             decrypter);
    if (AP4_FAILED(result)) return NULL;
    
    return decrypter;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackDecrypter::Create
+---------------------------------------------------------------------*/
AP4_Result
AP4_MarlinIpmpTrackDecrypter::Create(AP4_BlockCipherFactory&        cipher_factory,
                                     const AP4_UI08*                key,
                                     AP4_Size                       key_size,
                                     AP4_MarlinIpmpTrackDecrypter*& decrypter)
{
    decrypter = NULL;
    
    // create a sample decrypter
    AP4_MarlinIpmpSampleDecrypter* sample_decrypter = NULL;
    AP4_Result result = AP4_MarlinIpmpSampleDecrypter::Create(key, key_size, &cipher_factory, sample_decrypter);
    if (AP4_FAILED(result)) return result;
    
    // create the track decrypter
    decrypter = new AP4_MarlinIpmpTrackDecrypter(sample_decrypter);
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackDecrypter::~AP4_MarlinIpmpTrackDecrypter
+---------------------------------------------------------------------*/
AP4_MarlinIpmpTrackDecrypter::~AP4_MarlinIpmpTrackDecrypter()
{
    delete m_SampleDecrypter;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackDecrypter:GetProcessedSampleSize
+---------------------------------------------------------------------*/
AP4_Size 
AP4_MarlinIpmpTrackDecrypter::GetProcessedSampleSize(AP4_Sample& sample)
{
    return m_SampleDecrypter->GetDecryptedSampleSize(sample);
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackDecrypter::ProcessSample
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpTrackDecrypter::ProcessSample(AP4_DataBuffer& data_in,
                                            AP4_DataBuffer& data_out)
{
    return m_SampleDecrypter->DecryptSampleData(data_in, data_out);
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpEncryptingProcessor::AP4_MarlinIpmpEncryptingProcessor
+---------------------------------------------------------------------*/
AP4_MarlinIpmpEncryptingProcessor::AP4_MarlinIpmpEncryptingProcessor(
    bool                        use_group_key,       /* = false */
    const AP4_ProtectionKeyMap* key_map,             /* = NULL  */
    AP4_BlockCipherFactory*     block_cipher_factory /* = NULL  */) :
    m_UseGroupKey(use_group_key)
{
    if (key_map) {
        // copy the keys
        m_KeyMap.SetKeys(*key_map);
    }
    
    if (block_cipher_factory == NULL) {
        m_BlockCipherFactory = &AP4_DefaultBlockCipherFactory::Instance;
    } else {
        m_BlockCipherFactory = block_cipher_factory;
    }
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpEncryptingProcessor::Initialize
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpEncryptingProcessor::Initialize(
    AP4_AtomParent&                  top_level,
    AP4_ByteStream&                  /*stream*/,
    AP4_Processor::ProgressListener* /*listener = NULL*/)
{
    // get the moov atom
    AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    
    // deal with the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp) {
        // remove the atom, it will be replaced with a new one
        top_level.RemoveChild(ftyp);
        
        // keep the existing brand and compatible brands
        AP4_Array<AP4_UI32> compatible_brands;
        compatible_brands.EnsureCapacity(ftyp->GetCompatibleBrands().ItemCount()+1);
        for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
            compatible_brands.Append(ftyp->GetCompatibleBrands()[i]);
        }
        
        // add the MGSV compatible brand if it is not already there
        if (!ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV)) {
            compatible_brands.Append(AP4_MARLIN_BRAND_MGSV);
        }

        // create a replacement for the major brand
        AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV,
                                                  0x13c078c, //AP4_MARLIN_BRAND_MGSV_MAJOR_VERSION,
                                                  &compatible_brands[0],
                                                  compatible_brands.ItemCount());
        delete ftyp;
        ftyp = new_ftyp;
    } else {
        AP4_UI32 isom = AP4_FTYP_BRAND_ISOM;
        ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV, 0, &isom, 1);
    }
    
    // insert the ftyp atom as the first child
    top_level.AddChild(ftyp, 0);

    // create and 'mpod' track reference atom
    AP4_TrefTypeAtom* mpod = new AP4_TrefTypeAtom(AP4_ATOM_TYPE_MPOD);
    
    // look for an available track ID, starting at 1
    unsigned int od_track_id       = 0;
    unsigned int od_track_position = 0;
    for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
                                       trak_item;
                                       trak_item = trak_item->GetNext()) {
        AP4_TrakAtom* trak = trak_item->GetData();
        if (trak) {
            od_track_position++;
            if (trak->GetId() >= od_track_id) {
                od_track_id = trak->GetId()+1;
            }
            
            // if the track is encrypted, reference it in the mpod
            if (m_KeyMap.GetKey(trak->GetId())) {
                mpod->AddTrackId(trak->GetId());
            }
            
            //m_SinfEntries.Add(new SinfEntry(trak->GetId(), NULL));
        }   
    }
    
    // check that there was at least one track in the file
    if (od_track_id == 0) return AP4_ERROR_INVALID_FORMAT;
    
    // create an initial object descriptor
    AP4_InitialObjectDescriptor* iod = 
        // TODO: get real values from the property map
        new AP4_InitialObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_IOD,
                                        1022, // object descriptor id
                                        false, 
                                        0xFE,    // OD profile level (0xFE = No OD profile specified)
                                        0xFF,    // scene profile level
                                        0xFE,    // audio profile level
                                        0xFE,    // visual profile level
                                        0xFF);   // graphics profile

    // create an ES_ID_Inc subdescriptor and add it to the initial object descriptor
    AP4_EsIdIncDescriptor* es_id_inc = new AP4_EsIdIncDescriptor(od_track_id);
    iod->AddSubDescriptor(es_id_inc);
    
    // create an iods atom to hold the initial object descriptor
    AP4_IodsAtom* iods = new AP4_IodsAtom(iod);
    
    // add the iods atom to the moov atom (try to put it just after mvhd)
    int iods_position = 0;
    int item_position = 0;
    for (AP4_List<AP4_Atom>::Item* moov_item = moov->GetChildren().FirstItem();
                                   moov_item;
                                   moov_item = moov_item->GetNext()) {
         ++item_position;
         if (moov_item->GetData()->GetType() == AP4_ATOM_TYPE_MVHD) {
            iods_position = item_position;
            break;
         }
    }
    AP4_Result result = moov->AddChild(iods, iods_position);
    if (AP4_FAILED(result)) {
        delete iods;
        return result;
    }
    
    // create a sample table for the OD track
    AP4_SyntheticSampleTable* od_sample_table = new AP4_SyntheticSampleTable();
    
    // create the sample description for the OD track 
    AP4_MpegSystemSampleDescription* od_sample_description;
    od_sample_description = new AP4_MpegSystemSampleDescription(AP4_STREAM_TYPE_OD,
                                                                AP4_OTI_MPEG4_SYSTEM,
                                                                NULL,
                                                                32768, // buffer size
                                                                1024,  // max bitrate
                                                                512);  // avg bitrate
    od_sample_table->AddSampleDescription(od_sample_description, true);
    
    // create the OD descriptor update 
    AP4_DescriptorUpdateCommand od_update(AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        AP4_ObjectDescriptor* od = new AP4_ObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_OD, (AP4_UI16)(256+i)); // descriptor id = 256+i
        od->AddSubDescriptor(new AP4_EsIdRefDescriptor((AP4_UI16)(i+1)));     // index into mpod (1-based)
        od->AddSubDescriptor(new AP4_IpmpDescriptorPointer((AP4_UI08)(i+1))); // descriptor id = i+1
        od_update.AddDescriptor(od);
    }
    
    // create the IPMP descriptor update 
    AP4_DescriptorUpdateCommand ipmp_update(AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        // create the ipmp descriptor
        AP4_IpmpDescriptor* ipmp_descriptor = new AP4_IpmpDescriptor((AP4_UI08)(i+1), AP4_MARLIN_IPMPS_TYPE_MGSV);

        // create the sinf container
        AP4_ContainerAtom* sinf = new AP4_ContainerAtom(AP4_ATOM_TYPE_SINF);

        // add the scheme type atom
        sinf->AddChild(new AP4_SchmAtom(m_UseGroupKey?
                                        AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK:
                                        AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC, 
                                        0x0100, NULL, true));

        // create the 'schi' container
        AP4_ContainerAtom* schi = new AP4_ContainerAtom(AP4_ATOM_TYPE_SCHI);

        // add the content ID 
        const char* content_id = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "ContentId");
        if (content_id) {
            // add the content ID (8id_)
            schi->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_8ID_, content_id));
        }
                
        // find what the track type is (necessary for the next step) and the key
        const AP4_DataBuffer* key = NULL;
        AP4_Track::Type track_type = AP4_Track::TYPE_UNKNOWN;
        for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
                                           trak_item;
                                           trak_item = trak_item->GetNext()) {
            AP4_TrakAtom* trak = trak_item->GetData();
            if (trak->GetId() == mpod->GetTrackIds()[i]) {
                // find the handler type
                AP4_Atom* sub = trak->FindChild("mdia/hdlr");
                if (sub) {
                    AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, sub);
                    if (hdlr) {
                        AP4_UI32 type = hdlr->GetHandlerType();
                        if (type == AP4_HANDLER_TYPE_SOUN) {
                            track_type = AP4_Track::TYPE_AUDIO;
                        } else if (type == AP4_HANDLER_TYPE_VIDE) {
                            track_type = AP4_Track::TYPE_VIDEO;
                        }
                    }
                }
                
                // find the key
                key = m_KeyMap.GetKey(trak->GetId());
                break;
            }
        }
        
        // group key
        if (m_UseGroupKey && key) {
            // find the group key
            const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0);
            if (group_key) {
                AP4_DataBuffer wrapped_key;
                result = AP4_AesKeyWrap(group_key->GetData(), key->GetData(), key->GetDataSize(), wrapped_key);
                if (AP4_SUCCEEDED(result)) {
                    AP4_UnknownAtom* gkey = new AP4_UnknownAtom(AP4_ATOM_TYPE_GKEY,
                                                                wrapped_key.GetData(), 
                                                                wrapped_key.GetDataSize());
                    schi->AddChild(gkey);
                }
            }
        }
                
        // create and add the security attributes (satr)
        if (track_type != AP4_Track::TYPE_UNKNOWN && key != NULL && key != NULL) {
            AP4_ContainerAtom* satr = new AP4_ContainerAtom(AP4_ATOM_TYPE_SATR);
            switch (track_type) {
                case AP4_Track::TYPE_AUDIO:
                    satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_AUDIO));
                    break;
                case AP4_Track::TYPE_VIDEO:
                    satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_VIDEO));
                    break;
                default:
                    break;
            }
            
            // add the signed attributes, if any
            const char* signed_attributes = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "SignedAttributes");
            if (signed_attributes) {
                // decode the hex-encoded data
                unsigned int size = (unsigned int)AP4_StringLength(signed_attributes)/2;
                AP4_DataBuffer attributes_atoms;
                attributes_atoms.SetDataSize(size);
                if (AP4_SUCCEEDED(AP4_ParseHex(signed_attributes, attributes_atoms.UseData(), size))) {
                    // parse all the atoms encoded in the data and add them to the 'schi' container
                    AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream(attributes_atoms.GetData(), 
                                                                         attributes_atoms.GetDataSize());
                    AP4_DefaultAtomFactory atom_factory;
                    do {
                        AP4_Atom* atom = NULL;
                        result = atom_factory.CreateAtomFromStream(*mbs, atom);
                        if (AP4_SUCCEEDED(result) && atom) {
                            satr->AddChild(atom);
                        }
                    } while (AP4_SUCCEEDED(result));
                    mbs->Release();
                }
            }

            // compute the hmac
            AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream();
            satr->Write(*mbs);
            AP4_Hmac* digester = NULL;
            AP4_Hmac::Create(AP4_Hmac::SHA256, key->GetData(), key->GetDataSize(), digester);
            digester->Update(mbs->GetData(), mbs->GetDataSize());
            AP4_DataBuffer hmac_value;
            digester->Final(hmac_value);
            AP4_Atom* hmac = new AP4_UnknownAtom(AP4_ATOM_TYPE_HMAC, hmac_value.GetData(), hmac_value.GetDataSize());
            
            schi->AddChild(satr);
            schi->AddChild(hmac);
            
            mbs->Release();
        }
            
        sinf->AddChild(schi);
         
        // serialize the sinf atom to a buffer and set it as the ipmp data
        AP4_MemoryByteStream* sinf_data = new AP4_MemoryByteStream((AP4_Size)sinf->GetSize());
        sinf->Write(*sinf_data);
        ipmp_descriptor->SetData(sinf_data->GetData(), sinf_data->GetDataSize());
        sinf_data->Release();
        
        ipmp_update.AddDescriptor(ipmp_descriptor);
    }
    
    // add the sample with the descriptors and updates
    AP4_MemoryByteStream* sample_data = new AP4_MemoryByteStream();
    od_update.Write(*sample_data);
    ipmp_update.Write(*sample_data);
    od_sample_table->AddSample(*sample_data, 0, sample_data->GetDataSize(), 0, 0, 0, 0, true);
    
    // create the OD track
    AP4_TrakAtom* od_track = new AP4_TrakAtom(od_sample_table,
                                              AP4_HANDLER_TYPE_ODSM,
                                              "Bento4 Marlin OD Handler",
                                              od_track_id,
                                              0, 0,
                                              1, 1000, 1, 0, "und",
                                              0, 0);
    
    // add an entry in the processor's stream table to indicate that the 
    // media data for the OD track is not in the file stream, but in our
    // memory stream.
    m_ExternalTrackData.Add(new ExternalTrackData(od_track_id, sample_data));
    sample_data->Release();
    
    // add a tref track reference atom
    AP4_ContainerAtom* tref = new AP4_ContainerAtom(AP4_ATOM_TYPE_TREF);
    tref->AddChild(mpod);
    od_track->AddChild(tref, 1); // add after 'tkhd'
    
    // add the track to the moov atoms (just after the last track)
    moov->AddChild(od_track, od_track_position);
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpEncryptingProcessor::CreateTrackHandler
+---------------------------------------------------------------------*/
AP4_Processor::TrackHandler* 
AP4_MarlinIpmpEncryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak)
{
    // create a handler for this track if we have a key for it
    const AP4_DataBuffer* key;
    const AP4_DataBuffer* iv;
    if (AP4_SUCCEEDED(m_KeyMap.GetKeyAndIv(trak->GetId(), key, iv))) {
        // create the track handler
        AP4_MarlinIpmpTrackEncrypter* handler = NULL;
        AP4_Result result = AP4_MarlinIpmpTrackEncrypter::Create(*m_BlockCipherFactory, 
                                                                 key->GetData(), 
                                                                 key->GetDataSize(), 
                                                                 iv->GetData(), 
                                                                 iv->GetDataSize(),
                                                                 handler);
        if (AP4_FAILED(result)) return NULL;
        return handler;
    }
    
    // not encrypted
    return NULL;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackEncrypter::Create
+---------------------------------------------------------------------*/
AP4_Result
AP4_MarlinIpmpTrackEncrypter::Create(AP4_BlockCipherFactory&        cipher_factory,
                                     const AP4_UI08*                key,
                                     AP4_Size                       key_size,
                                     const AP4_UI08*                iv,
                                     AP4_Size                       iv_size,
                                     AP4_MarlinIpmpTrackEncrypter*& encrypter)
{
    // default value
    encrypter = NULL;
    
    // check args
    if (iv != NULL && iv_size != 16) {
        return AP4_ERROR_INVALID_PARAMETERS;
    }
    
    // create a block cipher
    AP4_BlockCipher* block_cipher = NULL;
    AP4_Result result = cipher_factory.CreateCipher(AP4_BlockCipher::AES_128, 
                                                    AP4_BlockCipher::ENCRYPT, 
                                                    AP4_BlockCipher::CBC,
                                                    NULL,
                                                    key, 
                                                    key_size, 
                                                    block_cipher);
    if (AP4_FAILED(result)) return result;

    // create a CBC cipher
    AP4_CbcStreamCipher* cbc_cipher = new AP4_CbcStreamCipher(block_cipher);
    
    // create the track encrypter
    encrypter = new AP4_MarlinIpmpTrackEncrypter(cbc_cipher, iv);
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackEncrypter::AP4_MarlinIpmpTrackEncrypter
+---------------------------------------------------------------------*/
AP4_MarlinIpmpTrackEncrypter::AP4_MarlinIpmpTrackEncrypter(AP4_StreamCipher* cipher, 
                                                           const AP4_UI08*   iv) :
    m_Cipher(cipher)
{
    // copy the IV
    AP4_CopyMemory(m_IV, iv, AP4_AES_BLOCK_SIZE);    
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackEncrypter::~AP4_MarlinIpmpTrackEncrypter
+---------------------------------------------------------------------*/
AP4_MarlinIpmpTrackEncrypter::~AP4_MarlinIpmpTrackEncrypter()
{
    delete m_Cipher;
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackEncrypter:GetProcessedSampleSize
+---------------------------------------------------------------------*/
AP4_Size 
AP4_MarlinIpmpTrackEncrypter::GetProcessedSampleSize(AP4_Sample& sample)
{
    return AP4_CIPHER_BLOCK_SIZE*(2+(sample.GetSize()/AP4_CIPHER_BLOCK_SIZE));
}

/*----------------------------------------------------------------------
|   AP4_MarlinIpmpTrackEncrypter:ProcessSample
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpTrackEncrypter::ProcessSample(AP4_DataBuffer& data_in,
                                            AP4_DataBuffer& data_out)
{
    AP4_Result result;
    
    const AP4_UI08* in = data_in.GetData();
    AP4_Size        in_size = data_in.GetDataSize();

    // default to 0 output 
    data_out.SetDataSize(0);

    // process the sample data
    AP4_Size out_size = AP4_CIPHER_BLOCK_SIZE*(2+(in_size/AP4_CIPHER_BLOCK_SIZE));
    data_out.SetDataSize(out_size);
    AP4_UI08* out = data_out.UseData();

    // write the IV
    AP4_CopyMemory(out, m_IV, AP4_CIPHER_BLOCK_SIZE);
    out_size -= AP4_CIPHER_BLOCK_SIZE;
    
    // encrypt the data
    m_Cipher->SetIV(m_IV);
    result = m_Cipher->ProcessBuffer(in, 
                                     in_size, 
                                     out+AP4_AES_BLOCK_SIZE,
                                     &out_size,
                                     true);
    if (AP4_FAILED(result)) return result;
    
    // update the payload size
    data_out.SetDataSize(out_size+AP4_AES_BLOCK_SIZE);
    
    return AP4_SUCCESS;
}


/*----------------------------------------------------------------------
|   AP4_MkidAtom::AP4_MkidAtom
+---------------------------------------------------------------------*/
AP4_MkidAtom::AP4_MkidAtom() :
    AP4_Atom(AP4_ATOM_TYPE_MKID, AP4_FULL_ATOM_HEADER_SIZE+4, 0, 0)
{
}

/*----------------------------------------------------------------------
|   AP4_MkidAtom::Create
+---------------------------------------------------------------------*/
AP4_MkidAtom*
AP4_MkidAtom::Create(AP4_Size size, AP4_ByteStream& stream)
{
    AP4_UI08 version;
    AP4_UI32 flags;
    if (size < AP4_FULL_ATOM_HEADER_SIZE) return NULL;
    if (AP4_FAILED(AP4_Atom::ReadFullHeader(stream, version, flags))) return NULL;
    if (version > 0) return NULL;
    return new AP4_MkidAtom(size, version, flags, stream);
}

/*----------------------------------------------------------------------
|   AP4_MkidAtom::AP4_MkidAtom
+---------------------------------------------------------------------*/
AP4_MkidAtom::AP4_MkidAtom(AP4_Size        size,
                           AP4_UI08        version,
                           AP4_UI32        flags,
                           AP4_ByteStream& stream) :
    AP4_Atom(AP4_ATOM_TYPE_MKID, size, version, flags)
{
    if (size < AP4_FULL_ATOM_HEADER_SIZE+4) return;
    AP4_Size available = size-(AP4_FULL_ATOM_HEADER_SIZE+4);
    AP4_UI32 entry_count = 0;
    stream.ReadUI32(entry_count);
    if (available < (AP4_UI64)entry_count*(16+4)) return;
    m_Entries.SetItemCount(entry_count);
    for (unsigned int i=0; i<entry_count && available >= 16+4; i++) {
        AP4_UI32 entry_size;
        stream.ReadUI32(entry_size);
        if (available < 4+entry_size) break;
        if (entry_size < 16) continue;
        available -= (4+entry_size);
        stream.Read(m_Entries[i].m_KID, 16);
        unsigned int content_id_size = entry_size-16; 
        char* content_id = new char[content_id_size];
        stream.Read(content_id, content_id_size);
        m_Entries[i].m_ContentId.Assign(content_id, content_id_size);
        delete[] content_id;
    }
}

/*----------------------------------------------------------------------
|   AP4_MkidAtom::AddEntry
+---------------------------------------------------------------------*/
AP4_Result              
AP4_MkidAtom::AddEntry(const AP4_UI08* kid, const char* content_id)
{
    unsigned int content_id_size = (unsigned int)AP4_StringLength(content_id);
    unsigned int entry_count = m_Entries.ItemCount();

    // add the entry
    m_Entries.SetItemCount(entry_count+1);
    AP4_CopyMemory(m_Entries[entry_count].m_KID, kid, 16);
    m_Entries[entry_count].m_ContentId.Assign(content_id, content_id_size);

    // update the size
    m_Size32 += 4+16+content_id_size;

    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------
|   AP4_MkidAtom::WriteFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_MkidAtom::WriteFields(AP4_ByteStream& stream)
{
    AP4_Result result;

    // encoding
    result = stream.WriteUI32(m_Entries.ItemCount());
    if (AP4_FAILED(result)) return result;

    // entries
    for (unsigned int i=0; i<m_Entries.ItemCount(); i++) {
        stream.WriteUI32(16+m_Entries[i].m_ContentId.GetLength());
        stream.Write(m_Entries[i].m_KID, 16);
        stream.Write(m_Entries[i].m_ContentId.GetChars(), m_Entries[i].m_ContentId.GetLength());
    }
    
    return result;
}

/*----------------------------------------------------------------------
|   AP4_MkidAtom::InspectFields
+---------------------------------------------------------------------*/
AP4_Result
AP4_MkidAtom::InspectFields(AP4_AtomInspector& inspector)
{
    inspector.AddField("entry_count", m_Entries.ItemCount());
    for (unsigned int i=0; i<m_Entries.ItemCount(); i++) {
        inspector.AddField("KID", m_Entries[i].m_KID, 16);
        inspector.AddField("content_id", m_Entries[i].m_ContentId.GetChars());
    }
    return AP4_SUCCESS;
}




