the big-pre-as-02-refactor
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2013, 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   OP1aHeader 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       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor))) )
195       {
196         if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
197           type = ESS_DCDATA_DOLBY_ATMOS;
198         else
199           type = ESS_DCDATA_UNKNOWN;
200       }
201     }
202
203   return result;
204 }
205
206 //
207 ASDCP::Result_t
208 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
209 {
210   ASDCP_TEST_NULL_STR(filename);
211   type = ESS_UNKNOWN;
212   ASDCP::FrameBuffer FB;
213   Kumu::FileReader Reader;
214   ASDCP::Wav::SimpleWaveHeader WavHeader;
215   ASDCP::RF64::SimpleRF64Header RF64Header;
216   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
217   Kumu::XMLElement TmpElement("Tmp");
218
219   ui32_t data_offset;
220   ui32_t read_count;
221   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
222
223   if ( Kumu::PathIsFile(filename) )
224     {
225       result = Reader.OpenRead(filename);
226
227       if ( ASDCP_SUCCESS(result) )
228         {
229           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
230           Reader.Close();
231         }
232
233       if ( ASDCP_SUCCESS(result) )
234         {
235           const byte_t* p = FB.RoData();
236           FB.Size(read_count);
237
238           ui32_t i = 0;
239           while ( p[i] == 0 ) i++;
240
241           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
242             {
243               type = ESS_MPEG2_VES;
244             }
245           else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
246             {
247               type = ESS_JPEG_2000;
248             }
249           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
250             {
251               switch ( WavHeader.samplespersec )
252                 {
253                 case 48000: type = ESS_PCM_24b_48k; break;
254                 case 96000: type = ESS_PCM_24b_96k; break;
255                 default:
256                   return RESULT_FORMAT;
257                 }
258             }
259           else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
260             {
261               switch ( RF64Header.samplespersec )
262                 {
263                 case 48000: type = ESS_PCM_24b_48k; break;
264                 case 96000: type = ESS_PCM_24b_96k; break;
265                 default:
266                   return RESULT_FORMAT;
267                 }
268             }
269           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
270             {
271               type = ESS_PCM_24b_48k;
272             }
273           else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
274             {
275               type = ESS_TIMED_TEXT;
276             }
277           else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
278             {
279               type = ESS_DCDATA_DOLBY_ATMOS;
280             }
281         }
282     }
283   else if ( Kumu::PathIsDirectory(filename) )
284     {
285       char next_file[Kumu::MaxFilePath];
286       Kumu::DirScanner Scanner;
287       Result_t result = Scanner.Open(filename);
288
289       if ( ASDCP_SUCCESS(result) )
290         {
291           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
292             {
293               if ( next_file[0] == '.' ) // no hidden files or internal links
294                 continue;
295
296               std::string Str(filename);
297               Str += "/";
298               Str += next_file;
299               result = Reader.OpenRead(Str.c_str());
300
301               if ( ASDCP_SUCCESS(result) )
302                 {
303                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
304                   Reader.Close();
305                 }
306
307               if ( ASDCP_SUCCESS(result) )
308                 {
309                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
310                     {
311                       type = ESS_JPEG_2000;
312                     }
313                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
314                     {
315                       switch ( WavHeader.samplespersec )
316                         {
317                         case 48000: type = ESS_PCM_24b_48k; break;
318                         case 96000: type = ESS_PCM_24b_96k; break;
319                         default:
320                           return RESULT_FORMAT;
321                         }
322                     }
323                   else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
324                     {
325                       switch ( RF64Header.samplespersec )
326                         {
327                         case 48000: type = ESS_PCM_24b_48k; break;
328                         case 96000: type = ESS_PCM_24b_96k; break;
329                         default:
330                           return RESULT_FORMAT;
331                         }
332                     }
333                   else if ( ASDCP::ATMOS::IsDolbyAtmos(Str.c_str()) )
334                     {
335                       type = ESS_DCDATA_DOLBY_ATMOS;
336                     }
337                 }
338               
339               break;
340             }
341         }
342     }
343
344   return result;
345 }
346
347 //
348 Result_t
349 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
350 {
351   ASDCP_TEST_NULL(Ctx);
352   FBout.Size(0);
353
354   // size the buffer
355   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
356
357   // write the IV
358   byte_t* p = FBout.Data();
359
360   // write the IV to the frame buffer
361   Ctx->GetIVec(p);
362   p += CBC_BLOCK_SIZE;
363
364
365   // encrypt the check value to the frame buffer
366   if ( ASDCP_SUCCESS(result) )
367     {
368       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
369       p += CBC_BLOCK_SIZE;
370     }
371
372   // write optional plaintext region
373   if ( FBin.PlaintextOffset() > 0 )
374     {
375       assert(FBin.PlaintextOffset() <= FBin.Size());
376       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
377       p += FBin.PlaintextOffset();
378     }
379
380   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
381   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
382   ui32_t block_size = ct_size - diff;
383   assert((block_size % CBC_BLOCK_SIZE) == 0);
384
385   // encrypt the ciphertext region essence data
386   if ( ASDCP_SUCCESS(result) )
387     {
388       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
389       p += block_size;
390     }
391
392   // construct and encrypt the padding
393   if ( ASDCP_SUCCESS(result) )
394     {
395       byte_t the_last_block[CBC_BLOCK_SIZE];
396
397       if ( diff > 0 )
398         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
399
400       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
401         the_last_block[diff] = i;
402
403       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
404     }
405
406   if ( ASDCP_SUCCESS(result) )
407     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
408
409   return result;
410 }
411
412 //
413 Result_t
414 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
415 {
416   ASDCP_TEST_NULL(Ctx);
417   assert(FBout.Capacity() >= FBin.SourceLength());
418
419   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
420   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
421   ui32_t block_size = ct_size - diff;
422   assert(block_size);
423   assert((block_size % CBC_BLOCK_SIZE) == 0);
424
425   const byte_t* buf = FBin.RoData();
426
427   // get ivec
428   Ctx->SetIVec(buf);
429   buf += CBC_BLOCK_SIZE;
430
431   // decrypt and test check value
432   byte_t CheckValue[CBC_BLOCK_SIZE];
433   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
434   buf += CBC_BLOCK_SIZE;
435
436   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
437     return RESULT_CHECKFAIL;
438
439   // copy plaintext region
440   if ( FBin.PlaintextOffset() > 0 )
441     {
442       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
443       buf += FBin.PlaintextOffset();
444     }
445
446   // decrypt all but last block
447   if ( ASDCP_SUCCESS(result) )
448     {
449       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
450       buf += block_size;
451     }
452
453   // decrypt last block
454   if ( ASDCP_SUCCESS(result) )
455     {
456       byte_t the_last_block[CBC_BLOCK_SIZE];
457       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
458
459       if ( the_last_block[diff] != 0 )
460         {
461           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
462           return RESULT_FORMAT;
463         }
464
465       if ( diff > 0 )
466         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
467     }
468
469   if ( ASDCP_SUCCESS(result) )
470     FBout.Size(FBin.SourceLength());
471
472   return result;
473 }
474
475
476 //
477 Result_t
478 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
479                                  ui32_t sequence, HMACContext* HMAC)
480 {
481   ASDCP_TEST_NULL(AssetID);
482   ASDCP_TEST_NULL(HMAC);
483   byte_t* p = Data;
484   HMAC->Reset();
485
486   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
487
488   // update HMAC with essence data
489   HMAC->Update(FB.RoData(), FB.Size());
490
491   // track file ID length
492   memcpy(p, ber_4, MXF_BER_LENGTH);
493   *(p+3) = UUIDlen;;
494   p += MXF_BER_LENGTH;
495
496   // track file ID
497   memcpy(p, AssetID, UUIDlen);
498   p += UUIDlen;
499
500   // sequence length
501   memcpy(p, ber_4, MXF_BER_LENGTH);
502   *(p+3) = sizeof(ui64_t);
503   p += MXF_BER_LENGTH;
504
505   // sequence number
506   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
507   p += sizeof(ui64_t);
508
509   // HMAC length
510   memcpy(p, ber_4, MXF_BER_LENGTH);
511   *(p+3) = HMAC_SIZE;
512   p += MXF_BER_LENGTH;
513
514   // update HMAC with intpack values
515   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
516
517   // finish & write HMAC
518   HMAC->Finalize();
519   HMAC->GetHMACValue(p);
520
521   assert(p + HMAC_SIZE == Data + klv_intpack_size);
522
523   return RESULT_OK;
524 }
525
526
527 Result_t
528 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
529                                  ui32_t sequence, HMACContext* HMAC)
530 {
531   ASDCP_TEST_NULL(AssetID);
532   ASDCP_TEST_NULL(HMAC);
533
534   // find the start of the intpack
535   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
536
537   // test the AssetID length
538   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
539         return RESULT_HMACFAIL;
540
541   // test the AssetID
542   if ( memcmp(p, AssetID, UUIDlen) != 0 )
543     {
544       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
545       return RESULT_HMACFAIL;
546     }
547   p += UUIDlen;
548   
549   // test the sequence length
550   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
551         return RESULT_HMACFAIL;
552
553   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
554
555   // test the sequence value
556   if ( test_sequence != sequence )
557     {
558       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
559       return RESULT_HMACFAIL;
560     }
561
562   p += sizeof(ui64_t);
563
564   // test the HMAC length
565   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
566         return RESULT_HMACFAIL;
567
568   // test the HMAC
569   HMAC->Reset();
570   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
571   HMAC->Finalize();
572
573   return HMAC->TestHMACValue(p);
574 }
575
576 //
577 // end AS_DCP_MXF.cpp
578 //