Example #1
0
/*----------------------------------------------------------------------
|   AP4_BlocAtom::AP4_BlocAtom
+---------------------------------------------------------------------*/
AP4_BlocAtom::AP4_BlocAtom() :
    AP4_Atom(AP4_ATOM_TYPE_BLOC, AP4_FULL_ATOM_HEADER_SIZE+256+256+512, 0, 0)
{
    AP4_SetMemory(m_BaseLocation, 0, 256+1);
    AP4_SetMemory(m_PurchaseLocation, 0, 256+1);
    AP4_SetMemory(m_Reserved, 0, 512);
}
/*----------------------------------------------------------------------
|   AP4_CbcStreamCipher::AP4_CbcStreamCipher
+---------------------------------------------------------------------*/
AP4_CbcStreamCipher::AP4_CbcStreamCipher(AP4_BlockCipher* block_cipher) :
    m_StreamOffset(0),
    m_OutputSkip(0),
    m_InBlockFullness(0),
    m_ChainBlockFullness(AP4_CIPHER_BLOCK_SIZE),
    m_BlockCipher(block_cipher),
    m_Eos(false)
{
    AP4_SetMemory(m_Iv, 0, AP4_CIPHER_BLOCK_SIZE);
    AP4_SetMemory(m_ChainBlock, 0, AP4_CIPHER_BLOCK_SIZE);
}
Example #3
0
/*----------------------------------------------------------------------
|   AP4_System_GenerateRandomBytes
+---------------------------------------------------------------------*/
AP4_Result
AP4_System_GenerateRandomBytes(AP4_UI08* buffer, AP4_Size buffer_size)
{
    AP4_SetMemory(buffer, 0, buffer_size);
    int random = open("/dev/random", O_RDONLY);
    if (random < 0) {
        return AP4_FAILURE;
    }
    AP4_Result result = AP4_SUCCESS;
    while (buffer_size) {
        int nb_read = (int)read(random, buffer, buffer_size);
        if (nb_read <= 0) {
            result = AP4_ERROR_READ_FAILED;
            goto end;
        }
        if ((AP4_Size)nb_read > buffer_size) {
            result = AP4_ERROR_INTERNAL;
            goto end;
        }
        buffer_size -= nb_read;
        buffer += nb_read;
    }
    
end:
    close(random);
    return result;
}
Example #4
0
/*----------------------------------------------------------------------
|   AP4_BlocAtom::SetPurchaseLocation
+---------------------------------------------------------------------*/
void
AP4_BlocAtom::SetPurchaseLocation(const char* purchase_location)
{
    unsigned int len = (unsigned int)AP4_StringLength(purchase_location);
    if (len > 256) len = 256;
    AP4_CopyMemory(m_PurchaseLocation, purchase_location, len);
    AP4_SetMemory(&m_PurchaseLocation[len], 0, 256-len+1);
}
Example #5
0
/*----------------------------------------------------------------------
|   AP4_MemoryByteStream::AP4_MemoryByteStream
+---------------------------------------------------------------------*/
AP4_MemoryByteStream::AP4_MemoryByteStream(AP4_Size size) :
    m_BufferIsLocal(true),
    m_Position(0),
    m_ReferenceCount(1)
{
    m_Buffer = new AP4_DataBuffer(size);
    AP4_SetMemory(m_Buffer->UseData(), 0, size);
    m_Buffer->SetDataSize(size);
}
Example #6
0
/*----------------------------------------------------------------------
|   AP4_IpmpDescriptor::AP4_IpmpDescriptor
+---------------------------------------------------------------------*/
AP4_IpmpDescriptor::AP4_IpmpDescriptor(AP4_UI08 descriptor_id, AP4_UI16 ipmps_type) :
    AP4_Descriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR, 2, 3),
    m_DescriptorId(descriptor_id),
    m_IpmpsType(ipmps_type),
    m_DescriptorIdEx(0),
    m_ControlPointCode(0),
    m_SequenceCode(0)
{
    AP4_SetMemory(m_ToolId, 0, sizeof(m_ToolId));
}
/*----------------------------------------------------------------------
|   AP4_CtrStreamCipher::SetIV
+---------------------------------------------------------------------*/
AP4_Result
AP4_CtrStreamCipher::SetIV(const AP4_UI08* iv)
{
    if (iv) {
        AP4_CopyMemory(m_IV, iv, AP4_CIPHER_BLOCK_SIZE);
    } else {
        AP4_SetMemory(m_IV, 0, AP4_CIPHER_BLOCK_SIZE);
    }

    // for the stream offset back to 0
    m_CacheValid = false;
    return SetStreamOffset(0);
}
/*----------------------------------------------------------------------
|   AP4_CtrStreamCipher::AP4_CtrStreamCipher
+---------------------------------------------------------------------*/
AP4_CtrStreamCipher::AP4_CtrStreamCipher(AP4_BlockCipher* block_cipher,
                                         AP4_Size         counter_size) :
    m_StreamOffset(0),
    m_CounterSize(counter_size),
    m_CacheValid(false),
    m_BlockCipher(block_cipher)
{
    if (m_CounterSize > 16) m_CounterSize = 16;

    // reset the stream offset
    AP4_SetMemory(m_IV, 0, AP4_CIPHER_BLOCK_SIZE);
    SetStreamOffset(0);
    SetIV(NULL);
}
Example #9
0
/*----------------------------------------------------------------------
|   AP4_OmaDcfCtrSampleDecrypter::DecryptSampleData
+---------------------------------------------------------------------*/
AP4_Result
AP4_OmaDcfCtrSampleDecrypter::DecryptSampleData(AP4_DataBuffer& data_in,
        AP4_DataBuffer& data_out,
        const AP4_UI08* /*iv*/)
{
    bool                 is_encrypted = true;
    const unsigned char* in = data_in.GetData();
    AP4_Size             in_size = data_in.GetDataSize();

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

    // check the selective encryption flag
    if (m_SelectiveEncryption) {
        if (in_size < 1) return AP4_ERROR_INVALID_FORMAT;
        is_encrypted = ((in[0]&0x80)!=0);
        in++;
    }

    // check the size
    unsigned int header_size = (m_SelectiveEncryption?1:0)+(is_encrypted?m_IvLength:0);
    if (header_size > in_size) return AP4_ERROR_INVALID_FORMAT;

    // process the sample data
    AP4_Size payload_size = in_size-header_size;
    AP4_CHECK(data_out.Reserve(payload_size));
    unsigned char* out = data_out.UseData();
    if (is_encrypted) {
        // set the IV
        if (m_IvLength == 16) {
            m_Cipher->SetIV(in);
        } else {
            AP4_UI08 iv[16];
            AP4_SetMemory(iv, 0, 16);
            AP4_CopyMemory(iv+16-m_IvLength, in, m_IvLength);
            m_Cipher->SetIV(iv);
        }
        AP4_CHECK(m_Cipher->ProcessBuffer(in+m_IvLength,
                                          payload_size,
                                          out));
    } else {
        AP4_CopyMemory(out, in, payload_size);
    }
    AP4_CHECK(data_out.SetDataSize(payload_size));

    return AP4_SUCCESS;
}
Example #10
0
/*----------------------------------------------------------------------
|   AP4_IpmpDescriptor::AP4_IpmpDescriptor
+---------------------------------------------------------------------*/
AP4_IpmpDescriptor::AP4_IpmpDescriptor(AP4_ByteStream& stream, 
                                       AP4_Size        header_size,
                                       AP4_Size        payload_size) :
    AP4_Descriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR, header_size, payload_size),
    m_DescriptorIdEx(0),
    m_ControlPointCode(0),
    m_SequenceCode(0)
{
    stream.ReadUI08(m_DescriptorId);
    stream.ReadUI16(m_IpmpsType);
    AP4_SetMemory(m_ToolId, 0, sizeof(m_ToolId));
    if (m_DescriptorId == 0xFF && m_IpmpsType == 0xFFFF) {
        AP4_Size fields_size = 3+3;
        stream.ReadUI16(m_DescriptorIdEx);
        stream.Read(m_ToolId, 16);
        stream.ReadUI08(m_ControlPointCode);
        if (m_ControlPointCode > 0) {
            stream.ReadUI08(m_SequenceCode);
            ++fields_size;
        }
        if (fields_size < payload_size) {
            m_Data.SetDataSize(payload_size-fields_size);
            stream.Read(m_Data.UseData(), payload_size-fields_size);
        }
    } else if (m_IpmpsType == 0) {
        if (payload_size > 3) {
            char* buffer = new char[1+payload_size-3];
            buffer[payload_size-3] = '\0';
            stream.Read(buffer, payload_size-3);
            m_Url.Assign(buffer, payload_size-3);
            delete[] buffer;
        }
    } else {
        if (payload_size > 3) {
            m_Data.SetDataSize(payload_size-3);
            stream.Read(m_Data.UseData(), payload_size-3);
        }
    }
}
Example #11
0
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc == 1) PrintUsageAndExit();

    // parse options
    const char* kms_uri = NULL;
    enum Method method  = METHOD_NONE;
    const char* input_filename = NULL;
    const char* output_filename = NULL;
    AP4_ProtectionKeyMap key_map;
    AP4_TrackPropertyMap property_map;
    bool                 show_progress = false;

    // parse the command line arguments
    char* arg;
    while ((arg = *++argv)) {
        if (!strcmp(arg, "--method")) {
            arg = *++argv;
            if (!strcmp(arg, "OMA-PDCF-CBC")) {
                method = METHOD_OMA_PDCF_CBC;
            } else if (!strcmp(arg, "OMA-PDCF-CTR")) {
                method = METHOD_OMA_PDCF_CTR;
            } else if (!strcmp(arg, "MARLIN-IPMP")) {
                method = METHOD_MARLIN_IPMP;
            } else if (!strcmp(arg, "ISMA-IAEC")) {
                method = METHOD_ISMA_AES;
            } else {
                fprintf(stderr, "ERROR: invalid value for --method argument\n");
                return 1;
            }
        } else if (!strcmp(arg, "--kms-uri")) {
            if (method != METHOD_ISMA_AES) {
                fprintf(stderr, "ERROR: --kms-uri only applies to method ISMA-IAEC\n");
                return 1;
            }
            kms_uri = *++argv;
        } else if (!strcmp(arg, "--show-progress")) {
            show_progress = true;
        } else if (!strcmp(arg, "--key")) {
            if (method == METHOD_NONE) {
                fprintf(stderr, "ERROR: --method argument must appear before --key\n");
                return 1;
            }
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --key option\n");
                return 1;
            }
            char* track_ascii = NULL;
            char* key_ascii = NULL;
            char* iv_ascii = NULL;
            if (AP4_FAILED(AP4_SplitArgs(arg, track_ascii, key_ascii, iv_ascii))) {
                fprintf(stderr, "ERROR: invalid argument for --key option\n");
                return 1;
            }
            unsigned char key[16];
            unsigned char iv[16];
            unsigned int track = strtoul(track_ascii, NULL, 10);
            AP4_SetMemory(key, 0, sizeof(key));
            AP4_SetMemory(iv, 0, sizeof(iv));
            if (AP4_ParseHex(key_ascii, key, 16)) {
                fprintf(stderr, "ERROR: invalid hex format for key\n");
            }
            if (AP4_ParseHex(iv_ascii, iv, 8)) {
                fprintf(stderr, "ERROR: invalid hex format for iv\n");
                return 1;
            }
            // check that the key is not already there
            if (key_map.GetKey(track)) {
                fprintf(stderr, "ERROR: key already set for track %d\n", track);
                return 1;
            }
            // set the key in the map
            key_map.SetKey(track, key, iv);
        } else if (!strcmp(arg, "--property")) {
            char* track_ascii = NULL;
            char* name = NULL;
            char* value = NULL;
            if (method != METHOD_OMA_PDCF_CBC && 
                method != METHOD_OMA_PDCF_CTR &&
                method != METHOD_MARLIN_IPMP) {
                fprintf(stderr, "ERROR: this method does not use properties\n");
                return 1;
            }
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --property option\n");
                return 1;
            }
            if (AP4_FAILED(AP4_SplitArgs(arg, track_ascii, name, value))) {
                fprintf(stderr, "ERROR: invalid argument for --property option\n");
                return 1;
            }
            unsigned int track = strtoul(track_ascii, NULL, 10);

            // check that the property is not already set
            if (property_map.GetProperty(track, name)) {
                fprintf(stderr, "ERROR: property %s already set for track %d\n",
                                name, track);
                return 1;
            }
            // set the property in the map
            property_map.SetProperty(track, name, value);
        } else if (input_filename == NULL) {
            input_filename = arg;
        } else if (output_filename == NULL) {
            output_filename = arg;
        } else {
            fprintf(stderr, "ERROR: unexpected argument (%s)\n", arg);
            return 1;
        }
    }

    // check the arguments
    if (method == METHOD_NONE) {
        fprintf(stderr, "ERROR: missing --method argument\n");
        return 1;
    }
    if (input_filename == NULL) {
        fprintf(stderr, "ERROR: missing input filename\n");
        return 1;
    }
    if (output_filename == NULL) {
        fprintf(stderr, "ERROR: missing output filename\n");
        return 1;
    }

    // create an encrypting processor
    AP4_Processor* processor;
    if (method == METHOD_ISMA_AES) {
        if (kms_uri == NULL) {
            fprintf(stderr, "ERROR: method ISMA-IAEC requires --kms-uri\n");
            return 1;
        }
        AP4_IsmaEncryptingProcessor* isma_processor = new AP4_IsmaEncryptingProcessor(kms_uri);
        isma_processor->GetKeyMap().SetKeys(key_map);
        processor = isma_processor;
    } else if (method == METHOD_MARLIN_IPMP) {
        AP4_MarlinIpmpEncryptingProcessor* marlin_processor = 
            new AP4_MarlinIpmpEncryptingProcessor();
        marlin_processor->GetKeyMap().SetKeys(key_map);
        marlin_processor->GetPropertyMap().SetProperties(property_map);
        processor = marlin_processor;
    } else {
        AP4_OmaDcfEncryptingProcessor* oma_processor = 
            new AP4_OmaDcfEncryptingProcessor(method == METHOD_OMA_PDCF_CTR?
                                              AP4_OMA_DCF_CIPHER_MODE_CTR :
                                              AP4_OMA_DCF_CIPHER_MODE_CBC);
        oma_processor->GetKeyMap().SetKeys(key_map);
        oma_processor->GetPropertyMap().SetProperties(property_map);
        processor = oma_processor;
    }

    // create the input stream
    AP4_ByteStream* input;
    try{
        input = new AP4_FileByteStream(input_filename,
            AP4_FileByteStream::STREAM_MODE_READ);
    } catch (AP4_Exception) {
        fprintf(stderr, "ERROR: cannot open input file (%s)\n", input_filename);
        return 1;
    }

    // create the output stream
    AP4_ByteStream* output;
    try {
        output = new AP4_FileByteStream(output_filename,
            AP4_FileByteStream::STREAM_MODE_WRITE);
    } catch (AP4_Exception) {
        fprintf(stderr, "ERROR: cannot open output file (%s)\n", output_filename);
        return 1;
    }

    // process/decrypt the file
    ProgressListener listener;
    AP4_Result result = processor->Process(*input, *output, show_progress?&listener:NULL);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: failed to process the file (%d)\n", result);
    }

    // cleanup
    delete processor;
    input->Release();
    output->Release();

    return 0;
}
Example #12
0
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc == 1) PrintUsageAndExit();

    // parse options
    AP4_UI08       encryption_method        = 0;
    bool           encryption_method_is_set = false;
    AP4_UI08       padding_scheme           = 0;
    const char*    input_filename           = NULL;
    const char*    output_filename          = NULL;
    bool           show_progress            = false;
    bool           key_is_set               = false;
    unsigned char  key[16];
    unsigned char  iv[16];
    AP4_BlockCipher::CipherMode cipher_mode = AP4_BlockCipher::CBC;
    const char*    content_type             = "";
    const char*    content_id               = "";
    const char*    rights_issuer_url        = "";
    AP4_LargeSize  plaintext_length         = 0;
    AP4_DataBuffer textual_headers_buffer;
    AP4_TrackPropertyMap textual_headers;

    AP4_SetMemory(key, 0, sizeof(key));
    AP4_SetMemory(iv, 0, sizeof(iv));

    // parse the command line arguments
    char* arg;
    while ((arg = *++argv)) {
        if (!strcmp(arg, "--method")) {
            arg = *++argv;
            if (!strcmp(arg, "CBC")) {
                encryption_method = AP4_OMA_DCF_ENCRYPTION_METHOD_AES_CBC;
                encryption_method_is_set = true;
                padding_scheme = AP4_OMA_DCF_PADDING_SCHEME_RFC_2630;
                cipher_mode = AP4_BlockCipher::CBC;
            } else if (!strcmp(arg, "CTR")) {
                encryption_method = AP4_OMA_DCF_ENCRYPTION_METHOD_AES_CTR;
                encryption_method_is_set = true;
                padding_scheme = AP4_OMA_DCF_PADDING_SCHEME_NONE;
                cipher_mode = AP4_BlockCipher::CTR;
            } else if (!strcmp(arg, "NULL")) {
                encryption_method = AP4_OMA_DCF_ENCRYPTION_METHOD_NULL;
                encryption_method_is_set = true;
                padding_scheme = AP4_OMA_DCF_PADDING_SCHEME_NONE;
            } else {
                fprintf(stderr, "ERROR: invalid value for --method argument\n");
                return 1;
            }
        } else if (!strcmp(arg, "--show-progress")) {
            show_progress = true;
        } else if (!strcmp(arg, "--content-type")) {
            content_type = *++argv;
            if (content_type == NULL) {
                fprintf(stderr, "ERROR: missing argument for --content-type option\n");
                return 1;
            }
        } else if (!strcmp(arg, "--content-id")) {
            content_id = *++argv;
            if (content_type == NULL) {
                fprintf(stderr, "ERROR: missing argument for --content-id option\n");
                return 1;
            }
        } else if (!strcmp(arg, "--rights-issuer")) {
             rights_issuer_url = *++argv;
            if (rights_issuer_url == NULL) {
                fprintf(stderr, "ERROR: missing argument for --rights-issuer option\n");
                return 1;
            }
        } else if (!strcmp(arg, "--key")) {
            if (!encryption_method_is_set) {
                fprintf(stderr, "ERROR: --method argument must appear before --key\n");
                return 1;
            } else if (encryption_method_is_set == AP4_OMA_DCF_ENCRYPTION_METHOD_NULL) {
                fprintf(stderr, "ERROR: --key cannot be used with --method NULL\n");
                return 1;
            }
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --key option\n");
                return 1;
            }
            char* key_ascii = NULL;
            char* iv_ascii = NULL;
            if (AP4_FAILED(AP4_SplitArgs(arg, key_ascii, iv_ascii))) {
                fprintf(stderr, "ERROR: invalid argument for --key option\n");
                return 1;
            }
            if (AP4_ParseHex(key_ascii, key, 16)) {
                fprintf(stderr, "ERROR: invalid hex format for key\n");
            }
            if (AP4_ParseHex(iv_ascii, iv, 16)) {
                fprintf(stderr, "ERROR: invalid hex format for iv\n");
                return 1;
            }
            // check that the key is not already there
            if (key_is_set) {
                fprintf(stderr, "ERROR: key already set\n");
                return 1;
            }
            key_is_set = true;
        } else if (!strcmp(arg, "--textual-header")) {
            char* name = NULL;
            char* value = NULL;
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --textual-header option\n");
                return 1;
            }
            if (AP4_FAILED(AP4_SplitArgs(arg, name, value))) {
                fprintf(stderr, "ERROR: invalid argument for --textual-header option\n");
                return 1;
            }

            // check that the property is not already set
            if (textual_headers.GetProperty(0, name)) {
                fprintf(stderr, "ERROR: textual header %s already set\n", name);
                return 1;
            }
            // set the property in the map
            textual_headers.SetProperty(0, name, value);
        } else if (input_filename == NULL) {
            input_filename = arg;
        } else if (output_filename == NULL) {
            output_filename = arg;
        } else {
            fprintf(stderr, "ERROR: unexpected argument (%s)\n", arg);
            return 1;
        }
    }

    // check the arguments
    if (!encryption_method_is_set) {
        fprintf(stderr, "ERROR: missing --method argument\n");
        return 1;
    }
    if (!key_is_set) {
        fprintf(stderr, "ERROR: encryption key not specified\n");
        return 1;
    }
    if (input_filename == NULL) {
        fprintf(stderr, "ERROR: missing input filename\n");
        return 1;
    }
    if (output_filename == NULL) {
        fprintf(stderr, "ERROR: missing output filename\n");
        return 1;
    }
    (void)show_progress; // avoid warnings

    // convert to a textual headers buffer
    textual_headers.GetTextualHeaders(0, textual_headers_buffer);

    // create the input stream
    AP4_Result result;
    AP4_ByteStream* input = NULL;
    result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input file (%s) %d\n", input_filename, result);
        return 1;
    }

    // get the size of the input
    result = input->GetSize(plaintext_length);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot get the size of the input\n");
        return 1;
    }
    
    // create an encrypting stream for the input
    AP4_ByteStream* encrypted_stream;
    if (encryption_method == AP4_OMA_DCF_ENCRYPTION_METHOD_NULL) {
        encrypted_stream = input;
    } else {
        result = AP4_EncryptingStream::Create(cipher_mode, *input, iv, 16, key, 16, true, &AP4_DefaultBlockCipherFactory::Instance, encrypted_stream);
        if (AP4_FAILED(result)) {
            fprintf(stderr, "ERROR: failed to create cipher (%d)\n", result);
            return 1;
        }
    }
    
    // create the output stream
    AP4_ByteStream* output = NULL;
    result = AP4_FileByteStream::Create(output_filename, AP4_FileByteStream::STREAM_MODE_WRITE, output);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open output file (%s) %d\n", output_filename, result);
        return 1;
    }

    // create the file
    AP4_File file;
    
    // set the brand
    AP4_UI32 compatible_brands[1] = {AP4_OMA_DCF_BRAND_ODCF};
    file.SetFileType(AP4_OMA_DCF_BRAND_ODCF, 2, compatible_brands, 1);
    
    // create the odrm atom (force a 64-bit size)
    AP4_ContainerAtom* odrm = new AP4_ContainerAtom(AP4_ATOM_TYPE_ODRM, AP4_FULL_ATOM_HEADER_SIZE_64, true, 0, 0);
    
    // create the ohdr atom
    AP4_OhdrAtom* ohdr = new AP4_OhdrAtom(encryption_method, 
                                          padding_scheme,
                                          plaintext_length,
                                          content_id,
                                          rights_issuer_url,
                                          textual_headers_buffer.GetData(),
                                          textual_headers_buffer.GetDataSize());

    // create the odhe atom (the ownership is transfered)
    AP4_OdheAtom* odhe = new AP4_OdheAtom(content_type, ohdr);
    odrm->AddChild(odhe);
    
    // create the odda atom
    AP4_OddaAtom* odda = new AP4_OddaAtom(*encrypted_stream);
    odrm->AddChild(odda);
    
    // add the odrm atom to the file (the owndership is transfered)
    file.GetTopLevelAtoms().Add(odrm);
    
    // write the file to the output
    AP4_FileWriter::Write(file, *output);
    
    // cleanup
    input->Release();
    output->Release();

    return 0;
}
Example #13
0
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc == 1) PrintUsageAndExit();

    // parse options
    const char*              kms_uri = NULL;
    enum Method              method  = METHOD_NONE;
    const char*              input_filename = NULL;
    const char*              output_filename = NULL;
    const char*              fragments_info_filename = NULL;
    AP4_ProtectionKeyMap     key_map;
    AP4_TrackPropertyMap     property_map;
    bool                     show_progress = false;
    bool                     strict = false;
    AP4_Array<AP4_PsshAtom*> pssh_atoms;
    AP4_DataBuffer           kids;
    unsigned int             kid_count = 0;
    AP4_Result               result;
    
    // parse the command line arguments
    char* arg;
    while ((arg = *++argv)) {
        if (!strcmp(arg, "--method")) {
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --method option\n");
                return 1;
            }
            if (!strcmp(arg, "OMA-PDCF-CBC")) {
                method = METHOD_OMA_PDCF_CBC;
            } else if (!strcmp(arg, "OMA-PDCF-CTR")) {
                method = METHOD_OMA_PDCF_CTR;
            } else if (!strcmp(arg, "MARLIN-IPMP-ACBC")) {
                method = METHOD_MARLIN_IPMP_ACBC;
            } else if (!strcmp(arg, "MARLIN-IPMP-ACGK")) {
                method = METHOD_MARLIN_IPMP_ACGK;
            } else if (!strcmp(arg, "PIFF-CBC")) {
                method = METHOD_PIFF_CBC;
            } else if (!strcmp(arg, "PIFF-CTR")) {
                method = METHOD_PIFF_CTR;
            } else if (!strcmp(arg, "MPEG-CENC")) {
                method = METHOD_MPEG_CENC;
            } else if (!strcmp(arg, "ISMA-IAEC")) {
                method = METHOD_ISMA_AES;
            } else {
                fprintf(stderr, "ERROR: invalid value for --method argument\n");
                return 1;
            }
        } else if (!strcmp(arg, "--fragments-info")) {
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --fragments-info option\n");
                return 1;
            }
            fragments_info_filename = arg;
        } else if (!strcmp(arg, "--pssh") || !strcmp(arg, "--pssh-v1")) {
            bool v1 = (strcmp(arg, "--pssh-v1") == 0);
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --pssh\n");
                return 1;
            }
            if (AP4_StringLength(arg) < 32+1 || arg[32] != ':') {
                fprintf(stderr, "ERROR: invalid argument syntax for --pssh\n");
                return 1;
            }
            unsigned char system_id[16];
            arg[32] = '\0';
            result = AP4_ParseHex(arg, system_id, 16);
            if (AP4_FAILED(result)) {
                fprintf(stderr, "ERROR: invalid argument syntax for --pssh\n");
                return 1;
            }
            const char* pssh_filename = arg+33;
            
            // load the pssh payload
            AP4_DataBuffer pssh_payload;
            if (pssh_filename[0]) {
                AP4_ByteStream* pssh_input = NULL;
                result = AP4_FileByteStream::Create(pssh_filename, AP4_FileByteStream::STREAM_MODE_READ, pssh_input);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: cannot open pssh payload file (%d)\n", result);
                    return 1;
                }
                AP4_LargeSize pssh_payload_size = 0;
                pssh_input->GetSize(pssh_payload_size);
                pssh_payload.SetDataSize((AP4_Size)pssh_payload_size);
                result = pssh_input->Read(pssh_payload.UseData(), (AP4_Size)pssh_payload_size);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: cannot read pssh payload from file (%d)\n", result);
                    return 1;
                }
            }
            AP4_PsshAtom* pssh;
            if (v1) {
                if (kid_count) {
                    pssh = new AP4_PsshAtom(system_id, kids.GetData(), kid_count);
                } else {
                    pssh = new AP4_PsshAtom(system_id);
                }
            } else {
                pssh = new AP4_PsshAtom(system_id);
            }
            if (pssh_payload.GetDataSize()) {
                pssh->SetData(pssh_payload.GetData(), pssh_payload.GetDataSize());
            }
            pssh_atoms.Append(pssh);
        } else if (!strcmp(arg, "--kms-uri")) {
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --kms-uri option\n");
                return 1;
            }
            if (method != METHOD_ISMA_AES) {
                fprintf(stderr, "ERROR: --kms-uri only applies to method ISMA-IAEC\n");
                return 1;
            }
            kms_uri = arg;
        } else if (!strcmp(arg, "--show-progress")) {
            show_progress = true;
        } else if (!strcmp(arg, "--strict")) {
            strict = true;
        } else if (!strcmp(arg, "--key")) {
            if (method == METHOD_NONE) {
                fprintf(stderr, "ERROR: --method argument must appear before --key\n");
                return 1;
            }
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --key option\n");
                return 1;
            }
            char* track_ascii = NULL;
            char* key_ascii = NULL;
            char* iv_ascii = NULL;
            if (AP4_FAILED(AP4_SplitArgs(arg, track_ascii, key_ascii, iv_ascii))) {
                fprintf(stderr, "ERROR: invalid argument for --key option\n");
                return 1;
            }
            unsigned int track = strtoul(track_ascii, NULL, 10);

            
            // parse the key value
            unsigned char key[16];
            AP4_SetMemory(key, 0, sizeof(key));
            if (AP4_CompareStrings(key_ascii, "random") == 0) {
                result = AP4_System_GenerateRandomBytes(key, 16);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to generate random key (%d)\n", result);
                    return 1;
                }
                char key_hex[32+1];
                key_hex[32] = '\0';
                AP4_FormatHex(key, 16, key_hex);
                printf("KEY.%d=%s\n", track, key_hex);
            } else {
                if (AP4_ParseHex(key_ascii, key, 16)) {
                    fprintf(stderr, "ERROR: invalid hex format for key\n");
                    return 1;
                }
            }
            
            // parse the iv
            unsigned char iv[16];
            AP4_SetMemory(iv, 0, sizeof(iv));
            if (AP4_CompareStrings(iv_ascii, "random") == 0) {
                result = AP4_System_GenerateRandomBytes(iv, 16);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: failed to generate random key (%d)\n", result);
                    return 1;
                }
                iv[0] &= 0x7F; // always set the MSB to 0 so we don't have wraparounds
            } else {
                unsigned int iv_size = (unsigned int)AP4_StringLength(iv_ascii)/2;
                if (AP4_ParseHex(iv_ascii, iv, iv_size)) {
                    fprintf(stderr, "ERROR: invalid hex format for iv\n");
                    return 1;
                }
            }
            switch (method) {
                case METHOD_OMA_PDCF_CTR:
                case METHOD_ISMA_AES:
                case METHOD_PIFF_CTR:
                case METHOD_MPEG_CENC:
                    // truncate the IV
                    AP4_SetMemory(&iv[8], 0, 8);
                    break;
                    
                default:
                    break;
            }
            
            // check that the key is not already there
            if (key_map.GetKey(track)) {
                fprintf(stderr, "ERROR: key already set for track %d\n", track);
                return 1;
            }
            
            // set the key in the map
            key_map.SetKey(track, key, 16, iv, 16);
        } else if (!strcmp(arg, "--property")) {
            char* track_ascii = NULL;
            char* name = NULL;
            char* value = NULL;
            if (method != METHOD_OMA_PDCF_CBC     && 
                method != METHOD_OMA_PDCF_CTR     &&
                method != METHOD_MARLIN_IPMP_ACBC &&
                method != METHOD_MARLIN_IPMP_ACGK &&
                method != METHOD_PIFF_CBC         &&
                method != METHOD_PIFF_CTR         &&
                method != METHOD_MPEG_CENC) {
                fprintf(stderr, "ERROR: this method does not use properties\n");
                return 1;
            }
            arg = *++argv;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --property option\n");
                return 1;
            }
            if (AP4_FAILED(AP4_SplitArgs(arg, track_ascii, name, value))) {
                fprintf(stderr, "ERROR: invalid argument for --property option\n");
                return 1;
            }
            unsigned int track = strtoul(track_ascii, NULL, 10);

            // check that the property is not already set
            if (property_map.GetProperty(track, name)) {
                fprintf(stderr, "ERROR: property %s already set for track %d\n",
                                name, track);
                return 1;
            }
            // set the property in the map
            property_map.SetProperty(track, name, value);
            
            // special treatment for KID properties
            if (!strcmp(name, "KID")) {
                if (AP4_StringLength(value) != 32) {
                    fprintf(stderr, "ERROR: invalid size for KID property (must be 32 hex chars)\n");
                    return 1;
                }
                AP4_UI08 kid[16];
                if (AP4_FAILED(AP4_ParseHex(value, kid, 16))) {
                    fprintf(stderr, "ERROR: invalid syntax for KID property (must be 32 hex chars)\n");
                    return 1;
                }
                
                // check if we already have this KID
                bool kid_already_present = false;
                for (unsigned int i=0; i<kid_count; i++) {
                    if (AP4_CompareMemory(kids.GetData()+(i*16), kid, 16) == 0) {
                        kid_already_present = true;
                        break;
                    }
                }
                if (!kid_already_present) {
                    ++kid_count;
                    kids.AppendData(kid, 16);
                }
            }
        } else if (!strcmp(arg, "--global-option")) {
            arg = *++argv;
            char* name = NULL;
            char* value = NULL;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument for --global-option option\n");
                return 1;
            }
            if (AP4_FAILED(AP4_SplitArgs(arg, name, value))) {
                fprintf(stderr, "ERROR: invalid argument for --global-option option\n");
                return 1;
            }
            AP4_GlobalOptions::SetString(name, value);
        } else if (input_filename == NULL) {
            input_filename = arg;
        } else if (output_filename == NULL) {
            output_filename = arg;
        } else {
            fprintf(stderr, "ERROR: unexpected argument (%s)\n", arg);
            return 1;
        }
    }

    // check the arguments
    if (method == METHOD_NONE) {
        fprintf(stderr, "ERROR: missing --method argument\n");
        return 1;
    }
    if (input_filename == NULL) {
        fprintf(stderr, "ERROR: missing input filename\n");
        return 1;
    }
    if (output_filename == NULL) {
        fprintf(stderr, "ERROR: missing output filename\n");
        return 1;
    }

    // create an encrypting processor
    AP4_Processor* processor = NULL;
    if (method == METHOD_ISMA_AES) {
        if (kms_uri == NULL) {
            fprintf(stderr, "ERROR: method ISMA-IAEC requires --kms-uri\n");
            return 1;
        }
        AP4_IsmaEncryptingProcessor* isma_processor = new AP4_IsmaEncryptingProcessor(kms_uri);
        isma_processor->GetKeyMap().SetKeys(key_map);
        processor = isma_processor;
    } else if (method == METHOD_MARLIN_IPMP_ACBC ||
               method == METHOD_MARLIN_IPMP_ACGK) {
        bool use_group_key = (method == METHOD_MARLIN_IPMP_ACGK);
        if (use_group_key) {
            // check that the group key is set
            if (key_map.GetKey(0) == NULL) {
                fprintf(stderr, "ERROR: method MARLIN-IPMP-ACGK requires a group key\n");
                return 1;
            }
        }
        AP4_MarlinIpmpEncryptingProcessor* marlin_processor = 
            new AP4_MarlinIpmpEncryptingProcessor(use_group_key);
        marlin_processor->GetKeyMap().SetKeys(key_map);
        marlin_processor->GetPropertyMap().SetProperties(property_map);
        processor = marlin_processor;
    } else if (method == METHOD_OMA_PDCF_CTR ||
               method == METHOD_OMA_PDCF_CBC) {
        AP4_OmaDcfEncryptingProcessor* oma_processor = 
            new AP4_OmaDcfEncryptingProcessor(method == METHOD_OMA_PDCF_CTR?
                                              AP4_OMA_DCF_CIPHER_MODE_CTR :
                                              AP4_OMA_DCF_CIPHER_MODE_CBC);
        oma_processor->GetKeyMap().SetKeys(key_map);
        oma_processor->GetPropertyMap().SetProperties(property_map);
        processor = oma_processor;
    } else if (method == METHOD_PIFF_CTR ||
               method == METHOD_PIFF_CBC ||
               method == METHOD_MPEG_CENC) {
        AP4_CencVariant variant = AP4_CENC_VARIANT_MPEG;
        switch (method) {
            case METHOD_PIFF_CBC:
                variant = AP4_CENC_VARIANT_PIFF_CBC;
                break;
                
            case METHOD_PIFF_CTR:
                variant = AP4_CENC_VARIANT_PIFF_CTR;
                break;
                
            case METHOD_MPEG_CENC:
                variant = AP4_CENC_VARIANT_MPEG;
                break;
                
            default:
                break;
        }
        AP4_CencEncryptingProcessor* cenc_processor = new AP4_CencEncryptingProcessor(variant);
        cenc_processor->GetKeyMap().SetKeys(key_map);
        cenc_processor->GetPropertyMap().SetProperties(property_map);
        for (unsigned int i=0; i<pssh_atoms.ItemCount(); i++) {
            cenc_processor->GetPsshAtoms().Append(pssh_atoms[i]);
        }
        processor = cenc_processor;
    }
    
    // create the input stream
    AP4_ByteStream* input = NULL;
    result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input file (%s)\n", input_filename);
        return 1;
    }

    // create the output stream
    AP4_ByteStream* output = NULL;
    result = AP4_FileByteStream::Create(output_filename, AP4_FileByteStream::STREAM_MODE_WRITE, output);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open output file (%s)\n", output_filename);
        return 1;
    }

    // create the fragments info stream if needed
    AP4_ByteStream* fragments_info = NULL;
    if (fragments_info_filename) {
        result = AP4_FileByteStream::Create(fragments_info_filename, AP4_FileByteStream::STREAM_MODE_READ, fragments_info);
        if (AP4_FAILED(result)) {
            fprintf(stderr, "ERROR: cannot open fragments info file (%s)\n", fragments_info_filename);
            return 1;
        }
    }
    
    // process/decrypt the file
    ProgressListener listener;
    if (fragments_info) {
        bool check = CheckWarning(*fragments_info, key_map, method);
        if (strict && check) return 1;
        result = processor->Process(*input, *output, *fragments_info, show_progress?&listener:NULL);
    } else {
        bool check = CheckWarning(*input, key_map, method);
        if (strict && check) return 1;
        result = processor->Process(*input, *output, show_progress?&listener:NULL);
    }
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: failed to process the file (%d)\n", result);
    }

    // cleanup
    delete processor;
    input->Release();
    output->Release();
    if (fragments_info) fragments_info->Release();
    for (unsigned int i=0; i<pssh_atoms.ItemCount(); i++) {
        delete pssh_atoms[i];
    }
    
    return 0;
}