/// <summary> /// Reads the custom code table, if there is one /// </summary> void ReadCodeTable() { // The length given includes the nearSize and sameSize bytes int compressedTableLength = IOHelper.ReadBigEndian7BitEncodedInt(delta) - 2; int nearSize = IOHelper.CheckedReadByte(delta); int sameSize = IOHelper.CheckedReadByte(delta); byte[] compressedTableData = IOHelper.CheckedReadBytes(delta, compressedTableLength); byte[] defaultTableData = CodeTable.Default.GetBytes(); MemoryStream tableOriginal = new MemoryStream(defaultTableData, false); MemoryStream tableDelta = new MemoryStream(compressedTableData, false); byte[] decompressedTableData = new byte[1536]; MemoryStream tableOutput = new MemoryStream(decompressedTableData, true); VcdiffDecoder.Decode(tableOriginal, tableDelta, tableOutput); if (tableOutput.Position != 1536) { throw new VcdiffFormatException("Compressed code table was incorrect size"); } _codeTable = new CodeTable(decompressedTableData); _cache = new AddressCache(nearSize, sameSize); }
/// <summary> /// Read the header, including any custom code table. The delta stream is left /// positioned at the start of the first window. /// </summary> private void ReadHeader() { byte[] header = IOHelper.CheckedReadBytes(delta, 4); if (header[0] != 0xd6 || header[1] != 0xc3 || header[2] != 0xc4) { throw new VcdiffFormatException("Invalid VCDIFF header in delta stream"); } if (header[3] != 0) { throw new VcdiffFormatException("VcdiffDecoder can only read delta streams of version 0"); } // Load the header indicator byte headerIndicator = IOHelper.CheckedReadByte(delta); if ((headerIndicator & 1) != 0) { throw new VcdiffFormatException("VcdiffDecoder does not handle delta stream using secondary compressors"); } bool customCodeTable = ((headerIndicator & 2) != 0); bool applicationHeader = ((headerIndicator & 4) != 0); if ((headerIndicator & 0xf8) != 0) { throw new VcdiffFormatException("Invalid header indicator - bits 3-7 not all zero."); } // Load the custom code table, if there is one if (customCodeTable) { ReadCodeTable(); } // Ignore the application header if we have one. This tells xdelta3 what the right filenames are. if (applicationHeader) { int appHeaderLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); IOHelper.CheckedReadBytes(delta, appHeaderLength); } }
/// <summary> /// Reads and decodes a window, returning whether or not there was /// any more data to read. /// </summary> /// <returns> /// Whether or not the delta stream had reached the end of its data. /// </returns> private bool DecodeWindow() { int windowIndicator = delta.ReadByte(); // Have we finished? if (windowIndicator == -1) { return(false); } // The stream to load source data from for this window, if any Stream sourceStream; // Where to reposition the source stream to after reading from it, if anywhere int sourceStreamPostReadSeek = -1; // xdelta3 uses an undocumented extra bit which indicates that there are an extra // 4 bytes at the end of the encoding for the window bool hasAdler32Checksum = ((windowIndicator & 4) == 4); // Get rid of the checksum bit for the rest windowIndicator &= 0xfb; // Work out what the source data is, and detect invalid window indicators switch (windowIndicator & 3) { // No source data used in this window case 0: sourceStream = null; break; // Source data comes from the original stream case 1: if (original == null) { throw new VcdiffFormatException ("Source stream requested by delta but not provided by caller."); } sourceStream = original; break; case 2: sourceStream = output; sourceStreamPostReadSeek = (int)output.Position; break; case 3: throw new VcdiffFormatException ("Invalid window indicator - bits 0 and 1 both set."); default: throw new VcdiffFormatException("Invalid window indicator - bits 3-7 not all zero."); } // Read the source data, if any byte[] sourceData = null; int sourceLength = 0; if (sourceStream != null) { sourceLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); int sourcePosition = IOHelper.ReadBigEndian7BitEncodedInt(delta); sourceStream.Position = sourcePosition; sourceData = IOHelper.CheckedReadBytes(sourceStream, sourceLength); // Reposition the source stream if appropriate if (sourceStreamPostReadSeek != -1) { sourceStream.Position = sourceStreamPostReadSeek; } } // Read how long the delta encoding is - then ignore it IOHelper.ReadBigEndian7BitEncodedInt(delta); // Read how long the target window is int targetLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); byte[] targetData = new byte[targetLength]; MemoryStream targetDataStream = new MemoryStream(targetData, true); // Read the indicator and the lengths of the different data sections byte deltaIndicator = IOHelper.CheckedReadByte(delta); if (deltaIndicator != 0) { throw new VcdiffFormatException("VcdiffDecoder is unable to handle compressed delta sections."); } int addRunDataLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); int instructionsLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); int addressesLength = IOHelper.ReadBigEndian7BitEncodedInt(delta); // If we've been given a checksum, we have to read it and we might as well // use it to check the data. int checksumInFile = 0; if (hasAdler32Checksum) { byte[] checksumBytes = IOHelper.CheckedReadBytes(delta, 4); checksumInFile = (checksumBytes[0] << 24) | (checksumBytes[1] << 16) | (checksumBytes[2] << 8) | checksumBytes[3]; } // Read all the data for this window byte[] addRunData = IOHelper.CheckedReadBytes(delta, addRunDataLength); byte[] instructions = IOHelper.CheckedReadBytes(delta, instructionsLength); byte[] addresses = IOHelper.CheckedReadBytes(delta, addressesLength); int addRunDataIndex = 0; MemoryStream instructionStream = new MemoryStream(instructions, false); _cache.Reset(addresses); while (true) { int instructionIndex = instructionStream.ReadByte(); if (instructionIndex == -1) { break; } for (int i = 0; i < 2; i++) { Instruction instruction = _codeTable[instructionIndex, i]; int size = instruction.Size; if (size == 0 && instruction.Type != InstructionType.NoOp) { size = IOHelper.ReadBigEndian7BitEncodedInt(instructionStream); } switch (instruction.Type) { case InstructionType.NoOp: break; case InstructionType.Add: targetDataStream.Write(addRunData, addRunDataIndex, size); addRunDataIndex += size; break; case InstructionType.Copy: int addr = _cache.DecodeAddress((int)targetDataStream.Position + sourceLength, instruction.Mode); if (sourceData != null && addr < sourceData.Length) { targetDataStream.Write(sourceData, addr, size); } else // Data is in target data { // Get rid of the offset addr -= sourceLength; // Can we just ignore overlap issues? if (addr + size < targetDataStream.Position) { targetDataStream.Write(targetData, addr, size); } else { for (int j = 0; j < size; j++) { targetDataStream.WriteByte(targetData[addr++]); } } } break; case InstructionType.Run: byte data = addRunData[addRunDataIndex++]; for (int j = 0; j < size; j++) { targetDataStream.WriteByte(data); } break; default: throw new VcdiffFormatException("Invalid instruction type found."); } } } output.Write(targetData, 0, targetLength); if (hasAdler32Checksum) { int actualChecksum = Adler32.ComputeChecksum(1, targetData); if (actualChecksum != checksumInFile) { throw new VcdiffFormatException("Invalid checksum after decoding window"); } } return(true); }