2 Copyright (c) 2004-2013, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file AS_DCP_MXF.cpp
29 \brief AS-DCP library, misc classes and subroutines
32 #include <KM_fileio.h>
34 #include "AS_DCP_internal.h"
42 //------------------------------------------------------------------------------------------
48 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
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;
58 if ( Info.EncryptedEssence )
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;
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;
74 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
81 fprintf(stream," ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
86 EncryptedEssence: %s\n",
87 Info.ProductVersion.c_str(),
88 Info.CompanyName.c_str(),
89 Info.ProductName.c_str(),
90 ( Info.EncryptedEssence ? "Yes" : "No" )
93 if ( Info.EncryptedEssence )
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));
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" :
108 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
110 ASDCP_TEST_NULL(InfoObj);
111 char tmp_str[IdentBufferLen];
113 Info.ProductName = "Unknown Product";
114 Info.ProductVersion = "Unknown Version";
115 Info.CompanyName = "Unknown Company";
116 memset(Info.ProductUUID, 0, UUIDlen);
118 InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
119 if ( *tmp_str ) Info.ProductName = tmp_str;
121 InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
122 if ( *tmp_str ) Info.ProductVersion = tmp_str;
124 InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
125 if ( *tmp_str ) Info.CompanyName = tmp_str;
127 memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
135 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
137 ASDCP_TEST_NULL(InfoObj);
139 Info.EncryptedEssence = true;
140 memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
141 memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
143 UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
144 UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
146 if ( InfoObj->MICAlgorithm == MIC_SHA1 )
147 Info.UsesHMAC = true;
149 else if ( InfoObj->MICAlgorithm == MIC_NONE )
150 Info.UsesHMAC = false;
154 DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
155 return RESULT_FORMAT;
164 ASDCP::EssenceType(const std::string& filename, EssenceType_t& type)
166 const Dictionary* m_Dict = &DefaultCompositeDict();
167 InterchangeObject* md_object = 0;
170 Kumu::FileReader Reader;
171 OP1aHeader TestHeader(m_Dict);
173 Result_t result = Reader.OpenRead(filename);
175 if ( ASDCP_SUCCESS(result) )
176 result = TestHeader.InitFromFile(Reader); // test UL and OP
178 if ( ASDCP_SUCCESS(result) )
182 if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OPAtom))
183 || TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_MXFInterop_OPAtom)) )
185 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
187 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
189 type = ESS_JPEG_2000_S;
193 type = ESS_JPEG_2000;
196 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
199 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
201 type = ESS_PCM_24b_96k;
205 type = ESS_PCM_24b_48k;
208 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
210 type = ESS_MPEG2_VES;
212 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
214 type = ESS_TIMED_TEXT;
216 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor))) )
218 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
220 type = ESS_DCDATA_DOLBY_ATMOS;
224 type = ESS_DCDATA_UNKNOWN;
228 else if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
230 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
232 type = ESS_AS02_JPEG_2000;
234 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
237 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
239 type = ESS_AS02_PCM_24b_96k;
243 type = ESS_AS02_PCM_24b_48k;
246 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
248 type = ESS_AS02_TIMED_TEXT;
253 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
254 return RESULT_FORMAT;
263 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
266 ASDCP::FrameBuffer FB;
267 Kumu::FileReader Reader;
268 ASDCP::Wav::SimpleWaveHeader WavHeader;
269 ASDCP::RF64::SimpleRF64Header RF64Header;
270 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
271 Kumu::XMLElement TmpElement("Tmp");
275 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
277 if ( Kumu::PathIsFile(filename) )
279 result = Reader.OpenRead(filename);
281 if ( ASDCP_SUCCESS(result) )
283 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
287 if ( ASDCP_SUCCESS(result) )
289 const byte_t* p = FB.RoData();
293 while ( p[i] == 0 ) i++;
295 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
297 type = ESS_MPEG2_VES;
299 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
301 type = ESS_JPEG_2000;
303 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
305 switch ( WavHeader.samplespersec )
307 case 48000: type = ESS_PCM_24b_48k; break;
308 case 96000: type = ESS_PCM_24b_96k; break;
310 return RESULT_FORMAT;
313 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
315 switch ( RF64Header.samplespersec )
317 case 48000: type = ESS_PCM_24b_48k; break;
318 case 96000: type = ESS_PCM_24b_96k; break;
320 return RESULT_FORMAT;
323 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
325 type = ESS_PCM_24b_48k;
327 else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
329 type = ESS_TIMED_TEXT;
331 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
333 type = ESS_DCDATA_DOLBY_ATMOS;
337 else if ( Kumu::PathIsDirectory(filename) )
339 char next_file[Kumu::MaxFilePath];
340 Kumu::DirScanner Scanner;
341 Result_t result = Scanner.Open(filename);
343 if ( ASDCP_SUCCESS(result) )
345 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
347 if ( next_file[0] == '.' ) // no hidden files or internal links
350 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
352 if ( ASDCP_SUCCESS(result) )
354 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
358 if ( ASDCP_SUCCESS(result) )
360 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
362 type = ESS_JPEG_2000;
364 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
366 switch ( WavHeader.samplespersec )
368 case 48000: type = ESS_PCM_24b_48k; break;
369 case 96000: type = ESS_PCM_24b_96k; break;
371 return RESULT_FORMAT;
374 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
376 switch ( RF64Header.samplespersec )
378 case 48000: type = ESS_PCM_24b_48k; break;
379 case 96000: type = ESS_PCM_24b_96k; break;
381 return RESULT_FORMAT;
384 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
386 type = ESS_DCDATA_DOLBY_ATMOS;
400 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
402 ASDCP_TEST_NULL(Ctx);
406 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
409 byte_t* p = FBout.Data();
411 // write the IV to the frame buffer
416 // encrypt the check value to the frame buffer
417 if ( ASDCP_SUCCESS(result) )
419 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
423 // write optional plaintext region
424 if ( FBin.PlaintextOffset() > 0 )
426 assert(FBin.PlaintextOffset() <= FBin.Size());
427 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
428 p += FBin.PlaintextOffset();
431 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
432 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
433 ui32_t block_size = ct_size - diff;
434 assert((block_size % CBC_BLOCK_SIZE) == 0);
436 // encrypt the ciphertext region essence data
437 if ( ASDCP_SUCCESS(result) )
439 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
443 // construct and encrypt the padding
444 if ( ASDCP_SUCCESS(result) )
446 byte_t the_last_block[CBC_BLOCK_SIZE];
449 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
451 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
452 the_last_block[diff] = i;
454 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
457 if ( ASDCP_SUCCESS(result) )
458 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
465 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
467 ASDCP_TEST_NULL(Ctx);
468 assert(FBout.Capacity() >= FBin.SourceLength());
470 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
471 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
472 ui32_t block_size = ct_size - diff;
474 assert((block_size % CBC_BLOCK_SIZE) == 0);
476 const byte_t* buf = FBin.RoData();
480 buf += CBC_BLOCK_SIZE;
482 // decrypt and test check value
483 byte_t CheckValue[CBC_BLOCK_SIZE];
484 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
485 buf += CBC_BLOCK_SIZE;
487 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
488 return RESULT_CHECKFAIL;
490 // copy plaintext region
491 if ( FBin.PlaintextOffset() > 0 )
493 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
494 buf += FBin.PlaintextOffset();
497 // decrypt all but last block
498 if ( ASDCP_SUCCESS(result) )
500 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
504 // decrypt last block
505 if ( ASDCP_SUCCESS(result) )
507 byte_t the_last_block[CBC_BLOCK_SIZE];
508 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
510 if ( the_last_block[diff] != 0 )
512 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
513 return RESULT_FORMAT;
517 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
520 if ( ASDCP_SUCCESS(result) )
521 FBout.Size(FBin.SourceLength());
529 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
530 ui32_t sequence, HMACContext* HMAC)
532 ASDCP_TEST_NULL(AssetID);
533 ASDCP_TEST_NULL(HMAC);
537 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
539 // update HMAC with essence data
540 HMAC->Update(FB.RoData(), FB.Size());
542 // track file ID length
543 memcpy(p, ber_4, MXF_BER_LENGTH);
548 memcpy(p, AssetID, UUIDlen);
552 memcpy(p, ber_4, MXF_BER_LENGTH);
553 *(p+3) = sizeof(ui64_t);
557 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
561 memcpy(p, ber_4, MXF_BER_LENGTH);
565 // update HMAC with intpack values
566 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
568 // finish & write HMAC
570 HMAC->GetHMACValue(p);
572 assert(p + HMAC_SIZE == Data + klv_intpack_size);
579 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
580 ui32_t sequence, HMACContext* HMAC)
582 ASDCP_TEST_NULL(AssetID);
583 ASDCP_TEST_NULL(HMAC);
585 // find the start of the intpack
586 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
588 // test the AssetID length
589 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
590 return RESULT_HMACFAIL;
593 if ( memcmp(p, AssetID, UUIDlen) != 0 )
595 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
596 return RESULT_HMACFAIL;
600 // test the sequence length
601 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
602 return RESULT_HMACFAIL;
604 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
606 // test the sequence value
607 if ( test_sequence != sequence )
609 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
610 return RESULT_HMACFAIL;
615 // test the HMAC length
616 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
617 return RESULT_HMACFAIL;
621 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
624 return HMAC->TestHMACValue(p);
628 // end AS_DCP_MXF.cpp