release
[asdcplib.git] / src / asdcp-test.cpp
1 /*
2 Copyright (c) 2003-2006, 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 <FileIO.h>
54 #include <PCMParserList.h>
55 #include <WavFileWriter.h>
56 #include <hex_utils.h>
57 #include <AS_DCP_UUID.h>
58 #include <MXF.h>
59 #include <Metadata.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, "%lu.%lu.%lu", VERSION_MAJOR, VERSION_APIMINOR, VERSION_IMPMINOR);
89       ProductVersion = s_buf;
90   }
91 } s_MyInfo;
92
93
94 // Macros used to test command option data state.
95
96 // True if a major mode has already been selected.
97 #define TEST_MAJOR_MODE()     ( info_flag||create_flag||extract_flag||genkey_flag||genid_flag||gop_start_flag )
98
99 // Causes the caller to return if a major mode has already been selected,
100 // otherwise sets the given flag.
101 #define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
102                                  { \
103                                    fputs("Conflicting major mode, choose one of -(gcixG)).\n", stderr); \
104                                    return; \
105                                  } \
106                                  (f) = true;
107
108 // Increment the iterator, test for an additional non-option command line argument.
109 // Causes the caller to return if there are no remaining arguments or if the next
110 // argument begins with '-'.
111 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
112                                  { \
113                                    fprintf(stderr, "Argument not found for option %c.\n", (c)); \
114                                    return; \
115                                  }
116 //
117 void
118 banner(FILE* stream = stderr)
119 {
120   fprintf(stream, "\n\
121 %s (asdcplib %s)\n\n\
122 Copyright (c) 2003-2005 John Hurst\n\n\
123 asdcplib may be copied only under the terms of the license found at\n\
124 the top of every file in the asdcplib distribution kit.\n\n\
125 Specify the -h (help) option for further information about %s\n\n",
126           PACKAGE, ASDCP::Version(), PACKAGE);
127 }
128
129 //
130 void
131 usage(FILE* stream = stderr)
132 {
133   fprintf(stream, "\
134 USAGE: %s [-i [-H, -n]|-c <filename> [-p <rate>, -e, -M, -R]|-x <root-name> [-m][-S]|-g|-u|-G|-V|-h]\n\
135        [-k <key-string>] [-j <key-id-string>] [-f <start-frame-num>] [-d <duration>]\n\
136        [-b <buf-size>] [-W] [-v [-s]] [<filename>, ...]\n\
137 \n", PACKAGE);
138
139   fprintf(stream, "\
140 Major modes:\n\
141   -i              - show file info\n\
142   -c <filename>   - create AS-DCP file from input(s)\n\
143   -x <root-name>  - extract essence from AS-DCP file to named file(s)\n\
144   -g              - generate a random 16 byte value to stdout\n\
145   -u              - generate a random UUID value to stdout\n\
146   -G              - Perform GOP start lookup test on MPEG file\n\
147   -V              - show version\n\
148   -h              - show help\n\
149 \n");
150
151   fprintf(stream, "\
152 Security Options:\n\
153   -j <key-id-str> - write key ID instead of creating a random value\n\
154   -k <key-string> - use key for ciphertext operations\n\
155   -e              - encrypt MPEG or JP2K headers (default)\n\
156   -E              - do not encrypt MPEG or JP2K headers\n\
157   -M              - do not create HMAC values when writing\n\
158   -m              - verify HMAC values when reading\n\
159 \n");
160
161   fprintf(stream, "\
162 Read/Write Options:\n\
163   -b <buf-size>   - Size (in bytes) of the picture frame buffer, default: 2097152 (2MB)\n\
164   -f <frame-num>  - starting frame number, default 0\n\
165   -d <duration>   - number of frames to process, default all\n\
166   -p <rate>       - fps of picture when wrapping PCM or JP2K:, use one of [23|24|48], 24 is default\n\
167   -L              - Write SMPTE UL values instead of MXF Interop\n\
168   -R              - Repeat the first frame over the entire file (picture essence only, requires -c, -d)\n\
169   -S              - Split Wave essence to stereo WAV files during extract (default = multichannel WAV)\n\
170   -W              - read input file only, do not write source file\n\
171 \n");
172
173   fprintf(stream, "\
174 Info Options:\n\
175   -H              - show MXF header metadata, used with option -i\n\
176   -n              - show index, used with option -i\n\
177 \n\
178 Other Options:\n\
179   -s <number>     - number of bytes of frame buffer to be dumped as hex to stderr (use with -v)\n\
180   -v              - verbose, show extra detail during run\n\
181 \n\
182   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
183          o All option arguments must be separated from the option by whitespace.\n\
184          o An argument of \"23\" to the -p option will be interpreted as 23000/1001 fps.\n\
185 \n");
186 }
187
188 //
189 //
190 class CommandOptions
191 {
192   CommandOptions();
193
194 public:
195   bool   error_flag;     // true if the given options are in error or not complete
196   bool   info_flag;      // true if the file info mode was selected
197   bool   create_flag;    // true if the file create mode was selected
198   bool   extract_flag;   // true if the file extract mode was selected
199   bool   genkey_flag;    // true if we are to generate a new key value
200   bool   genid_flag;     // true if we are to generate a new UUID value
201   bool   gop_start_flag; // true if we are to perform a GOP start lookup test
202   bool   key_flag;       // true if an encryption key was given
203   bool   key_id_flag;    // true if a key ID was given
204   bool   encrypt_header_flag; // true if mpeg headers are to be encrypted
205   bool   write_hmac;     // true if HMAC values are to be generated and written
206   bool   read_hmac;      // true if HMAC values are to be validated
207   bool   split_wav;      // true if PCM is to be extracted to stereo WAV files
208   bool   verbose_flag;   // true if the verbose option was selected
209   ui32_t fb_dump_size;   // number of bytes of frame buffer to dump
210   bool   showindex_flag; // true if index is to be displayed
211   bool   showheader_flag; // true if MXF file header is to be displayed
212   bool   no_write_flag;  // true if no output files are to be written
213   bool   version_flag;   // true if the version display option was selected
214   bool   help_flag;      // true if the help display option was selected
215   ui32_t start_frame;    // frame number to begin processing
216   ui32_t duration;       // number of frames to be processed
217   bool   duration_flag;  // true if duration argument given
218   bool   do_repeat;      // if true and -c -d, repeat first input frame
219   bool   use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
220   ui32_t picture_rate;   // fps of picture when wrapping PCM
221   ui32_t fb_size;        // size of picture frame buffer
222   ui32_t file_count;     // number of elements in filenames[]
223   const char* file_root; // filename pre for files written by the extract mode
224   const char* out_file;  // name of mxf file created by create mode
225   byte_t key_value[KeyLen];  // value of given encryption key (when key_flag is true)
226   byte_t key_id_value[UUIDlen];// value of given key ID (when key_id_flag is true)
227   const char* filenames[MAX_IN_FILES]; // list of filenames to be processed
228
229   //
230   Rational PictureRate()
231   {
232     if ( picture_rate == 23 ) return EditRate_23_98;
233     if ( picture_rate == 48 ) return EditRate_48;
234     return EditRate_24;
235   }
236
237   //
238   const char* szPictureRate()
239   {
240     if ( picture_rate == 23 ) return "23.976";
241     if ( picture_rate == 48 ) return "48";
242     return "24";
243   }
244
245   //
246   CommandOptions(int argc, const char** argv) :
247     error_flag(true), info_flag(false), create_flag(false),
248     extract_flag(false), genkey_flag(false), genid_flag(false), gop_start_flag(false),
249     key_flag(false), key_id_flag(false), encrypt_header_flag(true),
250     write_hmac(true), read_hmac(false), split_wav(false),
251     verbose_flag(false), fb_dump_size(0), showindex_flag(false), showheader_flag(false),
252     no_write_flag(false), version_flag(false), help_flag(false), start_frame(0),
253     duration(0xffffffff), duration_flag(false), do_repeat(false), use_smpte_labels(false),
254     picture_rate(24), fb_size(FRAME_BUFFER_SIZE), file_count(0), file_root(0), out_file(0)
255   {
256     memset(key_value, 0, KeyLen);
257     memset(key_id_value, 0, UUIDlen);
258
259     for ( int i = 1; i < argc; i++ )
260       {
261         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
262           {
263             switch ( argv[i][1] )
264               {
265               case 'i': TEST_SET_MAJOR_MODE(info_flag); break;
266               case 'G': TEST_SET_MAJOR_MODE(gop_start_flag); break;
267               case 'W': no_write_flag = true; break;
268               case 'n': showindex_flag = true; break;
269               case 'H': showheader_flag = true; break;
270               case 'R': do_repeat = true; break;
271               case 'S': split_wav = true; break;
272               case 'V': version_flag = true; break;
273               case 'h': help_flag = true; break;
274               case 'v': verbose_flag = true; break;
275               case 'g': genkey_flag = true; break;
276               case 'u': genid_flag = true; break;
277               case 'e': encrypt_header_flag = true; break;
278               case 'E': encrypt_header_flag = false; break;
279               case 'M': write_hmac = false; break;
280               case 'm': read_hmac = true; break;
281               case 'L': use_smpte_labels = true; break;
282
283               case 'c':
284                 TEST_SET_MAJOR_MODE(create_flag);
285                 TEST_EXTRA_ARG(i, 'c');
286                 out_file = argv[i];
287                 break;
288
289               case 'x':
290                 TEST_SET_MAJOR_MODE(extract_flag);
291                 TEST_EXTRA_ARG(i, 'x');
292                 file_root = argv[i];
293                 break;
294
295               case 'k': key_flag = true;
296                 TEST_EXTRA_ARG(i, 'k');
297                 {
298                   ui32_t length;
299                   hex2bin(argv[i], key_value, KeyLen, &length);
300
301                   if ( length != KeyLen )
302                     {
303                       fprintf(stderr, "Unexpected key length: %lu, expecting %lu characters.\n", KeyLen, length);
304                       return;
305                     }
306                 }
307                 break;
308
309               case 'j': key_id_flag = true;
310                 TEST_EXTRA_ARG(i, 'j');
311                 {
312                   ui32_t length;
313                   hex2bin(argv[i], key_id_value, UUIDlen, &length);
314
315                   if ( length != UUIDlen )
316                     {
317                       fprintf(stderr, "Unexpected key ID length: %lu, expecting %lu characters.\n", UUIDlen, length);
318                       return;
319                     }
320                 }
321                 break;
322
323               case 'f':
324                 TEST_EXTRA_ARG(i, 'f');
325                 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
326                 break;
327
328               case 'd':
329                 TEST_EXTRA_ARG(i, 'd');
330                 duration_flag = true;
331                 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
332                 break;
333
334               case 'p':
335                 TEST_EXTRA_ARG(i, 'p');
336                 picture_rate = atoi(argv[i]);
337                 break;
338
339               case 's':
340                 TEST_EXTRA_ARG(i, 's');
341                 fb_dump_size = atoi(argv[i]);
342                 break;
343
344               case 'b':
345                 TEST_EXTRA_ARG(i, 'b');
346                 fb_size = atoi(argv[i]);
347
348                 if ( verbose_flag )
349                   fprintf(stderr, "Frame Buffer size: %lu bytes.\n", fb_size);
350
351                 break;
352
353               default:
354                 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
355                 return;
356               }
357           }
358         else
359           {
360             filenames[file_count++] = argv[i];
361
362             if ( file_count >= MAX_IN_FILES )
363               {
364                 fprintf(stderr, "Filename lists exceeds maximum list size: %lu\n", MAX_IN_FILES);
365                 return;
366               }
367           }
368       }
369
370     if ( TEST_MAJOR_MODE() )
371       {
372         if ( ! genkey_flag && ! genid_flag && file_count == 0 )
373           {
374             fputs("Option requires at least one filename argument.\n", stderr);
375             return;
376           }
377       }
378
379     if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
380       {
381         fputs("No operation selected (use one of -(gcixG) or -h for help).\n", stderr);
382         return;
383       }
384
385     error_flag = false;
386   }
387 };
388
389 //------------------------------------------------------------------------------------------
390 // MPEG2 essence
391
392 // Write a plaintext MPEG2 Video Elementary Stream to a plaintext ASDCP file
393 // Write a plaintext MPEG2 Video Elementary Stream to a ciphertext ASDCP file
394 //
395 Result_t
396 write_MPEG2_file(CommandOptions& Options)
397 {
398   AESEncContext*     Context = 0;
399   HMACContext*       HMAC = 0;
400   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
401   MPEG2::Parser      Parser;
402   MPEG2::MXFWriter   Writer;
403   MPEG2::VideoDescriptor VDesc;
404   byte_t             IV_buf[CBC_BLOCK_SIZE];
405   FortunaRNG         RNG;
406
407   // set up essence parser
408   Result_t result = Parser.OpenRead(Options.filenames[0]);
409
410   // set up MXF writer
411   if ( ASDCP_SUCCESS(result) )
412     {
413       Parser.FillVideoDescriptor(VDesc);
414
415       if ( Options.verbose_flag )
416         {
417           fputs("MPEG-2 Pictures\n", stderr);
418           fputs("VideoDescriptor:\n", stderr);
419           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
420           MPEG2::VideoDescriptorDump(VDesc);
421         }
422     }
423
424   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
425     {
426       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
427       GenRandomUUID(RNG, Info.AssetUUID);
428
429       if ( Options.use_smpte_labels )
430         {
431           Info.LabelSetType = LS_MXF_INTEROP;
432           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
433         }
434
435       // configure encryption
436       if( Options.key_flag )
437         {
438           GenRandomUUID(RNG, Info.ContextID);
439           Info.EncryptedEssence = true;
440
441           if ( Options.key_id_flag )
442             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
443           else
444             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
445
446           Context = new AESEncContext;
447           result = Context->InitKey(Options.key_value);
448
449           if ( ASDCP_SUCCESS(result) )
450             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
451
452           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
453             {
454               Info.UsesHMAC = true;
455               HMAC = new HMACContext;
456               result = HMAC->InitKey(Options.key_value);
457             }
458         }
459
460       if ( ASDCP_SUCCESS(result) )
461         result = Writer.OpenWrite(Options.out_file, Info, VDesc);
462     }
463
464   if ( ASDCP_SUCCESS(result) )
465     // loop through the frames
466     {
467       result = Parser.Reset();
468       ui32_t duration = 0;
469
470       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
471         {
472           if ( ! Options.do_repeat || duration == 1 )
473             {
474               result = Parser.ReadFrame(FrameBuffer);
475
476               if ( ASDCP_SUCCESS(result) )
477                 {
478                   if ( Options.verbose_flag )
479                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
480                   
481                   if ( Options.encrypt_header_flag )
482                     FrameBuffer.PlaintextOffset(0);
483                 }
484             }
485
486           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
487             {
488               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
489
490               // The Writer class will forward the last block of ciphertext
491               // to the encryption context for use as the IV for the next
492               // frame. If you want to use non-sequitur IV values, un-comment
493               // the following  line of code.
494               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
495               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
496             }
497         }
498
499       if ( result == RESULT_ENDOFFILE )
500         result = RESULT_OK;
501     }
502
503   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
504     result = Writer.Finalize();
505
506   return result;
507 }
508
509 // Read a plaintext MPEG2 Video Elementary Stream from a plaintext ASDCP file
510 // Read a plaintext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
511 // Read a ciphertext MPEG2 Video Elementary Stream from a ciphertext ASDCP file
512 //
513 Result_t
514 read_MPEG2_file(CommandOptions& Options)
515 {
516   AESDecContext*     Context = 0;
517   HMACContext*       HMAC = 0;
518   MPEG2::MXFReader   Reader;
519   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
520   FileWriter         OutFile;
521   ui32_t             frame_count = 0;
522
523   Result_t result = Reader.OpenRead(Options.filenames[0]);
524
525   if ( ASDCP_SUCCESS(result) )
526     {
527       MPEG2::VideoDescriptor VDesc;
528       Reader.FillVideoDescriptor(VDesc);
529       frame_count = VDesc.ContainerDuration;
530
531       if ( Options.verbose_flag )
532         {
533           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
534           MPEG2::VideoDescriptorDump(VDesc);
535         }
536     }
537
538   if ( ASDCP_SUCCESS(result) )
539     {
540       char filename[256];
541       snprintf(filename, 256, "%s.ves", Options.file_root);
542       result = OutFile.OpenWrite(filename);
543     }
544
545   if ( ASDCP_SUCCESS(result) && Options.key_flag )
546     {
547       Context = new AESDecContext;
548       result = Context->InitKey(Options.key_value);
549
550       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
551         {
552           WriterInfo Info;
553           Reader.FillWriterInfo(Info);
554
555           if ( Info.UsesHMAC )
556             {
557               HMAC = new HMACContext;
558               result = HMAC->InitKey(Options.key_value);
559             }
560           else
561             {
562               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
563             }
564         }
565     }
566
567   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
568   if ( last_frame > frame_count )
569     last_frame = frame_count;
570
571   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
572     {
573       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
574
575       if ( ASDCP_SUCCESS(result) )
576         {
577           if ( Options.verbose_flag )
578             FrameBuffer.Dump(stderr, Options.fb_dump_size);
579
580           ui32_t write_count = 0;
581           result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
582         }
583     }
584
585   return result;
586 }
587
588
589 //
590 Result_t
591 gop_start_test(CommandOptions& Options)
592 {
593   using namespace ASDCP::MPEG2;
594
595   MXFReader   Reader;
596   MPEG2::FrameBuffer FrameBuffer(Options.fb_size);
597   ui32_t      frame_count = 0;
598
599   Result_t result = Reader.OpenRead(Options.filenames[0]);
600
601   if ( ASDCP_SUCCESS(result) )
602     {
603       MPEG2::VideoDescriptor VDesc;
604       Reader.FillVideoDescriptor(VDesc);
605       frame_count = VDesc.ContainerDuration;
606
607       if ( Options.verbose_flag )
608         {
609           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
610           MPEG2::VideoDescriptorDump(VDesc);
611         }
612     }
613
614   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
615   if ( last_frame > frame_count )
616     last_frame = frame_count;
617
618   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
619     {
620       result = Reader.ReadFrameGOPStart(i, FrameBuffer);
621
622       if ( ASDCP_SUCCESS(result) )
623         {
624           if ( Options.verbose_flag )
625             FrameBuffer.Dump(stderr, Options.fb_dump_size);
626
627           if ( FrameBuffer.FrameType() != FRAME_I )
628             fprintf(stderr, "Expecting an I frame, got %c\n", FrameTypeChar(FrameBuffer.FrameType()));
629
630           fprintf(stderr, "Requested frame %lu, got %lu\n", i, FrameBuffer.FrameNumber());
631         }
632     }
633
634   return result;
635 }
636
637 //------------------------------------------------------------------------------------------
638 // JPEG 2000 essence
639
640 // Write one or more plaintext JPEG 2000 codestreams to a plaintext ASDCP file
641 // Write one or more plaintext JPEG 2000 codestreams to a ciphertext ASDCP file
642 //
643 Result_t
644 write_JP2K_file(CommandOptions& Options)
645 {
646   AESEncContext*          Context = 0;
647   HMACContext*            HMAC = 0;
648   JP2K::MXFWriter         Writer;
649   JP2K::FrameBuffer       FrameBuffer(Options.fb_size);
650   JP2K::PictureDescriptor PDesc;
651   JP2K::SequenceParser    Parser;
652   byte_t           IV_buf[CBC_BLOCK_SIZE];
653   FortunaRNG       RNG;
654
655   // set up essence parser
656   Result_t result = Parser.OpenRead(Options.filenames[0]);
657
658   // set up MXF writer
659   if ( ASDCP_SUCCESS(result) )
660     {
661       Parser.FillPictureDescriptor(PDesc);
662       PDesc.EditRate = Options.PictureRate();
663
664       if ( Options.verbose_flag )
665         {
666           fprintf(stderr, "JPEG 2000 pictures\n");
667           fputs("PictureDescriptor:\n", stderr);
668           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
669           JP2K::PictureDescriptorDump(PDesc);
670         }
671     }
672
673   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
674     {
675       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
676       GenRandomUUID(RNG, Info.AssetUUID);
677
678       if ( Options.use_smpte_labels )
679         {
680           Info.LabelSetType = LS_MXF_INTEROP;
681           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
682         }
683
684       // configure encryption
685       if( Options.key_flag )
686         {
687           GenRandomUUID(RNG, Info.ContextID);
688           Info.EncryptedEssence = true;
689
690           if ( Options.key_id_flag )
691             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
692           else
693             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
694
695           Context = new AESEncContext;
696           result = Context->InitKey(Options.key_value);
697
698           if ( ASDCP_SUCCESS(result) )
699             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
700
701           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
702             {
703               Info.UsesHMAC = true;
704               HMAC = new HMACContext;
705               result = HMAC->InitKey(Options.key_value);
706             }
707         }
708
709       if ( ASDCP_SUCCESS(result) )
710         result = Writer.OpenWrite(Options.out_file, Info, PDesc);
711     }
712
713   if ( ASDCP_SUCCESS(result) )
714     {
715       ui32_t duration = 0;
716       result = Parser.Reset();
717
718       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
719         {
720           if ( ! Options.do_repeat || duration == 1 )
721             {
722               result = Parser.ReadFrame(FrameBuffer);
723
724               if ( ASDCP_SUCCESS(result) )
725                 {
726                   if ( Options.verbose_flag )
727                     FrameBuffer.Dump(stderr, Options.fb_dump_size);
728                   
729                   if ( Options.encrypt_header_flag )
730                     FrameBuffer.PlaintextOffset(0);
731                 }
732             }
733
734           if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
735             {
736               result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
737
738               // The Writer class will forward the last block of ciphertext
739               // to the encryption context for use as the IV for the next
740               // frame. If you want to use non-sequitur IV values, un-comment
741               // the following  line of code.
742               // if ( ASDCP_SUCCESS(result) && Options.key_flag )
743               //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
744             }
745         }
746
747       if ( result == RESULT_ENDOFFILE )
748         result = RESULT_OK;
749     }
750
751   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
752     result = Writer.Finalize();
753
754   return result;
755 }
756
757 // Read one or more plaintext JPEG 2000 codestreams from a plaintext ASDCP file
758 // Read one or more plaintext JPEG 2000 codestreams from a ciphertext ASDCP file
759 // Read one or more ciphertext JPEG 2000 codestreams from a ciphertext ASDCP file
760 //
761 Result_t
762 read_JP2K_file(CommandOptions& Options)
763 {
764   AESDecContext*     Context = 0;
765   HMACContext*       HMAC = 0;
766   JP2K::MXFReader    Reader;
767   JP2K::FrameBuffer  FrameBuffer(Options.fb_size);
768   ui32_t             frame_count = 0;
769
770   Result_t result = Reader.OpenRead(Options.filenames[0]);
771
772   if ( ASDCP_SUCCESS(result) )
773     {
774       JP2K::PictureDescriptor PDesc;
775       Reader.FillPictureDescriptor(PDesc);
776
777       frame_count = PDesc.ContainerDuration;
778
779       if ( Options.verbose_flag )
780         {
781           fprintf(stderr, "Frame Buffer size: %lu\n", Options.fb_size);
782           JP2K::PictureDescriptorDump(PDesc);
783         }
784     }
785
786   if ( ASDCP_SUCCESS(result) && Options.key_flag )
787     {
788       Context = new AESDecContext;
789       result = Context->InitKey(Options.key_value);
790
791       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
792         {
793           WriterInfo Info;
794           Reader.FillWriterInfo(Info);
795
796           if ( Info.UsesHMAC )
797             {
798               HMAC = new HMACContext;
799               result = HMAC->InitKey(Options.key_value);
800             }
801           else
802             {
803               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
804             }
805         }
806     }
807
808   ui32_t last_frame = Options.start_frame + ( Options.duration ? Options.duration : frame_count);
809   if ( last_frame > frame_count )
810     last_frame = frame_count;
811
812   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
813     {
814       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
815
816       if ( ASDCP_SUCCESS(result) )
817         {
818           FileWriter OutFile;
819           char filename[256];
820           ui32_t write_count;
821           snprintf(filename, 256, "%s%06lu.j2c", Options.file_root, i);
822           result = OutFile.OpenWrite(filename);
823
824           if ( ASDCP_SUCCESS(result) )
825             result = OutFile.Write(FrameBuffer.Data(), FrameBuffer.Size(), &write_count);
826
827           if ( Options.verbose_flag )
828             FrameBuffer.Dump(stderr, Options.fb_dump_size);
829         }
830     }
831
832   return result;
833 }
834
835 //------------------------------------------------------------------------------------------
836 // PCM essence
837
838
839 // Write one or more plaintext PCM audio streams to a plaintext ASDCP file
840 // Write one or more plaintext PCM audio streams to a ciphertext ASDCP file
841 //
842 Result_t
843 write_PCM_file(CommandOptions& Options)
844 {
845   AESEncContext*   Context = 0;
846   HMACContext*     HMAC = 0;
847   PCMParserList    Parser;
848   PCM::MXFWriter   Writer;
849   PCM::FrameBuffer FrameBuffer;
850   PCM::AudioDescriptor ADesc;
851   Rational         PictureRate = Options.PictureRate();
852   byte_t           IV_buf[CBC_BLOCK_SIZE];
853   FortunaRNG       RNG;
854
855   // set up essence parser
856   Result_t result = Parser.OpenRead(Options.file_count, Options.filenames, PictureRate);
857
858   // set up MXF writer
859   if ( ASDCP_SUCCESS(result) )
860     {
861       Parser.FillAudioDescriptor(ADesc);
862
863       ADesc.SampleRate = PictureRate;
864       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
865
866       if ( Options.verbose_flag )
867         {
868           fprintf(stderr, "48Khz PCM Audio, %s fps (%lu spf)\n",
869                   Options.szPictureRate(),
870                   PCM::CalcSamplesPerFrame(ADesc));
871           fputs("AudioDescriptor:\n", stderr);
872           PCM::AudioDescriptorDump(ADesc);
873         }
874     }
875
876   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
877     {
878       WriterInfo Info = s_MyInfo;  // fill in your favorite identifiers here
879       GenRandomUUID(RNG, Info.AssetUUID);
880
881       if ( Options.use_smpte_labels )
882         {
883           Info.LabelSetType = LS_MXF_INTEROP;
884           fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
885         }
886
887       // configure encryption
888       if( Options.key_flag )
889         {
890           GenRandomUUID(RNG, Info.ContextID);
891           Info.EncryptedEssence = true;
892
893           if ( Options.key_id_flag )
894             memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
895           else
896             RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
897
898           Context = new AESEncContext;
899           result = Context->InitKey(Options.key_value);
900
901           if ( ASDCP_SUCCESS(result) )
902             result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
903
904           if ( ASDCP_SUCCESS(result) && Options.write_hmac )
905             {
906               Info.UsesHMAC = true;
907               HMAC = new HMACContext;
908               result = HMAC->InitKey(Options.key_value);
909             }
910         }
911
912       if ( ASDCP_SUCCESS(result) )
913         result = Writer.OpenWrite(Options.out_file, Info, ADesc);
914     }
915
916   if ( ASDCP_SUCCESS(result) )
917     {
918       result = Parser.Reset();
919       ui32_t duration = 0;
920
921       while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
922         {
923           result = Parser.ReadFrame(FrameBuffer);
924
925           if ( ASDCP_SUCCESS(result) )
926             {
927               if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
928                 {
929                   fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
930                   fprintf(stderr, "Expecting %lu bytes, got %lu.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
931                   result = RESULT_ENDOFFILE;
932                   continue;
933                 }
934
935               if ( Options.verbose_flag )
936                 FrameBuffer.Dump(stderr, Options.fb_dump_size);
937
938               if ( ! Options.no_write_flag )
939                 {
940                   result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
941
942                   // The Writer class will forward the last block of ciphertext
943                   // to the encryption context for use as the IV for the next
944                   // frame. If you want to use non-sequitur IV values, un-comment
945                   // the following  line of code.
946                   // if ( ASDCP_SUCCESS(result) && Options.key_flag )
947                   //   Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
948                 }
949             }
950         }
951
952       if ( result == RESULT_ENDOFFILE )
953         result = RESULT_OK;
954     }
955
956   if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
957     result = Writer.Finalize();
958
959   return result;
960 }
961
962 // Read one or more plaintext PCM audio streams from a plaintext ASDCP file
963 // Read one or more plaintext PCM audio streams from a ciphertext ASDCP file
964 // Read one or more ciphertext PCM audio streams from a ciphertext ASDCP file
965 //
966 Result_t
967 read_PCM_file(CommandOptions& Options)
968 {
969   AESDecContext*     Context = 0;
970   HMACContext*       HMAC = 0;
971   PCM::MXFReader     Reader;
972   PCM::FrameBuffer   FrameBuffer;
973   WavFileWriter      OutWave;
974   PCM::AudioDescriptor ADesc;
975   ui32_t last_frame = 0;
976
977   Result_t result = Reader.OpenRead(Options.filenames[0]);
978
979   if ( ASDCP_SUCCESS(result) )
980     {
981       Reader.FillAudioDescriptor(ADesc);
982
983       if ( ADesc.SampleRate != EditRate_23_98
984            && ADesc.SampleRate != EditRate_24
985            && ADesc.SampleRate != EditRate_48 )
986         ADesc.SampleRate = Options.PictureRate();
987
988       FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
989
990       if ( Options.verbose_flag )
991         PCM::AudioDescriptorDump(ADesc);
992     }
993
994   if ( ASDCP_SUCCESS(result) )
995     {
996       last_frame = ADesc.ContainerDuration;
997
998       if ( Options.duration > 0 && Options.duration < last_frame )
999         last_frame = Options.duration;
1000
1001       if ( Options.start_frame > 0 )
1002         {
1003           if ( Options.start_frame > ADesc.ContainerDuration )
1004             {
1005               fprintf(stderr, "Start value greater than file duration.\n");
1006               return RESULT_FAIL;
1007             }
1008
1009           last_frame = xmin(Options.start_frame + last_frame, ADesc.ContainerDuration);
1010         }
1011
1012       ADesc.ContainerDuration = last_frame - Options.start_frame;
1013       OutWave.OpenWrite(ADesc, Options.file_root, Options.split_wav);
1014     }
1015
1016   if ( ASDCP_SUCCESS(result) && Options.key_flag )
1017     {
1018       Context = new AESDecContext;
1019       result = Context->InitKey(Options.key_value);
1020
1021       if ( ASDCP_SUCCESS(result) && Options.read_hmac )
1022         {
1023           WriterInfo Info;
1024           Reader.FillWriterInfo(Info);
1025
1026           if ( Info.UsesHMAC )
1027             {
1028               HMAC = new HMACContext;
1029               result = HMAC->InitKey(Options.key_value);
1030             }
1031           else
1032             {
1033               fputs("File does not contain HMAC values, ignoring -m option.\n", stderr);
1034             }
1035         }
1036     }
1037
1038   for ( ui32_t i = Options.start_frame; ASDCP_SUCCESS(result) && i < last_frame; i++ )
1039     {
1040       result = Reader.ReadFrame(i, FrameBuffer, Context, HMAC);
1041
1042       if ( ASDCP_SUCCESS(result) )
1043         {
1044           if ( Options.verbose_flag )
1045             FrameBuffer.Dump(stderr, Options.fb_dump_size);
1046
1047           result = OutWave.WriteFrame(FrameBuffer);
1048         }
1049     }
1050
1051   return result;
1052 }
1053
1054
1055 //------------------------------------------------------------------------------------------
1056 //
1057
1058 //
1059 // These classes wrap the irregular names in the asdcplib API
1060 // so that I can use a template to simplify the implementation
1061 // of show_file_info()
1062
1063 class MyVideoDescriptor : public MPEG2::VideoDescriptor
1064 {
1065  public:
1066   void FillDescriptor(MPEG2::MXFReader& Reader) {
1067     Reader.FillVideoDescriptor(*this);
1068   }
1069
1070   void Dump(FILE* stream) {
1071     MPEG2::VideoDescriptorDump(*this, stream);
1072   }
1073 };
1074
1075 class MyPictureDescriptor : public JP2K::PictureDescriptor
1076 {
1077  public:
1078   void FillDescriptor(JP2K::MXFReader& Reader) {
1079     Reader.FillPictureDescriptor(*this);
1080   }
1081
1082   void Dump(FILE* stream) {
1083     JP2K::PictureDescriptorDump(*this, stream);
1084   }
1085 };
1086
1087 class MyAudioDescriptor : public PCM::AudioDescriptor
1088 {
1089  public:
1090   void FillDescriptor(PCM::MXFReader& Reader) {
1091     Reader.FillAudioDescriptor(*this);
1092   }
1093
1094   void Dump(FILE* stream) {
1095     PCM::AudioDescriptorDump(*this, stream);
1096   }
1097 };
1098
1099
1100 // MSVC didn't like the function template, so now it's a static class method
1101 template<class ReaderT, class DescriptorT>
1102 class FileInfoWrapper
1103 {
1104 public:
1105   static void file_info(CommandOptions& Options, FILE* stream = 0)
1106   {
1107     if ( stream == 0 )
1108       stream = stdout;
1109
1110     if ( Options.verbose_flag || Options.showheader_flag )
1111       {
1112         ReaderT     Reader;
1113         Result_t result = Reader.OpenRead(Options.filenames[0]);
1114
1115         if ( ASDCP_SUCCESS(result) )
1116           {
1117             if ( Options.showheader_flag )
1118               Reader.DumpHeaderMetadata(stream);
1119
1120             WriterInfo WI;
1121             Reader.FillWriterInfo(WI);
1122             WriterInfoDump(WI, stream);
1123
1124             DescriptorT Desc;
1125             Desc.FillDescriptor(Reader);
1126             Desc.Dump(stream);
1127
1128             if ( Options.showindex_flag )
1129               Reader.DumpIndex(stream);
1130           }
1131         else if ( result == RESULT_FORMAT && Options.showheader_flag )
1132           {
1133             Reader.DumpHeaderMetadata(stream);
1134           }
1135       }
1136   }
1137 };
1138
1139 // Read header metadata from an ASDCP file
1140 //
1141 Result_t
1142 show_file_info(CommandOptions& Options)
1143 {
1144   EssenceType_t EssenceType;
1145   Result_t result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1146
1147   if ( ASDCP_FAILURE(result) )
1148     return result;
1149
1150   if ( EssenceType == ESS_MPEG2_VES )
1151     {
1152       fputs("File essence type is MPEG2 video.\n", stdout);
1153       FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options);
1154     }
1155   else if ( EssenceType == ESS_PCM_24b_48k )
1156     {
1157       fputs("File essence type is PCM audio.\n", stdout);
1158       FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options);
1159     }
1160   else if ( EssenceType == ESS_JPEG_2000 )
1161     {
1162       fputs("File essence type is JPEG 2000 pictures.\n", stdout);
1163       FileInfoWrapper<ASDCP::JP2K::MXFReader, MyPictureDescriptor>::file_info(Options);
1164     }
1165   else
1166     {
1167       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames[0]);
1168       FileReader   Reader;
1169       MXF::OPAtomHeader TestHeader;
1170
1171       result = Reader.OpenRead(Options.filenames[0]);
1172
1173       if ( ASDCP_SUCCESS(result) )
1174         result = TestHeader.InitFromFile(Reader); // test UL and OP
1175
1176       if ( ASDCP_SUCCESS(result) )
1177         {
1178           TestHeader.Partition::Dump();
1179
1180           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
1181             ID->Dump();
1182           else
1183             fputs("File contains no Identification object.\n", stdout);
1184
1185           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
1186             SP->Dump();
1187           else
1188             fputs("File contains no SourcePackage object.\n", stdout);
1189         }
1190       else
1191         {
1192           fputs("File is not MXF.\n", stdout);
1193         }
1194     }
1195
1196   return result;
1197 }
1198
1199
1200 //
1201 int
1202 main(int argc, const char** argv)
1203 {
1204   Result_t result = RESULT_OK;
1205   CommandOptions Options(argc, argv);
1206
1207   if ( Options.help_flag )
1208     {
1209       usage();
1210       return 0;
1211     }
1212
1213   if ( Options.error_flag )
1214     return 3;
1215
1216   if ( Options.version_flag )
1217     banner();
1218
1219   if ( Options.info_flag )
1220     {
1221       result = show_file_info(Options);
1222     }
1223   else if ( Options.gop_start_flag )
1224     {
1225       result = gop_start_test(Options);
1226     }
1227   else if ( Options.genkey_flag )
1228     {
1229       FortunaRNG RNG;
1230       byte_t bin_buf[KeyLen];
1231       char   str_buf[40];
1232
1233       RNG.FillRandom(bin_buf, KeyLen);
1234       printf("%s\n", bin2hex(bin_buf, KeyLen, str_buf, 40));
1235     }
1236   else if ( Options.genid_flag )
1237     {
1238       FortunaRNG RNG;
1239       byte_t bin_buf[KeyLen];
1240       char   str_buf[40];
1241
1242       GenRandomUUID(RNG, bin_buf);
1243       bin2hex(bin_buf, KeyLen, str_buf, 40);
1244       printf("%s\n", hyphenate_UUID(str_buf, 40));
1245     }
1246   else if ( Options.extract_flag )
1247     {
1248       EssenceType_t EssenceType;
1249       result = ASDCP::EssenceType(Options.filenames[0], EssenceType);
1250
1251       if ( ASDCP_SUCCESS(result) )
1252         {
1253           switch ( EssenceType )
1254             {
1255             case ESS_MPEG2_VES:
1256               result = read_MPEG2_file(Options);
1257               break;
1258
1259             case ESS_JPEG_2000:
1260               result = read_JP2K_file(Options);
1261               break;
1262
1263             case ESS_PCM_24b_48k:
1264               result = read_PCM_file(Options);
1265               break;
1266
1267             default:
1268               fprintf(stderr, "%s: Unknown file type, not ASDCP essence.\n", Options.filenames[0]);
1269               return 5;
1270             }
1271         }
1272     }
1273   else if ( Options.create_flag )
1274     {
1275       if ( Options.do_repeat && ! Options.duration_flag )
1276         {
1277           fputs("Option -R requires -d <duration>\n", stderr);
1278           return RESULT_FAIL;
1279         }
1280
1281       EssenceType_t EssenceType;
1282       result = ASDCP::RawEssenceType(Options.filenames[0], EssenceType);
1283
1284       if ( ASDCP_SUCCESS(result) )
1285         {
1286           switch ( EssenceType )
1287             {
1288             case ESS_MPEG2_VES:
1289               result = write_MPEG2_file(Options);
1290               break;
1291
1292             case ESS_JPEG_2000:
1293               result = write_JP2K_file(Options);
1294               break;
1295
1296             case ESS_PCM_24b_48k:
1297               result = write_PCM_file(Options);
1298               break;
1299
1300             default:
1301               fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
1302                       Options.filenames[0]);
1303               return 5;
1304             }
1305         }
1306     }
1307
1308   if ( result != RESULT_OK )
1309     {
1310       fputs("Program stopped on error.\n", stderr);
1311
1312       if ( result != RESULT_FAIL )
1313         {
1314           fputs(GetResultString(result), stderr);
1315           fputc('\n', stderr);
1316         }
1317
1318       return 1;
1319     }
1320
1321   return 0;
1322 }
1323
1324
1325 //
1326 // end asdcp-test.cpp
1327 //