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