c77cd8913d44775955d0574589b887aedc84263c
[asdcplib.git] / src / asdcp-test.cpp
1 /*
2 Copyright (c) 2003-2008, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    asdcp-test.cpp
28     \version $Id$       
29     \brief   AS-DCP file manipulation utility
30
31   This program provides command line access to the major features of the asdcplib
32   library, and serves as a library unit test which provides the functionality of
33   the supported use cases.
34
35   For more information about asdcplib, please refer to the header file AS_DCP.h
36
37   WARNING: While the asdcplib library attempts to provide a complete and secure
38   implementation of the cryptographic features of the AS-DCP file formats, this
39   unit test program is NOT secure and is therefore NOT SUITABLE FOR USE in a
40   production environment without some modification.
41
42   In particular, this program uses weak IV generation and externally generated
43   plaintext keys. These shortcomings exist because cryptographic-quality
44   random number generation and key management are outside the scope of the
45   asdcplib library. Developers using asdcplib for commercial implementations
46   claiming SMPTE conformance are expected to provide proper implementations of
47   these features.
48 */
49
50 #include <KM_fileio.h>
51 #include <KM_prng.h>
52 #include <PCMParserList.h>
53 #include <WavFileWriter.h>
54 #include <MXF.h>
55 #include <Metadata.h>
56 #include <openssl/sha.h>
57
58 #include <iostream>
59 #include <assert.h>
60
61 using namespace ASDCP;
62
63 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
64
65 //------------------------------------------------------------------------------------------
66 //
67 // command line option parser class
68
69 static const char* PROGRAM_NAME = "asdcp-test";  // program name for messages
70 const ui32_t MAX_IN_FILES = 16;             // maximum number of input files handled by
71                                             //   the command option parser
72
73 // local program identification info written to file headers
74 class MyInfo : public WriterInfo
75 {
76 public:
77   MyInfo()
78   {
79       static byte_t default_ProductUUID_Data[UUIDlen] =
80       { 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
81         0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
82       
83       memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
84       CompanyName = "WidgetCo";
85       ProductName = "asdcp-test";
86
87       char s_buf[128];
88       snprintf(s_buf, 128, "%u.%u.%u", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89       ProductVersion = s_buf;
90   }
91 } s_MyInfo;
92
93
94
95 // Increment the iterator, test for an additional non-option command line argument.
96 // Causes the caller to return if there are no remaining arguments or if the next
97 // argument begins with '-'.
98 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
99                                  { \
100                                    fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
101                                    return; \
102                                  }
103 //
104 void
105 banner(FILE* stream = stdout)
106 {
107   fprintf(stream, "\n\
108 %s (asdcplib %s)\n\n\
109 Copyright (c) 2003-2008 John Hurst\n\n\
110 asdcplib may be copied only under the terms of the license found at\n\
111 the top of every file in the asdcplib distribution kit.\n\n\
112 Specify the -h (help) option for further information about %s\n\n",
113           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
114 }
115
116 //
117 void
118 usage(FILE* stream = stdout)
119 {
120   fprintf(stream, "\
121 USAGE: %s -c <output-file> [-3] [-b <buffer-size>] [-d <duration>] [-e|-E]\n\
122        [-f <start-frame>] [-j <key-id-string>] [-k <key-string>] [-L] [-M]\n\
123        [-p <frame-rate>] [-R] [-s <num>] [-v] [-W]\n\
124        <input-file> [<input-file-2> ...]\n\
125 \n\
126        %s [-h|-help] [-V]\n\
127 \n\
128        %s -i [-H] [-n] [-v] <input-file>\n\
129 \n\
130        %s -g | -u\n\
131 \n\
132        %s -G [-v] <input-file>\n\
133 \n\
134        %s -t <input-file>\n\
135 \n\
136        %s -x <file-prefix> [-3] [-b <buffer-size>] [-d <duration>]\n\
137        [-f <starting-frame>] [-m] [-p <frame-rate>] [-R] [-s <num>] [-S|-1]\n\
138        [-v] [-W] <input-file>\n\
139 \n", PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
140
141   fprintf(stream, "\
142 Major modes:\n\
143   -3                - With -c, create a stereoscopic image file. Expects two\n\
144                       directories of JP2K codestreams (directories must have\n\
145                       an equal number of frames; left eye is first).\n\
146                     - With -x, force stereoscopic interpretation of a JP2K\n\
147                       track file.\n\
148   -c <output-file>  - Create an AS-DCP track file from input(s)\n\
149   -g                - Generate a random 16 byte value to stdout\n\
150   -G                - Perform GOP start lookup test on MXF+Interop MPEG file\n\
151   -h | -help        - Show help\n\
152   -i                - Show file info\n\
153   -t                - Calculate message digest of input file\n\
154   -U                - Dump UL catalog to stdout\n\
155   -u                - Generate a random UUID value to stdout\n\
156   -V                - Show version information\n\
157   -x <root-name>    - Extract essence from AS-DCP file to named file(s)\n\
158 \n");
159
160   fprintf(stream, "\
161 Security Options:\n\
162   -e                - Encrypt MPEG or JP2K headers (default)\n\
163   -E                - Do not encrypt MPEG or JP2K headers\n\
164   -j <key-id-str>   - Write key ID instead of creating a random value\n\
165   -k <key-string>   - Use key for ciphertext operations\n\
166   -m                - verify HMAC values when reading\n\
167   -M                - Do not create HMAC values when writing\n\
168 \n");
169
170   fprintf(stream, "\
171 Read/Write Options:\n\
172   -b <buffer-size>  - Specify size in bytes of picture frame buffer.\n\
173                       Defaults to 4,194,304 (4MB)\n\
174   -d <duration>     - Number of frames to process, default all\n\
175   -f <start-frame>  - Starting frame number, default 0\n\
176   -L                - Write SMPTE UL values instead of MXF Interop\n\
177   -p <rate>         - fps of picture when wrapping PCM or JP2K:\n\
178                       Use one of [23|24|48], 24 is default\n\
179   -R                - Repeat the first frame over the entire file (picture\n\
180                       essence only, requires -c, -d)\n\
181   -S                - Split Wave essence to stereo WAV files during extract.\n\
182                       Default is multichannel WAV\n\
183   -1                - Split Wave essence to mono WAV files during extract.\n\
184                       Default is multichannel WAV\n\
185   -W                - Read input file only, do not write source file\n\
186 \n");
187
188   fprintf(stream, "\
189 Info Options:\n\
190   -H                - Show MXF header metadata, used with option -i\n\
191   -n                - Show index, used with option -i\n\
192 \n\
193 Other Options:\n\
194   -s <num>          - Number of bytes of frame buffer to be dumped as hex to\n\
195                       stderr, used with option -v\n\
196   -v                - Verbose, prints informative messages to stderr\n\
197 \n\
198   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
199          o All option arguments must be separated from the option by whitespace.\n\
200          o An argument of \"23\" to the -p option will be interpreted\n\
201            as 23000/1001 fps.\n\
202 \n");
203 }
204
205 //
206 enum MajorMode_t
207 {
208   MMT_NONE,
209   MMT_INFO,
210   MMT_CREATE,
211   MMT_EXTRACT,
212   MMT_GEN_ID,
213   MMT_GEN_KEY,
214   MMT_GOP_START,
215   MMT_DIGEST,
216   MMT_UL_LIST,
217 };
218
219
220 //
221 //
222 class CommandOptions
223 {
224   CommandOptions();
225
226 public:
227   MajorMode_t mode;
228   bool   error_flag;     // true if the given options are in error or not complete
229   bool   key_flag;       // true if an encryption key was given
230   bool   key_id_flag;    // true if a key ID was given
231   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
232   bool   write_hmac;     // true if HMAC values are to be generated and written
233   bool   read_hmac;      // true if HMAC values are to be validated
234   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
235   bool   mono_wav;       // true if PCM is to be extracted to mono WAV files
236   bool   verbose_flag;   // true if the verbose option was selected
237   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
238   bool   showindex_flag; // true if index is to be displayed
239   bool   showheader_flag; // true if MXF file header is to be displayed
240   bool   no_write_flag;  // true if no output files are to be written
241   bool   version_flag;   // true if the version display option was selected
242   bool   help_flag;      // true if the help display option was selected
243   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
244   ui32_t start_frame;    // frame number to begin processing
245   ui32_t duration;       // number of frames to be processed
246   bool   duration_flag;  // true if duration argument given
247   bool   do_repeat;      // if true and -c -d, repeat first input frame
248   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
249   ui32_t picture_rate;   // fps of picture when wrapping PCM
250   ui32_t fb_size;        // size of picture frame buffer
251   ui32_t file_count;     // number of elements in filenames[]
252   const char* file_root; // filename pre for files written by the extract mode
253   const char* out_file;  // name of mxf file created by create mode
254   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
255   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
256   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
257
258   //
259   Rational PictureRate()
260   {
261     if ( picture_rate == 23 ) return EditRate_23_98;
262     if ( picture_rate == 48 ) return EditRate_48;
263     return EditRate_24;
264   }
265
266   //
267   const char* szPictureRate()
268   {
269     if ( picture_rate == 23 ) return "23.976";
270     if ( picture_rate == 48 ) return "48";
271     return "24";
272   }
273
274   //
275   CommandOptions(int argc, const char** argv) :
276     mode(MMT_NONE), error_flag(true), key_flag(false), key_id_flag(false), encrypt_header_flag(true),
277     write_hmac(true), read_hmac(false), split_wav(false), mono_wav(false),
278     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
279     no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false), start_frame(0),
280     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
281     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
282   {
283     memset(key_value, 0, KeyLen);
284     memset(key_id_value, 0, UUIDlen);
285
286     for ( int i = 1; i < argc; i++ )
287       {
288
289         if ( (strcmp( argv[i], "-help") == 0) )
290           {
291             help_flag = true;
292             continue;
293           }
294          
295         if ( argv[i][0] == '-'
296              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
297              && argv[i][2] == 0 )
298           {
299             switch ( argv[i][1] )
300               {
301               case '1': mono_wav = true; break;
302               case '2': split_wav = true; break;
303               case '3': stereo_image_flag = true; break;
304               case 'i': mode = MMT_INFO;        break;
305               case 'G': mode = MMT_GOP_START; break;
306               case 'W': no_write_flag = true; break;
307               case 'n': showindex_flag = true; break;
308               case 'H': showheader_flag = true; break;
309               case 'R': do_repeat = true; break;
310               case 'S': split_wav = true; break;
311               case 'V': version_flag = true; break;
312               case 'h': help_flag = true; break;
313               case 'v': verbose_flag = true; break;
314               case 'g': mode = MMT_GEN_KEY; break;
315               case 'U': mode = MMT_UL_LIST; break;
316               case 'u': mode = MMT_GEN_ID; break;
317               case 'e': encrypt_header_flag = true; break;
318               case 'E': encrypt_header_flag = false; break;
319               case 'M': write_hmac = false; break;
320               case 'm': read_hmac = true; break;
321               case 'L': use_smpte_labels = true; break;
322
323               case 'c':
324                 TEST_EXTRA_ARG(i, 'c');
325                 mode = MMT_CREATE;
326                 out_file = argv[i];
327                 break;
328
329               case 'x':
330                 TEST_EXTRA_ARG(i, 'x');
331                 mode = MMT_EXTRACT;
332                 file_root = argv[i];
333                 break;
334
335               case 'k': key_flag = true;
336                 TEST_EXTRA_ARG(i, 'k');
337                 {
338                   ui32_t length;
339                   Kumu::hex2bin(argv[i], key_value, KeyLen, &length);
340
341                   if ( length != KeyLen )
342                     {
343                       fprintf(stderr, "Unexpected key length: %u, expecting %u characters.\n", length, KeyLen);
344                       return;
345                     }
346                 }
347                 break;
348
349               case 'j': key_id_flag = true;
350                 TEST_EXTRA_ARG(i, 'j');
351                 {
352                   ui32_t length;
353                   Kumu::hex2bin(argv[i], key_id_value, UUIDlen, &length);
354
355                   if ( length != UUIDlen )
356                     {
357                       fprintf(stderr, "Unexpected key ID length: %u, expecting %u characters.\n", length, UUIDlen);
358                       return;
359                     }
360                 }
361                 break;
362
363               case 'f':
364                 TEST_EXTRA_ARG(i, 'f');
365                 start_frame = abs(atoi(argv[i]));
366                 break;
367
368               case 'd':
369                 TEST_EXTRA_ARG(i, 'd');
370                 duration_flag = true;
371                 duration = abs(atoi(argv[i]));
372                 break;
373
374               case 'p':
375                 TEST_EXTRA_ARG(i, 'p');
376                 picture_rate = abs(atoi(argv[i]));
377                 break;
378
379               case 's':
380                 TEST_EXTRA_ARG(i, 's');
381                 fb_dump_size = abs(atoi(argv[i]));
382                 break;
383
384               case 't': mode = MMT_DIGEST; break;
385
386               case 'b':
387                 TEST_EXTRA_ARG(i, 'b');
388                 fb_size = abs(atoi(argv[i]));
389
390                 if ( verbose_flag )
391                   fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
392
393                 break;
394
395               default:
396                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
397                 return;
398               }
399           }
400         else
401           {
402
403             if ( argv[i][0] != '-' )
404               {
405                 filenames[file_count++] = argv[i];
406               }
407             else
408               {
409                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
410                 return;
411               }
412
413             if ( file_count >= MAX_IN_FILES )
414               {
415                 fprintf(stderr, "Filename lists exceeds maximum list size: %u\n", MAX_IN_FILES);
416                 return;
417               }
418           }
419       }
420
421     if ( help_flag || version_flag )
422       return;
423     
424     if ( ( mode == MMT_INFO
425            || mode == MMT_CREATE
426            || mode == MMT_EXTRACT
427            || mode == MMT_GOP_START
428            || mode == MMT_DIGEST ) && file_count == 0 )
429       {
430         fputs("Option requires at least one filename argument.\n", stderr);
431         return;
432       }
433
434     if ( mode == MMT_NONE && ! help_flag && ! version_flag )
435       {
436         fputs("No operation selected (use one of -[gGcitux] or -h for help).\n", stderr);
437         return;
438       }
439
440     error_flag = false;
441   }
442 };
443
444 //------------------------------------------------------------------------------------------
445 // MPEG2 essence
446
447 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
448 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
449 //
450 Result_t
451 write_MPEG2_file(CommandOptions& Options)
452 {
453   AESEncContext*     Context = 0;
454   HMACContext*       HMAC = 0;
455   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
456   MPEG2::Parser      Parser;
457   MPEG2::MXFWriter   Writer;
458   MPEG2::VideoDescriptor VDesc;
459   byte_t             IV_buf[CBC_BLOCK_SIZE];
460   Kumu::FortunaRNG   RNG;
461
462   // set up essence parser
463   Result_t result = Parser.OpenRead(Options.filenames[0]);
464
465   // set up MXF writer
466   if ( ASDCP_SUCCESS(result) )
467     {
468       Parser.FillVideoDescriptor(VDesc);
469
470       if ( Options.verbose_flag )
471         {
472           fputs("MPEG-2 Pictures\n", stderr);
473           fputs("VideoDescriptor:\n", stderr);
474           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
475           MPEG2::VideoDescriptorDump(VDesc);
476         }
477     }
478
479   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
480     {
481       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
482       Kumu::GenRandomUUID(Info.AssetUUID);
483
484       if ( Options.use_smpte_labels )
485         {
486           Info.LabelSetType = LS_MXF_SMPTE;
487           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
488         }
489
490       // configure encryption
491       if( Options.key_flag )
492         {
493           Kumu::GenRandomUUID(Info.ContextID);
494           Info.EncryptedEssence = true;
495
496           if ( Options.key_id_flag )
497             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
498           else
499             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
500
501           Context = new AESEncContext;
502           result = Context->InitKey(Options.key_value);
503
504           if ( ASDCP_SUCCESS(result) )
505             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
506
507           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
508             {
509               Info.UsesHMAC = true;
510               HMAC = new HMACContext;
511               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
512             }
513         }
514
515       if ( ASDCP_SUCCESS(result) )
516         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
517     }
518
519   if ( ASDCP_SUCCESS(result) )
520     // loop through the frames
521     {
522       result = Parser.Reset();
523       ui32_t duration = 0;
524
525       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
526         {
527           if ( ! Options.do_repeat || duration == 1 )
528             {
529               result = Parser.ReadFrame(FrameBuffer);
530
531               if ( ASDCP_SUCCESS(result) )
532                 {
533                   if ( Options.verbose_flag )
534                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
535                   
536                   if ( Options.encrypt_header_flag )
537                     FrameBuffer.PlaintextOffset(0);
538                 }
539             }
540
541           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
542             {
543               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
544
545               // The Writer class will forward the last block of ciphertext
546               // to the encryption context for use as the IV for the next
547               // frame. If you want to use non-sequitur IV values, un-comment
548               // the following  line of code.
549               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
550               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
551             }
552         }
553
554       if ( result == RESULT_ENDOFFILE )
555         result = RESULT_OK;
556     }
557
558   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
559     result = Writer.Finalize();
560
561   return result;
562 }
563
564 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
565 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
566 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
567 //
568 Result_t
569 read_MPEG2_file(CommandOptions& Options)
570 {
571   AESDecContext*     Context = 0;
572   HMACContext*       HMAC = 0;
573   MPEG2::MXFReader   Reader;
574   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
575   Kumu::FileWriter   OutFile;
576   ui32_t             frame_count = 0;
577
578   Result_t result = Reader.OpenRead(Options.filenames[0]);
579
580   if ( ASDCP_SUCCESS(result) )
581     {
582       MPEG2::VideoDescriptor VDesc;
583       Reader.FillVideoDescriptor(VDesc);
584       frame_count = VDesc.ContainerDuration;
585
586       if ( Options.verbose_flag )
587         {
588           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
589           MPEG2::VideoDescriptorDump(VDesc);
590         }
591     }
592
593   if ( ASDCP_SUCCESS(result) )
594     {
595       char filename[256];
596       snprintf(filename, 256, "%s.ves", Options.file_root);
597       result = OutFile.OpenWrite(filename);
598     }
599
600   if ( ASDCP_SUCCESS(result) && Options.key_flag )
601     {
602       Context = new AESDecContext;
603       result = Context->InitKey(Options.key_value);
604
605       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
606         {
607           WriterInfo Info;
608           Reader.FillWriterInfo(Info);
609
610           if ( Info.UsesHMAC )
611             {
612               HMAC = new HMACContext;
613               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
614             }
615           else
616             {
617               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
618             }
619         }
620     }
621
622   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
623   if ( last_frame > frame_count )
624     last_frame = frame_count;
625
626   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
627     {
628       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
629
630       if ( ASDCP_SUCCESS(result) )
631         {
632           if ( Options.verbose_flag )
633             FrameBuffer.Dump(stderr, Options.fb_dump_size);
634
635           ui32_t write_count = 0;
636           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
637         }
638     }
639
640   return result;
641 }
642
643
644 //
645 Result_t
646 gop_start_test(CommandOptions& Options)
647 {
648   using namespace ASDCP::MPEG2;
649
650   MXFReader   Reader;
651   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
652   ui32_t      frame_count = 0;
653
654   Result_t result = Reader.OpenRead(Options.filenames[0]);
655
656   if ( ASDCP_SUCCESS(result) )
657     {
658       MPEG2::VideoDescriptor VDesc;
659       Reader.FillVideoDescriptor(VDesc);
660       frame_count = VDesc.ContainerDuration;
661
662       if ( Options.verbose_flag )
663         {
664           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
665           MPEG2::VideoDescriptorDump(VDesc);
666         }
667     }
668
669   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
670   if ( last_frame > frame_count )
671     last_frame = frame_count;
672
673   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
674     {
675       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
676
677       if ( ASDCP_SUCCESS(result) )
678         {
679           if ( Options.verbose_flag )
680             FrameBuffer.Dump(stderr, Options.fb_dump_size);
681
682           if ( FrameBuffer.FrameType() != FRAME_I )
683             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
684
685           fprintf(stderr, "Requested frame %u, got %u\n", i, FrameBuffer.FrameNumber());
686         }
687     }
688
689   return result;
690 }
691
692 //------------------------------------------------------------------------------------------
693 // JPEG 2000 essence
694
695 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a plaintext ASDCP file
696 // Write one or more plaintext JPEG 2000 stereoscopic codestream pairs to a ciphertext ASDCP file
697 //
698 Result_t
699 write_JP2K_S_file(CommandOptions& Options)
700 {
701   AESEncContext*          Context = 0;
702   HMACContext*            HMAC = 0;
703   JP2K::MXFSWriter        Writer;
704   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
705   JP2K::PictureDescriptor PDesc;
706   JP2K::SequenceParser    ParserLeft, ParserRight;
707   byte_t                  IV_buf[CBC_BLOCK_SIZE];
708   Kumu::FortunaRNG        RNG;
709
710   if ( Options.file_count != 2 )
711     {
712       fprintf(stderr, "Two inputs are required for stereoscopic option.\n");
713       return RESULT_FAIL;
714     }
715
716   // set up essence parser
717   Result_t result = ParserLeft.OpenRead(Options.filenames[0]);
718
719   if ( ASDCP_SUCCESS(result) )
720     result = ParserRight.OpenRead(Options.filenames[1]);
721
722   // set up MXF writer
723   if ( ASDCP_SUCCESS(result) )
724     {
725       ParserLeft.FillPictureDescriptor(PDesc);
726       PDesc.EditRate = Options.PictureRate();
727
728       if ( Options.verbose_flag )
729         {
730           fputs("JPEG 2000 stereoscopic pictures\nPictureDescriptor:\n", stderr);
731           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
732           JP2K::PictureDescriptorDump(PDesc);
733         }
734     }
735
736   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
737     {
738       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
739       Kumu::GenRandomUUID(Info.AssetUUID);
740
741       if ( Options.use_smpte_labels )
742         {
743           Info.LabelSetType = LS_MXF_SMPTE;
744           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
745         }
746
747       // configure encryption
748       if( Options.key_flag )
749         {
750           Kumu::GenRandomUUID(Info.ContextID);
751           Info.EncryptedEssence = true;
752
753           if ( Options.key_id_flag )
754             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
755           else
756             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
757
758           Context = new AESEncContext;
759           result = Context->InitKey(Options.key_value);
760
761           if ( ASDCP_SUCCESS(result) )
762             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
763
764           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
765             {
766               Info.UsesHMAC = true;
767               HMAC = new HMACContext;
768               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
769             }
770         }
771
772       if ( ASDCP_SUCCESS(result) )
773         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
774     }
775
776   if ( ASDCP_SUCCESS(result) )
777     {
778       ui32_t duration = 0;
779       result = ParserLeft.Reset();
780       if ( ASDCP_SUCCESS(result) ) result = ParserRight.Reset();
781
782       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
783         {
784           result = ParserLeft.ReadFrame(FrameBuffer);
785
786           if ( ASDCP_SUCCESS(result) )
787             {
788               if ( Options.verbose_flag )
789                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
790                   
791               if ( Options.encrypt_header_flag )
792                 FrameBuffer.PlaintextOffset(0);
793             }
794
795           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
796             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_LEFT, Context, HMAC);
797
798           if ( ASDCP_SUCCESS(result) )
799             result = ParserRight.ReadFrame(FrameBuffer);
800
801           if ( ASDCP_SUCCESS(result) )
802             {
803               if ( Options.verbose_flag )
804                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
805                   
806               if ( Options.encrypt_header_flag )
807                 FrameBuffer.PlaintextOffset(0);
808             }
809
810           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
811             result = Writer.WriteFrame(FrameBuffer, JP2K::SP_RIGHT, Context, HMAC);
812         }
813
814       if ( result == RESULT_ENDOFFILE )
815         result = RESULT_OK;
816     }
817
818   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
819     result = Writer.Finalize();
820
821   return result;
822 }
823
824 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a plaintext ASDCP file
825 // Read one or more plaintext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
826 // Read one or more ciphertext JPEG 2000 stereoscopic codestream pairs from a ciphertext ASDCP file
827 Result_t
828 read_JP2K_S_file(CommandOptions& Options)
829 {
830   AESDecContext*     Context = 0;
831   HMACContext*       HMAC = 0;
832   JP2K::MXFSReader    Reader;
833   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
834   ui32_t             frame_count = 0;
835
836   Result_t result = Reader.OpenRead(Options.filenames[0]);
837
838   if ( ASDCP_SUCCESS(result) )
839     {
840       JP2K::PictureDescriptor PDesc;
841       Reader.FillPictureDescriptor(PDesc);
842
843       frame_count = PDesc.ContainerDuration;
844
845       if ( Options.verbose_flag )
846         {
847           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
848           JP2K::PictureDescriptorDump(PDesc);
849         }
850     }
851
852   if ( ASDCP_SUCCESS(result) && Options.key_flag )
853     {
854       Context = new AESDecContext;
855       result = Context->InitKey(Options.key_value);
856
857       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
858         {
859           WriterInfo Info;
860           Reader.FillWriterInfo(Info);
861
862           if ( Info.UsesHMAC )
863             {
864               HMAC = new HMACContext;
865               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
866             }
867           else
868             {
869               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
870             }
871         }
872     }
873
874   const int filename_max = 1024;
875   char filename[filename_max];
876   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
877   if ( last_frame > frame_count )
878     last_frame = frame_count;
879
880   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
881     {
882       result = Reader.ReadFrame(i, JP2K::SP_LEFT, FrameBuffer, Context, HMAC);
883
884       if ( ASDCP_SUCCESS(result) )
885         {
886           Kumu::FileWriter OutFile;
887           ui32_t write_count;
888           snprintf(filename, filename_max, "%s%06uL.j2c", Options.file_root, i);
889           result = OutFile.OpenWrite(filename);
890
891           if ( ASDCP_SUCCESS(result) )
892             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
893
894           if ( Options.verbose_flag )
895             FrameBuffer.Dump(stderr, Options.fb_dump_size);
896         }
897
898       if ( ASDCP_SUCCESS(result) )
899         result = Reader.ReadFrame(i, JP2K::SP_RIGHT, FrameBuffer, Context, HMAC);
900
901       if ( ASDCP_SUCCESS(result) )
902         {
903           Kumu::FileWriter OutFile;
904           ui32_t write_count;
905           snprintf(filename, filename_max, "%s%06uR.j2c", Options.file_root, i);
906           result = OutFile.OpenWrite(filename);
907
908           if ( ASDCP_SUCCESS(result) )
909             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
910         }
911     }
912
913   return result;
914 }
915
916
917
918 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
919 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
920 //
921 Result_t
922 write_JP2K_file(CommandOptions& Options)
923 {
924   AESEncContext*          Context = 0;
925   HMACContext*            HMAC = 0;
926   JP2K::MXFWriter         Writer;
927   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
928   JP2K::PictureDescriptor PDesc;
929   JP2K::SequenceParser    Parser;
930   byte_t                  IV_buf[CBC_BLOCK_SIZE];
931   Kumu::FortunaRNG        RNG;
932
933   // set up essence parser
934   Result_t result = Parser.OpenRead(Options.filenames[0]);
935
936   // set up MXF writer
937   if ( ASDCP_SUCCESS(result) )
938     {
939       Parser.FillPictureDescriptor(PDesc);
940       PDesc.EditRate = Options.PictureRate();
941
942       if ( Options.verbose_flag )
943         {
944           fprintf(stderr, "JPEG 2000 pictures\n");
945           fputs("PictureDescriptor:\n", stderr);
946           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
947           JP2K::PictureDescriptorDump(PDesc);
948         }
949     }
950
951   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
952     {
953       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
954       Kumu::GenRandomUUID(Info.AssetUUID);
955
956       if ( Options.use_smpte_labels )
957         {
958           Info.LabelSetType = LS_MXF_SMPTE;
959           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
960         }
961
962       // configure encryption
963       if( Options.key_flag )
964         {
965           Kumu::GenRandomUUID(Info.ContextID);
966           Info.EncryptedEssence = true;
967
968           if ( Options.key_id_flag )
969             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
970           else
971             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
972
973           Context = new AESEncContext;
974           result = Context->InitKey(Options.key_value);
975
976           if ( ASDCP_SUCCESS(result) )
977             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
978
979           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
980             {
981               Info.UsesHMAC = true;
982               HMAC = new HMACContext;
983               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
984             }
985         }
986
987       if ( ASDCP_SUCCESS(result) )
988         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
989     }
990
991   if ( ASDCP_SUCCESS(result) )
992     {
993       ui32_t duration = 0;
994       result = Parser.Reset();
995
996       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
997         {
998           if ( ! Options.do_repeat || duration == 1 )
999             {
1000               result = Parser.ReadFrame(FrameBuffer);
1001
1002               if ( ASDCP_SUCCESS(result) )
1003                 {
1004                   if ( Options.verbose_flag )
1005                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
1006                   
1007                   if ( Options.encrypt_header_flag )
1008                     FrameBuffer.PlaintextOffset(0);
1009                 }
1010             }
1011
1012           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1013             {
1014               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1015
1016               // The Writer class will forward the last block of ciphertext
1017               // to the encryption context for use as the IV for the next
1018               // frame. If you want to use non-sequitur IV values, un-comment
1019               // the following  line of code.
1020               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1021               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1022             }
1023         }
1024
1025       if ( result == RESULT_ENDOFFILE )
1026         result = RESULT_OK;
1027     }
1028
1029   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1030     result = Writer.Finalize();
1031
1032   return result;
1033 }
1034
1035 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
1036 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
1037 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
1038 //
1039 Result_t
1040 read_JP2K_file(CommandOptions& Options)
1041 {
1042   AESDecContext*     Context = 0;
1043   HMACContext*       HMAC = 0;
1044   JP2K::MXFReader    Reader;
1045   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
1046   ui32_t             frame_count = 0;
1047
1048   Result_t result = Reader.OpenRead(Options.filenames[0]);
1049
1050   if ( ASDCP_SUCCESS(result) )
1051     {
1052       JP2K::PictureDescriptor PDesc;
1053       Reader.FillPictureDescriptor(PDesc);
1054
1055       frame_count = PDesc.ContainerDuration;
1056
1057       if ( Options.verbose_flag )
1058         {
1059           fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
1060           JP2K::PictureDescriptorDump(PDesc);
1061         }
1062     }
1063
1064   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1065     {
1066       Context = new AESDecContext;
1067       result = Context->InitKey(Options.key_value);
1068
1069       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1070         {
1071           WriterInfo Info;
1072           Reader.FillWriterInfo(Info);
1073
1074           if ( Info.UsesHMAC )
1075             {
1076               HMAC = new HMACContext;
1077               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1078             }
1079           else
1080             {
1081               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1082             }
1083         }
1084     }
1085
1086   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
1087   if ( last_frame > frame_count )
1088     last_frame = frame_count;
1089
1090   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1091     {
1092       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1093
1094       if ( ASDCP_SUCCESS(result) )
1095         {
1096           Kumu::FileWriter OutFile;
1097           char filename[256];
1098           ui32_t write_count;
1099           snprintf(filename, 256, "%s%06u.j2c", Options.file_root, i);
1100           result = OutFile.OpenWrite(filename);
1101
1102           if ( ASDCP_SUCCESS(result) )
1103             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
1104
1105           if ( Options.verbose_flag )
1106             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1107         }
1108     }
1109
1110   return result;
1111 }
1112
1113 //------------------------------------------------------------------------------------------
1114 // PCM essence
1115
1116
1117 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
1118 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
1119 //
1120 Result_t
1121 write_PCM_file(CommandOptions& Options)
1122 {
1123   AESEncContext*    Context = 0;
1124   HMACContext*      HMAC = 0;
1125   PCMParserList     Parser;
1126   PCM::MXFWriter    Writer;
1127   PCM::FrameBuffer  FrameBuffer;
1128   PCM::AudioDescriptor ADesc;
1129   Rational          PictureRate = Options.PictureRate();
1130   byte_t            IV_buf[CBC_BLOCK_SIZE];
1131   Kumu::FortunaRNG  RNG;
1132
1133   // set up essence parser
1134   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
1135
1136   // set up MXF writer
1137   if ( ASDCP_SUCCESS(result) )
1138     {
1139       Parser.FillAudioDescriptor(ADesc);
1140
1141       ADesc.SampleRate = PictureRate;
1142       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1143
1144       if ( Options.verbose_flag )
1145         {
1146           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n",
1147                   Options.szPictureRate(),
1148                   PCM::CalcSamplesPerFrame(ADesc));
1149           fputs("AudioDescriptor:\n", stderr);
1150           PCM::AudioDescriptorDump(ADesc);
1151         }
1152     }
1153
1154   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1155     {
1156       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1157       Kumu::GenRandomUUID(Info.AssetUUID);
1158
1159       if ( Options.use_smpte_labels )
1160         {
1161           Info.LabelSetType = LS_MXF_SMPTE;
1162           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1163         }
1164
1165       // configure encryption
1166       if( Options.key_flag )
1167         {
1168           Kumu::GenRandomUUID(Info.ContextID);
1169           Info.EncryptedEssence = true;
1170
1171           if ( Options.key_id_flag )
1172             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1173           else
1174             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1175
1176           Context = new AESEncContext;
1177           result = Context->InitKey(Options.key_value);
1178
1179           if ( ASDCP_SUCCESS(result) )
1180             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1181
1182           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1183             {
1184               Info.UsesHMAC = true;
1185               HMAC = new HMACContext;
1186               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1187             }
1188         }
1189
1190       if ( ASDCP_SUCCESS(result) )
1191         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
1192     }
1193
1194   if ( ASDCP_SUCCESS(result) )
1195     {
1196       result = Parser.Reset();
1197       ui32_t duration = 0;
1198
1199       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
1200         {
1201           result = Parser.ReadFrame(FrameBuffer);
1202
1203           if ( ASDCP_SUCCESS(result) )
1204             {
1205               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
1206                 {
1207                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
1208                   fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
1209                   result = RESULT_ENDOFFILE;
1210                   continue;
1211                 }
1212
1213               if ( Options.verbose_flag )
1214                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
1215
1216               if ( ! Options.no_write_flag )
1217                 {
1218                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
1219
1220                   // The Writer class will forward the last block of ciphertext
1221                   // to the encryption context for use as the IV for the next
1222                   // frame. If you want to use non-sequitur IV values, un-comment
1223                   // the following  line of code.
1224                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1225                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1226                 }
1227             }
1228         }
1229
1230       if ( result == RESULT_ENDOFFILE )
1231         result = RESULT_OK;
1232     }
1233
1234   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1235     result = Writer.Finalize();
1236
1237   return result;
1238 }
1239
1240 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
1241 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
1242 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
1243 //
1244 Result_t
1245 read_PCM_file(CommandOptions& Options)
1246 {
1247   AESDecContext*     Context = 0;
1248   HMACContext*       HMAC = 0;
1249   PCM::MXFReader     Reader;
1250   PCM::FrameBuffer   FrameBuffer;
1251   WavFileWriter      OutWave;
1252   PCM::AudioDescriptor ADesc;
1253   ui32_t last_frame = 0;
1254
1255   Result_t result = Reader.OpenRead(Options.filenames[0]);
1256
1257   if ( ASDCP_SUCCESS(result) )
1258     {
1259       Reader.FillAudioDescriptor(ADesc);
1260
1261       if ( ADesc.SampleRate != EditRate_23_98
1262            && ADesc.SampleRate != EditRate_24
1263            && ADesc.SampleRate != EditRate_48 )
1264         ADesc.SampleRate = Options.PictureRate();
1265
1266       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
1267
1268       if ( Options.verbose_flag )
1269         PCM::AudioDescriptorDump(ADesc);
1270     }
1271
1272   if ( ASDCP_SUCCESS(result) )
1273     {
1274       last_frame = ADesc.ContainerDuration;
1275
1276       if ( Options.duration > 0 && Options.duration < last_frame )
1277         last_frame = Options.duration;
1278
1279       if ( Options.start_frame > 0 )
1280         {
1281           if ( Options.start_frame > ADesc.ContainerDuration )
1282             {
1283               fprintf(stderr, "Start value greater than file duration.\n");
1284               return RESULT_FAIL;
1285             }
1286
1287           last_frame = Kumu::xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1288         }
1289
1290       ADesc.ContainerDuration = last_frame - Options.start_frame;
1291       OutWave.OpenWrite(ADesc, Options.file_root,
1292                         ( Options.split_wav ? WavFileWriter::ST_STEREO : 
1293                           ( Options.mono_wav ? WavFileWriter::ST_MONO : WavFileWriter::ST_NONE ) ));
1294     }
1295
1296   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1297     {
1298       Context = new AESDecContext;
1299       result = Context->InitKey(Options.key_value);
1300
1301       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1302         {
1303           WriterInfo Info;
1304           Reader.FillWriterInfo(Info);
1305
1306           if ( Info.UsesHMAC )
1307             {
1308               HMAC = new HMACContext;
1309               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1310             }
1311           else
1312             {
1313               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1314             }
1315         }
1316     }
1317
1318   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1319     {
1320       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1321
1322       if ( ASDCP_SUCCESS(result) )
1323         {
1324           if ( Options.verbose_flag )
1325             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1326
1327           result = OutWave.WriteFrame(FrameBuffer);
1328         }
1329     }
1330
1331   return result;
1332 }
1333
1334
1335 //------------------------------------------------------------------------------------------
1336 // TimedText essence
1337
1338
1339 // Write one or more plaintext timed text streams to a plaintext ASDCP file
1340 // Write one or more plaintext timed text streams to a ciphertext ASDCP file
1341 //
1342 Result_t
1343 write_timed_text_file(CommandOptions& Options)
1344 {
1345   AESEncContext*    Context = 0;
1346   HMACContext*      HMAC = 0;
1347   TimedText::DCSubtitleParser  Parser;
1348   TimedText::MXFWriter    Writer;
1349   TimedText::FrameBuffer  FrameBuffer;
1350   TimedText::TimedTextDescriptor TDesc;
1351   byte_t            IV_buf[CBC_BLOCK_SIZE];
1352   Kumu::FortunaRNG  RNG;
1353
1354   // set up essence parser
1355   Result_t result = Parser.OpenRead(Options.filenames[0]);
1356
1357   // set up MXF writer
1358   if ( ASDCP_SUCCESS(result) )
1359     {
1360       Parser.FillDescriptor(TDesc);
1361       FrameBuffer.Capacity(2*Kumu::Megabyte);
1362
1363       if ( Options.verbose_flag )
1364         {
1365           fputs("D-Cinema Timed-Text Descriptor:\n", stderr);
1366           TimedText::DescriptorDump(TDesc);
1367         }
1368     }
1369
1370   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1371     {
1372       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
1373       Kumu::GenRandomUUID(Info.AssetUUID);
1374
1375       if ( Options.use_smpte_labels )
1376         {
1377           Info.LabelSetType = LS_MXF_SMPTE;
1378           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
1379         }
1380
1381       // configure encryption
1382       if( Options.key_flag )
1383         {
1384           Kumu::GenRandomUUID(Info.ContextID);
1385           Info.EncryptedEssence = true;
1386
1387           if ( Options.key_id_flag )
1388             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
1389           else
1390             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
1391
1392           Context = new AESEncContext;
1393           result = Context->InitKey(Options.key_value);
1394
1395           if ( ASDCP_SUCCESS(result) )
1396             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1397
1398           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
1399             {
1400               Info.UsesHMAC = true;
1401               HMAC = new HMACContext;
1402               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1403             }
1404         }
1405
1406       if ( ASDCP_SUCCESS(result) )
1407         result = Writer.OpenWrite(Options.out_file, Info, TDesc);
1408     }
1409
1410   if ( ASDCP_FAILURE(result) )
1411     return result;
1412
1413   std::string XMLDoc;
1414   TimedText::ResourceList_t::const_iterator ri;
1415
1416   result = Parser.ReadTimedTextResource(XMLDoc);
1417
1418   if ( ASDCP_SUCCESS(result) )
1419     result = Writer.WriteTimedTextResource(XMLDoc, Context, HMAC);
1420
1421   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1422     {
1423       result = Parser.ReadAncillaryResource((*ri).ResourceID, FrameBuffer);
1424
1425       if ( ASDCP_SUCCESS(result) )
1426         {
1427           if ( Options.verbose_flag )
1428             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1429
1430           if ( ! Options.no_write_flag )
1431             {
1432               result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
1433               
1434               // The Writer class will forward the last block of ciphertext
1435               // to the encryption context for use as the IV for the next
1436               // frame. If you want to use non-sequitur IV values, un-comment
1437               // the following  line of code.
1438               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
1439               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
1440             }
1441         }
1442
1443       if ( result == RESULT_ENDOFFILE )
1444         result = RESULT_OK;
1445     }
1446
1447   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
1448     result = Writer.Finalize();
1449
1450   return result;
1451 }
1452
1453
1454 // Read one or more timed text streams from a plaintext ASDCP file
1455 // Read one or more timed text streams from a ciphertext ASDCP file
1456 // Read one or more timed text streams from a ciphertext ASDCP file
1457 //
1458 Result_t
1459 read_timed_text_file(CommandOptions& Options)
1460 {
1461   AESDecContext*     Context = 0;
1462   HMACContext*       HMAC = 0;
1463   TimedText::MXFReader     Reader;
1464   TimedText::FrameBuffer   FrameBuffer;
1465   TimedText::TimedTextDescriptor TDesc;
1466
1467   Result_t result = Reader.OpenRead(Options.filenames[0]);
1468
1469   if ( ASDCP_SUCCESS(result) )
1470     {
1471       Reader.FillDescriptor(TDesc);
1472       FrameBuffer.Capacity(2*Kumu::Megabyte);
1473
1474       if ( Options.verbose_flag )
1475         TimedText::DescriptorDump(TDesc);
1476     }
1477
1478   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1479     {
1480       Context = new AESDecContext;
1481       result = Context->InitKey(Options.key_value);
1482
1483       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1484         {
1485           WriterInfo Info;
1486           Reader.FillWriterInfo(Info);
1487
1488           if ( Info.UsesHMAC )
1489             {
1490               HMAC = new HMACContext;
1491               result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
1492             }
1493           else
1494             {
1495               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1496             }
1497         }
1498     }
1499
1500   if ( ASDCP_FAILURE(result) )
1501     return result;
1502
1503   std::string XMLDoc;
1504   TimedText::ResourceList_t::const_iterator ri;
1505
1506   result = Reader.ReadTimedTextResource(XMLDoc, Context, HMAC);
1507
1508   // do something with the XML here
1509   fprintf(stderr, "XMLDoc size: %lu\n", XMLDoc.size());
1510
1511   for ( ri = TDesc.ResourceList.begin() ; ri != TDesc.ResourceList.end() && ASDCP_SUCCESS(result); ri++ )
1512     {
1513       result = Reader.ReadAncillaryResource((*ri).ResourceID, FrameBuffer, Context, HMAC);
1514
1515       if ( ASDCP_SUCCESS(result) )
1516         {
1517           //      if ( Options.verbose_flag )
1518             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1519
1520           // do something with the resource data here
1521         }
1522     }
1523
1524   return result;
1525 }
1526
1527 //------------------------------------------------------------------------------------------
1528 //
1529
1530 //
1531 // These classes wrap the irregular names in the asdcplib API
1532 // so that I can use a template to simplify the implementation
1533 // of show_file_info()
1534
1535 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1536 {
1537  public:
1538   void FillDescriptor(MPEG2::MXFReader& Reader) {
1539     Reader.FillVideoDescriptor(*this);
1540   }
1541
1542   void Dump(FILE* stream) {
1543     MPEG2::VideoDescriptorDump(*this, stream);
1544   }
1545 };
1546
1547 class MyPictureDescriptor : public JP2K::PictureDescriptor
1548 {
1549  public:
1550   void FillDescriptor(JP2K::MXFReader& Reader) {
1551     Reader.FillPictureDescriptor(*this);
1552   }
1553
1554   void Dump(FILE* stream) {
1555     JP2K::PictureDescriptorDump(*this, stream);
1556   }
1557 };
1558
1559 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
1560 {
1561  public:
1562   void FillDescriptor(JP2K::MXFSReader& Reader) {
1563     Reader.FillPictureDescriptor(*this);
1564   }
1565
1566   void Dump(FILE* stream) {
1567     JP2K::PictureDescriptorDump(*this, stream);
1568   }
1569 };
1570
1571 class MyAudioDescriptor : public PCM::AudioDescriptor
1572 {
1573  public:
1574   void FillDescriptor(PCM::MXFReader& Reader) {
1575     Reader.FillAudioDescriptor(*this);
1576   }
1577
1578   void Dump(FILE* stream) {
1579     PCM::AudioDescriptorDump(*this, stream);
1580   }
1581 };
1582
1583 class MyTextDescriptor : public TimedText::TimedTextDescriptor
1584 {
1585  public:
1586   void FillDescriptor(TimedText::MXFReader& Reader) {
1587     Reader.FillDescriptor(*this);
1588   }
1589
1590   void Dump(FILE* stream) {
1591     TimedText::DescriptorDump(*this, stream);
1592   }
1593 };
1594
1595 // MSVC didn't like the function template, so now it's a static class method
1596 template<class ReaderT, class DescriptorT>
1597 class FileInfoWrapper
1598 {
1599 public:
1600   static Result_t
1601   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
1602   {
1603     assert(type_string);
1604     if ( stream == 0 )
1605       stream = stdout;
1606
1607     Result_t result = RESULT_OK;
1608
1609     if ( Options.verbose_flag || Options.showheader_flag )
1610       {
1611         ReaderT     Reader;
1612         result = Reader.OpenRead(Options.filenames[0]);
1613
1614         if ( ASDCP_SUCCESS(result) )
1615           {
1616             fprintf(stdout, "File essence type is %s.\n", type_string);
1617
1618             if ( Options.showheader_flag )
1619               Reader.DumpHeaderMetadata(stream);
1620
1621             WriterInfo WI;
1622             Reader.FillWriterInfo(WI);
1623             WriterInfoDump(WI, stream);
1624
1625             DescriptorT Desc;
1626             Desc.FillDescriptor(Reader);
1627             Desc.Dump(stream);
1628
1629             if ( Options.showindex_flag )
1630               Reader.DumpIndex(stream);
1631           }
1632         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1633           {
1634             Reader.DumpHeaderMetadata(stream);
1635           }
1636       }
1637
1638     return result;
1639   }
1640 };
1641
1642 // Read header metadata from an ASDCP file
1643 //
1644 Result_t
1645 show_file_info(CommandOptions& Options)
1646 {
1647   EssenceType_t EssenceType;
1648   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1649
1650   if ( ASDCP_FAILURE(result) )
1651     return result;
1652
1653   if ( EssenceType == ESS_MPEG2_VES )
1654     result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
1655
1656   else if ( EssenceType == ESS_PCM_24b_48k )
1657     result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
1658
1659   else if ( EssenceType == ESS_JPEG_2000 )
1660     {
1661       if ( Options.stereo_image_flag )
1662         result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1663         MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1664
1665       else
1666         result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
1667         MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
1668     }
1669   else if ( EssenceType == ESS_JPEG_2000_S )
1670     result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
1671     MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
1672
1673   else if ( EssenceType == ESS_TIMED_TEXT )
1674     result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
1675
1676   else
1677     {
1678       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1679       Kumu::FileReader   Reader;
1680       MXF::OPAtomHeader TestHeader;
1681
1682       result = Reader.OpenRead(Options.filenames[0]);
1683
1684       if ( ASDCP_SUCCESS(result) )
1685         result = TestHeader.InitFromFile(Reader); // test UL and OP
1686
1687       if ( ASDCP_SUCCESS(result) )
1688         {
1689           TestHeader.Partition::Dump();
1690
1691           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1692             ID->Dump();
1693           else
1694             fputs("File contains no Identification object.\n", stdout);
1695
1696           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1697             SP->Dump();
1698           else
1699             fputs("File contains no SourcePackage object.\n", stdout);
1700         }
1701       else
1702         {
1703           fputs("File is not MXF.\n", stdout);
1704         }
1705     }
1706
1707   return result;
1708 }
1709
1710
1711 //
1712 Result_t
1713 digest_file(const char* filename)
1714 {
1715   using namespace Kumu;
1716
1717   ASDCP_TEST_NULL_STR(filename);
1718   FileReader Reader;
1719   SHA_CTX Ctx;
1720   SHA1_Init(&Ctx);
1721   ByteString Buf(8192);
1722
1723   Result_t result = Reader.OpenRead(filename);
1724
1725   while ( ASDCP_SUCCESS(result) )
1726     {
1727       ui32_t read_count = 0;
1728       result = Reader.Read(Buf.Data(), Buf.Capacity(), &read_count);
1729
1730       if ( result == RESULT_ENDOFFILE )
1731         {
1732           result = RESULT_OK;
1733           break;
1734         }
1735
1736       if ( ASDCP_SUCCESS(result) )
1737         SHA1_Update(&Ctx, Buf.Data(), read_count);
1738     }
1739
1740   if ( ASDCP_SUCCESS(result) )
1741     {
1742       const ui32_t sha_len = 20;
1743       byte_t bin_buf[sha_len];
1744       char sha_buf[64];
1745       SHA1_Final(bin_buf, &Ctx);
1746
1747       fprintf(stdout, "%s %s\n", base64encode(bin_buf, sha_len, sha_buf, 64), filename);
1748     }
1749
1750   return result;
1751 }
1752
1753 //
1754 int
1755 main(int argc, const char** argv)
1756 {
1757   Result_t result = RESULT_OK;
1758   char     str_buf[64];
1759   CommandOptions Options(argc, argv);
1760
1761   if ( Options.version_flag )
1762     banner();
1763
1764   if ( Options.help_flag )
1765     usage();
1766
1767   if ( Options.version_flag || Options.help_flag )
1768     return 0;
1769
1770   if ( Options.error_flag )
1771     {
1772       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
1773       return 3;
1774     }
1775
1776   if ( Options.mode == MMT_INFO )
1777     {
1778       result = show_file_info(Options);
1779     }
1780   else if ( Options.mode == MMT_GOP_START )
1781     {
1782       result = gop_start_test(Options);
1783     }
1784   else if ( Options.mode == MMT_GEN_KEY )
1785     {
1786       Kumu::FortunaRNG RNG;
1787       byte_t bin_buf[KeyLen];
1788
1789       RNG.FillRandom(bin_buf, KeyLen);
1790       printf("%s\n", Kumu::bin2hex(bin_buf, KeyLen, str_buf, 64));
1791     }
1792   else if ( Options.mode == MMT_GEN_ID )
1793     {
1794       UUID TmpID;
1795       Kumu::GenRandomValue(TmpID);
1796       printf("%s\n", TmpID.EncodeHex(str_buf, 64));
1797     }
1798   else if ( Options.mode == MMT_DIGEST )
1799     {
1800       for ( ui32_t i = 0; i < Options.file_count && ASDCP_SUCCESS(result); i++ )
1801         result = digest_file(Options.filenames[i]);
1802     }
1803   else if ( Options.mode == MMT_UL_LIST )
1804     {
1805       MDD_t di = (MDD_t)0;
1806
1807       while ( di < MDD_Max )
1808         {
1809           MDDEntry TmpType = Dict::Type(di);
1810           UL TmpUL(TmpType.ul);
1811           fprintf(stdout, "%s: %s\n", TmpUL.EncodeString(str_buf, 64), TmpType.name);
1812           di = (MDD_t)(di + 1);
1813         }
1814     }
1815   else if ( Options.mode == MMT_EXTRACT )
1816     {
1817       EssenceType_t EssenceType;
1818       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1819
1820       if ( ASDCP_SUCCESS(result) )
1821         {
1822           switch ( EssenceType )
1823             {
1824             case ESS_MPEG2_VES:
1825               result = read_MPEG2_file(Options);
1826               break;
1827
1828             case ESS_JPEG_2000:
1829               if ( Options.stereo_image_flag )
1830                 result = read_JP2K_S_file(Options);
1831               else
1832                 result = read_JP2K_file(Options);
1833               break;
1834
1835             case ESS_JPEG_2000_S:
1836               result = read_JP2K_S_file(Options);
1837               break;
1838
1839             case ESS_PCM_24b_48k:
1840               result = read_PCM_file(Options);
1841               break;
1842
1843             case ESS_TIMED_TEXT:
1844               result = read_timed_text_file(Options);
1845               break;
1846
1847             default:
1848               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1849               return 5;
1850             }
1851         }
1852     }
1853   else if ( Options.mode == MMT_CREATE )
1854     {
1855       if ( Options.do_repeat && ! Options.duration_flag )
1856         {
1857           fputs("Option -R requires -d <duration>\n", stderr);
1858           return RESULT_FAIL;
1859         }
1860
1861       EssenceType_t EssenceType;
1862       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1863
1864       if ( ASDCP_SUCCESS(result) )
1865         {
1866           switch ( EssenceType )
1867             {
1868             case ESS_MPEG2_VES:
1869               result = write_MPEG2_file(Options);
1870               break;
1871
1872             case ESS_JPEG_2000:
1873               if ( Options.stereo_image_flag )
1874                 result = write_JP2K_S_file(Options);
1875
1876               else
1877                 result = write_JP2K_file(Options);
1878
1879               break;
1880
1881             case ESS_PCM_24b_48k:
1882               result = write_PCM_file(Options);
1883               break;
1884
1885             case ESS_TIMED_TEXT:
1886               result = write_timed_text_file(Options);
1887               break;
1888
1889             default:
1890               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1891                       Options.filenames[0]);
1892               return 5;
1893             }
1894         }
1895     }
1896   else
1897     {
1898       fprintf(stderr, "Unhandled mode: %d.\n", Options.mode);
1899       return 6;
1900     }
1901
1902   if ( ASDCP_FAILURE(result) )
1903     {
1904       fputs("Program stopped on error.\n", stderr);
1905
1906       if ( result == RESULT_SFORMAT )
1907         {
1908           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
1909         }
1910       else if ( result != RESULT_FAIL )
1911         {
1912           fputs(result, stderr);
1913           fputc('\n', stderr);
1914         }
1915
1916       return 1;
1917     }
1918
1919   return 0;
1920 }
1921
1922
1923 //
1924 // end asdcp-test.cpp
1925 //