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;
254 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
255 return RESULT_FORMAT;
264 string_is_xml(const ASDCP::FrameBuffer& buffer)
266 std::string ns_prefix, type_name, namespace_name;
267 Kumu::AttributeList doc_attr_list;
268 return GetXMLDocType(buffer.RoData(), buffer.Size(),
269 ns_prefix, type_name, namespace_name, doc_attr_list);
274 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
277 ASDCP::FrameBuffer FB;
278 Kumu::FileReader Reader;
279 ASDCP::Wav::SimpleWaveHeader WavHeader;
280 ASDCP::RF64::SimpleRF64Header RF64Header;
281 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
282 Kumu::XMLElement TmpElement("Tmp");
286 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
288 if ( Kumu::PathIsFile(filename) )
290 result = Reader.OpenRead(filename);
292 if ( ASDCP_SUCCESS(result) )
294 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
298 if ( ASDCP_SUCCESS(result) )
300 const byte_t* p = FB.RoData();
304 while ( p[i] == 0 ) i++;
306 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
308 type = ESS_MPEG2_VES;
310 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
312 type = ESS_JPEG_2000;
314 else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
316 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
318 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
320 if ( ASDCP_SUCCESS(result) )
322 switch ( WavHeader.samplespersec )
324 case 48000: type = ESS_PCM_24b_48k; break;
325 case 96000: type = ESS_PCM_24b_96k; break;
327 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
328 result = RESULT_FORMAT;
334 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
336 if ( ASDCP_SUCCESS(result) )
338 switch ( RF64Header.samplespersec )
340 case 48000: type = ESS_PCM_24b_48k; break;
341 case 96000: type = ESS_PCM_24b_96k; break;
343 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
344 result = RESULT_FORMAT;
349 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
351 type = ESS_PCM_24b_48k;
353 else if ( string_is_xml(FB) )
355 type = ESS_TIMED_TEXT;
357 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
359 type = ESS_DCDATA_DOLBY_ATMOS;
363 else if ( Kumu::PathIsDirectory(filename) )
365 char next_file[Kumu::MaxFilePath];
366 Kumu::DirScanner Scanner;
367 Result_t result = Scanner.Open(filename);
369 if ( ASDCP_SUCCESS(result) )
371 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
373 if ( next_file[0] == '.' ) // no hidden files or internal links
376 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
378 if ( ASDCP_SUCCESS(result) )
380 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
384 if ( ASDCP_SUCCESS(result) )
386 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
388 type = ESS_JPEG_2000;
390 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
392 switch ( WavHeader.samplespersec )
394 case 48000: type = ESS_PCM_24b_48k; break;
395 case 96000: type = ESS_PCM_24b_96k; break;
397 return RESULT_FORMAT;
400 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
402 switch ( RF64Header.samplespersec )
404 case 48000: type = ESS_PCM_24b_48k; break;
405 case 96000: type = ESS_PCM_24b_96k; break;
407 return RESULT_FORMAT;
410 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
412 type = ESS_DCDATA_DOLBY_ATMOS;
416 type = ESS_DCDATA_UNKNOWN;
430 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
432 ASDCP_TEST_NULL(Ctx);
436 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
439 byte_t* p = FBout.Data();
441 // write the IV to the frame buffer
446 // encrypt the check value to the frame buffer
447 if ( ASDCP_SUCCESS(result) )
449 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
453 // write optional plaintext region
454 if ( FBin.PlaintextOffset() > 0 )
456 assert(FBin.PlaintextOffset() <= FBin.Size());
457 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
458 p += FBin.PlaintextOffset();
461 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
462 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
463 ui32_t block_size = ct_size - diff;
464 assert((block_size % CBC_BLOCK_SIZE) == 0);
466 // encrypt the ciphertext region essence data
467 if ( ASDCP_SUCCESS(result) )
469 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
473 // construct and encrypt the padding
474 if ( ASDCP_SUCCESS(result) )
476 byte_t the_last_block[CBC_BLOCK_SIZE];
479 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
481 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
482 the_last_block[diff] = i;
484 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
487 if ( ASDCP_SUCCESS(result) )
488 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
495 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
497 ASDCP_TEST_NULL(Ctx);
498 assert(FBout.Capacity() >= FBin.SourceLength());
500 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
501 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
502 ui32_t block_size = ct_size - diff;
504 assert((block_size % CBC_BLOCK_SIZE) == 0);
506 const byte_t* buf = FBin.RoData();
510 buf += CBC_BLOCK_SIZE;
512 // decrypt and test check value
513 byte_t CheckValue[CBC_BLOCK_SIZE];
514 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
515 buf += CBC_BLOCK_SIZE;
517 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
518 return RESULT_CHECKFAIL;
520 // copy plaintext region
521 if ( FBin.PlaintextOffset() > 0 )
523 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
524 buf += FBin.PlaintextOffset();
527 // decrypt all but last block
528 if ( ASDCP_SUCCESS(result) )
530 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
534 // decrypt last block
535 if ( ASDCP_SUCCESS(result) )
537 byte_t the_last_block[CBC_BLOCK_SIZE];
538 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
540 if ( the_last_block[diff] != 0 )
542 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
543 return RESULT_FORMAT;
547 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
550 if ( ASDCP_SUCCESS(result) )
551 FBout.Size(FBin.SourceLength());
559 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
560 ui32_t sequence, HMACContext* HMAC)
562 ASDCP_TEST_NULL(AssetID);
563 ASDCP_TEST_NULL(HMAC);
567 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
569 // update HMAC with essence data
570 HMAC->Update(FB.RoData(), FB.Size());
572 // track file ID length
573 memcpy(p, ber_4, MXF_BER_LENGTH);
578 memcpy(p, AssetID, UUIDlen);
582 memcpy(p, ber_4, MXF_BER_LENGTH);
583 *(p+3) = sizeof(ui64_t);
587 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
591 memcpy(p, ber_4, MXF_BER_LENGTH);
595 // update HMAC with intpack values
596 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
598 // finish & write HMAC
600 HMAC->GetHMACValue(p);
602 assert(p + HMAC_SIZE == Data + klv_intpack_size);
609 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
610 ui32_t sequence, HMACContext* HMAC)
612 ASDCP_TEST_NULL(AssetID);
613 ASDCP_TEST_NULL(HMAC);
615 // find the start of the intpack
616 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
618 // test the AssetID length
619 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
620 return RESULT_HMACFAIL;
623 if ( memcmp(p, AssetID, UUIDlen) != 0 )
625 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
626 return RESULT_HMACFAIL;
630 // test the sequence length
631 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
632 return RESULT_HMACFAIL;
634 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
636 // test the sequence value
637 if ( test_sequence != sequence )
639 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
640 return RESULT_HMACFAIL;
645 // test the HMAC length
646 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
647 return RESULT_HMACFAIL;
651 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
654 return HMAC->TestHMACValue(p);
658 // end AS_DCP_MXF.cpp