build JP2K parsers with file lists
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2009, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    AS_DCP_MXF.cpp
28     \version $Id$
29     \brief   AS-DCP library, misc classes and subroutines
30 */
31
32 #include <KM_fileio.h>
33 #include <KM_xml.h>
34 #include "AS_DCP_internal.h"
35 #include "JP2K.h"
36 #include "MPEG.h"
37 #include "Wav.h"
38 #include <iostream>
39 #include <iomanip>
40
41
42 //------------------------------------------------------------------------------------------
43 // misc subroutines
44
45
46 //
47 std::ostream&
48 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
49 {
50   char str_buf[40];
51
52   strm << "       ProductUUID: " << UUID(Info.ProductUUID).EncodeHex(str_buf, 40) << std::endl;
53   strm << "    ProductVersion: " << Info.ProductVersion << std::endl;
54   strm << "       CompanyName: " << Info.CompanyName << std::endl;
55   strm << "       ProductName: " << Info.ProductName << std::endl;
56   strm << "  EncryptedEssence: " << (Info.EncryptedEssence ? "Yes" : "No") << std::endl;
57
58   if ( Info.EncryptedEssence )
59     {
60       strm << "              HMAC: " << (Info.UsesHMAC ? "Yes" : "No") << std::endl;
61       strm << "         ContextID: " << UUID(Info.ContextID).EncodeHex(str_buf, 40) << std::endl;
62       strm << "CryptographicKeyID: " << UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40) << std::endl;
63     }
64
65   strm << "         AssetUUID: " << UUID(Info.AssetUUID).EncodeHex(str_buf, 40) << std::endl;
66   strm << "    Label Set Type: " << (Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
67                                      (Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
68                                       "Unknown")) << std::endl;
69   return strm;
70 }
71
72 //
73 void
74 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
75 {
76   if ( stream == 0 )
77     stream = stderr;
78
79   char str_buf[40];
80
81   fprintf(stream,"       ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
82   fprintf(stream,"\
83     ProductVersion: %s\n\
84        CompanyName: %s\n\
85        ProductName: %s\n\
86   EncryptedEssence: %s\n",
87           Info.ProductVersion.c_str(),
88           Info.CompanyName.c_str(),
89           Info.ProductName.c_str(),
90           ( Info.EncryptedEssence ? "Yes" : "No" )
91           );
92
93   if ( Info.EncryptedEssence )
94     {
95       fprintf(stream, "              HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
96       fprintf(stream, "         ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
97       fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
98     }
99
100   fprintf(stream,"         AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
101   fprintf(stream,"    Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
102                                                ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
103                                                  "Unknown" ) ));
104 }
105
106 //
107 Result_t
108 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
109 {
110   ASDCP_TEST_NULL(InfoObj);
111   char tmp_str[IdentBufferLen];
112
113   Info.ProductName = "Unknown Product";
114   Info.ProductVersion = "Unknown Version";
115   Info.CompanyName = "Unknown Company";
116   memset(Info.ProductUUID, 0, UUIDlen);
117
118   InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
119   if ( *tmp_str ) Info.ProductName = tmp_str;
120
121   InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
122   if ( *tmp_str ) Info.ProductVersion = tmp_str;
123
124   InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
125   if ( *tmp_str ) Info.CompanyName = tmp_str;
126
127   memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
128
129   return RESULT_OK;
130 }
131
132
133 //
134 Result_t
135 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
136 {
137   ASDCP_TEST_NULL(InfoObj);
138
139   Info.EncryptedEssence = true;
140   memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
141   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
142
143   UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
144   UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
145
146   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
147     Info.UsesHMAC = true;
148
149   else if ( InfoObj->MICAlgorithm == MIC_NONE )
150     Info.UsesHMAC = false;
151
152   else
153     {
154       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
155       return RESULT_FORMAT;
156     }
157
158   return RESULT_OK;
159 }
160
161 //
162 //
163 ASDCP::Result_t
164 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
165 {
166   const Dictionary* m_Dict = &DefaultCompositeDict();
167   assert(m_Dict);
168
169   ASDCP_TEST_NULL_STR(filename);
170   Kumu::FileReader   Reader;
171   OPAtomHeader TestHeader(m_Dict);
172
173   Result_t result = Reader.OpenRead(filename);
174
175   if ( ASDCP_SUCCESS(result) )
176     result = TestHeader.InitFromFile(Reader); // test UL and OP
177
178   if ( ASDCP_SUCCESS(result) )
179     {
180       type = ESS_UNKNOWN;
181       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
182         {
183           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
184             type = ESS_JPEG_2000_S;
185           else
186             type = ESS_JPEG_2000;
187         }
188       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
189         type = ESS_PCM_24b_48k;
190       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
191         type = ESS_MPEG2_VES;
192       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
193         type = ESS_TIMED_TEXT;
194     }
195
196   return result;
197 }
198
199 //
200 ASDCP::Result_t
201 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
202 {
203   ASDCP_TEST_NULL_STR(filename);
204   type = ESS_UNKNOWN;
205   ASDCP::FrameBuffer FB;
206   Kumu::FileReader Reader;
207   ASDCP::Wav::SimpleWaveHeader WavHeader;
208   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
209   Kumu::XMLElement TmpElement("Tmp");
210
211   ui32_t data_offset;
212   ui32_t read_count;
213   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
214
215   if ( Kumu::PathIsFile(filename) )
216     {
217       result = Reader.OpenRead(filename);
218
219       if ( ASDCP_SUCCESS(result) )
220         {
221           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
222           Reader.Close();
223         }
224
225       if ( ASDCP_SUCCESS(result) )
226         {
227           const byte_t* p = FB.RoData();
228           FB.Size(read_count);
229
230           ui32_t i = 0;
231           while ( p[i] == 0 ) i++;
232
233           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
234             {
235               type = ESS_MPEG2_VES;
236             }
237           else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
238             {
239               type = ESS_JPEG_2000;
240             }
241           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
242             {
243               switch ( WavHeader.samplespersec )
244                 {
245                 case 48000: type = ESS_PCM_24b_48k; break;
246                 case 96000: type = ESS_PCM_24b_96k; break;
247                 default:
248                   return RESULT_FORMAT;
249                 }
250             }
251           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
252             {
253               type = ESS_PCM_24b_48k;
254             }
255           else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
256             {
257               type = ESS_TIMED_TEXT;
258             }
259         }
260     }
261   else if ( Kumu::PathIsDirectory(filename) )
262     {
263       char next_file[Kumu::MaxFilePath];
264       Kumu::DirScanner Scanner;
265       Result_t result = Scanner.Open(filename);
266
267       if ( ASDCP_SUCCESS(result) )
268         {
269           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
270             {
271               if ( next_file[0] == '.' ) // no hidden files or internal links
272                 continue;
273
274               std::string Str(filename);
275               Str += "/";
276               Str += next_file;
277               result = Reader.OpenRead(Str.c_str());
278
279               if ( ASDCP_SUCCESS(result) )
280                 {
281                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
282                   Reader.Close();
283                 }
284
285               if ( ASDCP_SUCCESS(result) )
286                 {
287                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
288                     {
289                       type = ESS_JPEG_2000;
290                     }
291                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
292                     {
293                       switch ( WavHeader.samplespersec )
294                         {
295                         case 48000: type = ESS_PCM_24b_48k; break;
296                         case 96000: type = ESS_PCM_24b_96k; break;
297                         default:
298                           return RESULT_FORMAT;
299                         }
300                     }
301                 }
302
303               break;
304             }
305         }
306     }
307
308   return result;
309 }
310
311 //
312 Result_t
313 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
314 {
315   ASDCP_TEST_NULL(Ctx);
316   FBout.Size(0);
317
318   // size the buffer
319   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
320
321   // write the IV
322   byte_t* p = FBout.Data();
323
324   // write the IV to the frame buffer
325   Ctx->GetIVec(p);
326   p += CBC_BLOCK_SIZE;
327
328
329   // encrypt the check value to the frame buffer
330   if ( ASDCP_SUCCESS(result) )
331     {
332       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
333       p += CBC_BLOCK_SIZE;
334     }
335
336   // write optional plaintext region
337   if ( FBin.PlaintextOffset() > 0 )
338     {
339       assert(FBin.PlaintextOffset() <= FBin.Size());
340       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
341       p += FBin.PlaintextOffset();
342     }
343
344   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
345   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
346   ui32_t block_size = ct_size - diff;
347   assert((block_size % CBC_BLOCK_SIZE) == 0);
348
349   // encrypt the ciphertext region essence data
350   if ( ASDCP_SUCCESS(result) )
351     {
352       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
353       p += block_size;
354     }
355
356   // construct and encrypt the padding
357   if ( ASDCP_SUCCESS(result) )
358     {
359       byte_t the_last_block[CBC_BLOCK_SIZE];
360
361       if ( diff > 0 )
362         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
363
364       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
365         the_last_block[diff] = i;
366
367       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
368     }
369
370   if ( ASDCP_SUCCESS(result) )
371     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
372
373   return result;
374 }
375
376 //
377 Result_t
378 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
379 {
380   ASDCP_TEST_NULL(Ctx);
381   assert(FBout.Capacity() >= FBin.SourceLength());
382
383   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
384   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
385   ui32_t block_size = ct_size - diff;
386   assert(block_size);
387   assert((block_size % CBC_BLOCK_SIZE) == 0);
388
389   const byte_t* buf = FBin.RoData();
390
391   // get ivec
392   Ctx->SetIVec(buf);
393   buf += CBC_BLOCK_SIZE;
394
395   // decrypt and test check value
396   byte_t CheckValue[CBC_BLOCK_SIZE];
397   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
398   buf += CBC_BLOCK_SIZE;
399
400   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
401     return RESULT_CHECKFAIL;
402
403   // copy plaintext region
404   if ( FBin.PlaintextOffset() > 0 )
405     {
406       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
407       buf += FBin.PlaintextOffset();
408     }
409
410   // decrypt all but last block
411   if ( ASDCP_SUCCESS(result) )
412     {
413       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
414       buf += block_size;
415     }
416
417   // decrypt last block
418   if ( ASDCP_SUCCESS(result) )
419     {
420       byte_t the_last_block[CBC_BLOCK_SIZE];
421       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
422
423       if ( the_last_block[diff] != 0 )
424         {
425           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
426           return RESULT_FORMAT;
427         }
428
429       if ( diff > 0 )
430         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
431     }
432
433   if ( ASDCP_SUCCESS(result) )
434     FBout.Size(FBin.SourceLength());
435
436   return result;
437 }
438
439
440 //
441 Result_t
442 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
443                                  ui32_t sequence, HMACContext* HMAC)
444 {
445   ASDCP_TEST_NULL(AssetID);
446   ASDCP_TEST_NULL(HMAC);
447   byte_t* p = Data;
448   HMAC->Reset();
449
450   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
451
452   // update HMAC with essence data
453   HMAC->Update(FB.RoData(), FB.Size());
454
455   // track file ID length
456   memcpy(p, ber_4, MXF_BER_LENGTH);
457   *(p+3) = UUIDlen;;
458   p += MXF_BER_LENGTH;
459
460   // track file ID
461   memcpy(p, AssetID, UUIDlen);
462   p += UUIDlen;
463
464   // sequence length
465   memcpy(p, ber_4, MXF_BER_LENGTH);
466   *(p+3) = sizeof(ui64_t);
467   p += MXF_BER_LENGTH;
468
469   // sequence number
470   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
471   p += sizeof(ui64_t);
472
473   // HMAC length
474   memcpy(p, ber_4, MXF_BER_LENGTH);
475   *(p+3) = HMAC_SIZE;
476   p += MXF_BER_LENGTH;
477
478   // update HMAC with intpack values
479   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
480
481   // finish & write HMAC
482   HMAC->Finalize();
483   HMAC->GetHMACValue(p);
484
485   assert(p + HMAC_SIZE == Data + klv_intpack_size);
486
487   return RESULT_OK;
488 }
489
490
491 Result_t
492 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
493                                  ui32_t sequence, HMACContext* HMAC)
494 {
495   ASDCP_TEST_NULL(AssetID);
496   ASDCP_TEST_NULL(HMAC);
497
498   // find the start of the intpack
499   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
500
501   // test the AssetID length
502   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
503         return RESULT_HMACFAIL;
504
505   // test the AssetID
506   if ( memcmp(p, AssetID, UUIDlen) != 0 )
507     {
508       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
509       return RESULT_HMACFAIL;
510     }
511   p += UUIDlen;
512   
513   // test the sequence length
514   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
515         return RESULT_HMACFAIL;
516
517   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
518
519   // test the sequence value
520   if ( test_sequence != sequence )
521     {
522       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
523       return RESULT_HMACFAIL;
524     }
525
526   p += sizeof(ui64_t);
527
528   // test the HMAC length
529   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
530         return RESULT_HMACFAIL;
531
532   // test the HMAC
533   HMAC->Reset();
534   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
535   HMAC->Finalize();
536
537   return HMAC->TestHMACValue(p);
538 }
539
540 //
541 // end AS_DCP_MXF.cpp
542 //