2 Copyright (c) 2004-2013, 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))) )
218 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
220 type = ESS_DCDATA_DOLBY_ATMOS;
224 type = ESS_DCDATA_UNKNOWN;
228 else if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
230 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
232 type = ESS_AS02_JPEG_2000;
234 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
237 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
239 type = ESS_AS02_PCM_24b_96k;
243 type = ESS_AS02_PCM_24b_48k;
246 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
248 type = ESS_AS02_TIMED_TEXT;
253 DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
254 return RESULT_FORMAT;
263 string_is_xml(const ASDCP::FrameBuffer& buffer)
265 std::string ns_prefix, type_name, namespace_name;
266 Kumu::AttributeList doc_attr_list;
267 return GetXMLDocType(buffer.RoData(), buffer.Size(),
268 ns_prefix, type_name, namespace_name, doc_attr_list);
273 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
276 ASDCP::FrameBuffer FB;
277 Kumu::FileReader Reader;
278 ASDCP::Wav::SimpleWaveHeader WavHeader;
279 ASDCP::RF64::SimpleRF64Header RF64Header;
280 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
281 Kumu::XMLElement TmpElement("Tmp");
285 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
287 if ( Kumu::PathIsFile(filename) )
289 result = Reader.OpenRead(filename);
291 if ( ASDCP_SUCCESS(result) )
293 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
297 if ( ASDCP_SUCCESS(result) )
299 const byte_t* p = FB.RoData();
303 while ( p[i] == 0 ) i++;
305 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
307 type = ESS_MPEG2_VES;
309 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
311 type = ESS_JPEG_2000;
313 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
315 switch ( WavHeader.samplespersec )
317 case 48000: type = ESS_PCM_24b_48k; break;
318 case 96000: type = ESS_PCM_24b_96k; break;
320 return RESULT_FORMAT;
323 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
325 switch ( RF64Header.samplespersec )
327 case 48000: type = ESS_PCM_24b_48k; break;
328 case 96000: type = ESS_PCM_24b_96k; break;
330 return RESULT_FORMAT;
333 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
335 type = ESS_PCM_24b_48k;
337 else if ( string_is_xml(FB) )
339 type = ESS_TIMED_TEXT;
341 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
343 type = ESS_DCDATA_DOLBY_ATMOS;
347 else if ( Kumu::PathIsDirectory(filename) )
349 char next_file[Kumu::MaxFilePath];
350 Kumu::DirScanner Scanner;
351 Result_t result = Scanner.Open(filename);
353 if ( ASDCP_SUCCESS(result) )
355 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
357 if ( next_file[0] == '.' ) // no hidden files or internal links
360 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
362 if ( ASDCP_SUCCESS(result) )
364 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
368 if ( ASDCP_SUCCESS(result) )
370 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
372 type = ESS_JPEG_2000;
374 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
376 switch ( WavHeader.samplespersec )
378 case 48000: type = ESS_PCM_24b_48k; break;
379 case 96000: type = ESS_PCM_24b_96k; break;
381 return RESULT_FORMAT;
384 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
386 switch ( RF64Header.samplespersec )
388 case 48000: type = ESS_PCM_24b_48k; break;
389 case 96000: type = ESS_PCM_24b_96k; break;
391 return RESULT_FORMAT;
394 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
396 type = ESS_DCDATA_DOLBY_ATMOS;
410 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
412 ASDCP_TEST_NULL(Ctx);
416 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
419 byte_t* p = FBout.Data();
421 // write the IV to the frame buffer
426 // encrypt the check value to the frame buffer
427 if ( ASDCP_SUCCESS(result) )
429 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
433 // write optional plaintext region
434 if ( FBin.PlaintextOffset() > 0 )
436 assert(FBin.PlaintextOffset() <= FBin.Size());
437 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
438 p += FBin.PlaintextOffset();
441 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
442 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
443 ui32_t block_size = ct_size - diff;
444 assert((block_size % CBC_BLOCK_SIZE) == 0);
446 // encrypt the ciphertext region essence data
447 if ( ASDCP_SUCCESS(result) )
449 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
453 // construct and encrypt the padding
454 if ( ASDCP_SUCCESS(result) )
456 byte_t the_last_block[CBC_BLOCK_SIZE];
459 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
461 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
462 the_last_block[diff] = i;
464 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
467 if ( ASDCP_SUCCESS(result) )
468 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
475 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
477 ASDCP_TEST_NULL(Ctx);
478 assert(FBout.Capacity() >= FBin.SourceLength());
480 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
481 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
482 ui32_t block_size = ct_size - diff;
484 assert((block_size % CBC_BLOCK_SIZE) == 0);
486 const byte_t* buf = FBin.RoData();
490 buf += CBC_BLOCK_SIZE;
492 // decrypt and test check value
493 byte_t CheckValue[CBC_BLOCK_SIZE];
494 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
495 buf += CBC_BLOCK_SIZE;
497 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
498 return RESULT_CHECKFAIL;
500 // copy plaintext region
501 if ( FBin.PlaintextOffset() > 0 )
503 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
504 buf += FBin.PlaintextOffset();
507 // decrypt all but last block
508 if ( ASDCP_SUCCESS(result) )
510 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
514 // decrypt last block
515 if ( ASDCP_SUCCESS(result) )
517 byte_t the_last_block[CBC_BLOCK_SIZE];
518 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
520 if ( the_last_block[diff] != 0 )
522 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
523 return RESULT_FORMAT;
527 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
530 if ( ASDCP_SUCCESS(result) )
531 FBout.Size(FBin.SourceLength());
539 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
540 ui32_t sequence, HMACContext* HMAC)
542 ASDCP_TEST_NULL(AssetID);
543 ASDCP_TEST_NULL(HMAC);
547 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
549 // update HMAC with essence data
550 HMAC->Update(FB.RoData(), FB.Size());
552 // track file ID length
553 memcpy(p, ber_4, MXF_BER_LENGTH);
558 memcpy(p, AssetID, UUIDlen);
562 memcpy(p, ber_4, MXF_BER_LENGTH);
563 *(p+3) = sizeof(ui64_t);
567 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
571 memcpy(p, ber_4, MXF_BER_LENGTH);
575 // update HMAC with intpack values
576 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
578 // finish & write HMAC
580 HMAC->GetHMACValue(p);
582 assert(p + HMAC_SIZE == Data + klv_intpack_size);
589 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
590 ui32_t sequence, HMACContext* HMAC)
592 ASDCP_TEST_NULL(AssetID);
593 ASDCP_TEST_NULL(HMAC);
595 // find the start of the intpack
596 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
598 // test the AssetID length
599 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
600 return RESULT_HMACFAIL;
603 if ( memcmp(p, AssetID, UUIDlen) != 0 )
605 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
606 return RESULT_HMACFAIL;
610 // test the sequence length
611 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
612 return RESULT_HMACFAIL;
614 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
616 // test the sequence value
617 if ( test_sequence != sequence )
619 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
620 return RESULT_HMACFAIL;
625 // test the HMAC length
626 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
627 return RESULT_HMACFAIL;
631 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
634 return HMAC->TestHMACValue(p);
638 // end AS_DCP_MXF.cpp