2 Copyright (c) 2004-2016, 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"
44 //------------------------------------------------------------------------------------------
50 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
54 strm << " ProductUUID: " << UUID(Info.ProductUUID).EncodeHex(str_buf, 40) << std::endl;
55 strm << " ProductVersion: " << Info.ProductVersion << std::endl;
56 strm << " CompanyName: " << Info.CompanyName << std::endl;
57 strm << " ProductName: " << Info.ProductName << std::endl;
58 strm << " EncryptedEssence: " << (Info.EncryptedEssence ? "Yes" : "No") << std::endl;
60 if ( Info.EncryptedEssence )
62 strm << " HMAC: " << (Info.UsesHMAC ? "Yes" : "No") << std::endl;
63 strm << " ContextID: " << UUID(Info.ContextID).EncodeHex(str_buf, 40) << std::endl;
64 strm << "CryptographicKeyID: " << UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40) << std::endl;
67 strm << " AssetUUID: " << UUID(Info.AssetUUID).EncodeHex(str_buf, 40) << std::endl;
68 strm << " Label Set Type: " << (Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
69 (Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
70 "Unknown")) << std::endl;
76 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
83 fprintf(stream," ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
88 EncryptedEssence: %s\n",
89 Info.ProductVersion.c_str(),
90 Info.CompanyName.c_str(),
91 Info.ProductName.c_str(),
92 ( Info.EncryptedEssence ? "Yes" : "No" )
95 if ( Info.EncryptedEssence )
97 fprintf(stream, " HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
98 fprintf(stream, " ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
99 fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
102 fprintf(stream," AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
103 fprintf(stream," Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
104 ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
110 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
112 ASDCP_TEST_NULL(InfoObj);
113 char tmp_str[IdentBufferLen];
115 Info.ProductName = "Unknown Product";
116 Info.ProductVersion = "Unknown Version";
117 Info.CompanyName = "Unknown Company";
118 memset(Info.ProductUUID, 0, UUIDlen);
120 InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
121 if ( *tmp_str ) Info.ProductName = tmp_str;
123 InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
124 if ( *tmp_str ) Info.ProductVersion = tmp_str;
126 InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
127 if ( *tmp_str ) Info.CompanyName = tmp_str;
129 memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
137 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
139 ASDCP_TEST_NULL(InfoObj);
141 Info.EncryptedEssence = true;
142 memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
143 memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
145 UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
146 UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
148 if ( InfoObj->MICAlgorithm == MIC_SHA1 )
149 Info.UsesHMAC = true;
151 else if ( InfoObj->MICAlgorithm == MIC_NONE )
152 Info.UsesHMAC = false;
156 DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
157 return RESULT_FORMAT;
166 ASDCP::EssenceType(const std::string& filename, EssenceType_t& type)
168 const Dictionary* m_Dict = &DefaultCompositeDict();
169 InterchangeObject* md_object = 0;
172 Kumu::FileReader Reader;
173 OP1aHeader TestHeader(m_Dict);
175 Result_t result = Reader.OpenRead(filename);
177 if ( ASDCP_SUCCESS(result) )
178 result = TestHeader.InitFromFile(Reader); // test UL and OP
180 if ( ASDCP_SUCCESS(result) )
184 if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OPAtom))
185 || TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_MXFInterop_OPAtom)) )
187 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
189 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
191 type = ESS_JPEG_2000_S;
195 type = ESS_JPEG_2000;
198 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
201 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
203 type = ESS_PCM_24b_96k;
207 type = ESS_PCM_24b_48k;
210 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
212 type = ESS_MPEG2_VES;
214 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
216 type = ESS_TIMED_TEXT;
218 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor)))
219 || ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PrivateDCDataDescriptor))) )
221 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
223 type = ESS_DCDATA_DOLBY_ATMOS;
227 type = ESS_DCDATA_UNKNOWN;
231 else if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
233 // ST 2065-5 Picture Descriptor does not have a mandatory SubDescriptor, check EssenceContainer instead
234 if (ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
236 MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
239 if ASDCP_SUCCESS(TestHeader.GetMDObjectByType(m_Dict->ul(MDD_RGBAEssenceDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor)))
241 if (rgba_descriptor->EssenceContainer == m_Dict->ul(MDD_MXFGCFrameWrappedACESPictures))
242 type = ESS_AS02_ACES;
245 if (type == ESS_UNKNOWN)
248 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
250 type = ESS_AS02_JPEG_2000;
252 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
255 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
257 type = ESS_AS02_PCM_24b_96k;
261 type = ESS_AS02_PCM_24b_48k;
264 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
266 type = ESS_AS02_TIMED_TEXT;
268 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PIMFDynamicMetadataDescriptor))) )
270 type = ESS_DCDATA_UNKNOWN;
272 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ISXDDataEssenceDescriptor))) )
274 type = ESS_AS02_ISXD;
276 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ACESPictureSubDescriptor))) )
278 type = ESS_AS02_ACES;
284 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
285 return RESULT_FORMAT;
294 string_is_xml(const ASDCP::FrameBuffer& buffer)
296 return (strncmp((const char *)buffer.RoData(), "<?xml", 5) == 0 ||
297 strncmp((const char *)buffer.RoData(), "\xEF\xBB\xBF<?xml", 8) == 0); // Allow BOM
304 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
307 ASDCP::FrameBuffer FB;
308 Kumu::FileReader Reader;
309 ASDCP::Wav::SimpleWaveHeader WavHeader;
310 ASDCP::RF64::SimpleRF64Header RF64Header;
311 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
312 Kumu::XMLElement TmpElement("Tmp");
316 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
318 if ( Kumu::PathIsFile(filename) )
320 result = Reader.OpenRead(filename);
322 if ( ASDCP_SUCCESS(result) )
324 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
328 if ( ASDCP_SUCCESS(result) )
330 const byte_t* p = FB.RoData();
334 while ( p[i] == 0 ) i++;
336 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
338 type = ESS_MPEG2_VES;
340 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
342 type = ESS_JPEG_2000;
344 else if(memcmp(FB.RoData(), AS_02::ACES::Magic, sizeof(AS_02::ACES::Magic)) == 0)
346 type = ESS_AS02_ACES;
348 else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
350 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
352 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
354 if ( ASDCP_SUCCESS(result) )
356 switch ( WavHeader.samplespersec )
358 case 48000: type = ESS_PCM_24b_48k; break;
359 case 96000: type = ESS_PCM_24b_96k; break;
361 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
362 result = RESULT_FORMAT;
368 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
370 if ( ASDCP_SUCCESS(result) )
372 switch ( RF64Header.samplespersec )
374 case 48000: type = ESS_PCM_24b_48k; break;
375 case 96000: type = ESS_PCM_24b_96k; break;
377 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
378 result = RESULT_FORMAT;
383 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
385 type = ESS_PCM_24b_48k;
387 else if ( string_is_xml(FB) )
389 type = ESS_TIMED_TEXT;
391 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
393 type = ESS_DCDATA_DOLBY_ATMOS;
397 else if ( Kumu::PathIsDirectory(filename) )
399 char next_file[Kumu::MaxFilePath];
400 Kumu::DirScanner Scanner;
401 Result_t result = Scanner.Open(filename);
403 if ( ASDCP_SUCCESS(result) )
405 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
407 if ( next_file[0] == '.' ) // no hidden files or internal links
410 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
412 if ( ASDCP_SUCCESS(result) )
414 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
418 if ( ASDCP_SUCCESS(result) )
420 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
422 type = ESS_JPEG_2000;
424 else if(memcmp(FB.RoData(), AS_02::ACES::Magic, sizeof(AS_02::ACES::Magic)) == 0)
426 type = ESS_AS02_ACES;
428 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
430 switch ( WavHeader.samplespersec )
432 case 48000: type = ESS_PCM_24b_48k; break;
433 case 96000: type = ESS_PCM_24b_96k; break;
435 return RESULT_FORMAT;
438 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
440 switch ( RF64Header.samplespersec )
442 case 48000: type = ESS_PCM_24b_48k; break;
443 case 96000: type = ESS_PCM_24b_96k; break;
445 return RESULT_FORMAT;
448 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
450 type = ESS_DCDATA_DOLBY_ATMOS;
454 type = ESS_DCDATA_UNKNOWN;
468 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
470 ASDCP_TEST_NULL(Ctx);
474 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
477 byte_t* p = FBout.Data();
479 // write the IV to the frame buffer
484 // encrypt the check value to the frame buffer
485 if ( ASDCP_SUCCESS(result) )
487 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
491 // write optional plaintext region
492 if ( FBin.PlaintextOffset() > 0 )
494 assert(FBin.PlaintextOffset() <= FBin.Size());
495 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
496 p += FBin.PlaintextOffset();
499 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
500 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
501 ui32_t block_size = ct_size - diff;
502 assert((block_size % CBC_BLOCK_SIZE) == 0);
504 // encrypt the ciphertext region essence data
505 if ( ASDCP_SUCCESS(result) )
507 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
511 // construct and encrypt the padding
512 if ( ASDCP_SUCCESS(result) )
514 byte_t the_last_block[CBC_BLOCK_SIZE];
517 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
519 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
520 the_last_block[diff] = i;
522 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
525 if ( ASDCP_SUCCESS(result) )
526 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
533 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
535 ASDCP_TEST_NULL(Ctx);
536 assert(FBout.Capacity() >= FBin.SourceLength());
538 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
539 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
540 ui32_t block_size = ct_size - diff;
542 assert((block_size % CBC_BLOCK_SIZE) == 0);
544 const byte_t* buf = FBin.RoData();
548 buf += CBC_BLOCK_SIZE;
550 // decrypt and test check value
551 byte_t CheckValue[CBC_BLOCK_SIZE];
552 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
553 buf += CBC_BLOCK_SIZE;
555 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
556 return RESULT_CHECKFAIL;
558 // copy plaintext region
559 if ( FBin.PlaintextOffset() > 0 )
561 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
562 buf += FBin.PlaintextOffset();
565 // decrypt all but last block
566 if ( ASDCP_SUCCESS(result) )
568 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
572 // decrypt last block
573 if ( ASDCP_SUCCESS(result) )
575 byte_t the_last_block[CBC_BLOCK_SIZE];
576 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
578 if ( the_last_block[diff] != 0 )
580 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
581 return RESULT_FORMAT;
585 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
588 if ( ASDCP_SUCCESS(result) )
589 FBout.Size(FBin.SourceLength());
597 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
598 ui32_t sequence, HMACContext* HMAC)
600 ASDCP_TEST_NULL(AssetID);
601 ASDCP_TEST_NULL(HMAC);
605 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
607 // update HMAC with essence data
608 HMAC->Update(FB.RoData(), FB.Size());
610 // track file ID length
611 memcpy(p, ber_4, MXF_BER_LENGTH);
616 memcpy(p, AssetID, UUIDlen);
620 memcpy(p, ber_4, MXF_BER_LENGTH);
621 *(p+3) = sizeof(ui64_t);
625 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
629 memcpy(p, ber_4, MXF_BER_LENGTH);
633 // update HMAC with intpack values
634 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
636 // finish & write HMAC
638 HMAC->GetHMACValue(p);
640 assert(p + HMAC_SIZE == Data + klv_intpack_size);
647 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
648 ui32_t sequence, HMACContext* HMAC)
650 ASDCP_TEST_NULL(AssetID);
651 ASDCP_TEST_NULL(HMAC);
653 // find the start of the intpack
654 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
656 // test the AssetID length
657 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
658 return RESULT_HMACFAIL;
661 if ( memcmp(p, AssetID, UUIDlen) != 0 )
663 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
664 return RESULT_HMACFAIL;
668 // test the sequence length
669 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
670 return RESULT_HMACFAIL;
672 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
674 // test the sequence value
675 if ( test_sequence != sequence )
677 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
678 return RESULT_HMACFAIL;
683 // test the HMAC length
684 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
685 return RESULT_HMACFAIL;
689 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
692 Result_t result = RESULT_OK;
693 result = HMAC->TestHMACValue(p);
695 if (KM_FAILURE(result))
697 Result_t r = RESULT_OK;
698 char hmac_str[HMAC_SIZE*10];
699 char found_str[HMAC_SIZE*10];
700 byte_t hmac_buf[HMAC_SIZE];
703 Kumu::bin2hex(p, HMAC_SIZE, found_str, HMAC_SIZE*10);
705 r = HMAC->GetHMACValue(hmac_buf);
706 if ( KM_SUCCESS( r ) )
708 Kumu::bin2hex(hmac_buf, HMAC_SIZE, hmac_str, HMAC_SIZE*10);
712 snprintf(hmac_str, HMAC_SIZE*10, " - read error - ");
715 DefaultLogSink().Error("IntegrityPack failure: HMAC is %s, expecting %s.\n", found_str, hmac_str);
722 // end AS_DCP_MXF.cpp