Bump patch version post tag.
[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.get());
184   dmf_obj->ObjectRef.set_has_value();
185
186   // Create a new SID on the RIP, located at the current file position
187   ui32_t max_sid = 0;
188   ASDCP::MXF::RIP::pair_iterator i;
189   for ( i = rip.PairArray.begin(); i != rip.PairArray.end(); ++i )
190     {
191       if ( max_sid < i->BodySID )
192         {
193           max_sid = i->BodySID;
194         }
195     }
196
197   if ( max_sid == 0 )
198     {
199       DefaultLogSink().Error("Unable to add a GS Partition before the essence container has been established.\n");
200       return RESULT_FORMAT;
201     }
202
203   rip.PairArray.push_back(RIP::PartitionPair(max_sid + 1, file_writer.Tell()));
204
205   // Add new GSTBS linked to DMF
206   GenericStreamTextBasedSet *gst_obj = new GenericStreamTextBasedSet(Dict);
207   header_part.AddChildObject(gst_obj);
208   gst_obj->InstanceUID = dmf_obj->ObjectRef;
209   gst_obj->GenericStreamSID = max_sid + 1;
210   gst_obj->PayloadSchemeID = UL(Dict->ul(MDD_MXFTextBasedFramework));
211   
212   return RESULT_OK;
213 }
214
215 //
216 ASDCP::h__ASDCPWriter::h__ASDCPWriter(const Dictionary& d) :
217   MXF::TrackFileWriter<OP1aHeader>(d), m_BodyPart(m_Dict), m_FooterPart(m_Dict) {}
218
219 ASDCP::h__ASDCPWriter::~h__ASDCPWriter() {}
220
221
222 //
223 Result_t
224 ASDCP::h__ASDCPWriter::CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit)
225 {
226   assert(m_Dict);
227   Result_t result = RESULT_OK;
228
229   // create a body partition if we're writing proper 429-3/OP-Atom
230   if ( m_Info.LabelSetType == LS_MXF_SMPTE )
231     {
232       // Body Partition
233       m_BodyPart.EssenceContainers = m_HeaderPart.EssenceContainers;
234       m_BodyPart.ThisPartition = m_File.Tell();
235       m_BodyPart.BodySID = 1;
236       UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
237       m_BodyPart.OperationalPattern = OPAtomUL;
238       m_RIP.PairArray.push_back(RIP::PartitionPair(1, m_BodyPart.ThisPartition)); // Second RIP Entry
239       
240       UL BodyUL(m_Dict->ul(MDD_ClosedCompleteBodyPartition));
241       result = m_BodyPart.WriteToFile(m_File, BodyUL);
242     }
243   else
244     {
245       m_HeaderPart.BodySID = 1;
246     }
247
248   if ( ASDCP_SUCCESS(result) )
249     {
250       // Index setup
251       Kumu::fpos_t ECoffset = m_File.Tell();
252       m_FooterPart.IndexSID = 129;
253
254       if ( BytesPerEditUnit == 0 )
255         {
256           m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
257         }
258       else
259         {
260           m_FooterPart.SetIndexParamsCBR(&m_HeaderPart.m_Primer, BytesPerEditUnit, EditRate);
261         }
262     }
263
264   return result;
265 }
266
267 //
268 Result_t
269 ASDCP::h__ASDCPWriter::WriteASDCPHeader(const std::string& PackageLabel, const UL& WrappingUL,
270                                         const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition,
271                                         const MXF::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit)
272 {
273   InitHeader(MXFVersion_2004);
274
275   // First RIP Entry
276   if ( m_Info.LabelSetType == LS_MXF_SMPTE )  // ERK
277     {
278       m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
279     }
280   else
281     {
282       m_RIP.PairArray.push_back(RIP::PartitionPair(1, 0)); // 2-part, essence in header
283     }
284
285   // timecode rate and essence rate are the same
286   AddSourceClip(EditRate, EditRate, TCFrameRate, TrackName, EssenceUL, DataDefinition, PackageLabel);
287   AddEssenceDescriptor(WrappingUL);
288
289 #ifdef ASDCP_GCMULTI_PATCH
290   UL GenericContainerUL(m_Dict->ul(MDD_GCMulti));
291   m_HeaderPart.EssenceContainers.push_back(GenericContainerUL);
292 #endif
293
294   Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
295
296   if ( KM_SUCCESS(result) )
297     result = CreateBodyPart(EditRate, BytesPerEditUnit);
298
299   return result;
300 }
301
302 //
303 Result_t
304 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
305                                        const ui32_t& MinEssenceElementBerLength,               
306                                        AESEncContext* Ctx, HMACContext* HMAC)
307 {
308   return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
309                            m_StreamOffset, FrameBuf, EssenceUL, MinEssenceElementBerLength,
310                            Ctx, HMAC);
311 }
312
313 // standard method of writing the header and footer of a completed MXF file
314 //
315 Result_t
316 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
317 {
318   // update all Duration properties
319   DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
320
321   for (; dli != m_DurationUpdateList.end(); ++dli )
322     {
323       **dli = m_FramesWritten;
324     }
325
326   m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
327   m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
328
329   Kumu::fpos_t here = m_File.Tell();
330   m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
331   m_HeaderPart.FooterPartition = here;
332
333   assert(m_Dict);
334   // re-label the header partition, set the footer
335   UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
336   m_HeaderPart.OperationalPattern = OPAtomUL;
337   m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
338   m_FooterPart.OperationalPattern = OPAtomUL;
339
340   m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
341   m_FooterPart.FooterPartition = here;
342   m_FooterPart.ThisPartition = here;
343
344   Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
345
346   if ( ASDCP_SUCCESS(result) )
347     result = m_RIP.WriteToFile(m_File);
348
349   if ( ASDCP_SUCCESS(result) )
350     result = m_File.Seek(0);
351
352   if ( ASDCP_SUCCESS(result) )
353     result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
354
355   m_File.Close();
356   return result;
357 }
358
359
360 //------------------------------------------------------------------------------------------
361 //
362
363
364 // standard method of writing a plaintext or encrypted frame
365 Result_t
366 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
367                          const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
368                          ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
369                          const ui32_t& MinEssenceElementBerLength,
370                          AESEncContext* Ctx, HMACContext* HMAC)
371 {
372   Result_t result = RESULT_OK;
373   IntegrityPack IntPack;
374
375   byte_t overhead[128];
376   Kumu::MemIOWriter Overhead(overhead, 128);
377
378   if ( FrameBuf.Size() == 0 )
379     {
380       DefaultLogSink().Error("Cannot write empty frame buffer\n");
381       return RESULT_EMPTY_FB;
382     }
383
384   if ( Info.EncryptedEssence )
385     {
386       if ( ! Ctx )
387         return RESULT_CRYPT_CTX;
388
389       if ( Info.UsesHMAC && ! HMAC )
390         return RESULT_HMAC_CTX;
391
392       if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
393         return RESULT_LARGE_PTO;
394
395       // encrypt the essence data (create encrypted source value)
396       result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
397
398       // create HMAC
399       if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
400         result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
401
402       if ( ASDCP_SUCCESS(result) )
403         { // write UL
404           Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
405
406           // construct encrypted triplet header
407           ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
408           ui32_t essence_element_BER_length = MinEssenceElementBerLength;
409
410           if ( Info.UsesHMAC )
411             ETLength += klv_intpack_size;
412           else
413             ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
414
415           if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
416             {
417               essence_element_BER_length = Kumu::get_BER_length_for_value(ETLength);
418
419               // the packet is longer by the difference in expected vs. actual BER length
420               ETLength += essence_element_BER_length - MXF_BER_LENGTH;
421
422               if ( essence_element_BER_length == 0 )
423                 result = RESULT_KLV_CODING;
424             }
425
426           if ( ASDCP_SUCCESS(result) )
427             {
428               if ( ! ( Overhead.WriteBER(ETLength, essence_element_BER_length)                      // write encrypted triplet length
429                        && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH)                // write ContextID length
430                        && Overhead.WriteRaw(Info.ContextID, UUIDlen)              // write ContextID
431                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write PlaintextOffset length
432                        && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset())          // write PlaintextOffset
433                        && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH)        // write essence UL length
434                        && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH)    // write the essence UL
435                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write SourceLength length
436                        && Overhead.WriteUi64BE(FrameBuf.Size())                     // write SourceLength
437                        && Overhead.WriteBER(CtFrameBuf.Size(), essence_element_BER_length) ) )    // write ESV length
438                 {
439                   result = RESULT_KLV_CODING;
440                 }
441             }
442
443           if ( ASDCP_SUCCESS(result) )
444             result = File.Writev(Overhead.Data(), Overhead.Length());
445         }
446
447       if ( ASDCP_SUCCESS(result) )
448         {
449           StreamOffset += Overhead.Length();
450           // write encrypted source value
451           result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
452         }
453
454       if ( ASDCP_SUCCESS(result) )
455         {
456           StreamOffset += CtFrameBuf.Size();
457
458           byte_t hmoverhead[512];
459           Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
460
461           // write the HMAC
462           if ( Info.UsesHMAC )
463             {
464               HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
465             }
466           else
467             { // we still need the var-pack length values if the intpack is empty
468               for ( ui32_t i = 0; i < 3 ; i++ )
469                 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
470             }
471
472           // write HMAC
473           result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
474           StreamOffset += HMACOverhead.Length();
475         }
476     }
477   else
478     {
479       ui32_t essence_element_BER_length = MinEssenceElementBerLength;
480
481       if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
482         {
483           essence_element_BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
484
485           if ( essence_element_BER_length == 0 )
486             result = RESULT_KLV_CODING;
487         }
488
489       Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
490       Overhead.WriteBER(FrameBuf.Size(), essence_element_BER_length);
491
492       if ( ASDCP_SUCCESS(result) )
493         result = File.Writev(Overhead.Data(), Overhead.Length());
494  
495       if ( ASDCP_SUCCESS(result) )
496         result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
497
498       if ( ASDCP_SUCCESS(result) )
499         StreamOffset += Overhead.Length() + FrameBuf.Size();
500     }
501
502   if ( ASDCP_SUCCESS(result) )
503     result = File.Writev();
504
505   return result;
506 }
507
508 //
509 // end h__Writer.cpp
510 //