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 char* filename, EssenceType_t& type)
166 const Dictionary* m_Dict = &DefaultCompositeDict();
167 InterchangeObject* md_object = 0;
171 ASDCP_TEST_NULL_STR(filename);
172 Kumu::FileReader Reader;
173 OP1aHeader TestHeader(m_Dict);
175 Result_t result = Reader.OpenRead(filename);
177 if ( ASDCP_SUCCESS(result) )
178 result = TestHeader.InitFromFile(Reader); // test UL and OP
180 if ( ASDCP_SUCCESS(result) )
183 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
185 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
187 type = ESS_JPEG_2000_S;
191 type = ESS_JPEG_2000;
194 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
197 if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
199 type = ESS_PCM_24b_96k;
203 type = ESS_PCM_24b_48k;
206 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
208 type = ESS_MPEG2_VES;
210 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
212 type = ESS_TIMED_TEXT;
214 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor))) )
216 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
218 type = ESS_DCDATA_DOLBY_ATMOS;
222 type = ESS_DCDATA_UNKNOWN;
232 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
234 ASDCP_TEST_NULL_STR(filename);
236 ASDCP::FrameBuffer FB;
237 Kumu::FileReader Reader;
238 ASDCP::Wav::SimpleWaveHeader WavHeader;
239 ASDCP::RF64::SimpleRF64Header RF64Header;
240 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
241 Kumu::XMLElement TmpElement("Tmp");
245 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
247 if ( Kumu::PathIsFile(filename) )
249 result = Reader.OpenRead(filename);
251 if ( ASDCP_SUCCESS(result) )
253 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
257 if ( ASDCP_SUCCESS(result) )
259 const byte_t* p = FB.RoData();
263 while ( p[i] == 0 ) i++;
265 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
267 type = ESS_MPEG2_VES;
269 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
271 type = ESS_JPEG_2000;
273 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
275 switch ( WavHeader.samplespersec )
277 case 48000: type = ESS_PCM_24b_48k; break;
278 case 96000: type = ESS_PCM_24b_96k; break;
280 return RESULT_FORMAT;
283 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
285 switch ( RF64Header.samplespersec )
287 case 48000: type = ESS_PCM_24b_48k; break;
288 case 96000: type = ESS_PCM_24b_96k; break;
290 return RESULT_FORMAT;
293 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
295 type = ESS_PCM_24b_48k;
297 else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
299 type = ESS_TIMED_TEXT;
301 else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
303 type = ESS_DCDATA_DOLBY_ATMOS;
307 else if ( Kumu::PathIsDirectory(filename) )
309 char next_file[Kumu::MaxFilePath];
310 Kumu::DirScanner Scanner;
311 Result_t result = Scanner.Open(filename);
313 if ( ASDCP_SUCCESS(result) )
315 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
317 if ( next_file[0] == '.' ) // no hidden files or internal links
320 std::string Str(filename);
323 result = Reader.OpenRead(Str.c_str());
325 if ( ASDCP_SUCCESS(result) )
327 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
331 if ( ASDCP_SUCCESS(result) )
333 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
335 type = ESS_JPEG_2000;
337 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
339 switch ( WavHeader.samplespersec )
341 case 48000: type = ESS_PCM_24b_48k; break;
342 case 96000: type = ESS_PCM_24b_96k; break;
344 return RESULT_FORMAT;
347 else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
349 switch ( RF64Header.samplespersec )
351 case 48000: type = ESS_PCM_24b_48k; break;
352 case 96000: type = ESS_PCM_24b_96k; break;
354 return RESULT_FORMAT;
357 else if ( ASDCP::ATMOS::IsDolbyAtmos(Str.c_str()) )
359 type = ESS_DCDATA_DOLBY_ATMOS;
373 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
375 ASDCP_TEST_NULL(Ctx);
379 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
382 byte_t* p = FBout.Data();
384 // write the IV to the frame buffer
389 // encrypt the check value to the frame buffer
390 if ( ASDCP_SUCCESS(result) )
392 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
396 // write optional plaintext region
397 if ( FBin.PlaintextOffset() > 0 )
399 assert(FBin.PlaintextOffset() <= FBin.Size());
400 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
401 p += FBin.PlaintextOffset();
404 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
405 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
406 ui32_t block_size = ct_size - diff;
407 assert((block_size % CBC_BLOCK_SIZE) == 0);
409 // encrypt the ciphertext region essence data
410 if ( ASDCP_SUCCESS(result) )
412 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
416 // construct and encrypt the padding
417 if ( ASDCP_SUCCESS(result) )
419 byte_t the_last_block[CBC_BLOCK_SIZE];
422 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
424 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
425 the_last_block[diff] = i;
427 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
430 if ( ASDCP_SUCCESS(result) )
431 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
438 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
440 ASDCP_TEST_NULL(Ctx);
441 assert(FBout.Capacity() >= FBin.SourceLength());
443 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
444 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
445 ui32_t block_size = ct_size - diff;
447 assert((block_size % CBC_BLOCK_SIZE) == 0);
449 const byte_t* buf = FBin.RoData();
453 buf += CBC_BLOCK_SIZE;
455 // decrypt and test check value
456 byte_t CheckValue[CBC_BLOCK_SIZE];
457 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
458 buf += CBC_BLOCK_SIZE;
460 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
461 return RESULT_CHECKFAIL;
463 // copy plaintext region
464 if ( FBin.PlaintextOffset() > 0 )
466 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
467 buf += FBin.PlaintextOffset();
470 // decrypt all but last block
471 if ( ASDCP_SUCCESS(result) )
473 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
477 // decrypt last block
478 if ( ASDCP_SUCCESS(result) )
480 byte_t the_last_block[CBC_BLOCK_SIZE];
481 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
483 if ( the_last_block[diff] != 0 )
485 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
486 return RESULT_FORMAT;
490 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
493 if ( ASDCP_SUCCESS(result) )
494 FBout.Size(FBin.SourceLength());
502 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
503 ui32_t sequence, HMACContext* HMAC)
505 ASDCP_TEST_NULL(AssetID);
506 ASDCP_TEST_NULL(HMAC);
510 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
512 // update HMAC with essence data
513 HMAC->Update(FB.RoData(), FB.Size());
515 // track file ID length
516 memcpy(p, ber_4, MXF_BER_LENGTH);
521 memcpy(p, AssetID, UUIDlen);
525 memcpy(p, ber_4, MXF_BER_LENGTH);
526 *(p+3) = sizeof(ui64_t);
530 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
534 memcpy(p, ber_4, MXF_BER_LENGTH);
538 // update HMAC with intpack values
539 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
541 // finish & write HMAC
543 HMAC->GetHMACValue(p);
545 assert(p + HMAC_SIZE == Data + klv_intpack_size);
552 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
553 ui32_t sequence, HMACContext* HMAC)
555 ASDCP_TEST_NULL(AssetID);
556 ASDCP_TEST_NULL(HMAC);
558 // find the start of the intpack
559 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
561 // test the AssetID length
562 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
563 return RESULT_HMACFAIL;
566 if ( memcmp(p, AssetID, UUIDlen) != 0 )
568 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
569 return RESULT_HMACFAIL;
573 // test the sequence length
574 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
575 return RESULT_HMACFAIL;
577 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
579 // test the sequence value
580 if ( test_sequence != sequence )
582 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
583 return RESULT_HMACFAIL;
588 // test the HMAC length
589 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
590 return RESULT_HMACFAIL;
594 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
597 return HMAC->TestHMACValue(p);
601 // end AS_DCP_MXF.cpp