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