2 Copyright (c) 2004-2013, 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
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 InterchangeObject* Object;
85 m_Info.LabelSetType = LS_MXF_UNKNOWN;
87 if ( m_HeaderPart.OperationalPattern.ExactMatch(MXFInterop_OPAtom_Entry().ul) )
89 m_Info.LabelSetType = LS_MXF_INTEROP;
91 else if ( m_HeaderPart.OperationalPattern.ExactMatch(SMPTE_390_OPAtom_Entry().ul) )
93 m_Info.LabelSetType = LS_MXF_SMPTE;
97 char strbuf[IdentBufferLen];
98 const MDDEntry* Entry = m_Dict->FindUL(m_HeaderPart.OperationalPattern.Value());
102 DefaultLogSink().Warn("Operational pattern is not OP-Atom: %s\n",
103 m_HeaderPart.OperationalPattern.EncodeString(strbuf, IdentBufferLen));
107 DefaultLogSink().Warn("Operational pattern is not OP-Atom: %s\n", Entry->name);
111 if ( m_RIP.PairArray.front().ByteOffset != 0 )
113 DefaultLogSink().Error("First Partition in RIP is not at offset 0.\n");
114 result = RESULT_FORMAT;
118 if ( m_RIP.PairArray.size() < 2 )
120 // OP-Atom states that there will be either two or three partitions:
121 // one closed header and one closed footer with an optional body
122 // SMPTE 429-5 files may have many partitions, see SMPTE ST 410.
123 DefaultLogSink().Warn("RIP entry count is less than 2: %u\n", m_RIP.PairArray.size());
125 else if ( m_RIP.PairArray.size() > 2 )
127 // if this is a three partition file, go to the body
128 // partition and read the partition pack
129 Array<RIP::Pair>::iterator r_i = m_RIP.PairArray.begin();
131 m_File.Seek((*r_i).ByteOffset);
132 result = m_BodyPart.InitFromFile(m_File);
134 if( ASDCP_FAILURE(result) )
136 DefaultLogSink().Error("ASDCP::h__ASDCPReader::OpenMXFRead, m_BodyPart.InitFromFile failed\n");
141 if ( KM_SUCCESS(result) )
143 // this position will be at either
144 // a) the spot in the header partition where essence units appear, or
145 // b) right after the body partition header (where essence units appear)
146 m_HeaderPart.BodyOffset = m_File.Tell();
148 result = m_File.Seek(m_HeaderPart.FooterPartition);
150 if ( ASDCP_SUCCESS(result) )
152 m_IndexAccess.m_Lookup = &m_HeaderPart.m_Primer;
153 result = m_IndexAccess.InitFromFile(m_File);
157 m_File.Seek(m_HeaderPart.BodyOffset);
161 // AS-DCP method of reading a plaintext or encrypted frame
163 ASDCP::h__ASDCPReader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
164 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
166 return ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::ReadEKLVFrame(m_HeaderPart.BodyOffset, FrameNum, FrameBuf,
167 EssenceUL, Ctx, HMAC);
171 ASDCP::h__ASDCPReader::LocateFrame(ui32_t FrameNum, Kumu::fpos_t& streamOffset,
172 i8_t& temporalOffset, i8_t& keyFrameOffset)
174 return ASDCP::MXF::TrackFileReader<OP1aHeader, OPAtomIndexFooter>::LocateFrame(m_HeaderPart.BodyOffset, FrameNum,
175 streamOffset, temporalOffset, keyFrameOffset);
179 //------------------------------------------------------------------------------------------
185 ASDCP::KLReader::ReadKLFromFile(Kumu::FileReader& Reader)
188 ui32_t header_length = SMPTE_UL_LENGTH + MXF_BER_LENGTH;
189 Result_t result = Reader.Read(m_KeyBuf, header_length, &read_count);
191 if ( ASDCP_FAILURE(result) )
194 if ( read_count != header_length )
195 return RESULT_READFAIL;
197 const byte_t* ber_start = m_KeyBuf + SMPTE_UL_LENGTH;
199 if ( ( *ber_start & 0x80 ) == 0 )
201 DefaultLogSink().Error("BER encoding error.\n");
202 return RESULT_FORMAT;
205 ui8_t ber_size = ( *ber_start & 0x0f ) + 1;
209 DefaultLogSink().Error("BER size encoding error.\n");
210 return RESULT_FORMAT;
213 if ( ber_size < MXF_BER_LENGTH )
215 DefaultLogSink().Error("BER size %d shorter than AS-DCP/AS-02 minimum %d.\n",
216 ber_size, MXF_BER_LENGTH);
217 return RESULT_FORMAT;
220 if ( ber_size > MXF_BER_LENGTH )
222 ui32_t diff = ber_size - MXF_BER_LENGTH;
223 assert((SMPTE_UL_LENGTH + MXF_BER_LENGTH + diff) <= (SMPTE_UL_LENGTH * 2));
224 result = Reader.Read(m_KeyBuf + SMPTE_UL_LENGTH + MXF_BER_LENGTH, diff, &read_count);
226 if ( ASDCP_FAILURE(result) )
229 if ( read_count != diff )
230 return RESULT_READFAIL;
232 header_length += diff;
235 return InitFromBuffer(m_KeyBuf, header_length);
239 //------------------------------------------------------------------------------------------
243 // base subroutine for reading a KLV packet, assumes file position is at the first byte of the packet
245 ASDCP::Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict,
246 const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf,
247 ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
248 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
251 Result_t result = Reader.ReadKLFromFile(File);
253 if ( KM_FAILURE(result) )
256 UL Key(Reader.Key());
257 ui64_t PacketLength = Reader.Length();
258 LastPosition = LastPosition + Reader.KLLength() + PacketLength;
260 if ( Key.MatchIgnoreStream(Dict.ul(MDD_CryptEssence)) ) // ignore the stream numbers
262 if ( ! Info.EncryptedEssence )
264 DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n");
265 return RESULT_FORMAT;
268 // read encrypted triplet value into internal buffer
269 assert(PacketLength <= 0xFFFFFFFFL);
270 CtFrameBuf.Capacity((ui32_t) PacketLength);
272 result = File.Read(CtFrameBuf.Data(), (ui32_t) PacketLength, &read_count);
274 if ( ASDCP_FAILURE(result) )
277 if ( read_count != PacketLength )
279 DefaultLogSink().Error("read length is smaller than EKLV packet length.\n");
280 return RESULT_FORMAT;
283 CtFrameBuf.Size((ui32_t) PacketLength);
285 // should be const but mxflib::ReadBER is not
286 byte_t* ess_p = CtFrameBuf.Data();
288 // read context ID length
289 if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) )
290 return RESULT_FORMAT;
292 // test the context ID
293 if ( memcmp(ess_p, Info.ContextID, UUIDlen) != 0 )
295 DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n");
296 return RESULT_FORMAT;
300 // read PlaintextOffset length
301 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
302 return RESULT_FORMAT;
304 ui32_t PlaintextOffset = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
305 ess_p += sizeof(ui64_t);
307 // read essence UL length
308 if ( ! Kumu::read_test_BER(&ess_p, SMPTE_UL_LENGTH) )
309 return RESULT_FORMAT;
312 if ( ! UL(ess_p).MatchIgnoreStream(EssenceUL) ) // ignore the stream number
314 char strbuf[IntBufferLen];
315 const MDDEntry* Entry = Dict.FindUL(Key.Value());
319 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
323 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
326 return RESULT_FORMAT;
329 ess_p += SMPTE_UL_LENGTH;
331 // read SourceLength length
332 if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
333 return RESULT_FORMAT;
335 ui32_t SourceLength = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
336 ess_p += sizeof(ui64_t);
337 assert(SourceLength);
339 if ( FrameBuf.Capacity() < SourceLength )
341 DefaultLogSink().Error("FrameBuf.Capacity: %u SourceLength: %u\n", FrameBuf.Capacity(), SourceLength);
342 return RESULT_SMALLBUF;
345 ui32_t esv_length = calc_esv_length(SourceLength, PlaintextOffset);
348 if ( ! Kumu::read_test_BER(&ess_p, esv_length) )
350 DefaultLogSink().Error("read_test_BER did not return %u\n", esv_length);
351 return RESULT_FORMAT;
354 ui32_t tmp_len = esv_length + (Info.UsesHMAC ? klv_intpack_size : 0);
356 if ( PacketLength < tmp_len )
358 DefaultLogSink().Error("Frame length is larger than EKLV packet length.\n");
359 return RESULT_FORMAT;
364 // wrap the pointer and length as a FrameBuffer for use by
365 // DecryptFrameBuffer() and TestValues()
366 FrameBuffer TmpWrapper;
367 TmpWrapper.SetData(ess_p, tmp_len);
368 TmpWrapper.Size(tmp_len);
369 TmpWrapper.SourceLength(SourceLength);
370 TmpWrapper.PlaintextOffset(PlaintextOffset);
372 result = DecryptFrameBuffer(TmpWrapper, FrameBuf, Ctx);
373 FrameBuf.FrameNumber(FrameNum);
375 // detect and test integrity pack
376 if ( ASDCP_SUCCESS(result) && Info.UsesHMAC && HMAC )
378 IntegrityPack IntPack;
379 result = IntPack.TestValues(TmpWrapper, Info.AssetUUID, SequenceNum, HMAC);
382 else // return ciphertext to caller
384 if ( FrameBuf.Capacity() < tmp_len )
386 char intbuf[IntBufferLen];
387 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
388 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
389 return RESULT_SMALLBUF;
392 memcpy(FrameBuf.Data(), ess_p, tmp_len);
393 FrameBuf.Size(tmp_len);
394 FrameBuf.FrameNumber(FrameNum);
395 FrameBuf.SourceLength(SourceLength);
396 FrameBuf.PlaintextOffset(PlaintextOffset);
399 else if ( Key.MatchIgnoreStream(EssenceUL) ) // ignore the stream number
400 { // read plaintext frame
401 if ( FrameBuf.Capacity() < PacketLength )
403 char intbuf[IntBufferLen];
404 DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
405 FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
406 return RESULT_SMALLBUF;
409 // read the data into the supplied buffer
411 assert(PacketLength <= 0xFFFFFFFFL);
412 result = File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
414 if ( ASDCP_FAILURE(result) )
417 if ( read_count != PacketLength )
419 char intbuf1[IntBufferLen];
420 char intbuf2[IntBufferLen];
421 DefaultLogSink().Error("read_count: %s != FrameLength: %s\n",
422 ui64sz(read_count, intbuf1),
423 ui64sz(PacketLength, intbuf2) );
425 return RESULT_READFAIL;
428 FrameBuf.FrameNumber(FrameNum);
429 FrameBuf.Size(read_count);
433 char strbuf[IntBufferLen];
434 const MDDEntry* Entry = Dict.FindUL(Key.Value());
438 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
442 DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
445 return RESULT_FORMAT;