Changed default UNIX file permissions to "666"
[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 ( std::string((const char*)FB.RoData() + 8, 4) == "WAVE" )
314             {
315               if ( std::string((const char*)FB.RoData(), 4) == "RIFF" )
316                 {
317                   result = WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
318
319                   if ( ASDCP_SUCCESS(result) )
320                     {
321                       switch ( WavHeader.samplespersec )
322                         {
323                         case 48000: type = ESS_PCM_24b_48k; break;
324                         case 96000: type = ESS_PCM_24b_96k; break;
325                         default:
326                           DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
327                           result = RESULT_FORMAT;
328                         }
329                     }
330                 }
331               else
332                 {
333                   result = RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset);
334                 
335                   if ( ASDCP_SUCCESS(result) )
336                     {
337                       switch ( RF64Header.samplespersec )
338                         {
339                         case 48000: type = ESS_PCM_24b_48k; break;
340                         case 96000: type = ESS_PCM_24b_96k; break;
341                         default:
342                           DefaultLogSink().Error("Unexpected sample rate: %d\n", WavHeader.samplespersec);
343                           result = RESULT_FORMAT;
344                         }
345                     }
346                 }
347             }
348           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
349             {
350               type = ESS_PCM_24b_48k;
351             }
352           else if ( string_is_xml(FB) )
353             {
354               type = ESS_TIMED_TEXT;
355             }
356           else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) )
357             {
358               type = ESS_DCDATA_DOLBY_ATMOS;
359             }
360         }
361     }
362   else if ( Kumu::PathIsDirectory(filename) )
363     {
364       char next_file[Kumu::MaxFilePath];
365       Kumu::DirScanner Scanner;
366       Result_t result = Scanner.Open(filename);
367
368       if ( ASDCP_SUCCESS(result) )
369         {
370           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
371             {
372               if ( next_file[0] == '.' ) // no hidden files or internal links
373                 continue;
374
375               result = Reader.OpenRead(Kumu::PathJoin(filename, next_file));
376
377               if ( ASDCP_SUCCESS(result) )
378                 {
379                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
380                   Reader.Close();
381                 }
382
383               if ( ASDCP_SUCCESS(result) )
384                 {
385                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
386                     {
387                       type = ESS_JPEG_2000;
388                     }
389                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
390                     {
391                       switch ( WavHeader.samplespersec )
392                         {
393                         case 48000: type = ESS_PCM_24b_48k; break;
394                         case 96000: type = ESS_PCM_24b_96k; break;
395                         default:
396                           return RESULT_FORMAT;
397                         }
398                     }
399                   else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
400                     {
401                       switch ( RF64Header.samplespersec )
402                         {
403                         case 48000: type = ESS_PCM_24b_48k; break;
404                         case 96000: type = ESS_PCM_24b_96k; break;
405                         default:
406                           return RESULT_FORMAT;
407                         }
408                     }
409                   else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) )
410                     {
411                       type = ESS_DCDATA_DOLBY_ATMOS;
412                     }
413                   else
414                     {
415                       type = ESS_DCDATA_UNKNOWN;
416                     }
417                 }
418               
419               break;
420             }
421         }
422     }
423
424   return result;
425 }
426
427 //
428 Result_t
429 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
430 {
431   ASDCP_TEST_NULL(Ctx);
432   FBout.Size(0);
433
434   // size the buffer
435   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
436
437   // write the IV
438   byte_t* p = FBout.Data();
439
440   // write the IV to the frame buffer
441   Ctx->GetIVec(p);
442   p += CBC_BLOCK_SIZE;
443
444
445   // encrypt the check value to the frame buffer
446   if ( ASDCP_SUCCESS(result) )
447     {
448       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
449       p += CBC_BLOCK_SIZE;
450     }
451
452   // write optional plaintext region
453   if ( FBin.PlaintextOffset() > 0 )
454     {
455       assert(FBin.PlaintextOffset() <= FBin.Size());
456       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
457       p += FBin.PlaintextOffset();
458     }
459
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);
464
465   // encrypt the ciphertext region essence data
466   if ( ASDCP_SUCCESS(result) )
467     {
468       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
469       p += block_size;
470     }
471
472   // construct and encrypt the padding
473   if ( ASDCP_SUCCESS(result) )
474     {
475       byte_t the_last_block[CBC_BLOCK_SIZE];
476
477       if ( diff > 0 )
478         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
479
480       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
481         the_last_block[diff] = i;
482
483       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
484     }
485
486   if ( ASDCP_SUCCESS(result) )
487     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
488
489   return result;
490 }
491
492 //
493 Result_t
494 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
495 {
496   ASDCP_TEST_NULL(Ctx);
497   assert(FBout.Capacity() >= FBin.SourceLength());
498
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;
502   assert(block_size);
503   assert((block_size % CBC_BLOCK_SIZE) == 0);
504
505   const byte_t* buf = FBin.RoData();
506
507   // get ivec
508   Ctx->SetIVec(buf);
509   buf += CBC_BLOCK_SIZE;
510
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;
515
516   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
517     return RESULT_CHECKFAIL;
518
519   // copy plaintext region
520   if ( FBin.PlaintextOffset() > 0 )
521     {
522       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
523       buf += FBin.PlaintextOffset();
524     }
525
526   // decrypt all but last block
527   if ( ASDCP_SUCCESS(result) )
528     {
529       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
530       buf += block_size;
531     }
532
533   // decrypt last block
534   if ( ASDCP_SUCCESS(result) )
535     {
536       byte_t the_last_block[CBC_BLOCK_SIZE];
537       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
538
539       if ( the_last_block[diff] != 0 )
540         {
541           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
542           return RESULT_FORMAT;
543         }
544
545       if ( diff > 0 )
546         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
547     }
548
549   if ( ASDCP_SUCCESS(result) )
550     FBout.Size(FBin.SourceLength());
551
552   return result;
553 }
554
555
556 //
557 Result_t
558 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
559                                  ui32_t sequence, HMACContext* HMAC)
560 {
561   ASDCP_TEST_NULL(AssetID);
562   ASDCP_TEST_NULL(HMAC);
563   byte_t* p = Data;
564   HMAC->Reset();
565
566   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
567
568   // update HMAC with essence data
569   HMAC->Update(FB.RoData(), FB.Size());
570
571   // track file ID length
572   memcpy(p, ber_4, MXF_BER_LENGTH);
573   *(p+3) = UUIDlen;;
574   p += MXF_BER_LENGTH;
575
576   // track file ID
577   memcpy(p, AssetID, UUIDlen);
578   p += UUIDlen;
579
580   // sequence length
581   memcpy(p, ber_4, MXF_BER_LENGTH);
582   *(p+3) = sizeof(ui64_t);
583   p += MXF_BER_LENGTH;
584
585   // sequence number
586   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
587   p += sizeof(ui64_t);
588
589   // HMAC length
590   memcpy(p, ber_4, MXF_BER_LENGTH);
591   *(p+3) = HMAC_SIZE;
592   p += MXF_BER_LENGTH;
593
594   // update HMAC with intpack values
595   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
596
597   // finish & write HMAC
598   HMAC->Finalize();
599   HMAC->GetHMACValue(p);
600
601   assert(p + HMAC_SIZE == Data + klv_intpack_size);
602
603   return RESULT_OK;
604 }
605
606
607 Result_t
608 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID,
609                                  ui32_t sequence, HMACContext* HMAC)
610 {
611   ASDCP_TEST_NULL(AssetID);
612   ASDCP_TEST_NULL(HMAC);
613
614   // find the start of the intpack
615   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
616
617   // test the AssetID length
618   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
619         return RESULT_HMACFAIL;
620
621   // test the AssetID
622   if ( memcmp(p, AssetID, UUIDlen) != 0 )
623     {
624       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
625       return RESULT_HMACFAIL;
626     }
627   p += UUIDlen;
628   
629   // test the sequence length
630   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
631         return RESULT_HMACFAIL;
632
633   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
634
635   // test the sequence value
636   if ( test_sequence != sequence )
637     {
638       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
639       return RESULT_HMACFAIL;
640     }
641
642   p += sizeof(ui64_t);
643
644   // test the HMAC length
645   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
646         return RESULT_HMACFAIL;
647
648   // test the HMAC
649   HMAC->Reset();
650   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
651   HMAC->Finalize();
652
653   return HMAC->TestHMACValue(p);
654 }
655
656 //
657 // end AS_DCP_MXF.cpp
658 //