Bump version
[asdcplib-cth.git] / src / h__Writer.cpp
1 /*
2 Copyright (c) 2004-2015, 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: h__Writer.cpp,v 1.59 2015/10/09 23:41:11 jhurst Exp $
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 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
69   CryptographicFramework* CFW = new CryptographicFramework(Dict);
70   HeaderPart.AddChildObject(CFW);
71   Segment->DMFramework = CFW->InstanceUID;
72
73   CryptographicContext* Context = new CryptographicContext(Dict);
74   HeaderPart.AddChildObject(Context);
75   CFW->ContextSR = Context->InstanceUID;
76
77   Context->ContextID.Set(Descr.ContextID);
78   Context->SourceEssenceContainer = WrappingUL; // ??????
79   Context->CipherAlgorithm.Set(Dict->ul(MDD_CipherAlgorithm_AES));
80   Context->MICAlgorithm.Set( Descr.UsesHMAC ? Dict->ul(MDD_MICAlgorithm_HMAC_SHA1) : Dict->ul(MDD_MICAlgorithm_NONE) );
81   Context->CryptographicKeyID.Set(Descr.CryptographicKeyID);
82 }
83
84
85
86 //
87 ASDCP::h__ASDCPWriter::h__ASDCPWriter(const Dictionary& d) :
88   MXF::TrackFileWriter<OP1aHeader>(d), m_BodyPart(m_Dict), m_FooterPart(m_Dict) {}
89
90 ASDCP::h__ASDCPWriter::~h__ASDCPWriter() {}
91
92
93 //
94 Result_t
95 ASDCP::h__ASDCPWriter::CreateBodyPart(const MXF::Rational& EditRate, ui32_t BytesPerEditUnit)
96 {
97   assert(m_Dict);
98   Result_t result = RESULT_OK;
99
100   // create a body partition if we're writing proper 429-3/OP-Atom
101   if ( m_Info.LabelSetType == LS_MXF_SMPTE )
102     {
103       // Body Partition
104       m_BodyPart.EssenceContainers = m_HeaderPart.EssenceContainers;
105       m_BodyPart.ThisPartition = m_File.Tell();
106       m_BodyPart.BodySID = 1;
107       UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
108       m_BodyPart.OperationalPattern = OPAtomUL;
109       m_RIP.PairArray.push_back(RIP::PartitionPair(1, m_BodyPart.ThisPartition)); // Second RIP Entry
110
111       UL BodyUL(m_Dict->ul(MDD_ClosedCompleteBodyPartition));
112       result = m_BodyPart.WriteToFile(m_File, BodyUL);
113     }
114   else
115     {
116       m_HeaderPart.BodySID = 1;
117     }
118
119   if ( ASDCP_SUCCESS(result) )
120     {
121       // Index setup
122       Kumu::fpos_t ECoffset = m_File.Tell();
123       m_FooterPart.IndexSID = 129;
124
125       if ( BytesPerEditUnit == 0 )
126         {
127           m_FooterPart.SetIndexParamsVBR(&m_HeaderPart.m_Primer, EditRate, ECoffset);
128         }
129       else
130         {
131           m_FooterPart.SetIndexParamsCBR(&m_HeaderPart.m_Primer, BytesPerEditUnit, EditRate);
132         }
133     }
134
135   return result;
136 }
137
138 //
139 Result_t
140 ASDCP::h__ASDCPWriter::WriteASDCPHeader(const std::string& PackageLabel, const UL& WrappingUL,
141                                         const std::string& TrackName, const UL& EssenceUL, const UL& DataDefinition,
142                                         const MXF::Rational& EditRate, ui32_t TCFrameRate, ui32_t BytesPerEditUnit)
143 {
144   InitHeader();
145
146   // First RIP Entry
147   if ( m_Info.LabelSetType == LS_MXF_SMPTE )  // ERK
148     {
149       m_RIP.PairArray.push_back(RIP::PartitionPair(0, 0)); // 3-part, no essence in header
150     }
151   else
152     {
153       m_RIP.PairArray.push_back(RIP::PartitionPair(1, 0)); // 2-part, essence in header
154     }
155
156   // timecode rate and essence rate are the same
157   AddSourceClip(EditRate, EditRate, TCFrameRate, TrackName, EssenceUL, DataDefinition, PackageLabel);
158   AddEssenceDescriptor(WrappingUL);
159
160   Result_t result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
161
162   if ( KM_SUCCESS(result) )
163     result = CreateBodyPart(EditRate, BytesPerEditUnit);
164
165   return result;
166 }
167
168 //
169 Result_t
170 ASDCP::h__ASDCPWriter::WriteEKLVPacket(const ASDCP::FrameBuffer& FrameBuf,const byte_t* EssenceUL,
171                                        AESEncContext* Ctx, HMACContext* HMAC, std::string* hash)
172 {
173   return Write_EKLV_Packet(m_File, *m_Dict, m_HeaderPart, m_Info, m_CtFrameBuf, m_FramesWritten,
174                            m_StreamOffset, FrameBuf, EssenceUL, Ctx, HMAC, hash);
175 }
176
177 Result_t
178 ASDCP::h__ASDCPWriter::FakeWriteEKLVPacket(int size)
179 {
180   m_StreamOffset += size;
181   m_File.Seek(size, Kumu::SP_POS);
182
183   return RESULT_OK;
184 }
185
186 // standard method of writing the header and footer of a completed MXF file
187 //
188 Result_t
189 ASDCP::h__ASDCPWriter::WriteASDCPFooter()
190 {
191   // update all Duration properties
192   DurationElementList_t::iterator dli = m_DurationUpdateList.begin();
193
194   for (; dli != m_DurationUpdateList.end(); ++dli )
195     {
196       **dli = m_FramesWritten;
197     }
198
199   m_EssenceDescriptor->ContainerDuration = m_FramesWritten;
200   m_FooterPart.PreviousPartition = m_RIP.PairArray.back().ByteOffset;
201
202   Kumu::fpos_t here = m_File.Tell();
203   m_RIP.PairArray.push_back(RIP::PartitionPair(0, here)); // Last RIP Entry
204   m_HeaderPart.FooterPartition = here;
205
206   assert(m_Dict);
207   // re-label the header partition, set the footer
208   UL OPAtomUL(m_Dict->ul(MDD_OPAtom));
209   m_HeaderPart.OperationalPattern = OPAtomUL;
210   m_HeaderPart.m_Preface->OperationalPattern = OPAtomUL;
211   m_FooterPart.OperationalPattern = OPAtomUL;
212
213   m_FooterPart.EssenceContainers = m_HeaderPart.EssenceContainers;
214   m_FooterPart.FooterPartition = here;
215   m_FooterPart.ThisPartition = here;
216
217   Result_t result = m_FooterPart.WriteToFile(m_File, m_FramesWritten);
218
219   if ( ASDCP_SUCCESS(result) )
220     result = m_RIP.WriteToFile(m_File);
221
222   if ( ASDCP_SUCCESS(result) )
223     result = m_File.Seek(0);
224
225   if ( ASDCP_SUCCESS(result) )
226     result = m_HeaderPart.WriteToFile(m_File, m_HeaderSize);
227
228   m_File.Close();
229   return result;
230 }
231
232
233 //------------------------------------------------------------------------------------------
234 //
235
236
237 // standard method of writing a plaintext or encrypted frame
238 Result_t
239 ASDCP::Write_EKLV_Packet(Kumu::FileWriter& File, const ASDCP::Dictionary& Dict, const MXF::OP1aHeader&,
240                          const ASDCP::WriterInfo& Info, ASDCP::FrameBuffer& CtFrameBuf, ui32_t& FramesWritten,
241                          ui64_t & StreamOffset, const ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL,
242                          AESEncContext* Ctx, HMACContext* HMAC, std::string* hash)
243 {
244   Result_t result = RESULT_OK;
245   IntegrityPack IntPack;
246
247   if (hash)
248     {
249       File.StartHashing();
250     }
251
252   byte_t overhead[128];
253   Kumu::MemIOWriter Overhead(overhead, 128);
254
255   if ( FrameBuf.Size() == 0 )
256     {
257       DefaultLogSink().Error("Cannot write empty frame buffer\n");
258       return RESULT_EMPTY_FB;
259     }
260
261   if ( Info.EncryptedEssence )
262     {
263       if ( ! Ctx )
264         return RESULT_CRYPT_CTX;
265
266       if ( Info.UsesHMAC && ! HMAC )
267         return RESULT_HMAC_CTX;
268
269       if ( FrameBuf.PlaintextOffset() > FrameBuf.Size() )
270         return RESULT_LARGE_PTO;
271
272       // encrypt the essence data (create encrypted source value)
273       result = EncryptFrameBuffer(FrameBuf, CtFrameBuf, Ctx);
274
275       // create HMAC
276       if ( ASDCP_SUCCESS(result) && Info.UsesHMAC )
277         result = IntPack.CalcValues(CtFrameBuf, Info.AssetUUID, FramesWritten + 1, HMAC);
278
279       if ( ASDCP_SUCCESS(result) )
280         { // write UL
281           Overhead.WriteRaw(Dict.ul(MDD_CryptEssence), SMPTE_UL_LENGTH);
282
283           // construct encrypted triplet header
284           ui32_t ETLength = klv_cryptinfo_size + CtFrameBuf.Size();
285           ui32_t BER_length = MXF_BER_LENGTH;
286
287           if ( Info.UsesHMAC )
288             ETLength += klv_intpack_size;
289           else
290             ETLength += (MXF_BER_LENGTH * 3); // for empty intpack
291
292           if ( ETLength > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
293             {
294               BER_length = Kumu::get_BER_length_for_value(ETLength);
295
296               // the packet is longer by the difference in expected vs. actual BER length
297               ETLength += BER_length - MXF_BER_LENGTH;
298
299               if ( BER_length == 0 )
300                 result = RESULT_KLV_CODING;
301             }
302
303           if ( ASDCP_SUCCESS(result) )
304             {
305               if ( ! ( Overhead.WriteBER(ETLength, BER_length)                      // write encrypted triplet length
306                        && Overhead.WriteBER(UUIDlen, MXF_BER_LENGTH)                // write ContextID length
307                        && Overhead.WriteRaw(Info.ContextID, UUIDlen)              // write ContextID
308                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write PlaintextOffset length
309                        && Overhead.WriteUi64BE(FrameBuf.PlaintextOffset())          // write PlaintextOffset
310                        && Overhead.WriteBER(SMPTE_UL_LENGTH, MXF_BER_LENGTH)        // write essence UL length
311                        && Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH)    // write the essence UL
312                        && Overhead.WriteBER(sizeof(ui64_t), MXF_BER_LENGTH)         // write SourceLength length
313                        && Overhead.WriteUi64BE(FrameBuf.Size())                     // write SourceLength
314                        && Overhead.WriteBER(CtFrameBuf.Size(), BER_length) ) )    // write ESV length
315                 {
316                   result = RESULT_KLV_CODING;
317                 }
318             }
319
320           if ( ASDCP_SUCCESS(result) )
321             result = File.Writev(Overhead.Data(), Overhead.Length());
322         }
323
324       if ( ASDCP_SUCCESS(result) )
325         {
326           StreamOffset += Overhead.Length();
327           // write encrypted source value
328           result = File.Writev((byte_t*)CtFrameBuf.RoData(), CtFrameBuf.Size());
329         }
330
331       if ( ASDCP_SUCCESS(result) )
332         {
333           StreamOffset += CtFrameBuf.Size();
334
335           byte_t hmoverhead[512];
336           Kumu::MemIOWriter HMACOverhead(hmoverhead, 512);
337
338           // write the HMAC
339           if ( Info.UsesHMAC )
340             {
341               HMACOverhead.WriteRaw(IntPack.Data, klv_intpack_size);
342             }
343           else
344             { // we still need the var-pack length values if the intpack is empty
345               for ( ui32_t i = 0; i < 3 ; i++ )
346                 HMACOverhead.WriteBER(0, MXF_BER_LENGTH);
347             }
348
349           // write HMAC
350           result = File.Writev(HMACOverhead.Data(), HMACOverhead.Length());
351           StreamOffset += HMACOverhead.Length();
352         }
353     }
354   else
355     {
356       ui32_t BER_length = MXF_BER_LENGTH;
357
358       if ( FrameBuf.Size() > 0x00ffffff ) // Need BER integer longer than MXF_BER_LENGTH bytes
359         {
360           BER_length = Kumu::get_BER_length_for_value(FrameBuf.Size());
361
362           if ( BER_length == 0 )
363             result = RESULT_KLV_CODING;
364         }
365
366       Overhead.WriteRaw((byte_t*)EssenceUL, SMPTE_UL_LENGTH);
367       Overhead.WriteBER(FrameBuf.Size(), BER_length);
368
369       if ( ASDCP_SUCCESS(result) )
370         result = File.Writev(Overhead.Data(), Overhead.Length());
371
372       if ( ASDCP_SUCCESS(result) )
373         result = File.Writev((byte_t*)FrameBuf.RoData(), FrameBuf.Size());
374
375       if ( ASDCP_SUCCESS(result) )
376         StreamOffset += Overhead.Length() + FrameBuf.Size();
377     }
378
379   if ( ASDCP_SUCCESS(result) )
380     result = File.Writev();
381
382   if (hash)
383     {
384       *hash = File.StopHashing();
385     }
386
387   return result;
388 }
389
390 //
391 // end h__Writer.cpp
392 //