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"
43 //------------------------------------------------------------------------------------------
49 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
53 strm << " ProductUUID: " << UUID(Info.ProductUUID).EncodeHex(str_buf, 40) << std::endl;
54 strm << " ProductVersion: " << Info.ProductVersion << std::endl;
55 strm << " CompanyName: " << Info.CompanyName << std::endl;
56 strm << " ProductName: " << Info.ProductName << std::endl;
57 strm << " EncryptedEssence: " << (Info.EncryptedEssence ? "Yes" : "No") << std::endl;
59 if ( Info.EncryptedEssence )
61 strm << " HMAC: " << (Info.UsesHMAC ? "Yes" : "No") << std::endl;
62 strm << " ContextID: " << UUID(Info.ContextID).EncodeHex(str_buf, 40) << std::endl;
63 strm << "CryptographicKeyID: " << UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40) << std::endl;
66 strm << " AssetUUID: " << UUID(Info.AssetUUID).EncodeHex(str_buf, 40) << std::endl;
67 strm << " Label Set Type: " << (Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
68 (Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
69 "Unknown")) << std::endl;
75 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
82 fprintf(stream," ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
87 EncryptedEssence: %s\n",
88 Info.ProductVersion.c_str(),
89 Info.CompanyName.c_str(),
90 Info.ProductName.c_str(),
91 ( Info.EncryptedEssence ? "Yes" : "No" )
94 if ( Info.EncryptedEssence )
96 fprintf(stream, " HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
97 fprintf(stream, " ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
98 fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
101 fprintf(stream," AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
102 fprintf(stream," Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
103 ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
109 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
111 ASDCP_TEST_NULL(InfoObj);
112 char tmp_str[IdentBufferLen];
114 Info.ProductName = "Unknown Product";
115 Info.ProductVersion = "Unknown Version";
116 Info.CompanyName = "Unknown Company";
117 memset(Info.ProductUUID, 0, UUIDlen);
119 InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
120 if ( *tmp_str ) Info.ProductName = tmp_str;
122 InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
123 if ( *tmp_str ) Info.ProductVersion = tmp_str;
125 InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
126 if ( *tmp_str ) Info.CompanyName = tmp_str;
128 memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
136 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
138 ASDCP_TEST_NULL(InfoObj);
140 Info.EncryptedEssence = true;
141 memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
142 memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
144 UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
145 UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
147 if ( InfoObj->MICAlgorithm == MIC_SHA1 )
148 Info.UsesHMAC = true;
150 else if ( InfoObj->MICAlgorithm == MIC_NONE )
151 Info.UsesHMAC = false;
155 DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
156 return RESULT_FORMAT;
165 ASDCP::EssenceType(const std::string& filename, EssenceType_t& type)
167 const Dictionary* m_Dict = &DefaultCompositeDict();
168 InterchangeObject* md_object = 0;
171 Kumu::FileReader Reader;
172 OP1aHeader TestHeader(m_Dict);
174 Result_t result = Reader.OpenRead(filename);
176 if ( ASDCP_SUCCESS(result) )
177 result = TestHeader.InitFromFile(Reader); // test UL and OP
179 if ( ASDCP_SUCCESS(result) )
183 if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OPAtom))
184 || TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_MXFInterop_OPAtom)) )
186 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
188 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
190 type = ESS_JPEG_2000_S;
194 type = ESS_JPEG_2000;
197 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
200 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
202 type = ESS_PCM_24b_96k;
206 type = ESS_PCM_24b_48k;
209 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
211 type = ESS_MPEG2_VES;
213 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
215 type = ESS_TIMED_TEXT;
217 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor)))
218 || ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PrivateDCDataDescriptor))) )
220 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
222 type = ESS_DCDATA_DOLBY_ATMOS;
226 type = ESS_DCDATA_UNKNOWN;
230 else if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
232 // ST 2065-5 Picture Descriptor does not have a mandatory SubDescriptor, check EssenceContainer instead
233 if (ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
235 MXF::RGBAEssenceDescriptor *rgba_descriptor = 0;
238 if ASDCP_SUCCESS(TestHeader.GetMDObjectByType(m_Dict->ul(MDD_RGBAEssenceDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&rgba_descriptor)))
240 if (rgba_descriptor->EssenceContainer == m_Dict->ul(MDD_MXFGCFrameWrappedACESPictures))
241 type = ESS_AS02_ACES;
244 if (type == ESS_UNKNOWN)
247 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
249 type = ESS_AS02_JPEG_2000;
251 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
254 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
256 type = ESS_AS02_PCM_24b_96k;
260 type = ESS_AS02_PCM_24b_48k;
263 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
265 type = ESS_AS02_TIMED_TEXT;
267 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PIMFDynamicMetadataDescriptor))) )
269 type = ESS_DCDATA_UNKNOWN;
271 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ISXDDataEssenceDescriptor))) )
273 type = ESS_AS02_ISXD;
275 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ACESPictureSubDescriptor))) )
277 type = ESS_AS02_ACES;
283 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
284 return RESULT_FORMAT;
293 string_is_xml(const ASDCP::FrameBuffer& buffer)
295 return (strncmp((const char *)buffer.RoData(), "<?xml", 5) == 0 ||
296 strncmp((const char *)buffer.RoData(), "\xEF\xBB\xBF<?xml", 8) == 0); // Allow BOM
303 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
306 ASDCP::FrameBuffer FB;
307 Kumu::FileReader Reader;
308 ASDCP::Wav::SimpleWaveHeader WavHeader;
309 ASDCP::RF64::SimpleRF64Header RF64Header;
310 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
311 Kumu::XMLElement TmpElement("Tmp");
315 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
317 if ( Kumu::PathIsFile(filename) )
319 result = Reader.OpenRead(filename);
321 if ( ASDCP_SUCCESS(result) )
323 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
327 if ( ASDCP_SUCCESS(result) )
329 const byte_t* p = FB.RoData();
333 while ( p[i] == 0 ) i++;
335 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
337 type = ESS_MPEG2_VES;
339 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
341 type = ESS_JPEG_2000;
343 else if(memcmp(FB.RoData(), AS_02::ACES::Magic, sizeof(AS_02::ACES::Magic)) == 0)
345 type = ESS_AS02_ACES;
347 else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
349 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
351 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
353 if ( ASDCP_SUCCESS(result) )
355 switch ( WavHeader.samplespersec )
357 case 48000: type = ESS_PCM_24b_48k; break;
358 case 96000: type = ESS_PCM_24b_96k; break;
360 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
361 result = RESULT_FORMAT;
367 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
369 if ( ASDCP_SUCCESS(result) )
371 switch ( RF64Header.samplespersec )
373 case 48000: type = ESS_PCM_24b_48k; break;
374 case 96000: type = ESS_PCM_24b_96k; break;
376 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
377 result = RESULT_FORMAT;
382 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
384 type = ESS_PCM_24b_48k;
386 else if ( string_is_xml(FB) )
388 type = ESS_TIMED_TEXT;
390 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
392 type = ESS_DCDATA_DOLBY_ATMOS;
396 else if ( Kumu::PathIsDirectory(filename) )
398 char next_file[Kumu::MaxFilePath];
399 Kumu::DirScanner Scanner;
400 Result_t result = Scanner.Open(filename);
402 if ( ASDCP_SUCCESS(result) )
404 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
406 if ( next_file[0] == '.' ) // no hidden files or internal links
409 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
411 if ( ASDCP_SUCCESS(result) )
413 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
417 if ( ASDCP_SUCCESS(result) )
419 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
421 type = ESS_JPEG_2000;
423 else if(memcmp(FB.RoData(), AS_02::ACES::Magic, sizeof(AS_02::ACES::Magic)) == 0)
425 type = ESS_AS02_ACES;
427 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
429 switch ( WavHeader.samplespersec )
431 case 48000: type = ESS_PCM_24b_48k; break;
432 case 96000: type = ESS_PCM_24b_96k; break;
434 return RESULT_FORMAT;
437 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
439 switch ( RF64Header.samplespersec )
441 case 48000: type = ESS_PCM_24b_48k; break;
442 case 96000: type = ESS_PCM_24b_96k; break;
444 return RESULT_FORMAT;
447 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
449 type = ESS_DCDATA_DOLBY_ATMOS;
453 type = ESS_DCDATA_UNKNOWN;
467 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
469 ASDCP_TEST_NULL(Ctx);
473 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
476 byte_t* p = FBout.Data();
478 // write the IV to the frame buffer
483 // encrypt the check value to the frame buffer
484 if ( ASDCP_SUCCESS(result) )
486 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
490 // write optional plaintext region
491 if ( FBin.PlaintextOffset() > 0 )
493 assert(FBin.PlaintextOffset() <= FBin.Size());
494 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
495 p += FBin.PlaintextOffset();
498 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
499 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
500 ui32_t block_size = ct_size - diff;
501 assert((block_size % CBC_BLOCK_SIZE) == 0);
503 // encrypt the ciphertext region essence data
504 if ( ASDCP_SUCCESS(result) )
506 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
510 // construct and encrypt the padding
511 if ( ASDCP_SUCCESS(result) )
513 byte_t the_last_block[CBC_BLOCK_SIZE];
516 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
518 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
519 the_last_block[diff] = i;
521 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
524 if ( ASDCP_SUCCESS(result) )
525 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
532 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
534 ASDCP_TEST_NULL(Ctx);
535 assert(FBout.Capacity() >= FBin.SourceLength());
537 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
538 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
539 ui32_t block_size = ct_size - diff;
541 assert((block_size % CBC_BLOCK_SIZE) == 0);
543 const byte_t* buf = FBin.RoData();
547 buf += CBC_BLOCK_SIZE;
549 // decrypt and test check value
550 byte_t CheckValue[CBC_BLOCK_SIZE];
551 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
552 buf += CBC_BLOCK_SIZE;
554 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
555 return RESULT_CHECKFAIL;
557 // copy plaintext region
558 if ( FBin.PlaintextOffset() > 0 )
560 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
561 buf += FBin.PlaintextOffset();
564 // decrypt all but last block
565 if ( ASDCP_SUCCESS(result) )
567 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
571 // decrypt last block
572 if ( ASDCP_SUCCESS(result) )
574 byte_t the_last_block[CBC_BLOCK_SIZE];
575 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
577 if ( the_last_block[diff] != 0 )
579 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
580 return RESULT_FORMAT;
584 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
587 if ( ASDCP_SUCCESS(result) )
588 FBout.Size(FBin.SourceLength());
596 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
597 ui32_t sequence, HMACContext* HMAC)
599 ASDCP_TEST_NULL(AssetID);
600 ASDCP_TEST_NULL(HMAC);
604 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
606 // update HMAC with essence data
607 HMAC->Update(FB.RoData(), FB.Size());
609 // track file ID length
610 memcpy(p, ber_4, MXF_BER_LENGTH);
615 memcpy(p, AssetID, UUIDlen);
619 memcpy(p, ber_4, MXF_BER_LENGTH);
620 *(p+3) = sizeof(ui64_t);
624 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
628 memcpy(p, ber_4, MXF_BER_LENGTH);
632 // update HMAC with intpack values
633 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
635 // finish & write HMAC
637 HMAC->GetHMACValue(p);
639 assert(p + HMAC_SIZE == Data + klv_intpack_size);
646 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
647 ui32_t sequence, HMACContext* HMAC)
649 ASDCP_TEST_NULL(AssetID);
650 ASDCP_TEST_NULL(HMAC);
652 // find the start of the intpack
653 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
655 // test the AssetID length
656 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
657 return RESULT_HMACFAIL;
660 if ( memcmp(p, AssetID, UUIDlen) != 0 )
662 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
663 return RESULT_HMACFAIL;
667 // test the sequence length
668 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
669 return RESULT_HMACFAIL;
671 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
673 // test the sequence value
674 if ( test_sequence != sequence )
676 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
677 return RESULT_HMACFAIL;
682 // test the HMAC length
683 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
684 return RESULT_HMACFAIL;
688 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
691 return HMAC->TestHMACValue(p);
695 // end AS_DCP_MXF.cpp