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