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"
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)))
217 || ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PrivateDCDataDescriptor))) )
219 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
221 type = ESS_DCDATA_DOLBY_ATMOS;
225 type = ESS_DCDATA_UNKNOWN;
229 else if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
231 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
233 type = ESS_AS02_JPEG_2000;
235 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
238 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
240 type = ESS_AS02_PCM_24b_96k;
244 type = ESS_AS02_PCM_24b_48k;
247 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
249 type = ESS_AS02_TIMED_TEXT;
251 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PIMFDynamicMetadataDescriptor))) )
253 type = ESS_DCDATA_UNKNOWN;
255 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ISXDDataEssenceDescriptor))) )
257 type = ESS_AS02_ISXD;
259 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ACESPictureSubDescriptor))) )
261 type = ESS_AS02_ACES;
266 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
267 return RESULT_FORMAT;
276 string_is_xml(const ASDCP::FrameBuffer& buffer)
278 return (strncmp((const char *)buffer.RoData(), "<?xml", 5) == 0 ||
279 strncmp((const char *)buffer.RoData(), "\xEF\xBB\xBF<?xml", 8) == 0); // Allow BOM
286 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
289 ASDCP::FrameBuffer FB;
290 Kumu::FileReader Reader;
291 ASDCP::Wav::SimpleWaveHeader WavHeader;
292 ASDCP::RF64::SimpleRF64Header RF64Header;
293 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
294 Kumu::XMLElement TmpElement("Tmp");
298 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
300 if ( Kumu::PathIsFile(filename) )
302 result = Reader.OpenRead(filename);
304 if ( ASDCP_SUCCESS(result) )
306 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
310 if ( ASDCP_SUCCESS(result) )
312 const byte_t* p = FB.RoData();
316 while ( p[i] == 0 ) i++;
318 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
320 type = ESS_MPEG2_VES;
322 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
324 type = ESS_JPEG_2000;
326 else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
328 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
330 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
332 if ( ASDCP_SUCCESS(result) )
334 switch ( WavHeader.samplespersec )
336 case 48000: type = ESS_PCM_24b_48k; break;
337 case 96000: type = ESS_PCM_24b_96k; break;
339 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
340 result = RESULT_FORMAT;
346 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
348 if ( ASDCP_SUCCESS(result) )
350 switch ( RF64Header.samplespersec )
352 case 48000: type = ESS_PCM_24b_48k; break;
353 case 96000: type = ESS_PCM_24b_96k; break;
355 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
356 result = RESULT_FORMAT;
361 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
363 type = ESS_PCM_24b_48k;
365 else if ( string_is_xml(FB) )
367 type = ESS_TIMED_TEXT;
369 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
371 type = ESS_DCDATA_DOLBY_ATMOS;
375 else if ( Kumu::PathIsDirectory(filename) )
377 char next_file[Kumu::MaxFilePath];
378 Kumu::DirScanner Scanner;
379 Result_t result = Scanner.Open(filename);
381 if ( ASDCP_SUCCESS(result) )
383 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
385 if ( next_file[0] == '.' ) // no hidden files or internal links
388 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
390 if ( ASDCP_SUCCESS(result) )
392 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
396 if ( ASDCP_SUCCESS(result) )
398 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
400 type = ESS_JPEG_2000;
402 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
404 switch ( WavHeader.samplespersec )
406 case 48000: type = ESS_PCM_24b_48k; break;
407 case 96000: type = ESS_PCM_24b_96k; break;
409 return RESULT_FORMAT;
412 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
414 switch ( RF64Header.samplespersec )
416 case 48000: type = ESS_PCM_24b_48k; break;
417 case 96000: type = ESS_PCM_24b_96k; break;
419 return RESULT_FORMAT;
422 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
424 type = ESS_DCDATA_DOLBY_ATMOS;
428 type = ESS_DCDATA_UNKNOWN;
442 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
444 ASDCP_TEST_NULL(Ctx);
448 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
451 byte_t* p = FBout.Data();
453 // write the IV to the frame buffer
458 // encrypt the check value to the frame buffer
459 if ( ASDCP_SUCCESS(result) )
461 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
465 // write optional plaintext region
466 if ( FBin.PlaintextOffset() > 0 )
468 assert(FBin.PlaintextOffset() <= FBin.Size());
469 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
470 p += FBin.PlaintextOffset();
473 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
474 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
475 ui32_t block_size = ct_size - diff;
476 assert((block_size % CBC_BLOCK_SIZE) == 0);
478 // encrypt the ciphertext region essence data
479 if ( ASDCP_SUCCESS(result) )
481 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
485 // construct and encrypt the padding
486 if ( ASDCP_SUCCESS(result) )
488 byte_t the_last_block[CBC_BLOCK_SIZE];
491 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
493 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
494 the_last_block[diff] = i;
496 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
499 if ( ASDCP_SUCCESS(result) )
500 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
507 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
509 ASDCP_TEST_NULL(Ctx);
510 assert(FBout.Capacity() >= FBin.SourceLength());
512 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
513 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
514 ui32_t block_size = ct_size - diff;
516 assert((block_size % CBC_BLOCK_SIZE) == 0);
518 const byte_t* buf = FBin.RoData();
522 buf += CBC_BLOCK_SIZE;
524 // decrypt and test check value
525 byte_t CheckValue[CBC_BLOCK_SIZE];
526 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
527 buf += CBC_BLOCK_SIZE;
529 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
530 return RESULT_CHECKFAIL;
532 // copy plaintext region
533 if ( FBin.PlaintextOffset() > 0 )
535 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
536 buf += FBin.PlaintextOffset();
539 // decrypt all but last block
540 if ( ASDCP_SUCCESS(result) )
542 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
546 // decrypt last block
547 if ( ASDCP_SUCCESS(result) )
549 byte_t the_last_block[CBC_BLOCK_SIZE];
550 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
552 if ( the_last_block[diff] != 0 )
554 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
555 return RESULT_FORMAT;
559 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
562 if ( ASDCP_SUCCESS(result) )
563 FBout.Size(FBin.SourceLength());
571 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
572 ui32_t sequence, HMACContext* HMAC)
574 ASDCP_TEST_NULL(AssetID);
575 ASDCP_TEST_NULL(HMAC);
579 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
581 // update HMAC with essence data
582 HMAC->Update(FB.RoData(), FB.Size());
584 // track file ID length
585 memcpy(p, ber_4, MXF_BER_LENGTH);
590 memcpy(p, AssetID, UUIDlen);
594 memcpy(p, ber_4, MXF_BER_LENGTH);
595 *(p+3) = sizeof(ui64_t);
599 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
603 memcpy(p, ber_4, MXF_BER_LENGTH);
607 // update HMAC with intpack values
608 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
610 // finish & write HMAC
612 HMAC->GetHMACValue(p);
614 assert(p + HMAC_SIZE == Data + klv_intpack_size);
621 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
622 ui32_t sequence, HMACContext* HMAC)
624 ASDCP_TEST_NULL(AssetID);
625 ASDCP_TEST_NULL(HMAC);
627 // find the start of the intpack
628 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
630 // test the AssetID length
631 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
632 return RESULT_HMACFAIL;
635 if ( memcmp(p, AssetID, UUIDlen) != 0 )
637 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
638 return RESULT_HMACFAIL;
642 // test the sequence length
643 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
644 return RESULT_HMACFAIL;
646 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
648 // test the sequence value
649 if ( test_sequence != sequence )
651 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
652 return RESULT_HMACFAIL;
657 // test the HMAC length
658 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
659 return RESULT_HMACFAIL;
663 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
666 return HMAC->TestHMACValue(p);
670 // end AS_DCP_MXF.cpp