o Fixed bugs reported by Dolby:
[asdcplib.git] / src / h__Writer.cpp
1 /*
2 Copyright (c) 2004-2018, 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__Writer.cpp
28     \version $Id$
29     \brief   MXF file writer 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 ui32_t
40 ASDCP::derive_timecode_rate_from_edit_rate(const ASDCP::Rational& edit_rate)
41 {
42   return (ui32_t)floor(0.5 + edit_rate.Quotient());
43 }
44
45 //
46 // add DMS CryptographicFramework entry to source package
47 void
48 ASDCP::AddDmsCrypt(Partition& HeaderPart, SourcePackage& Package,
49                    WriterInfo& Descr, const UL& WrappingUL, const Dictionary*& Dict)
50 {
51   assert(Dict);
52   // Essence Track
53   StaticTrack* NewTrack = new StaticTrack(Dict);
54   HeaderPart.AddChildObject(NewTrack);
55   Package.Tracks.push_back(NewTrack->InstanceUID);
56   NewTrack->TrackName = "Descriptive Track";
57   NewTrack->TrackID = 3;
58
59   Sequence* Seq = new Sequence(Dict);
60   HeaderPart.AddChildObject(Seq);
61   NewTrack->Sequence = Seq->InstanceUID;
62   Seq->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
63
64   DMSegment* Segment = new DMSegment(Dict);
65   HeaderPart.AddChildObject(Segment);
66   Seq->StructuralComponents.push_back(Segment->InstanceUID);
67   Segment->EventComment = "AS-DCP KLV Encryption";
68   Segment->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
69
70   CryptographicFramework* CFW = new CryptographicFramework(Dict);
71   HeaderPart.AddChildObject(CFW);
72   Segment->DMFramework = CFW->InstanceUID;
73
74   CryptographicContext* Context = new CryptographicContext(Dict);
75   HeaderPart.AddChildObject(Context);
76   CFW->ContextSR = Context->InstanceUID;
77
78   Context->ContextID.Set(Descr.ContextID);
79   Context->SourceEssenceContainer = WrappingUL; // ??????
80   Context->CipherAlgorithm.Set(Dict->ul(MDD_CipherAlgorithm_AES));
81   Context->MICAlgorithm.Set( Descr.UsesHMAC ? Dict->ul(MDD_MICAlgorithm_HMAC_SHA1) : Dict->ul(MDD_MICAlgorithm_NONE) );
82   Context->CryptographicKeyID.Set(Descr.CryptographicKeyID);
83 }
84
85 static std::string const rp2057_static_track_label = "SMPTE RP 2057 Generic Stream Text-Based Set";
86
87 //
88 static bool
89 id_batch_contains(const Array<Kumu::UUID>& batch, const Kumu::UUID& value)
90 {
91   Array<Kumu::UUID>::const_iterator i;
92   for ( i = batch.begin(); i != batch.end(); ++i )
93     {
94       if ( *i == value )
95         {
96           return true;
97         }
98     }
99   return false;
100 }
101
102 //
103 Result_t
104 ASDCP::AddDmsTrackGenericPartUtf8Text(Kumu::FileWriter& file_writer, MXF::OP1aHeader& header_part,
105                                       SourcePackage& source_package, MXF::RIP& rip, const Dictionary*& Dict)
106 {
107   Sequence* Sequence_obj = 0;
108   InterchangeObject* tmp_iobj = 0;
109   std::list<InterchangeObject*> object_list;
110
111   // get the SourcePackage else die
112   header_part.GetMDObjectByType(Dict->ul(MDD_SourcePackage), &tmp_iobj);
113   SourcePackage *SourcePackage_obj = dynamic_cast<SourcePackage*>(tmp_iobj);
114   if ( SourcePackage_obj == 0 )
115     {
116       DefaultLogSink().Error("MXF Metadata contains no SourcePackage Set.\n");
117       return RESULT_FORMAT;
118     }
119
120   // find the first StaticTrack object, having the right label, that is ref'd by the source package
121   StaticTrack *StaticTrack_obj = 0;
122   header_part.GetMDObjectsByType(Dict->ul(MDD_StaticTrack), object_list);
123   std::list<InterchangeObject*>::iterator j;
124   for ( j = object_list.begin(); j != object_list.end(); ++j )
125     {
126       StaticTrack_obj = dynamic_cast<StaticTrack*>(*j);
127       assert(StaticTrack_obj);
128       if ( id_batch_contains(SourcePackage_obj->Tracks, StaticTrack_obj->InstanceUID)
129            && StaticTrack_obj->TrackName.get() == rp2057_static_track_label )
130         {
131           break;
132         }
133       StaticTrack_obj = 0;
134     }
135
136   // find the Sequence associated with this Track
137   if ( StaticTrack_obj )
138     {
139       object_list.clear();
140       header_part.GetMDObjectsByType(Dict->ul(MDD_Sequence), object_list);
141       for ( j = object_list.begin(); j != object_list.end(); ++j )
142         {
143           Sequence_obj = dynamic_cast<Sequence*>(*j);
144           assert(Sequence_obj);
145           if ( Sequence_obj->InstanceUID == StaticTrack_obj->Sequence )
146             {
147               break;
148             }
149           Sequence_obj = 0;
150         }
151     }
152
153   if ( Sequence_obj == 0 )
154     {
155       // this is the first insertion, create the static track
156       assert(Dict);
157       StaticTrack* static_track = new StaticTrack(Dict);
158       header_part.AddChildObject(static_track);
159       source_package.Tracks.push_back(static_track->InstanceUID);
160       static_track->TrackName = "Descriptive Track";
161       static_track->TrackID = 4;
162
163       Sequence_obj = new Sequence(Dict);
164       header_part.AddChildObject(Sequence_obj);
165       static_track->Sequence = Sequence_obj->InstanceUID;
166       Sequence_obj->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
167       header_part.m_Preface->DMSchemes.push_back(UL(Dict->ul(MDD_MXFTextBasedFramework)));
168     }
169
170   assert(Sequence_obj);
171   // Create the DM segment and framework packs
172   DMSegment* Segment = new DMSegment(Dict);
173   header_part.AddChildObject(Segment);
174   Sequence_obj->StructuralComponents.push_back(Segment->InstanceUID);
175   Segment->EventComment = rp2057_static_track_label;
176   Segment->DataDefinition = UL(Dict->ul(MDD_DescriptiveMetaDataDef));
177
178   //
179   TextBasedDMFramework *dmf_obj = new TextBasedDMFramework(Dict);
180   assert(dmf_obj);
181   header_part.AddChildObject(dmf_obj);
182   Segment->DMFramework = dmf_obj->InstanceUID;
183   GenRandomValue(dmf_obj->ObjectRef);
184
185   // Create a new SID on the RIP, located at the current file position
186   ui32_t max_sid = 0;
187   ASDCP::MXF::RIP::pair_iterator i;
188   for ( i = rip.PairArray.begin(); i != rip.PairArray.end(); ++i )
189     {
190       if ( max_sid < i->BodySID )
191         {
192           max_sid = i->BodySID;
193         }
194     }
195
196   if ( max_sid == 0 )
197     {
198       DefaultLogSink().Error("Unable to add a GS Partition before the essence container has been established.\n");
199       return RESULT_FORMAT;
200     }
201
202   rip.PairArray.push_back(RIP::PartitionPair(max_sid + 1, file_writer.Tell()));
203
204   // Add new GSTBS linked to DMF
205   GenericStreamTextBasedSet *gst_obj = new GenericStreamTextBasedSet(Dict);
206   header_part.AddChildObject(gst_obj);
207   gst_obj->InstanceUID = dmf_obj->ObjectRef;
208   gst_obj->GenericStreamSID = max_sid + 1;
209   gst_obj->PayloadSchemeID = UL(Dict->ul(MDD_MXFTextBasedFramework));
210   
211   return RESULT_OK;
212 }
213
214 //
215 ASDCP::h__ASDCPWriter::h__ASDCPWriter(const Dictionary& d) :
216   MXF::TrackFileWriter<OP1aHeader>(d), m_BodyPart(m_Dict), m_FooterPart(m_Dict) {}
217
218 ASDCP::h__ASDCPWriter::~h__ASDCPWriter() {}
219
220
221 //
222 Result_t
223 ASDCP::h__ASDCPWriter::CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit)
224 {
225   assert(m_Dict);
226   Result_t result = RESULT_OK;
227
228   // create a body partition if we're writing proper 429-3/OP-Atom
229   if ( m_Info.LabelSetType == LS_MXF_SMPTE )
230     {
231       // Body Partition
232       m_BodyPart.EssenceContainers = m_HeaderPart.EssenceContainers;
233       m_BodyPart.ThisPartition = m_File.Tell();
234       m_BodyPart.BodySID = 1;
235       UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
236       m_BodyPart.OperationalPattern = OPAtomUL;
237       m_RIP.PairArray.push_back(RIP::PartitionPair(1, m_BodyPart.ThisPartition)); // Second RIP Entry
238       
239       UL BodyUL(m_Dict->ul(MDD_ClosedCompleteBodyPartition));
240       result = m_BodyPart.WriteToFile(m_File, BodyUL);
241     }
242   else
243     {
244       m_HeaderPart.BodySID = 1;
245     }
246
247   if ( ASDCP_SUCCESS(result) )
248     {
249       // Index setup
250       Kumu::fpos_t ECoffset = m_File.Tell();
251       m_FooterPart.IndexSID = 129;
252
253       if ( BytesPerEditUnit == 0 )
254         {
255           m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
256         }
257       else
258         {
259           m_FooterPart.SetIndexParamsCBR(&m_HeaderPart.m_Primer, BytesPerEditUnit, EditRate);
260         }
261     }
262
263   return result;
264 }
265
266 //
267 Result_t
268 ASDCP::h__ASDCPWriter::WriteASDCPHeader(const std::string& PackageLabel, const UL& WrappingUL,
269                                         const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition,
270                                         const MXF::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit)
271 {
272   InitHeader(MXFVersion_2004);
273
274   // First RIP Entry
275   if ( m_Info.LabelSetType == LS_MXF_SMPTE )  // ERK
276     {
277       m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
278     }
279   else
280     {
281       m_RIP.PairArray.push_back(RIP::PartitionPair(1, 0)); // 2-part, essence in header
282     }
283
284   // timecode rate and essence rate are the same
285   AddSourceClip(EditRate, EditRate, TCFrameRate, TrackName, EssenceUL, DataDefinition, PackageLabel);
286   AddEssenceDescriptor(WrappingUL);
287
288   Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
289
290   if ( KM_SUCCESS(result) )
291     result = CreateBodyPart(EditRate, BytesPerEditUnit);
292
293   return result;
294 }
295
296 //
297 Result_t
298 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
299                                        AESEncContext* Ctx, HMACContext* HMAC)
300 {
301   return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
302                            m_StreamOffset, FrameBuf, EssenceUL, Ctx, HMAC);
303 }
304
305 // standard method of writing the header and footer of a completed MXF file
306 //
307 Result_t
308 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
309 {
310   // update all Duration properties
311   DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
312
313   for (; dli != m_DurationUpdateList.end(); ++dli )
314     {
315       **dli = m_FramesWritten;
316     }
317
318   m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
319   m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
320
321   Kumu::fpos_t here = m_File.Tell();
322   m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
323   m_HeaderPart.FooterPartition = here;
324
325   assert(m_Dict);
326   // re-label the header partition, set the footer
327   UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
328   m_HeaderPart.OperationalPattern = OPAtomUL;
329   m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
330   m_FooterPart.OperationalPattern = OPAtomUL;
331
332   m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
333   m_FooterPart.FooterPartition = here;
334   m_FooterPart.ThisPartition = here;
335
336   Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
337
338   if ( ASDCP_SUCCESS(result) )
339     result = m_RIP.WriteToFile(m_File);
340
341   if ( ASDCP_SUCCESS(result) )
342     result = m_File.Seek(0);
343
344   if ( ASDCP_SUCCESS(result) )
345     result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
346
347   m_File.Close();
348   return result;
349 }
350
351
352 //------------------------------------------------------------------------------------------
353 //
354
355
356 // standard method of writing a plaintext or encrypted frame
357 Result_t
358 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
359                          const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
360                          ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
361                          AESEncContext* Ctx, HMACContext* HMAC)
362 {
363   Result_t result = RESULT_OK;
364   IntegrityPack IntPack;
365
366   byte_t overhead[128];
367   Kumu::MemIOWriter Overhead(overhead, 128);
368
369   if ( FrameBuf.Size() == 0 )
370     {
371       DefaultLogSink().Error("Cannot write empty frame buffer\n");
372       return RESULT_EMPTY_FB;
373     }
374
375   if ( Info.EncryptedEssence )
376     {
377       if ( ! Ctx )
378         return RESULT_CRYPT_CTX;
379
380       if ( Info.UsesHMAC && ! HMAC )
381         return RESULT_HMAC_CTX;
382
383       if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
384         return RESULT_LARGE_PTO;
385
386       // encrypt the essence data (create encrypted source value)
387       result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
388
389       // create HMAC
390       if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
391         result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
392
393       if ( ASDCP_SUCCESS(result) )
394         { // write UL
395           Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
396
397           // construct encrypted triplet header
398           ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
399           ui32_t BER_length = MXF_BER_LENGTH;
400
401           if ( Info.UsesHMAC )
402             ETLength += klv_intpack_size;
403           else
404             ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
405
406           if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
407             {
408               BER_length = Kumu::get_BER_length_for_value(ETLength);
409
410               // the packet is longer by the difference in expected vs. actual BER length
411               ETLength += BER_length - MXF_BER_LENGTH;
412
413               if ( BER_length == 0 )
414                 result = RESULT_KLV_CODING;
415             }
416
417           if ( ASDCP_SUCCESS(result) )
418             {
419               if ( ! ( Overhead.WriteBER(ETLength, BER_length)                      // write encrypted triplet length
420                        && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH)                // write ContextID length
421                        && Overhead.WriteRaw(Info.ContextID, UUIDlen)              // write ContextID
422                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write PlaintextOffset length
423                        && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset())          // write PlaintextOffset
424                        && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH)        // write essence UL length
425                        && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH)    // write the essence UL
426                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write SourceLength length
427                        && Overhead.WriteUi64BE(FrameBuf.Size())                     // write SourceLength
428                        && Overhead.WriteBER(CtFrameBuf.Size(), BER_length) ) )    // write ESV length
429                 {
430                   result = RESULT_KLV_CODING;
431                 }
432             }
433
434           if ( ASDCP_SUCCESS(result) )
435             result = File.Writev(Overhead.Data(), Overhead.Length());
436         }
437
438       if ( ASDCP_SUCCESS(result) )
439         {
440           StreamOffset += Overhead.Length();
441           // write encrypted source value
442           result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
443         }
444
445       if ( ASDCP_SUCCESS(result) )
446         {
447           StreamOffset += CtFrameBuf.Size();
448
449           byte_t hmoverhead[512];
450           Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
451
452           // write the HMAC
453           if ( Info.UsesHMAC )
454             {
455               HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
456             }
457           else
458             { // we still need the var-pack length values if the intpack is empty
459               for ( ui32_t i = 0; i < 3 ; i++ )
460                 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
461             }
462
463           // write HMAC
464           result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
465           StreamOffset += HMACOverhead.Length();
466         }
467     }
468   else
469     {
470       ui32_t BER_length = MXF_BER_LENGTH;
471
472       if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
473         {
474           BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
475
476           if ( BER_length == 0 )
477             result = RESULT_KLV_CODING;
478         }
479
480       Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
481       Overhead.WriteBER(FrameBuf.Size(), BER_length);
482
483       if ( ASDCP_SUCCESS(result) )
484         result = File.Writev(Overhead.Data(), Overhead.Length());
485  
486       if ( ASDCP_SUCCESS(result) )
487         result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
488
489       if ( ASDCP_SUCCESS(result) )
490         StreamOffset += Overhead.Length() + FrameBuf.Size();
491     }
492
493   if ( ASDCP_SUCCESS(result) )
494     result = File.Writev();
495
496   return result;
497 }
498
499 //
500 // end h__Writer.cpp
501 //