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