banner updates to 2015
[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 static bool
263 string_is_xml(const ASDCP::FrameBuffer& buffer)
264 {
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);
269 }
270
271 //
272 ASDCP::Result_t
273 ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type)
274 {
275   type = ESS_UNKNOWN;
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");
282
283   ui32_t data_offset;
284   ui32_t read_count;
285   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
286
287   if ( Kumu::PathIsFile(filename) )
288     {
289       result = Reader.OpenRead(filename);
290
291       if ( ASDCP_SUCCESS(result) )
292         {
293           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
294           Reader.Close();
295         }
296
297       if ( ASDCP_SUCCESS(result) )
298         {
299           const byte_t* p = FB.RoData();
300           FB.Size(read_count);
301
302           ui32_t i = 0;
303           while ( p[i] == 0 ) i++;
304
305           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
306             {
307               type = ESS_MPEG2_VES;
308             }
309           else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
310             {
311               type = ESS_JPEG_2000;
312             }
313           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
314             {
315               switch ( WavHeader.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(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
324             {
325               switch ( RF64Header.samplespersec )
326                 {
327                 case 48000: type = ESS_PCM_24b_48k; break;
328                 case 96000: type = ESS_PCM_24b_96k; break;
329                 default:
330                   return RESULT_FORMAT;
331                 }
332             }
333           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
334             {
335               type = ESS_PCM_24b_48k;
336             }
337           else if ( string_is_xml(FB) )
338             {
339               type = ESS_TIMED_TEXT;
340             }
341           else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
342             {
343               type = ESS_DCDATA_DOLBY_ATMOS;
344             }
345         }
346     }
347   else if ( Kumu::PathIsDirectory(filename) )
348     {
349       char next_file[Kumu::MaxFilePath];
350       Kumu::DirScanner Scanner;
351       Result_t result = Scanner.Open(filename);
352
353       if ( ASDCP_SUCCESS(result) )
354         {
355           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
356             {
357               if ( next_file[0] == '.' ) // no hidden files or internal links
358                 continue;
359
360               result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
361
362               if ( ASDCP_SUCCESS(result) )
363                 {
364                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
365                   Reader.Close();
366                 }
367
368               if ( ASDCP_SUCCESS(result) )
369                 {
370                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
371                     {
372                       type = ESS_JPEG_2000;
373                     }
374                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
375                     {
376                       switch ( WavHeader.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_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
385                     {
386                       switch ( RF64Header.samplespersec )
387                         {
388                         case 48000: type = ESS_PCM_24b_48k; break;
389                         case 96000: type = ESS_PCM_24b_96k; break;
390                         default:
391                           return RESULT_FORMAT;
392                         }
393                     }
394                   else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
395                     {
396                       type = ESS_DCDATA_DOLBY_ATMOS;
397                     }
398                 }
399               
400               break;
401             }
402         }
403     }
404
405   return result;
406 }
407
408 //
409 Result_t
410 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
411 {
412   ASDCP_TEST_NULL(Ctx);
413   FBout.Size(0);
414
415   // size the buffer
416   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
417
418   // write the IV
419   byte_t* p = FBout.Data();
420
421   // write the IV to the frame buffer
422   Ctx->GetIVec(p);
423   p += CBC_BLOCK_SIZE;
424
425
426   // encrypt the check value to the frame buffer
427   if ( ASDCP_SUCCESS(result) )
428     {
429       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
430       p += CBC_BLOCK_SIZE;
431     }
432
433   // write optional plaintext region
434   if ( FBin.PlaintextOffset() > 0 )
435     {
436       assert(FBin.PlaintextOffset() <= FBin.Size());
437       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
438       p += FBin.PlaintextOffset();
439     }
440
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);
445
446   // encrypt the ciphertext region essence data
447   if ( ASDCP_SUCCESS(result) )
448     {
449       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
450       p += block_size;
451     }
452
453   // construct and encrypt the padding
454   if ( ASDCP_SUCCESS(result) )
455     {
456       byte_t the_last_block[CBC_BLOCK_SIZE];
457
458       if ( diff > 0 )
459         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
460
461       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
462         the_last_block[diff] = i;
463
464       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
465     }
466
467   if ( ASDCP_SUCCESS(result) )
468     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
469
470   return result;
471 }
472
473 //
474 Result_t
475 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
476 {
477   ASDCP_TEST_NULL(Ctx);
478   assert(FBout.Capacity() >= FBin.SourceLength());
479
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;
483   assert(block_size);
484   assert((block_size % CBC_BLOCK_SIZE) == 0);
485
486   const byte_t* buf = FBin.RoData();
487
488   // get ivec
489   Ctx->SetIVec(buf);
490   buf += CBC_BLOCK_SIZE;
491
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;
496
497   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
498     return RESULT_CHECKFAIL;
499
500   // copy plaintext region
501   if ( FBin.PlaintextOffset() > 0 )
502     {
503       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
504       buf += FBin.PlaintextOffset();
505     }
506
507   // decrypt all but last block
508   if ( ASDCP_SUCCESS(result) )
509     {
510       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
511       buf += block_size;
512     }
513
514   // decrypt last block
515   if ( ASDCP_SUCCESS(result) )
516     {
517       byte_t the_last_block[CBC_BLOCK_SIZE];
518       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
519
520       if ( the_last_block[diff] != 0 )
521         {
522           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
523           return RESULT_FORMAT;
524         }
525
526       if ( diff > 0 )
527         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
528     }
529
530   if ( ASDCP_SUCCESS(result) )
531     FBout.Size(FBin.SourceLength());
532
533   return result;
534 }
535
536
537 //
538 Result_t
539 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
540                                  ui32_t sequence, HMACContext* HMAC)
541 {
542   ASDCP_TEST_NULL(AssetID);
543   ASDCP_TEST_NULL(HMAC);
544   byte_t* p = Data;
545   HMAC->Reset();
546
547   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
548
549   // update HMAC with essence data
550   HMAC->Update(FB.RoData(), FB.Size());
551
552   // track file ID length
553   memcpy(p, ber_4, MXF_BER_LENGTH);
554   *(p+3) = UUIDlen;;
555   p += MXF_BER_LENGTH;
556
557   // track file ID
558   memcpy(p, AssetID, UUIDlen);
559   p += UUIDlen;
560
561   // sequence length
562   memcpy(p, ber_4, MXF_BER_LENGTH);
563   *(p+3) = sizeof(ui64_t);
564   p += MXF_BER_LENGTH;
565
566   // sequence number
567   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
568   p += sizeof(ui64_t);
569
570   // HMAC length
571   memcpy(p, ber_4, MXF_BER_LENGTH);
572   *(p+3) = HMAC_SIZE;
573   p += MXF_BER_LENGTH;
574
575   // update HMAC with intpack values
576   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
577
578   // finish & write HMAC
579   HMAC->Finalize();
580   HMAC->GetHMACValue(p);
581
582   assert(p + HMAC_SIZE == Data + klv_intpack_size);
583
584   return RESULT_OK;
585 }
586
587
588 Result_t
589 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
590                                  ui32_t sequence, HMACContext* HMAC)
591 {
592   ASDCP_TEST_NULL(AssetID);
593   ASDCP_TEST_NULL(HMAC);
594
595   // find the start of the intpack
596   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
597
598   // test the AssetID length
599   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
600         return RESULT_HMACFAIL;
601
602   // test the AssetID
603   if ( memcmp(p, AssetID, UUIDlen) != 0 )
604     {
605       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
606       return RESULT_HMACFAIL;
607     }
608   p += UUIDlen;
609   
610   // test the sequence length
611   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
612         return RESULT_HMACFAIL;
613
614   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
615
616   // test the sequence value
617   if ( test_sequence != sequence )
618     {
619       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
620       return RESULT_HMACFAIL;
621     }
622
623   p += sizeof(ui64_t);
624
625   // test the HMAC length
626   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
627         return RESULT_HMACFAIL;
628
629   // test the HMAC
630   HMAC->Reset();
631   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
632   HMAC->Finalize();
633
634   return HMAC->TestHMACValue(p);
635 }
636
637 //
638 // end AS_DCP_MXF.cpp
639 //