ISXDDataEssenceDescriptor_NamespaceURI UL fixed
[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 #ifdef ASDCP_GCMULTI_PATCH
289   UL GenericContainerUL(m_Dict->ul(MDD_GCMulti));
290   m_HeaderPart.EssenceContainers.push_back(GenericContainerUL);
291 #endif
292
293   Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
294
295   if ( KM_SUCCESS(result) )
296     result = CreateBodyPart(EditRate, BytesPerEditUnit);
297
298   return result;
299 }
300
301 //
302 Result_t
303 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
304                                        const ui32_t& MinEssenceElementBerLength,               
305                                        AESEncContext* Ctx, HMACContext* HMAC)
306 {
307   return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
308                            m_StreamOffset, FrameBuf, EssenceUL, MinEssenceElementBerLength,
309                            Ctx, HMAC);
310 }
311
312 // standard method of writing the header and footer of a completed MXF file
313 //
314 Result_t
315 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
316 {
317   // update all Duration properties
318   DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
319
320   for (; dli != m_DurationUpdateList.end(); ++dli )
321     {
322       **dli = m_FramesWritten;
323     }
324
325   m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
326   m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
327
328   Kumu::fpos_t here = m_File.Tell();
329   m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
330   m_HeaderPart.FooterPartition = here;
331
332   assert(m_Dict);
333   // re-label the header partition, set the footer
334   UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
335   m_HeaderPart.OperationalPattern = OPAtomUL;
336   m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
337   m_FooterPart.OperationalPattern = OPAtomUL;
338
339   m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
340   m_FooterPart.FooterPartition = here;
341   m_FooterPart.ThisPartition = here;
342
343   Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
344
345   if ( ASDCP_SUCCESS(result) )
346     result = m_RIP.WriteToFile(m_File);
347
348   if ( ASDCP_SUCCESS(result) )
349     result = m_File.Seek(0);
350
351   if ( ASDCP_SUCCESS(result) )
352     result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
353
354   m_File.Close();
355   return result;
356 }
357
358
359 //------------------------------------------------------------------------------------------
360 //
361
362
363 // standard method of writing a plaintext or encrypted frame
364 Result_t
365 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader& HeaderPart,
366                          const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
367                          ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
368                          const ui32_t& MinEssenceElementBerLength,
369                          AESEncContext* Ctx, HMACContext* HMAC)
370 {
371   Result_t result = RESULT_OK;
372   IntegrityPack IntPack;
373
374   byte_t overhead[128];
375   Kumu::MemIOWriter Overhead(overhead, 128);
376
377   if ( FrameBuf.Size() == 0 )
378     {
379       DefaultLogSink().Error("Cannot write empty frame buffer\n");
380       return RESULT_EMPTY_FB;
381     }
382
383   if ( Info.EncryptedEssence )
384     {
385       if ( ! Ctx )
386         return RESULT_CRYPT_CTX;
387
388       if ( Info.UsesHMAC && ! HMAC )
389         return RESULT_HMAC_CTX;
390
391       if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
392         return RESULT_LARGE_PTO;
393
394       // encrypt the essence data (create encrypted source value)
395       result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
396
397       // create HMAC
398       if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
399         result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
400
401       if ( ASDCP_SUCCESS(result) )
402         { // write UL
403           Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
404
405           // construct encrypted triplet header
406           ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
407           ui32_t essence_element_BER_length = MinEssenceElementBerLength;
408
409           if ( Info.UsesHMAC )
410             ETLength += klv_intpack_size;
411           else
412             ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
413
414           if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
415             {
416               essence_element_BER_length = Kumu::get_BER_length_for_value(ETLength);
417
418               // the packet is longer by the difference in expected vs. actual BER length
419               ETLength += essence_element_BER_length - MXF_BER_LENGTH;
420
421               if ( essence_element_BER_length == 0 )
422                 result = RESULT_KLV_CODING;
423             }
424
425           if ( ASDCP_SUCCESS(result) )
426             {
427               if ( ! ( Overhead.WriteBER(ETLength, essence_element_BER_length)                      // write encrypted triplet length
428                        && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH)                // write ContextID length
429                        && Overhead.WriteRaw(Info.ContextID, UUIDlen)              // write ContextID
430                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write PlaintextOffset length
431                        && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset())          // write PlaintextOffset
432                        && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH)        // write essence UL length
433                        && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH)    // write the essence UL
434                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write SourceLength length
435                        && Overhead.WriteUi64BE(FrameBuf.Size())                     // write SourceLength
436                        && Overhead.WriteBER(CtFrameBuf.Size(), essence_element_BER_length) ) )    // write ESV length
437                 {
438                   result = RESULT_KLV_CODING;
439                 }
440             }
441
442           if ( ASDCP_SUCCESS(result) )
443             result = File.Writev(Overhead.Data(), Overhead.Length());
444         }
445
446       if ( ASDCP_SUCCESS(result) )
447         {
448           StreamOffset += Overhead.Length();
449           // write encrypted source value
450           result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
451         }
452
453       if ( ASDCP_SUCCESS(result) )
454         {
455           StreamOffset += CtFrameBuf.Size();
456
457           byte_t hmoverhead[512];
458           Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
459
460           // write the HMAC
461           if ( Info.UsesHMAC )
462             {
463               HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
464             }
465           else
466             { // we still need the var-pack length values if the intpack is empty
467               for ( ui32_t i = 0; i < 3 ; i++ )
468                 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
469             }
470
471           // write HMAC
472           result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
473           StreamOffset += HMACOverhead.Length();
474         }
475     }
476   else
477     {
478       ui32_t essence_element_BER_length = MinEssenceElementBerLength;
479
480       if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
481         {
482           essence_element_BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
483
484           if ( essence_element_BER_length == 0 )
485             result = RESULT_KLV_CODING;
486         }
487
488       Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
489       Overhead.WriteBER(FrameBuf.Size(), essence_element_BER_length);
490
491       if ( ASDCP_SUCCESS(result) )
492         result = File.Writev(Overhead.Data(), Overhead.Length());
493  
494       if ( ASDCP_SUCCESS(result) )
495         result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
496
497       if ( ASDCP_SUCCESS(result) )
498         StreamOffset += Overhead.Length() + FrameBuf.Size();
499     }
500
501   if ( ASDCP_SUCCESS(result) )
502     result = File.Writev();
503
504   return result;
505 }
506
507 //
508 // end h__Writer.cpp
509 //