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