2 Copyright (c) 2004-2012, 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 h__Reader.cpp
28 \version $Id: h__Reader.cpp,v 1.31 2012/02/07 18:54:25 jhurst Exp $
29 \brief MXF file reader base class
32 #define DEFAULT_MD_DECL
33 #include "AS_DCP_internal.h"
36 using namespace ASDCP;
37 using namespace ASDCP::MXF;
39 static Kumu::Mutex sg_DefaultMDInitLock;
40 static bool sg_DefaultMDTypesInit = false;
41 static const ASDCP::Dictionary *sg_dict;
45 ASDCP::default_md_object_init()
47 if ( ! sg_DefaultMDTypesInit )
49 Kumu::AutoMutex BlockLock(sg_DefaultMDInitLock);
51 if ( ! sg_DefaultMDTypesInit )
53 sg_dict = &DefaultSMPTEDict();
54 g_OPAtomHeader = new ASDCP::MXF::OPAtomHeader(sg_dict);
55 g_OPAtomIndexFooter = new ASDCP::MXF::OPAtomIndexFooter(sg_dict);
56 sg_DefaultMDTypesInit = true;
63 ASDCP::h__Reader::h__Reader(const Dictionary& d) :
64 m_Dict(&d), m_HeaderPart(m_Dict), m_BodyPart(m_Dict), m_FooterPart(m_Dict), m_EssenceStart(0)
66 default_md_object_init();
69 ASDCP::h__Reader::~h__Reader()
75 ASDCP::h__Reader::Close()
80 //------------------------------------------------------------------------------------------
85 ASDCP::h__Reader::InitInfo()
88 InterchangeObject* Object;
90 m_Info.LabelSetType = LS_MXF_UNKNOWN;
92 if ( m_HeaderPart.OperationalPattern.ExactMatch(MXFInterop_OPAtom_Entry().ul) )
93 m_Info.LabelSetType = LS_MXF_INTEROP;
94 else if ( m_HeaderPart.OperationalPattern.ExactMatch(SMPTE_390_OPAtom_Entry().ul) )
95 m_Info.LabelSetType = LS_MXF_SMPTE;
98 Result_t result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(Identification), &Object);
100 if( ASDCP_SUCCESS(result) )
101 MD_to_WriterInfo((Identification*)Object, m_Info);
104 if( ASDCP_SUCCESS(result) )
105 result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(SourcePackage), &Object);
107 if( ASDCP_SUCCESS(result) )
109 SourcePackage* SP = (SourcePackage*)Object;
110 memcpy(m_Info.AssetUUID, SP->PackageUID.Value() + 16, UUIDlen);
113 // optional CryptographicContext
114 if( ASDCP_SUCCESS(result) )
116 Result_t cr_result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(CryptographicContext), &Object);
118 if( ASDCP_SUCCESS(cr_result) )
119 MD_to_CryptoInfo((CryptographicContext*)Object, m_Info, *m_Dict);
126 // standard method of opening an MXF file for read
128 ASDCP::h__Reader::OpenMXFRead(const char* filename)
131 Result_t result = m_File.OpenRead(filename);
133 if ( ASDCP_SUCCESS(result) )
134 result = m_HeaderPart.InitFromFile(m_File);
136 if ( ASDCP_SUCCESS(result) )
138 // if this is a three partition file, go to the body
139 // partition and read the partition pack
140 if ( m_HeaderPart.m_RIP.PairArray.size() > 2 )
142 Array<RIP::Pair>::iterator r_i = m_HeaderPart.m_RIP.PairArray.begin();
144 m_File.Seek((*r_i).ByteOffset);
146 result = m_BodyPart.InitFromFile(m_File);
149 m_EssenceStart = m_File.Tell();
156 // standard method of populating the in-memory index
158 ASDCP::h__Reader::InitMXFIndex()
160 if ( ! m_File.IsOpen() )
163 Result_t result = m_File.Seek(m_HeaderPart.FooterPartition);
165 if ( ASDCP_SUCCESS(result) )
167 m_FooterPart.m_Lookup = &m_HeaderPart.m_Primer;
168 result = m_FooterPart.InitFromFile(m_File);
171 if ( ASDCP_SUCCESS(result) )
172 m_File.Seek(m_EssenceStart);
179 ASDCP::KLReader::ReadKLFromFile(Kumu::FileReader& Reader)
182 ui32_t header_length = SMPTE_UL_LENGTH + MXF_BER_LENGTH;
183 Result_t result = Reader.Read(m_KeyBuf, header_length, &read_count);
185 if ( ASDCP_FAILURE(result) )
188 if ( read_count != header_length )
189 return RESULT_READFAIL;
191 const byte_t* ber_start = m_KeyBuf + SMPTE_UL_LENGTH;
193 if ( ( *ber_start & 0x80 ) == 0 )
195 DefaultLogSink().Error("BER encoding error.\n");
196 return RESULT_FORMAT;
199 ui8_t ber_size = ( *ber_start & 0x0f ) + 1;
203 DefaultLogSink().Error("BER size encoding error.\n");
204 return RESULT_FORMAT;
207 if ( ber_size < MXF_BER_LENGTH )
209 DefaultLogSink().Error("BER size %d shorter than AS-DCP minimum %d.\n",
210 ber_size, MXF_BER_LENGTH);
211 return RESULT_FORMAT;
214 if ( ber_size > MXF_BER_LENGTH )
216 ui32_t diff = ber_size - MXF_BER_LENGTH;
217 assert((SMPTE_UL_LENGTH + MXF_BER_LENGTH + diff) <= (SMPTE_UL_LENGTH * 2));
218 result = Reader.Read(m_KeyBuf + SMPTE_UL_LENGTH + MXF_BER_LENGTH, diff, &read_count);
220 if ( ASDCP_FAILURE(result) )
223 if ( read_count != diff )
224 return RESULT_READFAIL;
226 header_length += diff;
229 return InitFromBuffer(m_KeyBuf, header_length);
232 // standard method of reading a plaintext or encrypted frame
234 ASDCP::h__Reader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
235 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
237 // look up frame index node
238 IndexTableSegment::IndexEntry TmpEntry;
240 if ( ASDCP_FAILURE(m_FooterPart.Lookup(FrameNum, TmpEntry)) )
242 DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum);
246 // get frame position and go read the frame's key and length
247 Kumu::fpos_t FilePosition = m_EssenceStart + TmpEntry.StreamOffset;
248 Result_t result = RESULT_OK;
250 if ( FilePosition != m_LastPosition )
252 m_LastPosition = FilePosition;
253 result = m_File.Seek(FilePosition);
256 if( ASDCP_SUCCESS(result) )
257 result = ReadEKLVPacket(FrameNum, FrameNum + 1, FrameBuf, EssenceUL, Ctx, HMAC);
264 ASDCP::h__Reader::ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
265 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
268 Result_t result = Reader.ReadKLFromFile(m_File);
270 if ( ASDCP_FAILURE(result) )
273 UL Key(Reader.Key());
274 ui64_t PacketLength = Reader.Length();
275 m_LastPosition = m_LastPosition + Reader.KLLength() + PacketLength;
278 if ( Key.MatchIgnoreStream(m_Dict->ul(MDD_CryptEssence)) ) // ignore the stream numbers
280 if ( ! m_Info.EncryptedEssence )
282 DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n");
283 return RESULT_FORMAT;
286 // read encrypted triplet value into internal buffer
287 assert(PacketLength <= 0xFFFFFFFFL);
288 m_CtFrameBuf.Capacity((ui32_t) PacketLength);
290 result = m_File.Read(m_CtFrameBuf.Data(), (ui32_t) PacketLength,
293 if ( ASDCP_FAILURE(result) )
296 if ( read_count != PacketLength )
298 DefaultLogSink().Error("read length is smaller than EKLV packet length.\n");
299 return RESULT_FORMAT;
302 m_CtFrameBuf.Size((ui32_t) PacketLength);
304 // should be const but mxflib::ReadBER is not
305 byte_t* ess_p = m_CtFrameBuf.Data();
307 // read context ID length
308 if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) )
309 return RESULT_FORMAT;
311 // test the context ID
312 if ( memcmp(ess_p, m_Info.ContextID, UUIDlen) != 0 )
314 DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n");
315 return RESULT_FORMAT;
319 // read PlaintextOffset length
320 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
321 return RESULT_FORMAT;
323 ui32_t PlaintextOffset = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
324 ess_p += sizeof(ui64_t);
326 // read essence UL length
327 if ( ! Kumu::read_test_BER(&ess_p, SMPTE_UL_LENGTH) )
328 return RESULT_FORMAT;
331 if ( ! UL(ess_p).MatchIgnoreStream(EssenceUL) ) // ignore the stream number
333 char strbuf[IntBufferLen];
334 const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
336 DefaultLogSink().Warn("Unexpected Encrypted Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
338 DefaultLogSink().Warn("Unexpected Encrypted Essence UL found: %s.\n", Entry->name);
339 return RESULT_FORMAT;
341 ess_p += SMPTE_UL_LENGTH;
343 // read SourceLength length
344 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
345 return RESULT_FORMAT;
347 ui32_t SourceLength = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
348 ess_p += sizeof(ui64_t);
349 assert(SourceLength);
351 if ( FrameBuf.Capacity() < SourceLength )
353 DefaultLogSink().Error("FrameBuf.Capacity: %u SourceLength: %u\n", FrameBuf.Capacity(), SourceLength);
354 return RESULT_SMALLBUF;
357 ui32_t esv_length = calc_esv_length(SourceLength, PlaintextOffset);
360 if ( ! Kumu::read_test_BER(&ess_p, esv_length) )
362 DefaultLogSink().Error("read_test_BER did not return %u\n", esv_length);
363 return RESULT_FORMAT;
366 ui32_t tmp_len = esv_length + (m_Info.UsesHMAC ? klv_intpack_size : 0);
368 if ( PacketLength < tmp_len )
370 DefaultLogSink().Error("Frame length is larger than EKLV packet length.\n");
371 return RESULT_FORMAT;
376 // wrap the pointer and length as a FrameBuffer for use by
377 // DecryptFrameBuffer() and TestValues()
378 FrameBuffer TmpWrapper;
379 TmpWrapper.SetData(ess_p, tmp_len);
380 TmpWrapper.Size(tmp_len);
381 TmpWrapper.SourceLength(SourceLength);
382 TmpWrapper.PlaintextOffset(PlaintextOffset);
384 result = DecryptFrameBuffer(TmpWrapper, FrameBuf, Ctx);
385 FrameBuf.FrameNumber(FrameNum);
387 // detect and test integrity pack
388 if ( ASDCP_SUCCESS(result) && m_Info.UsesHMAC && HMAC )
390 IntegrityPack IntPack;
391 result = IntPack.TestValues(TmpWrapper, m_Info.AssetUUID, SequenceNum, HMAC);
394 else // return ciphertext to caller
396 if ( FrameBuf.Capacity() < tmp_len )
398 char intbuf[IntBufferLen];
399 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
400 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
401 return RESULT_SMALLBUF;
404 memcpy(FrameBuf.Data(), ess_p, tmp_len);
405 FrameBuf.Size(tmp_len);
406 FrameBuf.FrameNumber(FrameNum);
407 FrameBuf.SourceLength(SourceLength);
408 FrameBuf.PlaintextOffset(PlaintextOffset);
411 else if ( Key.MatchIgnoreStream(EssenceUL) ) // ignore the stream number
412 { // read plaintext frame
413 if ( FrameBuf.Capacity() < PacketLength )
415 char intbuf[IntBufferLen];
416 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
417 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
418 return RESULT_SMALLBUF;
421 // read the data into the supplied buffer
423 assert(PacketLength <= 0xFFFFFFFFL);
424 result = m_File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
426 if ( ASDCP_FAILURE(result) )
429 if ( read_count != PacketLength )
431 char intbuf1[IntBufferLen];
432 char intbuf2[IntBufferLen];
433 DefaultLogSink().Error("read_count: %s != FrameLength: %s\n",
434 ui64sz(read_count, intbuf1),
435 ui64sz(PacketLength, intbuf2) );
437 return RESULT_READFAIL;
440 FrameBuf.FrameNumber(FrameNum);
441 FrameBuf.Size(read_count);
445 char strbuf[IntBufferLen];
446 const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
448 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
450 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
451 return RESULT_FORMAT;