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(IMFDynamicMetadataDescriptor))) )
253 type = ESS_DCDATA_UNKNOWN;
258 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
259 return RESULT_FORMAT;
268 string_is_xml(const ASDCP::FrameBuffer& buffer)
270 return (strncmp((const char *)buffer.RoData(), "<?xml", 5) == 0 ||
271 strncmp((const char *)buffer.RoData(), "\xEF\xBB\xBF<?xml", 8) == 0); // Allow BOM
278 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
281 ASDCP::FrameBuffer FB;
282 Kumu::FileReader Reader;
283 ASDCP::Wav::SimpleWaveHeader WavHeader;
284 ASDCP::RF64::SimpleRF64Header RF64Header;
285 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
286 Kumu::XMLElement TmpElement("Tmp");
290 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
292 if ( Kumu::PathIsFile(filename) )
294 result = Reader.OpenRead(filename);
296 if ( ASDCP_SUCCESS(result) )
298 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
302 if ( ASDCP_SUCCESS(result) )
304 const byte_t* p = FB.RoData();
308 while ( p[i] == 0 ) i++;
310 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
312 type = ESS_MPEG2_VES;
314 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
316 type = ESS_JPEG_2000;
318 else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
320 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
322 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
324 if ( ASDCP_SUCCESS(result) )
326 switch ( WavHeader.samplespersec )
328 case 48000: type = ESS_PCM_24b_48k; break;
329 case 96000: type = ESS_PCM_24b_96k; break;
331 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
332 result = RESULT_FORMAT;
338 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
340 if ( ASDCP_SUCCESS(result) )
342 switch ( RF64Header.samplespersec )
344 case 48000: type = ESS_PCM_24b_48k; break;
345 case 96000: type = ESS_PCM_24b_96k; break;
347 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
348 result = RESULT_FORMAT;
353 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
355 type = ESS_PCM_24b_48k;
357 else if ( string_is_xml(FB) )
359 type = ESS_TIMED_TEXT;
361 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
363 type = ESS_DCDATA_DOLBY_ATMOS;
367 else if ( Kumu::PathIsDirectory(filename) )
369 char next_file[Kumu::MaxFilePath];
370 Kumu::DirScanner Scanner;
371 Result_t result = Scanner.Open(filename);
373 if ( ASDCP_SUCCESS(result) )
375 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
377 if ( next_file[0] == '.' ) // no hidden files or internal links
380 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
382 if ( ASDCP_SUCCESS(result) )
384 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
388 if ( ASDCP_SUCCESS(result) )
390 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
392 type = ESS_JPEG_2000;
394 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
396 switch ( WavHeader.samplespersec )
398 case 48000: type = ESS_PCM_24b_48k; break;
399 case 96000: type = ESS_PCM_24b_96k; break;
401 return RESULT_FORMAT;
404 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
406 switch ( RF64Header.samplespersec )
408 case 48000: type = ESS_PCM_24b_48k; break;
409 case 96000: type = ESS_PCM_24b_96k; break;
411 return RESULT_FORMAT;
414 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
416 type = ESS_DCDATA_DOLBY_ATMOS;
420 type = ESS_DCDATA_UNKNOWN;
434 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
436 ASDCP_TEST_NULL(Ctx);
440 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
443 byte_t* p = FBout.Data();
445 // write the IV to the frame buffer
450 // encrypt the check value to the frame buffer
451 if ( ASDCP_SUCCESS(result) )
453 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
457 // write optional plaintext region
458 if ( FBin.PlaintextOffset() > 0 )
460 assert(FBin.PlaintextOffset() <= FBin.Size());
461 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
462 p += FBin.PlaintextOffset();
465 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
466 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
467 ui32_t block_size = ct_size - diff;
468 assert((block_size % CBC_BLOCK_SIZE) == 0);
470 // encrypt the ciphertext region essence data
471 if ( ASDCP_SUCCESS(result) )
473 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
477 // construct and encrypt the padding
478 if ( ASDCP_SUCCESS(result) )
480 byte_t the_last_block[CBC_BLOCK_SIZE];
483 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
485 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
486 the_last_block[diff] = i;
488 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
491 if ( ASDCP_SUCCESS(result) )
492 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
499 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
501 ASDCP_TEST_NULL(Ctx);
502 assert(FBout.Capacity() >= FBin.SourceLength());
504 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
505 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
506 ui32_t block_size = ct_size - diff;
508 assert((block_size % CBC_BLOCK_SIZE) == 0);
510 const byte_t* buf = FBin.RoData();
514 buf += CBC_BLOCK_SIZE;
516 // decrypt and test check value
517 byte_t CheckValue[CBC_BLOCK_SIZE];
518 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
519 buf += CBC_BLOCK_SIZE;
521 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
522 return RESULT_CHECKFAIL;
524 // copy plaintext region
525 if ( FBin.PlaintextOffset() > 0 )
527 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
528 buf += FBin.PlaintextOffset();
531 // decrypt all but last block
532 if ( ASDCP_SUCCESS(result) )
534 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
538 // decrypt last block
539 if ( ASDCP_SUCCESS(result) )
541 byte_t the_last_block[CBC_BLOCK_SIZE];
542 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
544 if ( the_last_block[diff] != 0 )
546 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
547 return RESULT_FORMAT;
551 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
554 if ( ASDCP_SUCCESS(result) )
555 FBout.Size(FBin.SourceLength());
563 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
564 ui32_t sequence, HMACContext* HMAC)
566 ASDCP_TEST_NULL(AssetID);
567 ASDCP_TEST_NULL(HMAC);
571 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
573 // update HMAC with essence data
574 HMAC->Update(FB.RoData(), FB.Size());
576 // track file ID length
577 memcpy(p, ber_4, MXF_BER_LENGTH);
582 memcpy(p, AssetID, UUIDlen);
586 memcpy(p, ber_4, MXF_BER_LENGTH);
587 *(p+3) = sizeof(ui64_t);
591 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
595 memcpy(p, ber_4, MXF_BER_LENGTH);
599 // update HMAC with intpack values
600 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
602 // finish & write HMAC
604 HMAC->GetHMACValue(p);
606 assert(p + HMAC_SIZE == Data + klv_intpack_size);
613 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
614 ui32_t sequence, HMACContext* HMAC)
616 ASDCP_TEST_NULL(AssetID);
617 ASDCP_TEST_NULL(HMAC);
619 // find the start of the intpack
620 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
622 // test the AssetID length
623 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
624 return RESULT_HMACFAIL;
627 if ( memcmp(p, AssetID, UUIDlen) != 0 )
629 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
630 return RESULT_HMACFAIL;
634 // test the sequence length
635 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
636 return RESULT_HMACFAIL;
638 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
640 // test the sequence value
641 if ( test_sequence != sequence )
643 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
644 return RESULT_HMACFAIL;
649 // test the HMAC length
650 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
651 return RESULT_HMACFAIL;
655 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
658 return HMAC->TestHMACValue(p);
662 // end AS_DCP_MXF.cpp