o Added support for SMPTE RDD 47 "ISXD Track File"
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2016, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
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.
15
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.
26 */
27 /*! \file    AS_DCP_MXF.cpp
28     \version $Id$
29     \brief   AS-DCP library, misc classes and subroutines
30 */
31
32 #include <KM_fileio.h>
33 #include <KM_xml.h>
34 #include "AS_DCP_internal.h"
35 #include "JP2K.h"
36 #include "MPEG.h"
37 #include "Wav.h"
38 #include <iostream>
39 #include <iomanip>
40
41
42 //------------------------------------------------------------------------------------------
43 // misc subroutines
44
45
46 //
47 std::ostream&
48 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
49 {
50   char str_buf[40];
51
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;
57
58   if ( Info.EncryptedEssence )
59     {
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;
63     }
64
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;
69   return strm;
70 }
71
72 //
73 void
74 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
75 {
76   if ( stream == 0 )
77     stream = stderr;
78
79   char str_buf[40];
80
81   fprintf(stream,"       ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
82   fprintf(stream,"\
83     ProductVersion: %s\n\
84        CompanyName: %s\n\
85        ProductName: %s\n\
86   EncryptedEssence: %s\n",
87           Info.ProductVersion.c_str(),
88           Info.CompanyName.c_str(),
89           Info.ProductName.c_str(),
90           ( Info.EncryptedEssence ? "Yes" : "No" )
91           );
92
93   if ( Info.EncryptedEssence )
94     {
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));
98     }
99
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" :
103                                                  "Unknown" ) ));
104 }
105
106 //
107 Result_t
108 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
109 {
110   ASDCP_TEST_NULL(InfoObj);
111   char tmp_str[IdentBufferLen];
112
113   Info.ProductName = "Unknown Product";
114   Info.ProductVersion = "Unknown Version";
115   Info.CompanyName = "Unknown Company";
116   memset(Info.ProductUUID, 0, UUIDlen);
117
118   InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
119   if ( *tmp_str ) Info.ProductName = tmp_str;
120
121   InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
122   if ( *tmp_str ) Info.ProductVersion = tmp_str;
123
124   InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
125   if ( *tmp_str ) Info.CompanyName = tmp_str;
126
127   memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
128
129   return RESULT_OK;
130 }
131
132
133 //
134 Result_t
135 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
136 {
137   ASDCP_TEST_NULL(InfoObj);
138
139   Info.EncryptedEssence = true;
140   memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
141   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
142
143   UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
144   UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
145
146   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
147     Info.UsesHMAC = true;
148
149   else if ( InfoObj->MICAlgorithm == MIC_NONE )
150     Info.UsesHMAC = false;
151
152   else
153     {
154       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
155       return RESULT_FORMAT;
156     }
157
158   return RESULT_OK;
159 }
160
161 //
162 //
163 ASDCP::Result_t
164 ASDCP::EssenceType(const std::string& filename, EssenceType_t& type)
165 {
166   const Dictionary* m_Dict = &DefaultCompositeDict();
167   InterchangeObject* md_object = 0;
168
169   assert(m_Dict);
170   Kumu::FileReader   Reader;
171   OP1aHeader TestHeader(m_Dict);
172
173   Result_t result = Reader.OpenRead(filename);
174
175   if ( ASDCP_SUCCESS(result) )
176     result = TestHeader.InitFromFile(Reader); // test UL and OP
177
178   if ( ASDCP_SUCCESS(result) )
179     {
180       type = ESS_UNKNOWN;
181
182       if ( TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OPAtom))
183            || TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_MXFInterop_OPAtom)) )
184         {
185           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
186             {
187               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
188                 {
189                   type = ESS_JPEG_2000_S;
190                 }
191               else
192                 {
193                   type = ESS_JPEG_2000;
194                 }
195             }
196           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
197             {
198               assert(md_object);
199               if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
200                 {
201                   type = ESS_PCM_24b_96k;
202                 }
203               else
204                 {
205                   type = ESS_PCM_24b_48k;
206                 }
207             }
208           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
209             {
210               type = ESS_MPEG2_VES;
211             }
212           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
213             {
214               type = ESS_TIMED_TEXT;
215             }
216           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCDataDescriptor)))
217                     || ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PrivateDCDataDescriptor))) )
218             {
219               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
220                 {
221                   type = ESS_DCDATA_DOLBY_ATMOS;
222                 }
223               else
224                 {
225                   type = ESS_DCDATA_UNKNOWN;
226                 }
227             }
228         }
229       else if (  TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
230         {
231           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
232             {
233               type = ESS_AS02_JPEG_2000;
234             }
235           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
236             {
237               assert(md_object);
238               if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
239                 {
240                   type = ESS_AS02_PCM_24b_96k;
241                 }
242               else
243                 {
244                   type = ESS_AS02_PCM_24b_48k;
245                 }
246             }
247           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
248             {
249               type = ESS_AS02_TIMED_TEXT;
250             }
251           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(PIMFDynamicMetadataDescriptor))) )
252             {
253               type = ESS_DCDATA_UNKNOWN;
254             }
255           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ISXDDataEssenceDescriptor))) )
256             {
257               type = ESS_AS02_ISXD;
258             }
259           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(ACESPictureSubDescriptor))) )
260             {
261               type = ESS_AS02_ACES;
262             }
263         }
264       else
265         {
266           DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
267           return RESULT_FORMAT;
268         }
269     }
270
271   return result;
272 }
273
274 //
275 static bool
276 string_is_xml(const ASDCP::FrameBuffer& buffer)
277 {
278   return (strncmp((const char *)buffer.RoData(),             "<?xml", 5) == 0 ||
279           strncmp((const char *)buffer.RoData(), "\xEF\xBB\xBF<?xml", 8) == 0); // Allow BOM
280  }
281  
282  //
283
284 //
285 ASDCP::Result_t
286 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
287 {
288   type = ESS_UNKNOWN;
289   ASDCP::FrameBuffer FB;
290   Kumu::FileReader Reader;
291   ASDCP::Wav::SimpleWaveHeader WavHeader;
292   ASDCP::RF64::SimpleRF64Header RF64Header;
293   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
294   Kumu::XMLElement TmpElement("Tmp");
295
296   ui32_t data_offset;
297   ui32_t read_count;
298   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
299
300   if ( Kumu::PathIsFile(filename) )
301     {
302       result = Reader.OpenRead(filename);
303
304       if ( ASDCP_SUCCESS(result) )
305         {
306           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
307           Reader.Close();
308         }
309
310       if ( ASDCP_SUCCESS(result) )
311         {
312           const byte_t* p = FB.RoData();
313           FB.Size(read_count);
314
315           ui32_t i = 0;
316           while ( p[i] == 0 ) i++;
317
318           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
319             {
320               type = ESS_MPEG2_VES;
321             }
322           else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
323             {
324               type = ESS_JPEG_2000;
325             }
326           else if ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
327             {
328               if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
329                 {
330                   result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
331
332                   if ( ASDCP_SUCCESS(result) )
333                     {
334                       switch ( WavHeader.samplespersec )
335                         {
336                         case 48000: type = ESS_PCM_24b_48k; break;
337                         case 96000: type = ESS_PCM_24b_96k; break;
338                         default:
339                           DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
340                           result = RESULT_FORMAT;
341                         }
342                     }
343                 }
344               else
345                 {
346                   result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
347                 
348                   if ( ASDCP_SUCCESS(result) )
349                     {
350                       switch ( RF64Header.samplespersec )
351                         {
352                         case 48000: type = ESS_PCM_24b_48k; break;
353                         case 96000: type = ESS_PCM_24b_96k; break;
354                         default:
355                           DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
356                           result = RESULT_FORMAT;
357                         }
358                     }
359                 }
360             }
361           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
362             {
363               type = ESS_PCM_24b_48k;
364             }
365           else if ( string_is_xml(FB) )
366             {
367               type = ESS_TIMED_TEXT;
368             }
369           else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
370             {
371               type = ESS_DCDATA_DOLBY_ATMOS;
372             }
373         }
374     }
375   else if ( Kumu::PathIsDirectory(filename) )
376     {
377       char next_file[Kumu::MaxFilePath];
378       Kumu::DirScanner Scanner;
379       Result_t result = Scanner.Open(filename);
380
381       if ( ASDCP_SUCCESS(result) )
382         {
383           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
384             {
385               if ( next_file[0] == '.' ) // no hidden files or internal links
386                 continue;
387
388               result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
389
390               if ( ASDCP_SUCCESS(result) )
391                 {
392                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
393                   Reader.Close();
394                 }
395
396               if ( ASDCP_SUCCESS(result) )
397                 {
398                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
399                     {
400                       type = ESS_JPEG_2000;
401                     }
402                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
403                     {
404                       switch ( WavHeader.samplespersec )
405                         {
406                         case 48000: type = ESS_PCM_24b_48k; break;
407                         case 96000: type = ESS_PCM_24b_96k; break;
408                         default:
409                           return RESULT_FORMAT;
410                         }
411                     }
412                   else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
413                     {
414                       switch ( RF64Header.samplespersec )
415                         {
416                         case 48000: type = ESS_PCM_24b_48k; break;
417                         case 96000: type = ESS_PCM_24b_96k; break;
418                         default:
419                           return RESULT_FORMAT;
420                         }
421                     }
422                   else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
423                     {
424                       type = ESS_DCDATA_DOLBY_ATMOS;
425                     }
426                   else
427                     {
428                       type = ESS_DCDATA_UNKNOWN;
429                     }
430                 }
431               
432               break;
433             }
434         }
435     }
436
437   return result;
438 }
439
440 //
441 Result_t
442 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
443 {
444   ASDCP_TEST_NULL(Ctx);
445   FBout.Size(0);
446
447   // size the buffer
448   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
449
450   // write the IV
451   byte_t* p = FBout.Data();
452
453   // write the IV to the frame buffer
454   Ctx->GetIVec(p);
455   p += CBC_BLOCK_SIZE;
456
457
458   // encrypt the check value to the frame buffer
459   if ( ASDCP_SUCCESS(result) )
460     {
461       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
462       p += CBC_BLOCK_SIZE;
463     }
464
465   // write optional plaintext region
466   if ( FBin.PlaintextOffset() > 0 )
467     {
468       assert(FBin.PlaintextOffset() <= FBin.Size());
469       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
470       p += FBin.PlaintextOffset();
471     }
472
473   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
474   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
475   ui32_t block_size = ct_size - diff;
476   assert((block_size % CBC_BLOCK_SIZE) == 0);
477
478   // encrypt the ciphertext region essence data
479   if ( ASDCP_SUCCESS(result) )
480     {
481       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
482       p += block_size;
483     }
484
485   // construct and encrypt the padding
486   if ( ASDCP_SUCCESS(result) )
487     {
488       byte_t the_last_block[CBC_BLOCK_SIZE];
489
490       if ( diff > 0 )
491         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
492
493       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
494         the_last_block[diff] = i;
495
496       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
497     }
498
499   if ( ASDCP_SUCCESS(result) )
500     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
501
502   return result;
503 }
504
505 //
506 Result_t
507 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
508 {
509   ASDCP_TEST_NULL(Ctx);
510   assert(FBout.Capacity() >= FBin.SourceLength());
511
512   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
513   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
514   ui32_t block_size = ct_size - diff;
515   assert(block_size);
516   assert((block_size % CBC_BLOCK_SIZE) == 0);
517
518   const byte_t* buf = FBin.RoData();
519
520   // get ivec
521   Ctx->SetIVec(buf);
522   buf += CBC_BLOCK_SIZE;
523
524   // decrypt and test check value
525   byte_t CheckValue[CBC_BLOCK_SIZE];
526   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
527   buf += CBC_BLOCK_SIZE;
528
529   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
530     return RESULT_CHECKFAIL;
531
532   // copy plaintext region
533   if ( FBin.PlaintextOffset() > 0 )
534     {
535       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
536       buf += FBin.PlaintextOffset();
537     }
538
539   // decrypt all but last block
540   if ( ASDCP_SUCCESS(result) )
541     {
542       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
543       buf += block_size;
544     }
545
546   // decrypt last block
547   if ( ASDCP_SUCCESS(result) )
548     {
549       byte_t the_last_block[CBC_BLOCK_SIZE];
550       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
551
552       if ( the_last_block[diff] != 0 )
553         {
554           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
555           return RESULT_FORMAT;
556         }
557
558       if ( diff > 0 )
559         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
560     }
561
562   if ( ASDCP_SUCCESS(result) )
563     FBout.Size(FBin.SourceLength());
564
565   return result;
566 }
567
568
569 //
570 Result_t
571 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
572                                  ui32_t sequence, HMACContext* HMAC)
573 {
574   ASDCP_TEST_NULL(AssetID);
575   ASDCP_TEST_NULL(HMAC);
576   byte_t* p = Data;
577   HMAC->Reset();
578
579   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
580
581   // update HMAC with essence data
582   HMAC->Update(FB.RoData(), FB.Size());
583
584   // track file ID length
585   memcpy(p, ber_4, MXF_BER_LENGTH);
586   *(p+3) = UUIDlen;;
587   p += MXF_BER_LENGTH;
588
589   // track file ID
590   memcpy(p, AssetID, UUIDlen);
591   p += UUIDlen;
592
593   // sequence length
594   memcpy(p, ber_4, MXF_BER_LENGTH);
595   *(p+3) = sizeof(ui64_t);
596   p += MXF_BER_LENGTH;
597
598   // sequence number
599   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
600   p += sizeof(ui64_t);
601
602   // HMAC length
603   memcpy(p, ber_4, MXF_BER_LENGTH);
604   *(p+3) = HMAC_SIZE;
605   p += MXF_BER_LENGTH;
606
607   // update HMAC with intpack values
608   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
609
610   // finish & write HMAC
611   HMAC->Finalize();
612   HMAC->GetHMACValue(p);
613
614   assert(p + HMAC_SIZE == Data + klv_intpack_size);
615
616   return RESULT_OK;
617 }
618
619
620 Result_t
621 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
622                                  ui32_t sequence, HMACContext* HMAC)
623 {
624   ASDCP_TEST_NULL(AssetID);
625   ASDCP_TEST_NULL(HMAC);
626
627   // find the start of the intpack
628   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
629
630   // test the AssetID length
631   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
632         return RESULT_HMACFAIL;
633
634   // test the AssetID
635   if ( memcmp(p, AssetID, UUIDlen) != 0 )
636     {
637       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
638       return RESULT_HMACFAIL;
639     }
640   p += UUIDlen;
641   
642   // test the sequence length
643   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
644         return RESULT_HMACFAIL;
645
646   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
647
648   // test the sequence value
649   if ( test_sequence != sequence )
650     {
651       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
652       return RESULT_HMACFAIL;
653     }
654
655   p += sizeof(ui64_t);
656
657   // test the HMAC length
658   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
659         return RESULT_HMACFAIL;
660
661   // test the HMAC
662   HMAC->Reset();
663   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
664   HMAC->Finalize();
665
666   return HMAC->TestHMACValue(p);
667 }
668
669 //
670 // end AS_DCP_MXF.cpp
671 //