2 Copyright (c) 2004-2015, 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.39 2015/10/09 23:41:11 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 = 0;
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_OP1aHeader = new ASDCP::MXF::OP1aHeader(sg_dict);
55 g_OPAtomIndexFooter = new ASDCP::MXF::OPAtomIndexFooter(sg_dict);
56 g_RIP = new ASDCP::MXF::RIP(sg_dict);
57 sg_DefaultMDTypesInit = true;
63 //------------------------------------------------------------------------------------------
67 ASDCP::h__ASDCPReader::h__ASDCPReader(const Dictionary& d) : MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>(d), m_BodyPart(m_Dict) {}
68 ASDCP::h__ASDCPReader::~h__ASDCPReader() {}
71 // AS-DCP method of opening an MXF file for read
73 ASDCP::h__ASDCPReader::OpenMXFRead(const std::string& filename)
75 Result_t result = ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::OpenMXFRead(filename);
77 if ( KM_SUCCESS(result) )
78 result = ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::InitInfo();
80 if( KM_SUCCESS(result) )
83 m_Info.LabelSetType = LS_MXF_UNKNOWN;
85 if ( m_HeaderPart.OperationalPattern.ExactMatch(MXFInterop_OPAtom_Entry().ul) )
87 m_Info.LabelSetType = LS_MXF_INTEROP;
89 else if ( m_HeaderPart.OperationalPattern.ExactMatch(SMPTE_390_OPAtom_Entry().ul) )
91 m_Info.LabelSetType = LS_MXF_SMPTE;
95 char strbuf[IdentBufferLen];
96 const MDDEntry* Entry = m_Dict->FindUL(m_HeaderPart.OperationalPattern.Value());
100 DefaultLogSink().Warn("Operational pattern is not OP-Atom: %s\n",
101 m_HeaderPart.OperationalPattern.EncodeString(strbuf, IdentBufferLen));
105 DefaultLogSink().Warn("Operational pattern is not OP-Atom: %s\n", Entry->name);
109 if ( m_RIP.PairArray.front().ByteOffset != 0 )
111 DefaultLogSink().Error("First Partition in RIP is not at offset 0.\n");
112 result = RESULT_FORMAT;
116 if ( m_RIP.PairArray.size() < 2 )
118 // OP-Atom states that there will be either two or three partitions:
119 // one closed header and one closed footer with an optional body
120 // SMPTE 429-5 files may have many partitions, see SMPTE ST 410.
121 DefaultLogSink().Warn("RIP entry count is less than 2: %u\n", m_RIP.PairArray.size());
123 else if ( m_RIP.PairArray.size() > 2 )
125 // if this is a three partition file, go to the body
126 // partition and read the partition pack
127 RIP::const_pair_iterator r_i = m_RIP.PairArray.begin();
129 m_File.Seek((*r_i).ByteOffset);
130 result = m_BodyPart.InitFromFile(m_File);
132 if( ASDCP_FAILURE(result) )
134 DefaultLogSink().Error("ASDCP::h__ASDCPReader::OpenMXFRead, m_BodyPart.InitFromFile failed\n");
139 if ( KM_SUCCESS(result) )
141 // this position will be at either
142 // a) the spot in the header partition where essence units appear, or
143 // b) right after the body partition header (where essence units appear)
144 m_HeaderPart.BodyOffset = m_File.Tell();
146 result = m_File.Seek(m_HeaderPart.FooterPartition);
148 if ( ASDCP_SUCCESS(result) )
150 m_IndexAccess.m_Lookup = &m_HeaderPart.m_Primer;
151 result = m_IndexAccess.InitFromFile(m_File);
155 m_File.Seek(m_HeaderPart.BodyOffset);
159 // AS-DCP method of reading a plaintext or encrypted frame
161 ASDCP::h__ASDCPReader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
162 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
164 return ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::ReadEKLVFrame(m_HeaderPart.BodyOffset, FrameNum, FrameBuf,
165 EssenceUL, Ctx, HMAC);
169 ASDCP::h__ASDCPReader::LocateFrame(ui32_t FrameNum, Kumu::fpos_t& streamOffset,
170 i8_t& temporalOffset, i8_t& keyFrameOffset)
172 return ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::LocateFrame(m_HeaderPart.BodyOffset, FrameNum,
173 streamOffset, temporalOffset, keyFrameOffset);
177 //------------------------------------------------------------------------------------------
183 ASDCP::KLReader::ReadKLFromFile(Kumu::FileReader& Reader)
186 ui32_t header_length = SMPTE_UL_LENGTH + MXF_BER_LENGTH;
187 Result_t result = Reader.Read(m_KeyBuf, header_length, &read_count);
189 if ( ASDCP_FAILURE(result) )
192 if ( read_count != header_length )
193 return RESULT_READFAIL;
195 const byte_t* ber_start = m_KeyBuf + SMPTE_UL_LENGTH;
197 if ( ( *ber_start & 0x80 ) == 0 )
199 DefaultLogSink().Error("BER encoding error.\n");
200 return RESULT_FORMAT;
203 ui8_t ber_size = ( *ber_start & 0x0f ) + 1;
207 DefaultLogSink().Error("BER size encoding error.\n");
208 return RESULT_FORMAT;
211 if ( ber_size < MXF_BER_LENGTH )
213 DefaultLogSink().Error("BER size %d shorter than AS-DCP/AS-02 minimum %d.\n",
214 ber_size, MXF_BER_LENGTH);
215 return RESULT_FORMAT;
218 if ( ber_size > MXF_BER_LENGTH )
220 ui32_t diff = ber_size - MXF_BER_LENGTH;
221 assert((SMPTE_UL_LENGTH + MXF_BER_LENGTH + diff) <= (SMPTE_UL_LENGTH * 2));
222 result = Reader.Read(m_KeyBuf + SMPTE_UL_LENGTH + MXF_BER_LENGTH, diff, &read_count);
224 if ( ASDCP_FAILURE(result) )
227 if ( read_count != diff )
228 return RESULT_READFAIL;
230 header_length += diff;
233 return InitFromBuffer(m_KeyBuf, header_length);
237 //------------------------------------------------------------------------------------------
241 // base subroutine for reading a KLV packet, assumes file position is at the first byte of the packet
243 ASDCP::Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict,
244 const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf,
245 ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
246 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
249 Result_t result = Reader.ReadKLFromFile(File);
251 if ( KM_FAILURE(result) )
254 UL Key(Reader.Key());
255 ui64_t PacketLength = Reader.Length();
256 LastPosition = LastPosition + Reader.KLLength() + PacketLength;
258 if ( Key.MatchIgnoreStream(Dict.ul(MDD_CryptEssence)) ) // ignore the stream numbers
260 if ( ! Info.EncryptedEssence )
262 DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n");
263 return RESULT_FORMAT;
266 // read encrypted triplet value into internal buffer
267 assert(PacketLength <= 0xFFFFFFFFL);
268 CtFrameBuf.Capacity((ui32_t) PacketLength);
270 result = File.Read(CtFrameBuf.Data(), (ui32_t) PacketLength, &read_count);
272 if ( ASDCP_FAILURE(result) )
275 if ( read_count != PacketLength )
277 DefaultLogSink().Error("read length is smaller than EKLV packet length.\n");
278 return RESULT_FORMAT;
281 CtFrameBuf.Size((ui32_t) PacketLength);
283 // should be const but mxflib::ReadBER is not
284 byte_t* ess_p = CtFrameBuf.Data();
286 // read context ID length
287 if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) )
288 return RESULT_FORMAT;
290 // test the context ID
291 if ( memcmp(ess_p, Info.ContextID, UUIDlen) != 0 )
293 DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n");
294 return RESULT_FORMAT;
298 // read PlaintextOffset length
299 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
300 return RESULT_FORMAT;
302 ui32_t PlaintextOffset = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
303 ess_p += sizeof(ui64_t);
305 // read essence UL length
306 if ( ! Kumu::read_test_BER(&ess_p, SMPTE_UL_LENGTH) )
307 return RESULT_FORMAT;
310 if ( ! UL(ess_p).MatchIgnoreStream(EssenceUL) ) // ignore the stream number
312 char strbuf[IntBufferLen];
313 const MDDEntry* Entry = Dict.FindUL(Key.Value());
317 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
321 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
324 return RESULT_FORMAT;
327 ess_p += SMPTE_UL_LENGTH;
329 // read SourceLength length
330 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
331 return RESULT_FORMAT;
333 ui32_t SourceLength = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
334 ess_p += sizeof(ui64_t);
335 assert(SourceLength);
337 if ( FrameBuf.Capacity() < SourceLength )
339 DefaultLogSink().Error("FrameBuf.Capacity: %u SourceLength: %u\n", FrameBuf.Capacity(), SourceLength);
340 return RESULT_SMALLBUF;
343 ui32_t esv_length = calc_esv_length(SourceLength, PlaintextOffset);
346 if ( ! Kumu::read_test_BER(&ess_p, esv_length) )
348 DefaultLogSink().Error("read_test_BER did not return %u\n", esv_length);
349 return RESULT_FORMAT;
352 ui32_t tmp_len = esv_length + (Info.UsesHMAC ? klv_intpack_size : 0);
354 if ( PacketLength < tmp_len )
356 DefaultLogSink().Error("Frame length is larger than EKLV packet length.\n");
357 return RESULT_FORMAT;
362 // wrap the pointer and length as a FrameBuffer for use by
363 // DecryptFrameBuffer() and TestValues()
364 FrameBuffer TmpWrapper;
365 TmpWrapper.SetData(ess_p, tmp_len);
366 TmpWrapper.Size(tmp_len);
367 TmpWrapper.SourceLength(SourceLength);
368 TmpWrapper.PlaintextOffset(PlaintextOffset);
370 result = DecryptFrameBuffer(TmpWrapper, FrameBuf, Ctx);
371 FrameBuf.FrameNumber(FrameNum);
373 // detect and test integrity pack
374 if ( ASDCP_SUCCESS(result) && Info.UsesHMAC && HMAC )
376 IntegrityPack IntPack;
377 result = IntPack.TestValues(TmpWrapper, Info.AssetUUID, SequenceNum, HMAC);
380 else // return ciphertext to caller
382 if ( FrameBuf.Capacity() < tmp_len )
384 char intbuf[IntBufferLen];
385 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
386 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
387 return RESULT_SMALLBUF;
390 memcpy(FrameBuf.Data(), ess_p, tmp_len);
391 FrameBuf.Size(tmp_len);
392 FrameBuf.FrameNumber(FrameNum);
393 FrameBuf.SourceLength(SourceLength);
394 FrameBuf.PlaintextOffset(PlaintextOffset);
397 else if ( Key.MatchIgnoreStream(EssenceUL) ) // ignore the stream number
398 { // read plaintext frame
399 if ( FrameBuf.Capacity() < PacketLength )
401 char intbuf[IntBufferLen];
402 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
403 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
404 return RESULT_SMALLBUF;
407 // read the data into the supplied buffer
409 assert(PacketLength <= 0xFFFFFFFFL);
410 result = File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
412 if ( ASDCP_FAILURE(result) )
415 if ( read_count != PacketLength )
417 char intbuf1[IntBufferLen];
418 char intbuf2[IntBufferLen];
419 DefaultLogSink().Error("read_count: %s != FrameLength: %s\n",
420 ui64sz(read_count, intbuf1),
421 ui64sz(PacketLength, intbuf2) );
423 return RESULT_READFAIL;
426 FrameBuf.FrameNumber(FrameNum);
427 FrameBuf.Size(read_count);
431 char strbuf[IntBufferLen];
432 const MDDEntry* Entry = Dict.FindUL(Key.Value());
436 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
440 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
443 return RESULT_FORMAT;