harder than it looks
[asdcplib.git] / src / h__Reader.cpp
1 /*
2 Copyright (c) 2004-2009, 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    h__Reader.cpp
28     \version $Id$
29     \brief   MXF file reader base class
30 */
31
32 #include "AS_DCP_internal.h"
33 #include "KLV.h"
34
35 using namespace ASDCP;
36 using namespace ASDCP::MXF;
37
38 //
39 ASDCP::h__Reader::h__Reader(const Dictionary& d) :
40   m_HeaderPart(m_Dict), m_BodyPart(m_Dict), m_FooterPart(m_Dict), m_Dict(&d), m_EssenceStart(0)
41 {
42 }
43
44 ASDCP::h__Reader::~h__Reader()
45 {
46   Close();
47 }
48
49 void
50 ASDCP::h__Reader::Close()
51 {
52   m_File.Close();
53 }
54
55 //------------------------------------------------------------------------------------------
56 //
57
58 //
59 Result_t
60 ASDCP::h__Reader::InitInfo()
61 {
62   assert(m_Dict);
63   InterchangeObject* Object;
64
65   m_Info.LabelSetType = LS_MXF_UNKNOWN;
66   UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
67   UL Interop_OPAtomUL(m_Dict->ul(MDD_MXFInterop_OPAtom));
68
69   if ( m_HeaderPart.OperationalPattern == Interop_OPAtomUL )
70     m_Info.LabelSetType = LS_MXF_INTEROP;
71   else if ( m_HeaderPart.OperationalPattern == OPAtomUL )
72     m_Info.LabelSetType = LS_MXF_SMPTE;
73
74   // Identification
75   Result_t result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(Identification), &Object);
76
77   if( ASDCP_SUCCESS(result) )
78     MD_to_WriterInfo((Identification*)Object, m_Info);
79
80   // SourcePackage
81   if( ASDCP_SUCCESS(result) )
82     result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(SourcePackage), &Object);
83
84   if( ASDCP_SUCCESS(result) )
85     {
86       SourcePackage* SP = (SourcePackage*)Object;
87       memcpy(m_Info.AssetUUID, SP->PackageUID.Value() + 16, UUIDlen);
88     }
89
90   // optional CryptographicContext
91   if( ASDCP_SUCCESS(result) )
92     {
93       Result_t cr_result = m_HeaderPart.GetMDObjectByType(OBJ_TYPE_ARGS(CryptographicContext), &Object);
94
95       if( ASDCP_SUCCESS(cr_result) )
96         MD_to_CryptoInfo((CryptographicContext*)Object, m_Info, *m_Dict);
97     }
98
99   return result;
100 }
101
102
103 // standard method of opening an MXF file for read
104 Result_t
105 ASDCP::h__Reader::OpenMXFRead(const char* filename)
106 {
107   m_LastPosition = 0;
108   Result_t result = m_File.OpenRead(filename);
109
110   if ( ASDCP_SUCCESS(result) )
111     result = m_HeaderPart.InitFromFile(m_File);
112
113   if ( ASDCP_SUCCESS(result) )
114     {
115       // if this is a three partition file, go to the body
116       // partition and read the partition pack
117       if ( m_HeaderPart.m_RIP.PairArray.size() > 2 )
118         {
119           Array<RIP::Pair>::iterator r_i = m_HeaderPart.m_RIP.PairArray.begin();
120           r_i++;
121           m_File.Seek((*r_i).ByteOffset);
122
123           result = m_BodyPart.InitFromFile(m_File);
124         }
125
126       m_EssenceStart = m_File.Tell();
127     }
128
129   return result;
130 }
131
132
133 // standard method of populating the in-memory index
134 Result_t
135 ASDCP::h__Reader::InitMXFIndex()
136 {
137   if ( ! m_File.IsOpen() )
138     return RESULT_INIT;
139
140   Result_t result = m_File.Seek(m_HeaderPart.FooterPartition);
141
142   if ( ASDCP_SUCCESS(result) )
143     {
144       m_FooterPart.m_Lookup = &m_HeaderPart.m_Primer;
145       result = m_FooterPart.InitFromFile(m_File);
146     }
147
148   if ( ASDCP_SUCCESS(result) )
149     m_File.Seek(m_EssenceStart);
150
151   return result;
152 }
153
154 //
155 Result_t
156 ASDCP::KLReader::ReadKLFromFile(Kumu::FileReader& Reader)
157 {
158   ui32_t read_count;
159   ui32_t header_length = SMPTE_UL_LENGTH + MXF_BER_LENGTH;
160   Result_t result = Reader.Read(m_KeyBuf, header_length, &read_count);
161
162   if ( ASDCP_SUCCESS(result) )
163     {
164       if ( read_count != header_length )
165         result = RESULT_READFAIL;
166   
167       else
168         result = InitFromBuffer(m_KeyBuf, header_length);
169     }
170
171   return result;
172 }
173
174 // standard method of reading a plaintext or encrypted frame
175 Result_t
176 ASDCP::h__Reader::ReadEKLVFrame(ui32_t FrameNum, ASDCP::FrameBuffer& FrameBuf,
177                                 const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
178 {
179   // look up frame index node
180   IndexTableSegment::IndexEntry TmpEntry;
181
182   if ( ASDCP_FAILURE(m_FooterPart.Lookup(FrameNum, TmpEntry)) )
183     {
184       DefaultLogSink().Error("Frame value out of range: %u\n", FrameNum);
185       return RESULT_RANGE;
186     }
187
188   // get frame position and go read the frame's key and length
189   Kumu::fpos_t FilePosition = m_EssenceStart + TmpEntry.StreamOffset;
190   Result_t result = RESULT_OK;
191
192   if ( FilePosition != m_LastPosition )
193     {
194       m_LastPosition = FilePosition;
195       result = m_File.Seek(FilePosition);
196     }
197
198   if( ASDCP_SUCCESS(result) )
199     result = ReadEKLVPacket(FrameNum, FrameNum + 1, FrameBuf, EssenceUL, Ctx, HMAC);
200
201   return result;
202 }
203
204
205 Result_t
206 ASDCP::h__Reader::ReadEKLVPacket(ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf,
207                                  const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC)
208 {
209   KLReader Reader;
210   Result_t result = Reader.ReadKLFromFile(m_File);
211
212   if ( ASDCP_FAILURE(result) )
213     return result;
214
215   UL Key(Reader.Key());
216   ui64_t PacketLength = Reader.Length();
217   m_LastPosition = m_LastPosition + Reader.KLLength() + PacketLength;
218   assert(m_Dict);
219
220   if ( memcmp(Key.Value(), m_Dict->ul(MDD_CryptEssence), Key.Size() - 1) == 0  // ignore the stream numbers
221        || memcmp(Key.Value(), m_Dict->ul(MDD_MXFInterop_CryptEssence), Key.Size() - 1) == 0 )
222     {
223       if ( ! m_Info.EncryptedEssence )
224         {
225           DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n");
226           return RESULT_FORMAT;
227         }
228
229       // read encrypted triplet value into internal buffer
230       assert(PacketLength <= 0xFFFFFFFFL);
231       m_CtFrameBuf.Capacity((ui32_t) PacketLength);
232       ui32_t read_count;
233       result = m_File.Read(m_CtFrameBuf.Data(), (ui32_t) PacketLength,
234                            &read_count);
235
236       if ( ASDCP_FAILURE(result) )
237         return result;
238
239       if ( read_count != PacketLength )
240         {
241           DefaultLogSink().Error("read length is smaller than EKLV packet length.\n");
242           return RESULT_FORMAT;
243         }
244
245       m_CtFrameBuf.Size((ui32_t) PacketLength);
246
247       // should be const but mxflib::ReadBER is not
248       byte_t* ess_p = m_CtFrameBuf.Data();
249
250       // read context ID length
251       if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) )
252         return RESULT_FORMAT;
253
254       // test the context ID
255       if ( memcmp(ess_p, m_Info.ContextID, UUIDlen) != 0 )
256         {
257           DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n");
258           return RESULT_FORMAT;
259         }
260       ess_p += UUIDlen;
261
262       // read PlaintextOffset length
263       if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
264         return RESULT_FORMAT;
265
266       ui32_t PlaintextOffset = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
267       ess_p += sizeof(ui64_t);
268
269       // read essence UL length
270       if ( ! Kumu::read_test_BER(&ess_p, SMPTE_UL_LENGTH) )
271         return RESULT_FORMAT;
272
273       // test essence UL
274       if ( memcmp(ess_p, EssenceUL, SMPTE_UL_LENGTH - 1) != 0 ) // ignore the stream number
275         {
276           char strbuf[IntBufferLen];
277           const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
278           if ( Entry == 0 )
279             DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
280           else
281             DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
282           return RESULT_FORMAT;
283         }
284       ess_p += SMPTE_UL_LENGTH;
285
286       // read SourceLength length
287       if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) )
288         return RESULT_FORMAT;
289
290       ui32_t SourceLength = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p));
291       ess_p += sizeof(ui64_t);
292       assert(SourceLength);
293           
294       if ( FrameBuf.Capacity() < SourceLength )
295         {
296           DefaultLogSink().Error("FrameBuf.Capacity: %u SourceLength: %u\n", FrameBuf.Capacity(), SourceLength);
297           return RESULT_SMALLBUF;
298         }
299
300       ui32_t esv_length = calc_esv_length(SourceLength, PlaintextOffset);
301
302       // read ESV length
303       if ( ! Kumu::read_test_BER(&ess_p, esv_length) )
304         {
305           DefaultLogSink().Error("read_test_BER did not return %u\n", esv_length);
306           return RESULT_FORMAT;
307         }
308
309       ui32_t tmp_len = esv_length + (m_Info.UsesHMAC ? klv_intpack_size : 0);
310
311       if ( PacketLength < tmp_len )
312         {
313           DefaultLogSink().Error("Frame length is larger than EKLV packet length.\n");
314           return RESULT_FORMAT;
315         }
316
317       if ( Ctx )
318         {
319           // wrap the pointer and length as a FrameBuffer for use by
320           // DecryptFrameBuffer() and TestValues()
321           FrameBuffer TmpWrapper;
322           TmpWrapper.SetData(ess_p, tmp_len);
323           TmpWrapper.Size(tmp_len);
324           TmpWrapper.SourceLength(SourceLength);
325           TmpWrapper.PlaintextOffset(PlaintextOffset);
326
327           result = DecryptFrameBuffer(TmpWrapper, FrameBuf, Ctx);
328           FrameBuf.FrameNumber(FrameNum);
329   
330           // detect and test integrity pack
331           if ( ASDCP_SUCCESS(result) && m_Info.UsesHMAC && HMAC )
332             {
333               IntegrityPack IntPack;
334               result = IntPack.TestValues(TmpWrapper, m_Info.AssetUUID, SequenceNum, HMAC);
335             }
336         }
337       else // return ciphertext to caller
338         {
339           if ( FrameBuf.Capacity() < tmp_len )
340             {
341               char intbuf[IntBufferLen];
342               DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
343                                      FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
344               return RESULT_SMALLBUF;
345             }
346
347           memcpy(FrameBuf.Data(), ess_p, tmp_len);
348           FrameBuf.Size(tmp_len);
349           FrameBuf.SourceLength(SourceLength);
350           FrameBuf.PlaintextOffset(PlaintextOffset);
351         }
352     }
353   else if ( memcmp(Key.Value(), EssenceUL, Key.Size() - 1) == 0 ) // ignore the stream number
354     { // read plaintext frame
355        if ( FrameBuf.Capacity() < PacketLength )
356         {
357           char intbuf[IntBufferLen];
358           DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n",
359                                  FrameBuf.Capacity(), ui64sz(PacketLength, intbuf));
360           return RESULT_SMALLBUF;
361         }
362
363       // read the data into the supplied buffer
364       ui32_t read_count;
365       assert(PacketLength <= 0xFFFFFFFFL);
366       result = m_File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count);
367           
368       if ( ASDCP_FAILURE(result) )
369         return result;
370
371       if ( read_count != PacketLength )
372         {
373           char intbuf1[IntBufferLen];
374           char intbuf2[IntBufferLen];
375           DefaultLogSink().Error("read_count: %s != FrameLength: %s\n",
376                                  ui64sz(read_count, intbuf1),
377                                  ui64sz(PacketLength, intbuf2) );
378           
379           return RESULT_READFAIL;
380         }
381
382       FrameBuf.FrameNumber(FrameNum);
383       FrameBuf.Size(read_count);
384     }
385   else
386     {
387       char strbuf[IntBufferLen];
388       const MDDEntry* Entry = m_Dict->FindUL(Key.Value());
389       if ( Entry == 0 )
390         DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen));
391       else
392         DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name);
393       return RESULT_FORMAT;
394     }
395
396   return result;
397 }
398
399
400 //
401 // end h__Reader.cpp
402 //