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