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 ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
315 if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
317 result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
319 if ( ASDCP_SUCCESS(result) )
321 switch ( WavHeader.samplespersec )
323 case 48000: type = ESS_PCM_24b_48k; break;
324 case 96000: type = ESS_PCM_24b_96k; break;
326 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
327 result = RESULT_FORMAT;
333 result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
335 if ( ASDCP_SUCCESS(result) )
337 switch ( RF64Header.samplespersec )
339 case 48000: type = ESS_PCM_24b_48k; break;
340 case 96000: type = ESS_PCM_24b_96k; break;
342 DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
343 result = RESULT_FORMAT;
348 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
350 type = ESS_PCM_24b_48k;
352 else if ( string_is_xml(FB) )
354 type = ESS_TIMED_TEXT;
356 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
358 type = ESS_DCDATA_DOLBY_ATMOS;
362 else if ( Kumu::PathIsDirectory(filename) )
364 char next_file[Kumu::MaxFilePath];
365 Kumu::DirScanner Scanner;
366 Result_t result = Scanner.Open(filename);
368 if ( ASDCP_SUCCESS(result) )
370 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
372 if ( next_file[0] == '.' ) // no hidden files or internal links
375 result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
377 if ( ASDCP_SUCCESS(result) )
379 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
383 if ( ASDCP_SUCCESS(result) )
385 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
387 type = ESS_JPEG_2000;
389 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
391 switch ( WavHeader.samplespersec )
393 case 48000: type = ESS_PCM_24b_48k; break;
394 case 96000: type = ESS_PCM_24b_96k; break;
396 return RESULT_FORMAT;
399 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
401 switch ( RF64Header.samplespersec )
403 case 48000: type = ESS_PCM_24b_48k; break;
404 case 96000: type = ESS_PCM_24b_96k; break;
406 return RESULT_FORMAT;
409 else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
411 type = ESS_DCDATA_DOLBY_ATMOS;
415 type = ESS_DCDATA_UNKNOWN;
429 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
431 ASDCP_TEST_NULL(Ctx);
435 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
438 byte_t* p = FBout.Data();
440 // write the IV to the frame buffer
445 // encrypt the check value to the frame buffer
446 if ( ASDCP_SUCCESS(result) )
448 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
452 // write optional plaintext region
453 if ( FBin.PlaintextOffset() > 0 )
455 assert(FBin.PlaintextOffset() <= FBin.Size());
456 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
457 p += FBin.PlaintextOffset();
460 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
461 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
462 ui32_t block_size = ct_size - diff;
463 assert((block_size % CBC_BLOCK_SIZE) == 0);
465 // encrypt the ciphertext region essence data
466 if ( ASDCP_SUCCESS(result) )
468 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
472 // construct and encrypt the padding
473 if ( ASDCP_SUCCESS(result) )
475 byte_t the_last_block[CBC_BLOCK_SIZE];
478 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
480 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
481 the_last_block[diff] = i;
483 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
486 if ( ASDCP_SUCCESS(result) )
487 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
494 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
496 ASDCP_TEST_NULL(Ctx);
497 assert(FBout.Capacity() >= FBin.SourceLength());
499 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
500 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
501 ui32_t block_size = ct_size - diff;
503 assert((block_size % CBC_BLOCK_SIZE) == 0);
505 const byte_t* buf = FBin.RoData();
509 buf += CBC_BLOCK_SIZE;
511 // decrypt and test check value
512 byte_t CheckValue[CBC_BLOCK_SIZE];
513 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
514 buf += CBC_BLOCK_SIZE;
516 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
517 return RESULT_CHECKFAIL;
519 // copy plaintext region
520 if ( FBin.PlaintextOffset() > 0 )
522 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
523 buf += FBin.PlaintextOffset();
526 // decrypt all but last block
527 if ( ASDCP_SUCCESS(result) )
529 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
533 // decrypt last block
534 if ( ASDCP_SUCCESS(result) )
536 byte_t the_last_block[CBC_BLOCK_SIZE];
537 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
539 if ( the_last_block[diff] != 0 )
541 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
542 return RESULT_FORMAT;
546 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
549 if ( ASDCP_SUCCESS(result) )
550 FBout.Size(FBin.SourceLength());
558 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
559 ui32_t sequence, HMACContext* HMAC)
561 ASDCP_TEST_NULL(AssetID);
562 ASDCP_TEST_NULL(HMAC);
566 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
568 // update HMAC with essence data
569 HMAC->Update(FB.RoData(), FB.Size());
571 // track file ID length
572 memcpy(p, ber_4, MXF_BER_LENGTH);
577 memcpy(p, AssetID, UUIDlen);
581 memcpy(p, ber_4, MXF_BER_LENGTH);
582 *(p+3) = sizeof(ui64_t);
586 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
590 memcpy(p, ber_4, MXF_BER_LENGTH);
594 // update HMAC with intpack values
595 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
597 // finish & write HMAC
599 HMAC->GetHMACValue(p);
601 assert(p + HMAC_SIZE == Data + klv_intpack_size);
608 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
609 ui32_t sequence, HMACContext* HMAC)
611 ASDCP_TEST_NULL(AssetID);
612 ASDCP_TEST_NULL(HMAC);
614 // find the start of the intpack
615 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
617 // test the AssetID length
618 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
619 return RESULT_HMACFAIL;
622 if ( memcmp(p, AssetID, UUIDlen) != 0 )
624 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
625 return RESULT_HMACFAIL;
629 // test the sequence length
630 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
631 return RESULT_HMACFAIL;
633 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
635 // test the sequence value
636 if ( test_sequence != sequence )
638 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
639 return RESULT_HMACFAIL;
644 // test the HMAC length
645 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
646 return RESULT_HMACFAIL;
650 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
653 return HMAC->TestHMACValue(p);
657 // end AS_DCP_MXF.cpp