MPEG parser fixes: zero run-in patch, header buffer increase
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2006, 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 "AS_DCP_internal.h"
34 #include "JP2K.h"
35 #include "MPEG.h"
36 #include "Wav.h"
37
38
39 //------------------------------------------------------------------------------------------
40 // misc subroutines
41
42
43 //
44 void
45 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
46 {
47   if ( stream == 0 )
48     stream = stderr;
49
50   char str_buf[40];
51
52   fprintf(stream,"       ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
53   fprintf(stream,"\
54     ProductVersion: %s\n\
55        CompanyName: %s\n\
56        ProductName: %s\n\
57   EncryptedEssence: %s\n",
58           Info.ProductVersion.c_str(),
59           Info.CompanyName.c_str(),
60           Info.ProductName.c_str(),
61           ( Info.EncryptedEssence ? "Yes" : "No" )
62           );
63
64   if ( Info.EncryptedEssence )
65     {
66       fprintf(stream, "              HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
67       fprintf(stream, "         ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
68       fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
69     }
70
71   fprintf(stream,"         AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
72   fprintf(stream,"    Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
73                                                ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
74                                                  "Unknown" ) ));
75 }
76
77 //
78 Result_t
79 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
80 {
81   ASDCP_TEST_NULL(InfoObj);
82   char tmp_str[IdentBufferLen];
83
84   Info.ProductName = "Unknown Product";
85   Info.ProductVersion = "Unknown Version";
86   Info.CompanyName = "Unknown Company";
87   memset(Info.ProductUUID, 0, UUIDlen);
88
89   InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
90   if ( *tmp_str ) Info.ProductName = tmp_str;
91
92   InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
93   if ( *tmp_str ) Info.ProductVersion = tmp_str;
94
95   InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
96   if ( *tmp_str ) Info.CompanyName = tmp_str;
97
98   memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
99
100   return RESULT_OK;
101 }
102
103
104 //
105 Result_t
106 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info)
107 {
108   ASDCP_TEST_NULL(InfoObj);
109
110   Info.EncryptedEssence = true;
111   memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
112   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
113
114   UL MIC_SHA1(Dict::ul(MDD_MICAlgorithm_HMAC_SHA1));
115   UL MIC_NONE(Dict::ul(MDD_MICAlgorithm_NONE));
116
117   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
118     Info.UsesHMAC = true;
119
120   else if ( InfoObj->MICAlgorithm == MIC_NONE )
121     Info.UsesHMAC = false;
122
123   else
124     {
125       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
126       return RESULT_FORMAT;
127     }
128
129   return RESULT_OK;
130 }
131
132 //
133 //
134 ASDCP::Result_t
135 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
136 {
137   ASDCP_TEST_NULL_STR(filename);
138   Kumu::FileReader   Reader;
139   OPAtomHeader TestHeader;
140
141   Result_t result = Reader.OpenRead(filename);
142
143   if ( ASDCP_SUCCESS(result) )
144     result = TestHeader.InitFromFile(Reader); // test UL and OP
145
146   if ( ASDCP_SUCCESS(result) )
147     {
148       type = ESS_UNKNOWN;
149       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
150         type = ESS_JPEG_2000;
151       else
152         {
153           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
154             type = ESS_PCM_24b_48k;
155           else
156             {
157               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
158                 type = ESS_MPEG2_VES;
159             }
160         }
161     }
162
163   return result;
164 }
165
166 //
167 ASDCP::Result_t
168 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
169 {
170   ASDCP_TEST_NULL_STR(filename);
171   type = ESS_UNKNOWN;
172   ASDCP::FrameBuffer FB;
173   Kumu::FileReader Reader;
174   ASDCP::Wav::SimpleWaveHeader WavHeader;
175   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
176   ui32_t data_offset;
177   ui32_t read_count;
178   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
179
180   if ( Kumu::PathIsFile(filename) )
181     {
182       result = Reader.OpenRead(filename);
183
184       if ( ASDCP_SUCCESS(result) )
185         {
186           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
187           Reader.Close();
188         }
189
190       if ( ASDCP_SUCCESS(result) )
191         {
192           const byte_t* p = FB.RoData();
193           ui32_t i = 0;
194           while ( p[i] == 0 ) i++;
195
196           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
197             type = ESS_MPEG2_VES;
198
199           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
200             type = ESS_PCM_24b_48k;
201
202           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
203             type = ESS_PCM_24b_48k;
204         }
205     }
206   else if ( Kumu::PathIsDirectory(filename) )
207     {
208       char next_file[Kumu::MaxFilePath];
209       Kumu::DirScanner Scanner;
210       Result_t result = Scanner.Open(filename);
211
212       if ( ASDCP_SUCCESS(result) )
213         {
214           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
215             {
216               if ( next_file[0] == '.' ) // no hidden files or internal links
217                 continue;
218
219               std::string Str(filename);
220               Str += "/";
221               Str += next_file;
222               result = Reader.OpenRead(Str.c_str());
223
224               if ( ASDCP_SUCCESS(result) )
225                 {
226                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
227                   Reader.Close();
228                 }
229
230               if ( ASDCP_SUCCESS(result) )
231                 {
232                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
233                     type = ESS_JPEG_2000;
234
235                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
236                     type = ESS_PCM_24b_48k;
237                 }
238
239               break;
240             }
241         }
242     }
243
244   return result;
245 }
246
247 //
248 Result_t
249 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
250 {
251   ASDCP_TEST_NULL(Ctx);
252   FBout.Size(0);
253
254   // size the buffer
255   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
256
257   // write the IV
258   byte_t* p = FBout.Data();
259
260   // write the IV to the frame buffer
261   Ctx->GetIVec(p);
262   p += CBC_BLOCK_SIZE;
263
264
265   // encrypt the check value to the frame buffer
266   if ( ASDCP_SUCCESS(result) )
267     {
268       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
269       p += CBC_BLOCK_SIZE;
270     }
271
272   // write optional plaintext region
273   if ( FBin.PlaintextOffset() > 0 )
274     {
275       assert(FBin.PlaintextOffset() <= FBin.Size());
276       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
277       p += FBin.PlaintextOffset();
278     }
279
280   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
281   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
282   ui32_t block_size = ct_size - diff;
283   assert((block_size % CBC_BLOCK_SIZE) == 0);
284
285   // encrypt the ciphertext region essence data
286   if ( ASDCP_SUCCESS(result) )
287     {
288       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
289       p += block_size;
290     }
291
292   // construct and encrypt the padding
293   if ( ASDCP_SUCCESS(result) )
294     {
295       byte_t the_last_block[CBC_BLOCK_SIZE];
296
297       if ( diff > 0 )
298         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
299
300       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
301         the_last_block[diff] = i;
302
303       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
304     }
305
306   if ( ASDCP_SUCCESS(result) )
307     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
308
309   return result;
310 }
311
312 //
313 Result_t
314 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
315 {
316   ASDCP_TEST_NULL(Ctx);
317   assert(FBout.Capacity() >= FBin.SourceLength());
318
319   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
320   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
321   ui32_t block_size = ct_size - diff;
322   assert(block_size);
323   assert((block_size % CBC_BLOCK_SIZE) == 0);
324
325   const byte_t* buf = FBin.RoData();
326
327   // get ivec
328   Ctx->SetIVec(buf);
329   buf += CBC_BLOCK_SIZE;
330
331   // decrypt and test check value
332   byte_t CheckValue[CBC_BLOCK_SIZE];
333   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
334   buf += CBC_BLOCK_SIZE;
335
336   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
337     return RESULT_CHECKFAIL;
338
339   // copy plaintext region
340   if ( FBin.PlaintextOffset() > 0 )
341     {
342       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
343       buf += FBin.PlaintextOffset();
344     }
345
346   // decrypt all but last block
347   if ( ASDCP_SUCCESS(result) )
348     {
349       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
350       buf += block_size;
351     }
352
353   // decrypt last block
354   if ( ASDCP_SUCCESS(result) )
355     {
356       byte_t the_last_block[CBC_BLOCK_SIZE];
357       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
358
359       if ( the_last_block[diff] != 0 )
360         {
361           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
362           return RESULT_FORMAT;
363         }
364
365       if ( diff > 0 )
366         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
367     }
368
369   if ( ASDCP_SUCCESS(result) )
370     FBout.Size(FBin.SourceLength());
371
372   return result;
373 }
374
375
376 //
377 Result_t
378 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
379                                  ui32_t sequence, HMACContext* HMAC)
380 {
381   ASDCP_TEST_NULL(AssetID);
382   ASDCP_TEST_NULL(HMAC);
383   byte_t* p = Data;
384   HMAC->Reset();
385
386   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0};
387
388   // update HMAC with essence data
389   HMAC->Update(FB.RoData(), FB.Size());
390
391   // track file ID length
392   memcpy(p, ber_4, MXF_BER_LENGTH);
393   *(p+3) = UUIDlen;;
394   p += MXF_BER_LENGTH;
395
396   // track file ID
397   memcpy(p, AssetID, UUIDlen);
398   p += UUIDlen;
399
400   // sequence length
401   memcpy(p, ber_4, MXF_BER_LENGTH);
402   *(p+3) = sizeof(ui64_t);
403   p += MXF_BER_LENGTH;
404
405   // sequence number
406   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
407   p += sizeof(ui64_t);
408
409   // HMAC length
410   memcpy(p, ber_4, MXF_BER_LENGTH);
411   *(p+3) = HMAC_SIZE;
412   p += MXF_BER_LENGTH;
413
414   // update HMAC with intpack values
415   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
416
417   // finish & write HMAC
418   HMAC->Finalize();
419   HMAC->GetHMACValue(p);
420
421   assert(p + HMAC_SIZE == Data + klv_intpack_size);
422
423   return RESULT_OK;
424 }
425
426
427 Result_t
428 ASDCP::IntegrityPack::TestValues(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
434   // find the start of the intpack
435   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
436
437   // test the AssetID length
438   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
439         return RESULT_HMACFAIL;
440
441   // test the AssetID
442   if ( memcmp(p, AssetID, UUIDlen) != 0 )
443     {
444       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
445       return RESULT_HMACFAIL;
446     }
447   p += UUIDlen;
448   
449   // test the sequence length
450   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
451         return RESULT_HMACFAIL;
452
453   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
454
455   // test the sequence value
456   if ( test_sequence != sequence )
457     {
458       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
459       return RESULT_HMACFAIL;
460     }
461
462   p += sizeof(ui64_t);
463
464   // test the HMAC length
465   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
466         return RESULT_HMACFAIL;
467
468   // test the HMAC
469   HMAC->Reset();
470   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
471   HMAC->Finalize();
472
473   return HMAC->TestHMACValue(p);
474 }
475
476 //
477 // end AS_DCP_MXF.cpp
478 //