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