/*! DRAGONS: We use the current UTF16String SetString trait to ensure that we always have the correct handling, * even if the user wants these strings handled differently (i.e. we do it their way!) */ void SetStringArray(MDObjectPtr &Array, const std::list<std::string> &Strings) { // Abort if we are send a NULL pointer if(!Array) return; // Don't bother if we have nothing to do if(Strings.empty()) return; // Build a working string static MDTypePtr ValType = MDType::Find("UTF16String"); MDObjectPtr Value = ValType ? new MDObject(ValType) : NULL; if(!Value) { error("Can't build UTF16String value required by SetStringArray() - need this type to be defined in the dictionary file\n"); return; } // Get a buffer in which to build the final string array, make it quite granular as it will keep growing DataChunkPtr Buffer = new DataChunk(); Buffer->SetGranularity(16 * 1024); std::list<std::string>::const_iterator it = Strings.begin(); while(it != Strings.end()) { Value->SetString(*it); DataChunkPtr ThisString = Value->PutData(); Buffer->Append(ThisString); // Add terminator if required, i.e. if the sub-string we just added did not end in a zero UInt8 *p = &ThisString->Data[ThisString->Size]; // Terminate if either of the last two bytes in the current buffer is non-zero - or if the string was too short to have a terminator if((ThisString->Size) < 2 || ((*(--p) != 0) || (*(--p) != 0))) { const UInt8 Term[2] = { 0, 0}; Buffer->Set(2, Term, Buffer->Size); } it++; } // Set the value from this buffer Array->SetValue(Buffer); }
/*! The primer will be <b>appended</b> to the DataChunk */ UInt32 Primer::WritePrimer(DataChunkPtr &Buffer) { UInt32 Bytes; // Work out the primer value size first (to allow us to pre-allocate) UInt64 PrimerLen = UInt64(size()) * 18 + 8; // Re-size buffer to the probable final size Buffer->ResizeBuffer((UInt32)(Buffer->Size + 16 + 4 + PrimerLen)); // Lookup the type to get the key - Static so only need to lookup once MDOTypePtr PrimerType = MDOType::Find(Primer_UL); mxflib_assert(PrimerType); Buffer->Append(PrimerType->GetKey()); Bytes = static_cast<UInt32>(PrimerType->GetKey().Size); // Add the length DataChunkPtr BER = MakeBER(PrimerLen); Buffer->Append(*BER); Bytes += static_cast<UInt32>(BER->Size); // Add the vector header UInt8 Temp[4]; PutU32(static_cast<UInt32>(size()), Temp); Buffer->Append(4, Temp); Bytes += 4; PutU32(18, Temp); Buffer->Append(4, Temp); Bytes += 4; // Write the primer data iterator it = begin(); while(it != end()) { PutU16((*it).first, Temp); Buffer->Append(2, Temp); Buffer->Append(16, (*it).second.GetValue()); it++; } return Bytes; }
/*! \param Offset Offset from the start of the KLV value from which to start reading * \param Size Number of bytes to read, if -1 all available bytes will be read (which could be billions!) * \return The number of bytes read */ size_t KLVEObject::ReadDataFrom(Position Offset, size_t Size /*=-1*/) { // Don't decrypt if we have no decryption wrapper if(!Decrypt) return Base_ReadDataFrom(Offset, Size); // Load the header if required (and if we can!) if(!DataLoaded) if(!LoadData()) return 0; // Don't bother reading zero bytes or off the end of the value if((Size == 0) || (Offset >= ValueLength)) { Data.Resize(0); return 0; } // Load the IV and check the Check value if this is the first read if( CurrentReadOffset == 0) { if( Base_ReadDataFrom(DataOffset - EncryptionOverhead, EncryptionOverhead) < EncryptionOverhead) { error("Unable to read Initialization Vector and Check Value in KLVEObject::ReadDataFrom()\n"); return 0; } // Update the current hash if we are calculating one if(ReadHasher) ReadHasher->HashData(Data); // Initialize the decryption engine with the specified Initialization Vector Decrypt->SetIV(16, Data.Data, true); // Decrypt the check value... DataChunkPtr PlainCheck = Decrypt->Decrypt(16, &Data.Data[16]); // Encrypt the check value... (Which is "CHUKCHUKCHUKCHUK" who ever said Chuck Harrison has no ego?) const UInt8 DefinitivePlainCheck[16] = { 0x43, 0x48, 0x55, 0x4B, 0x43, 0x48, 0x55, 0x4B, 0x43, 0x48, 0x55, 0x4B, 0x43, 0x48, 0x55, 0x4B }; if((!PlainCheck) || (memcmp(PlainCheck->Data, DefinitivePlainCheck, PlainCheck->Size) != 0)) { error("Check value did not correctly decrypt in KLVEObject::ReadDataFrom() - is the encryption key correct?\n"); return 0; } } // If all the requested bytes are encrypted read-and-decrypt if(Offset >= PlaintextOffset) { // Check if an attempt is being made to random access the encrypted data - and barf if this is so if(Offset != CurrentReadOffset) { error("Attempt to perform random-access reading of an encrypted KLV value field\n"); return 0; } size_t Ret = ReadCryptoDataFrom(Offset, Size); // Read the AS-DCP footer if we have read the last of the data if(CurrentReadOffset >= ValueLength) if(!ReadFooter()) { Ret = 0; Data.Resize(0); } return Ret; } // If all the bytes requested are plaintext use the base read if( (Size != static_cast<size_t>(-1)) && ((Offset + Size) < PlaintextOffset) ) { // Check if an attempt is being made to random access the plaintext while hashing - and barf if this is so if(ReadHasher && (Offset != CurrentReadOffset)) { error("Attempt to perform random-access reading of an encrypted KLV value field\n"); return 0; } size_t Ret = Base_ReadDataFrom(DataOffset + Offset, Size); // Update the read pointer (it is possible to random access within the plaintext area) CurrentReadOffset = Offset + Ret; // Update the current hash if we are calculating one if(ReadHasher) ReadHasher->HashData(Data); // Read the AS-DCP footer if we have read the last of the data if(CurrentReadOffset >= ValueLength) if(!ReadFooter()) { Ret = 0; Data.Resize(0); } return Ret; } /* We will be mixing plaintext and encrypted */ // Check if an attempt is being made to random access the encrypted data - and barf if this is so // DRAGONS: It is possible to re-load the initial IV to allow a "rewind" but this is not implemented if(CurrentReadOffset > PlaintextOffset) { error("Attempt to perform random-access reading of an encrypted KLV value field\n"); return 0; } // Determine how many plaintext bytes could be available (maximum) Length PlainSize = PlaintextOffset - Offset; // Validate the size if((sizeof(size_t) < 8) && (PlainSize > 0xffffffff)) { error("Encrypted KLV contains > 4GBytes of plaintext, but this platform can only handle <= 4GByte chunks\n"); return 0; } // Try and read them all size_t PlainBytes = Base_ReadDataFrom(DataOffset + Offset, static_cast<size_t>(PlainSize)); // Update the current hash if we are calculating one if(ReadHasher) ReadHasher->HashData(Data); // If we couldn't read them all then the data ends before any encrypted bytes so we can exit now if(PlainBytes < static_cast<size_t>(PlainSize)) { // DRAGONS: We don't read the footer if we ran out of data early... should we issue an error or a warning? //# Read the AS-DCP footer if we have read the last of the data //# if(CurrentReadOffset >= ValueLength) ReadFooter(); return PlainBytes; } /* We have all the plaintext bytes from Offset forwards, now we read all encrypted bytes too */ // Take the buffer from the current DataChunk to preserve it DataChunkPtr PlainData = new DataChunk; PlainData->TakeBuffer(Data, true); // Work out how many encrypted bytes to read size_t EncSize; if(Size == static_cast<size_t>(-1)) EncSize = Size; else EncSize = Size - static_cast<size_t>(PlainSize); // Read the encrypted bytes ReadCryptoDataFrom(PlaintextOffset, EncSize); // Append the decrypted data to the plaintext data PlainData->Append(Data); // Transfer this data to the "current" DataChunk Data.TakeBuffer(PlainData); // Set the "next" position to just after the end of what we read CurrentReadOffset = Offset + Data.Size; // Read the AS-DCP footer if we have read the last of the data if(CurrentReadOffset >= ValueLength) if(!ReadFooter()) Data.Resize(0); // Return the total number of bytes return Data.Size; }