newlines are valid inside elements!
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2013, 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             {
218               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DolbyAtmosSubDescriptor))) )
219                 {
220                   type = ESS_DCDATA_DOLBY_ATMOS;
221                 }
222               else
223                 {
224                   type = ESS_DCDATA_UNKNOWN;
225                 }
226             }
227         }
228       else if (  TestHeader.OperationalPattern == UL(m_Dict->ul(MDD_OP1a)) )
229         {
230           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
231             {
232               type = ESS_AS02_JPEG_2000;
233             }
234           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor), &md_object)) )
235             {
236               assert(md_object);
237               if ( static_cast<ASDCP::MXF::WaveAudioDescriptor*>(md_object)->AudioSamplingRate == SampleRate_96k )
238                 {
239                   type = ESS_AS02_PCM_24b_96k;
240                 }
241               else
242                 {
243                   type = ESS_AS02_PCM_24b_48k;
244                 }
245             }
246           else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
247             {
248               type = ESS_AS02_TIMED_TEXT;
249             }
250         }
251       else
252         {
253           DefaultLogSink().Error("Unsupported MXF Operational Pattern.\n");
254           return RESULT_FORMAT;
255         }
256     }
257
258   return result;
259 }
260
261 //
262 ASDCP::Result_t
263 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
264 {
265   type = ESS_UNKNOWN;
266   ASDCP::FrameBuffer FB;
267   Kumu::FileReader Reader;
268   ASDCP::Wav::SimpleWaveHeader WavHeader;
269   ASDCP::RF64::SimpleRF64Header RF64Header;
270   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
271   Kumu::XMLElement TmpElement("Tmp");
272
273   ui32_t data_offset;
274   ui32_t read_count;
275   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
276
277   if ( Kumu::PathIsFile(filename) )
278     {
279       result = Reader.OpenRead(filename);
280
281       if ( ASDCP_SUCCESS(result) )
282         {
283           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
284           Reader.Close();
285         }
286
287       if ( ASDCP_SUCCESS(result) )
288         {
289           const byte_t* p = FB.RoData();
290           FB.Size(read_count);
291
292           ui32_t i = 0;
293           while ( p[i] == 0 ) i++;
294
295           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
296             {
297               type = ESS_MPEG2_VES;
298             }
299           else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
300             {
301               type = ESS_JPEG_2000;
302             }
303           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
304             {
305               switch ( WavHeader.samplespersec )
306                 {
307                 case 48000: type = ESS_PCM_24b_48k; break;
308                 case 96000: type = ESS_PCM_24b_96k; break;
309                 default:
310                   return RESULT_FORMAT;
311                 }
312             }
313           else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
314             {
315               switch ( RF64Header.samplespersec )
316                 {
317                 case 48000: type = ESS_PCM_24b_48k; break;
318                 case 96000: type = ESS_PCM_24b_96k; break;
319                 default:
320                   return RESULT_FORMAT;
321                 }
322             }
323           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
324             {
325               type = ESS_PCM_24b_48k;
326             }
327           else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
328             {
329               type = ESS_TIMED_TEXT;
330             }
331           else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
332             {
333               type = ESS_DCDATA_DOLBY_ATMOS;
334             }
335         }
336     }
337   else if ( Kumu::PathIsDirectory(filename) )
338     {
339       char next_file[Kumu::MaxFilePath];
340       Kumu::DirScanner Scanner;
341       Result_t result = Scanner.Open(filename);
342
343       if ( ASDCP_SUCCESS(result) )
344         {
345           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
346             {
347               if ( next_file[0] == '.' ) // no hidden files or internal links
348                 continue;
349
350               result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
351
352               if ( ASDCP_SUCCESS(result) )
353                 {
354                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
355                   Reader.Close();
356                 }
357
358               if ( ASDCP_SUCCESS(result) )
359                 {
360                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
361                     {
362                       type = ESS_JPEG_2000;
363                     }
364                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
365                     {
366                       switch ( WavHeader.samplespersec )
367                         {
368                         case 48000: type = ESS_PCM_24b_48k; break;
369                         case 96000: type = ESS_PCM_24b_96k; break;
370                         default:
371                           return RESULT_FORMAT;
372                         }
373                     }
374                   else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
375                     {
376                       switch ( RF64Header.samplespersec )
377                         {
378                         case 48000: type = ESS_PCM_24b_48k; break;
379                         case 96000: type = ESS_PCM_24b_96k; break;
380                         default:
381                           return RESULT_FORMAT;
382                         }
383                     }
384                   else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
385                     {
386                       type = ESS_DCDATA_DOLBY_ATMOS;
387                     }
388                 }
389               
390               break;
391             }
392         }
393     }
394
395   return result;
396 }
397
398 //
399 Result_t
400 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
401 {
402   ASDCP_TEST_NULL(Ctx);
403   FBout.Size(0);
404
405   // size the buffer
406   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
407
408   // write the IV
409   byte_t* p = FBout.Data();
410
411   // write the IV to the frame buffer
412   Ctx->GetIVec(p);
413   p += CBC_BLOCK_SIZE;
414
415
416   // encrypt the check value to the frame buffer
417   if ( ASDCP_SUCCESS(result) )
418     {
419       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
420       p += CBC_BLOCK_SIZE;
421     }
422
423   // write optional plaintext region
424   if ( FBin.PlaintextOffset() > 0 )
425     {
426       assert(FBin.PlaintextOffset() <= FBin.Size());
427       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
428       p += FBin.PlaintextOffset();
429     }
430
431   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
432   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
433   ui32_t block_size = ct_size - diff;
434   assert((block_size % CBC_BLOCK_SIZE) == 0);
435
436   // encrypt the ciphertext region essence data
437   if ( ASDCP_SUCCESS(result) )
438     {
439       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
440       p += block_size;
441     }
442
443   // construct and encrypt the padding
444   if ( ASDCP_SUCCESS(result) )
445     {
446       byte_t the_last_block[CBC_BLOCK_SIZE];
447
448       if ( diff > 0 )
449         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
450
451       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
452         the_last_block[diff] = i;
453
454       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
455     }
456
457   if ( ASDCP_SUCCESS(result) )
458     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
459
460   return result;
461 }
462
463 //
464 Result_t
465 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
466 {
467   ASDCP_TEST_NULL(Ctx);
468   assert(FBout.Capacity() >= FBin.SourceLength());
469
470   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
471   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
472   ui32_t block_size = ct_size - diff;
473   assert(block_size);
474   assert((block_size % CBC_BLOCK_SIZE) == 0);
475
476   const byte_t* buf = FBin.RoData();
477
478   // get ivec
479   Ctx->SetIVec(buf);
480   buf += CBC_BLOCK_SIZE;
481
482   // decrypt and test check value
483   byte_t CheckValue[CBC_BLOCK_SIZE];
484   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
485   buf += CBC_BLOCK_SIZE;
486
487   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
488     return RESULT_CHECKFAIL;
489
490   // copy plaintext region
491   if ( FBin.PlaintextOffset() > 0 )
492     {
493       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
494       buf += FBin.PlaintextOffset();
495     }
496
497   // decrypt all but last block
498   if ( ASDCP_SUCCESS(result) )
499     {
500       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
501       buf += block_size;
502     }
503
504   // decrypt last block
505   if ( ASDCP_SUCCESS(result) )
506     {
507       byte_t the_last_block[CBC_BLOCK_SIZE];
508       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
509
510       if ( the_last_block[diff] != 0 )
511         {
512           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
513           return RESULT_FORMAT;
514         }
515
516       if ( diff > 0 )
517         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
518     }
519
520   if ( ASDCP_SUCCESS(result) )
521     FBout.Size(FBin.SourceLength());
522
523   return result;
524 }
525
526
527 //
528 Result_t
529 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
530                                  ui32_t sequence, HMACContext* HMAC)
531 {
532   ASDCP_TEST_NULL(AssetID);
533   ASDCP_TEST_NULL(HMAC);
534   byte_t* p = Data;
535   HMAC->Reset();
536
537   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
538
539   // update HMAC with essence data
540   HMAC->Update(FB.RoData(), FB.Size());
541
542   // track file ID length
543   memcpy(p, ber_4, MXF_BER_LENGTH);
544   *(p+3) = UUIDlen;;
545   p += MXF_BER_LENGTH;
546
547   // track file ID
548   memcpy(p, AssetID, UUIDlen);
549   p += UUIDlen;
550
551   // sequence length
552   memcpy(p, ber_4, MXF_BER_LENGTH);
553   *(p+3) = sizeof(ui64_t);
554   p += MXF_BER_LENGTH;
555
556   // sequence number
557   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
558   p += sizeof(ui64_t);
559
560   // HMAC length
561   memcpy(p, ber_4, MXF_BER_LENGTH);
562   *(p+3) = HMAC_SIZE;
563   p += MXF_BER_LENGTH;
564
565   // update HMAC with intpack values
566   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
567
568   // finish & write HMAC
569   HMAC->Finalize();
570   HMAC->GetHMACValue(p);
571
572   assert(p + HMAC_SIZE == Data + klv_intpack_size);
573
574   return RESULT_OK;
575 }
576
577
578 Result_t
579 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
580                                  ui32_t sequence, HMACContext* HMAC)
581 {
582   ASDCP_TEST_NULL(AssetID);
583   ASDCP_TEST_NULL(HMAC);
584
585   // find the start of the intpack
586   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
587
588   // test the AssetID length
589   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
590         return RESULT_HMACFAIL;
591
592   // test the AssetID
593   if ( memcmp(p, AssetID, UUIDlen) != 0 )
594     {
595       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
596       return RESULT_HMACFAIL;
597     }
598   p += UUIDlen;
599   
600   // test the sequence length
601   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
602         return RESULT_HMACFAIL;
603
604   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
605
606   // test the sequence value
607   if ( test_sequence != sequence )
608     {
609       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
610       return RESULT_HMACFAIL;
611     }
612
613   p += sizeof(ui64_t);
614
615   // test the HMAC length
616   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
617         return RESULT_HMACFAIL;
618
619   // test the HMAC
620   HMAC->Reset();
621   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
622   HMAC->Finalize();
623
624   return HMAC->TestHMACValue(p);
625 }
626
627 //
628 // end AS_DCP_MXF.cpp
629 //