convert brotli java decoder to c# library

This commit is contained in:
brezza92 2017-03-02 14:23:42 +07:00
parent 9fa1ad5a91
commit d55f9a11a9
19 changed files with 2610 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

View File

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpBrotli", "CSharpBrotli\CSharpBrotli.csproj", "{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpBrotliTest", "CSharpBrotliTest\CSharpBrotliTest.csproj", "{AE8BE423-ACAB-47D7-AD71-771773C47652}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.SharpZLib", "..\..\..\..\Visual Studio 2015\Projects\SrcSamples\src\ICSharpCode.SharpZLib.csproj", "{0E7413FF-EB9E-4714-ACF2-BE3A6A7B2FFD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}.Release|Any CPU.Build.0 = Release|Any CPU
{AE8BE423-ACAB-47D7-AD71-771773C47652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE8BE423-ACAB-47D7-AD71-771773C47652}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE8BE423-ACAB-47D7-AD71-771773C47652}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE8BE423-ACAB-47D7-AD71-771773C47652}.Release|Any CPU.Build.0 = Release|Any CPU
{0E7413FF-EB9E-4714-ACF2-BE3A6A7B2FFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E7413FF-EB9E-4714-ACF2-BE3A6A7B2FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E7413FF-EB9E-4714-ACF2-BE3A6A7B2FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E7413FF-EB9E-4714-ACF2-BE3A6A7B2FFD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{50443CB1-3712-49A1-BBF8-91A31D0C4BA1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CSharpBrotli</RootNamespace>
<AssemblyName>CSharpBrotli</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile44</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="Decode\BitReader.cs" />
<Compile Include="Decode\BritliInputStream.cs" />
<Compile Include="Decode\BrotliRuntimeExpression.cs" />
<Compile Include="Decode\Context.cs" />
<Compile Include="Decode\Decode.cs" />
<Compile Include="Decode\Dictionary.cs" />
<Compile Include="Decode\Huffman.cs" />
<Compile Include="Decode\HuffmanTreeGroup.cs" />
<Compile Include="Decode\IntBufferReader.cs" />
<Compile Include="Decode\Prefix.cs" />
<Compile Include="Decode\RunningStage.cs" />
<Compile Include="Decode\State.cs" />
<Compile Include="Decode\Transform.cs" />
<Compile Include="Decode\Utils.cs" />
<Compile Include="Decode\WordTransformType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,212 @@
using System;
using System.IO;
namespace CSharpBrotli.Decode
{
/// <summary>
/// Bit reading helpers.
/// </summary>
public class BitReader
{
/// <summary>
/// Input byte buffer, consist of a ring-buffer and a "slack" region where bytes from the start of
/// the ring-buffer are copied.
/// </summary>
private const int READ_SIZE = 4096;
private const int BUF_SIZE = READ_SIZE + 64;
//private const ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUF_SIZE).order(ByteOrder.LITTLE_ENDIAN);
//private const IntBuffer intBuffer = byteBuffer.asIntBuffer();
private MemoryStream byteBuffer;// = CreatesStream();
private IntBufferReader intBuffer;// = new BinaryReader(byteBuffer);
private byte[] shadowBuffer = new byte[BUF_SIZE];
private Stream input;
/// <summary>
/// Input stream is finished.
/// </summary>
private bool endOfStreamReached;
private long acc;
/// <summary>
/// Pre-fetched bits.
/// </summary>
public long accumulator
{
get
{
return acc;
}
set
{
acc = value;
}
}
/// <summary>
/// Current bit-reading position in accumulator.
/// </summary>
public int bitOffset;
/// <summary>
/// Number of 32-bit integers availabale for reading.
/// </summary>
private int available;
private int tailBytes = 0;
public BitReader()
{
byteBuffer = new MemoryStream(BUF_SIZE);
intBuffer = new IntBufferReader(byteBuffer);
}
/// <summary>
/// Fills up the input buffer.
/// <para>No-op if there are at least 36 bytes present after current position.</para>
/// <para>After encountering the end of the input stream, 64 additional zero bytes are copied
/// to the buffer.</para>
/// </summary>
public static void ReadMoreInput(BitReader br)
{
if (br.available > 9)
{
return;
}
if (br.endOfStreamReached)
{
if (br.available > 4)
{
return;
}
throw new BrotliRuntimeException("No more input");
}
int readOffset = (int)(br.intBuffer.Position << 2);
int bytesRead = READ_SIZE - readOffset;
Array.Copy(br.shadowBuffer, readOffset, br.shadowBuffer, 0, bytesRead);
try
{
while (bytesRead < READ_SIZE)
{
int len = br.input.Read(br.shadowBuffer, bytesRead, READ_SIZE - bytesRead);
if (len <= 0)
{
br.endOfStreamReached = true;
Utils.FillWithZeroes(br.shadowBuffer, bytesRead, 64);
bytesRead += 64;
br.tailBytes = bytesRead & 3;
break;
}
bytesRead += len;
}
}
catch (IOException e)
{
throw new BrotliRuntimeException("Failed to read input", e);
}
br.byteBuffer.SetLength(0);//clear
br.byteBuffer.Write(br.shadowBuffer, 0, bytesRead & 0xFFFC);
br.intBuffer.Position = 0;//rewind
br.available = bytesRead >> 2;
}
public static void CheckHealth(BitReader br)
{
if (!br.endOfStreamReached)
{
return;
}
/* When end of stream is reached, we "borrow" up to 64 zeroes to bit reader.
* If compressed stream is valid, then borrowed zeroes should remain unused.
*/
int valentBytes = (br.available << 2) + ((64 - br.bitOffset) >> 3);
int borrowedBytes = 64 - br.tailBytes;
if (valentBytes != borrowedBytes)
{
throw new BrotliRuntimeException("Read after end");
}
}
/// <summary>
/// Advances the Read buffer by 5 bytes to make room for reading next 24 bits.
/// </summary>
public static void FillBitWindow(BitReader br)
{
if (br.bitOffset >= 32)
{
int temp = br.intBuffer.ReadInt32();
br.accumulator = ((long)temp << 32) | (long)((ulong)br.accumulator >> 32);
br.bitOffset -= 32;
br.available--;
}
}
/// <summary>
/// Reads the specified number of bits from Read Buffer.
/// </summary>
public static int ReadBits(BitReader br, int n)
{
FillBitWindow(br);
int val = (int)((ulong)br.accumulator >> br.bitOffset) & ((1 << n) - 1);
br.bitOffset += n;
return val;
}
/// <summary>
/// Initialize bit reader.
/// <para>Initialisation turns bit reader to a ready state. Also a number of bytes is prefetched to</para>
/// <para>accumulator. Because of that this method may block until enough data could be read from input.</para>
/// </summary>
/// <param name="br">BitReader POJO</param>
/// <param name="input">data source</param>
public static void Init(BitReader br, Stream input)
{
if (br.input != null)
{
throw new InvalidOperationException("Bit reader already has associated input stream");
}
br.input = input;
br.accumulator = 0;
br.intBuffer.Position = (READ_SIZE >> 2);
br.bitOffset = 64;
br.available = 0;
br.endOfStreamReached = false;
ReadMoreInput(br);
/* This situation is impossible in current implementation. */
if (br.available == 0)
{
throw new BrotliRuntimeException("Can't initialize reader");
}
FillBitWindow(br);
FillBitWindow(br);
}
public static void Close(BitReader br)
{
try
{
Stream input = br.input;
br.input = null;
input = null;
}
catch (IOException ex)
{
throw ex;
}
}
public static void JumpToByteBoundry(BitReader br)
{
int padding = (64 - br.bitOffset) & 7;
if (padding != 0)
{
int paddingBits = BitReader.ReadBits(br, padding);
if (paddingBits != 0)
{
throw new BrotliRuntimeException("Corrupted padding bits ");
}
}
}
}
}

View File

@ -0,0 +1,199 @@
using System;
using System.IO;
namespace CSharpBrotli.Decode
{
/// <summary>
/// {@link InputStream} decorator that decompresses brotli data.
/// <para>Not thread-safe.</para>
/// </summary>
public class BrotliInputStream
{
public const int DEFAULT_INTERNAL_BUFFER_SIZE = 16384;
/// <summary>
/// Internal buffer used for efficient byte-by-byte reading.
/// </summary>
private byte[] buffer;
/// <summary>
/// Number of decoded but still unused bytes in internal buffer.
/// </summary>
private int remainingBufferBytes;
/// <summary>
/// Next unused byte offset.
/// </summary>
private int bufferOffset;
/// <summary>
/// Decoder state.
/// </summary>
private readonly State state = new State();
/// <summary>
/// Creates a <see cref="Stream"/> wrapper that decompresses brotli data.
/// <para>For byte-by-byte reading (<see cref="Read"/>) internal buffer with
/// <see cref="DEFAULT_INTERNAL_BUFFER_SIZE"/> size is allocated and used.
/// </para>
/// <para>Will block the thread until first kilobyte of data of source is available.</para>
/// </summary>
/// <param name="source">underlying data source</param>
public BrotliInputStream(Stream source) : this(source, DEFAULT_INTERNAL_BUFFER_SIZE, null) { }
/// <summary>
/// Creates a <see cref="Stream"/> wrapper that decompresses brotli data.
/// <para>For byte-by-byte reading (<see cref="Read"/> ) internal buffer of specified size is
/// allocated and used.
/// </para>
/// <para>Will block the thread until first kilobyte of data of source is available.</para>
/// </summary>
/// <param name="source">compressed data source</param>
/// <param name="byteReadBufferSize">size of internal buffer used in case of byte-by-byte reading</param>
public BrotliInputStream(Stream source, int byteReadBufferSize) : this(source, byteReadBufferSize, null) { }
/// <summary>
/// Creates a <see cref="Stream"/> wrapper that decompresses brotli data.
/// <para>For byte-by-byte reading (<see cref="Read"/> ) internal buffer of specified size is
/// allocated and used.
/// </para>
/// <para>Will block the thread until first kilobyte of data of source is available.</para>
/// </summary>
/// <param name="source">compressed data source</param>
/// <param name="byteReadBufferSize">size of internal buffer used in case of byte-by-byte reading</param>
/// <param name="customDictionary">custom dictionary data; null if not used</param>
public BrotliInputStream(Stream source, int byteReadBufferSize, byte[] customDictionary)
{
try
{
if (byteReadBufferSize <= 0)
{
throw new InvalidOperationException("Bad buffer size:" + byteReadBufferSize);
}
else if (source == null)
{
throw new InvalidOperationException("source is null");
}
this.buffer = new byte[byteReadBufferSize];
this.remainingBufferBytes = 0;
this.bufferOffset = 0;
try
{
State.SetInput(state, source);
}
catch (BrotliRuntimeException ex)
{
throw new IOException(ex.Message, ex.InnerException);
}
if (customDictionary != null)
{
Decode.SetCustomDictionary(state, customDictionary);
}
}
catch(IOException ex)
{
throw ex;
}
}
public void Close()
{
try
{
State.Close(state);
}
catch (IOException ex)
{
throw ex;
}
}
public int Read()
{
try
{
if (bufferOffset >= remainingBufferBytes)
{
remainingBufferBytes = Read(buffer, 0, buffer.Length);
bufferOffset = 0;
if (remainingBufferBytes == -1)
{
return -1;
}
}
return buffer[bufferOffset++] & 0xFF;
}
catch(IOException ex)
{
throw ex;
}
catch(BrotliRuntimeException ex)
{
throw new IOException(ex.Message, ex.InnerException);
}
}
public int Read(byte[] destBuffer)
{
return Read(destBuffer, 0, destBuffer.Length);
}
public int Read(byte[] destBuffer, int destOffset, int destLen)
{
try
{
if (destOffset < 0)
{
throw new InvalidOperationException("Bad offset: " + destOffset);
}
else if (destLen < 0)
{
throw new InvalidOperationException("Bad length: " + destLen);
}
else if (destOffset + destLen > destBuffer.Length)
{
throw new InvalidOperationException(
"Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.Length);
}
else if (destLen == 0)
{
return 0;
}
int copyLen = Math.Max(remainingBufferBytes - bufferOffset, 0);
if (copyLen != 0)
{
copyLen = Math.Min(copyLen, destLen);
Array.Copy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
bufferOffset += copyLen;
destOffset += copyLen;
destLen -= copyLen;
if (destLen == 0)
{
return copyLen;
}
}
try
{
state.output = destBuffer;
state.outputOffset = destOffset;
state.outputLength = destLen;
state.outputUsed = 0;
Decode.Decompress(state);
if (state.outputUsed == 0)
{
return -1;
}
return state.outputUsed + copyLen;
}
catch (BrotliRuntimeException ex)
{
throw new IOException(ex.Message, ex.InnerException);
}
}
catch (IOException ex)
{
throw ex;
}
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace CSharpBrotli.Decode
{
/// <summary>
/// Unchecked exception used internally.
/// </summary>
public class BrotliRuntimeException : Exception
{
public BrotliRuntimeException() : base() { }
public BrotliRuntimeException(string message) : base(message) { }
public BrotliRuntimeException(string message, Exception innerException) : base(message, innerException) { }
}
}

View File

@ -0,0 +1,159 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Common context lookup table for all context modes.
/// </summary>
public sealed class Context
{
public static readonly int[] LOOKUP =
{
// CONTEXT_UTF8, last byte.
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8, 12, 16, 12, 12, 20, 12, 16, 24, 28, 12, 12, 32, 12, 36, 12,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 32, 32, 24, 40, 28, 12,
12, 48, 52, 52, 52, 48, 52, 52, 52, 48, 52, 52, 52, 52, 52, 48,
52, 52, 52, 52, 52, 48, 52, 52, 52, 52, 52, 24, 12, 28, 12, 12,
12, 56, 60, 60, 60, 56, 60, 60, 60, 56, 60, 60, 60, 60, 60, 56,
60, 60, 60, 60, 60, 56, 60, 60, 60, 60, 60, 24, 12, 28, 12, 0,
// UTF8 continuation byte range.
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
// UTF8 lead byte range.
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
// CONTEXT_UTF8 second last byte.
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0,
// UTF8 continuation byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// UTF8 lead byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// CONTEXT_SIGNED, second last byte.
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
// CONTEXT_SIGNED, last byte, same as the above values shifted by 3 bits.
0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 56,
// CONTEXT_LSB6, last byte.
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
// CONTEXT_MSB6, last byte.
0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63,
// CONTEXT_{M,L}SB6, second last byte,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
public static readonly int[] LOOKUP_OFFSETS =
{
// CONTEXT_LSB6
1024, 1536,
// CONTEXT_MSB6
1280, 1536,
// CONTEXT_UTF8
0, 256,
// CONTEXT_SIGNED
768, 512
};
}
}

View File

@ -0,0 +1,969 @@
using System;
namespace CSharpBrotli.Decode
{
public sealed class Decode
{
private const int DEFAULT_CODE_LENGTH = 8;
private const int CODE_LENGTH_REPEAT_CODE = 16;
private const int NUM_LITERAL_CODES = 256;
private const int NUM_INSERT_AND_COPY_CODES = 704;
private const int NUM_BLOCK_LENGTH_CODES = 26;
private const int LITERAL_CONTEXT_BITS = 6;
private const int DISTANCE_CONTEXT_BITS = 2;
private const int HUFFMAN_TABLE_BITS = 8;
private const int HUFFMAN_TABLE_MASK = 0xFF;
private const int CODE_LENGTH_CODES = 18;
private static readonly int[] CODE_LENGTH_CODE_ORDER =
{
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15,
};
private const int NUM_DISTANCE_SHORT_CODES = 16;
private static readonly int[] DISTANCE_SHORT_CODE_INDEX_OFFSET =
{
3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2
};
private static readonly int[] DISTANCE_SHORT_CODE_VALUE_OFFSET =
{
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
};
/// <summary>
/// Static Huffman code for the code length code lengths.
/// </summary>
private static readonly int[] FIXED_TABLE =
{
0x020000, 0x020004, 0x020003, 0x030002, 0x020000, 0x020004, 0x020003, 0x040001,
0x020000, 0x020004, 0x020003, 0x030002, 0x020000, 0x020004, 0x020003, 0x040005
};
/// <summary>
/// Decodes a number in the range [0..255], by reading 1 - 11 bits.
/// </summary>
private static int DecodeVarLenUnsignedByte(BitReader br)
{
if (BitReader.ReadBits(br, 1) != 0)
{
int n = BitReader.ReadBits(br, 3);
if (n == 0)
{
return 1;
}
else
{
return BitReader.ReadBits(br, n) + (1 << n);
}
}
return 0;
}
private static void DecodeMetaBlockLength(BitReader br, State state)
{
state.inputEnd = BitReader.ReadBits(br, 1) == 1;
state.metaBlockLength = 0;
state.isUncompressed = false;
state.isMetadata = false;
if (state.inputEnd && BitReader.ReadBits(br, 1) != 0)
{
return;
}
int sizeNibbles = BitReader.ReadBits(br, 2) + 4;
if (sizeNibbles == 7)
{
state.isMetadata = true;
if (BitReader.ReadBits(br, 1) != 0)
{
throw new BrotliRuntimeException("Corrupted reserved bit");
}
int sizeBytes = BitReader.ReadBits(br, 2);
if (sizeBytes == 0)
{
return;
}
for (int i = 0; i < sizeBytes; i++)
{
int bits = BitReader.ReadBits(br, 8);
if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1)
{
throw new BrotliRuntimeException("Exuberant nibble");
}
state.metaBlockLength |= bits << (i * 8);
}
}
else
{
for (int i = 0; i < sizeNibbles; i++)
{
int bits = BitReader.ReadBits(br, 4);
if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4)
{
throw new BrotliRuntimeException("Exuberant nibble");
}
state.metaBlockLength |= bits << (i * 4);
}
}
state.metaBlockLength++;
if (!state.inputEnd)
{
state.isUncompressed = BitReader.ReadBits(br, 1) == 1;
}
}
private static int ReadSymbol(int[] table, int offset, BitReader br)
{
BitReader.FillBitWindow(br);
offset += (int)((ulong)br.accumulator >> br.bitOffset) & HUFFMAN_TABLE_MASK;
int n = (table[offset] >> 16) - HUFFMAN_TABLE_BITS;
if (n > 0)
{
br.bitOffset += HUFFMAN_TABLE_BITS;
offset += table[offset] & 0xFFFF;
offset += (int)((ulong)br.accumulator >> br.bitOffset) & ((1 << n) - 1);
}
br.bitOffset += table[offset] >> 16;
return table[offset] & 0xFFFF;
}
private static int ReadBlockLength(int[] table, int offset, BitReader br)
{
int code = ReadSymbol(table, offset, br);
int n = Prefix.BLOCK_LENGTH_N_BITS[code];
return Prefix.BLOCK_LENGTH_OFFSET[code] + BitReader.ReadBits(br, n);
}
private static int TranslateShortCodes(int code, int[] ringBuffer, int index)
{
if (code < NUM_DISTANCE_SHORT_CODES)
{
index += DISTANCE_SHORT_CODE_INDEX_OFFSET[code];
index &= 3;
return ringBuffer[index] + DISTANCE_SHORT_CODE_VALUE_OFFSET[code];
}
return code - NUM_DISTANCE_SHORT_CODES + 1;
}
private static void MoveToFront(int[] v, int index)
{
int value = v[index];
for (; index > 0; index--)
{
v[index] = v[index - 1];
}
v[0] = value;
}
private static void InverseMoveToFrontTransform(byte[] v, int vLen)
{
int[] mtf = new int[256];
for (int i = 0; i < 256; i++)
{
mtf[i] = i;
}
for (int i = 0; i < vLen; i++)
{
int index = v[i] & 0xFF;
v[i] = (byte)mtf[index];
if (index != 0)
{
MoveToFront(mtf, index);
}
}
}
private static void ReadHuffmanCodeLengths(
int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths, BitReader br)
{
int symbol = 0;
int prevCodeLen = DEFAULT_CODE_LENGTH;
int repeat = 0;
int repeatCodeLen = 0;
int space = 32768;
int[] table = new int[32];
Huffman.BuildHuffmanTable(table, 0, 5, codeLengthCodeLengths, CODE_LENGTH_CODES);
while (symbol < numSymbols && space > 0)
{
BitReader.ReadMoreInput(br);
BitReader.FillBitWindow(br);
int p = (int)(((ulong)br.accumulator >> br.bitOffset)) & 31;
br.bitOffset += table[p] >> 16;
int codeLen = table[p] & 0xFFFF;
if (codeLen < CODE_LENGTH_REPEAT_CODE)
{
repeat = 0;
codeLengths[symbol++] = codeLen;
if (codeLen != 0)
{
prevCodeLen = codeLen;
space -= 32768 >> codeLen;
}
}
else
{
int extraBits = codeLen - 14;
int newLen = 0;
if (codeLen == CODE_LENGTH_REPEAT_CODE)
{
newLen = prevCodeLen;
}
if (repeatCodeLen != newLen)
{
repeat = 0;
repeatCodeLen = newLen;
}
int oldRepeat = repeat;
if (repeat > 0)
{
repeat -= 2;
repeat <<= extraBits;
}
repeat += BitReader.ReadBits(br, extraBits) + 3;
int repeatDelta = repeat - oldRepeat;
if (symbol + repeatDelta > numSymbols)
{
throw new BrotliRuntimeException("symbol + repeatDelta > numSymbols"); // COV_NF_LINE
}
for (int i = 0; i < repeatDelta; i++)
{
codeLengths[symbol++] = repeatCodeLen;
}
if (repeatCodeLen != 0)
{
space -= repeatDelta << (15 - repeatCodeLen);
}
}
}
if (space != 0)
{
throw new BrotliRuntimeException("Unused space"); // COV_NF_LINE
}
// TODO: Pass max_symbol to Huffman table builder instead?
Utils.FillWithZeroes(codeLengths, symbol, numSymbols - symbol);
}
public static void ReadHuffmanCode(int alphabetSize, int[] table, int offset, BitReader br)
{
bool ok = true;
int simpleCodeOrSkip;
BitReader.ReadMoreInput(br);
// TODO: Avoid allocation.
int[] codeLengths = new int[alphabetSize];
simpleCodeOrSkip = BitReader.ReadBits(br, 2);
if (simpleCodeOrSkip == 1)
{ // Read symbols, codes & code lengths directly.
int maxBitsCounter = alphabetSize - 1;
int maxBits = 0;
int[] symbols = new int[4];
int numSymbols = BitReader.ReadBits(br, 2) + 1;
while (maxBitsCounter != 0)
{
maxBitsCounter >>= 1;
maxBits++;
}
Utils.FillWithZeroes(codeLengths, 0, alphabetSize);
for (int i = 0; i < numSymbols; i++)
{
symbols[i] = BitReader.ReadBits(br, maxBits) % alphabetSize;
codeLengths[symbols[i]] = 2;
}
codeLengths[symbols[0]] = 1;
switch (numSymbols)
{
case 1:
break;
case 2:
ok = symbols[0] != symbols[1];
codeLengths[symbols[1]] = 1;
break;
case 3:
ok = symbols[0] != symbols[1] && symbols[0] != symbols[2] && symbols[1] != symbols[2];
break;
case 4:
ok = symbols[0] != symbols[1] && symbols[0] != symbols[2] && symbols[0] != symbols[3]
&& symbols[1] != symbols[2] && symbols[1] != symbols[3] && symbols[2] != symbols[3];
if (BitReader.ReadBits(br, 1) == 1)
{
codeLengths[symbols[2]] = 3;
codeLengths[symbols[3]] = 3;
}
else
{
codeLengths[symbols[0]] = 2;
}
break;
}
}
else
{ // Decode Huffman-coded code lengths.
int[] codeLengthCodeLengths = new int[CODE_LENGTH_CODES];
int space = 32;
int numCodes = 0;
for (int i = simpleCodeOrSkip; i < CODE_LENGTH_CODES && space > 0; i++)
{
int codeLenIdx = CODE_LENGTH_CODE_ORDER[i];
BitReader.FillBitWindow(br);
int p = (int)((ulong)br.accumulator >> br.bitOffset) & 15;
// TODO: Demultiplex FIXED_TABLE.
br.bitOffset += FIXED_TABLE[p] >> 16;
int v = FIXED_TABLE[p] & 0xFFFF;
codeLengthCodeLengths[codeLenIdx] = v;
if (v != 0)
{
space -= (32 >> v);
numCodes++;
}
}
ok = (numCodes == 1 || space == 0);
ReadHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths, br);
}
if (!ok)
{
throw new BrotliRuntimeException("Can't readHuffmanCode"); // COV_NF_LINE
}
Huffman.BuildHuffmanTable(table, offset, HUFFMAN_TABLE_BITS, codeLengths, alphabetSize);
}
private static int DecodeContextMap(int contextMapSize, byte[] contextMap, BitReader br)
{
BitReader.ReadMoreInput(br);
int numTrees = DecodeVarLenUnsignedByte(br) + 1;
if (numTrees == 1)
{
Utils.FillWithZeroes(contextMap, 0, contextMapSize);
return numTrees;
}
bool useRleForZeros = BitReader.ReadBits(br, 1) == 1;
int maxRunLengthPrefix = 0;
if (useRleForZeros)
{
maxRunLengthPrefix = BitReader.ReadBits(br, 4) + 1;
}
int[] table = new int[Huffman.HUFFMAN_MAX_TABLE_SIZE];
ReadHuffmanCode(numTrees + maxRunLengthPrefix, table, 0, br);
for (int i = 0; i < contextMapSize;)
{
BitReader.ReadMoreInput(br);
int code = ReadSymbol(table, 0, br);
if (code == 0)
{
contextMap[i] = 0;
i++;
}
else if (code <= maxRunLengthPrefix)
{
int reps = (1 << code) + BitReader.ReadBits(br, code);
while (reps != 0)
{
if (i >= contextMapSize)
{
throw new BrotliRuntimeException("Corrupted context map"); // COV_NF_LINE
}
contextMap[i] = 0;
i++;
reps--;
}
}
else
{
contextMap[i] = (byte)(code - maxRunLengthPrefix);
i++;
}
}
if (BitReader.ReadBits(br, 1) == 1)
{
InverseMoveToFrontTransform(contextMap, contextMapSize);
}
return numTrees;
}
private static void DecodeBlockTypeAndLength(State state, int treeType)
{
BitReader br = state.br;
int[] ringBuffers = state.blockTypeRb;
int offset = treeType * 2;
int blockType = ReadSymbol(state.blockTypeTrees, treeType * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
state.blockLength[treeType] = ReadBlockLength(state.blockLenTrees,
treeType * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
if (blockType == 1)
{
blockType = ringBuffers[offset + 1] + 1;
}
else if (blockType == 0)
{
blockType = ringBuffers[offset];
}
else
{
blockType -= 2;
}
if (blockType >= state.numBlockTypes[treeType])
{
blockType -= state.numBlockTypes[treeType];
}
ringBuffers[offset] = ringBuffers[offset + 1];
ringBuffers[offset + 1] = blockType;
}
private static void DecodeLiteralBlockSwitch(State state)
{
DecodeBlockTypeAndLength(state, 0);
int literalBlockType = state.blockTypeRb[1];
state.contextMapSlice = literalBlockType << LITERAL_CONTEXT_BITS;
state.literalTreeIndex = state.contextMap[state.contextMapSlice] & 0xFF;
state.literalTree = state.hGroup0.trees[state.literalTreeIndex];
int contextMode = state.contextModes[literalBlockType];
state.contextLookupOffset1 = Context.LOOKUP_OFFSETS[contextMode];
state.contextLookupOffset2 = Context.LOOKUP_OFFSETS[contextMode + 1];
}
private static void DecodeCommandBlockSwitch(State state)
{
DecodeBlockTypeAndLength(state, 1);
state.treeCommandOffset = state.hGroup1.trees[state.blockTypeRb[3]];
}
private static void DecodeDistanceBlockSwitch(State state)
{
DecodeBlockTypeAndLength(state, 2);
state.distContextMapSlice = state.blockTypeRb[5] << DISTANCE_CONTEXT_BITS;
}
public static void MaybeReallocateRingBuffer(State state)
{
int newSize = state.maxRingBufferSize;
if ((long)newSize > state.expectedTotalSize)
{
/* TODO: Handle 2GB+ cases more gracefully. */
int minimalNewSize = (int)state.expectedTotalSize + state.customDictionary.Length;
while ((newSize >> 1) > minimalNewSize)
{
newSize >>= 1;
}
if (!state.inputEnd && newSize < 16384 && state.maxRingBufferSize >= 16384)
{
newSize = 16384;
}
}
if (newSize <= state.ringBufferSize)
{
return;
}
int ringBufferSizeWithSlack = newSize + Dictionary.MAX_TRANSFORMED_WORD_LENGTH;
byte[] newBuffer = new byte[ringBufferSizeWithSlack];
if (state.ringBuffer != null)
{
Array.Copy(state.ringBuffer, 0, newBuffer, 0, state.ringBufferSize);
}
else
{
/* Prepend custom dictionary, if any. */
if (state.customDictionary.Length != 0)
{
int length = state.customDictionary.Length;
int offset = 0;
if (length > state.maxBackwardDistance)
{
offset = length - state.maxBackwardDistance;
length = state.maxBackwardDistance;
}
Array.Copy(state.customDictionary, offset, newBuffer, 0, length);
state.pos = length;
state.bytesToIgnore = length;
}
}
state.ringBuffer = newBuffer;
state.ringBufferSize = newSize;
}
/// <summary>
/// Reads next metablock header.
/// </summary>
/// <param name="state">decoding state</param>
public static void ReadMeablockInfo(State state)
{
BitReader br = state.br;
if (state.inputEnd)
{
state.nextRunningState = RunningStage.FINISHED;
state.bytesToWrite = state.pos & (state.ringBufferSize - 1);
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
return;
}
// TODO: Reset? Do we need this?
state.hGroup0.codes = null;
state.hGroup0.trees = null;
state.hGroup1.codes = null;
state.hGroup1.trees = null;
state.hGroup2.codes = null;
state.hGroup2.trees = null;
BitReader.ReadMoreInput(br);
DecodeMetaBlockLength(br, state);
if (state.metaBlockLength == 0 && !state.isMetadata)
{
return;
}
if (state.isUncompressed || state.isMetadata)
{
BitReader.JumpToByteBoundry(br);
state.runningState = state.isMetadata ? RunningStage.READ_METADATA : RunningStage.COPY_UNCOMPRESSED;
}
else
{
state.runningState = RunningStage.COMPRESSED_BLOCK_START;
}
if (state.isMetadata)
{
return;
}
state.expectedTotalSize += state.metaBlockLength;
if (state.ringBufferSize < state.maxRingBufferSize)
{
MaybeReallocateRingBuffer(state);
}
}
public static void ReadMetablockHuffmanCodesAndContextMaps(State state)
{
BitReader br = state.br;
for (int i = 0; i < 3; i++)
{
state.numBlockTypes[i] = DecodeVarLenUnsignedByte(br) + 1;
state.blockLength[i] = 1 << 28;
if (state.numBlockTypes[i] > 1)
{
ReadHuffmanCode(state.numBlockTypes[i] + 2, state.blockTypeTrees,
i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
ReadHuffmanCode(NUM_BLOCK_LENGTH_CODES, state.blockLenTrees,
i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
state.blockLength[i] = ReadBlockLength(state.blockLenTrees,
i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
}
}
BitReader.ReadMoreInput(br);
state.distancePostfixBits = BitReader.ReadBits(br, 2);
state.numDirectDistanceCodes =
NUM_DISTANCE_SHORT_CODES + (BitReader.ReadBits(br, 4) << state.distancePostfixBits);
state.distancePostfixMask = (1 << state.distancePostfixBits) - 1;
int numDistanceCodes = state.numDirectDistanceCodes + (48 << state.distancePostfixBits);
// TODO: Reuse?
state.contextModes = new byte[state.numBlockTypes[0]];
for (int i = 0; i < state.numBlockTypes[0];)
{
/* Ensure that less than 256 bits read between readMoreInput. */
int limit = Math.Min(i + 96, state.numBlockTypes[0]);
for (; i < limit; ++i)
{
state.contextModes[i] = (byte)(BitReader.ReadBits(br, 2) << 1);
}
BitReader.ReadMoreInput(br);
}
// TODO: Reuse?
state.contextMap = new byte[state.numBlockTypes[0] << LITERAL_CONTEXT_BITS];
int numLiteralTrees = DecodeContextMap(state.numBlockTypes[0] << LITERAL_CONTEXT_BITS,
state.contextMap, br);
state.trivialLiteralContext = true;
for (int j = 0; j < state.numBlockTypes[0] << LITERAL_CONTEXT_BITS; j++)
{
if (state.contextMap[j] != j >> LITERAL_CONTEXT_BITS)
{
state.trivialLiteralContext = false;
break;
}
}
// TODO: Reuse?
state.distContextMap = new byte[state.numBlockTypes[2] << DISTANCE_CONTEXT_BITS];
int numDistTrees = DecodeContextMap(state.numBlockTypes[2] << DISTANCE_CONTEXT_BITS,
state.distContextMap, br);
HuffmanTreeGroup.Init(state.hGroup0, NUM_LITERAL_CODES, numLiteralTrees);
HuffmanTreeGroup.Init(state.hGroup1, NUM_INSERT_AND_COPY_CODES, state.numBlockTypes[1]);
HuffmanTreeGroup.Init(state.hGroup2, numDistanceCodes, numDistTrees);
HuffmanTreeGroup.Decode(state.hGroup0, br);
HuffmanTreeGroup.Decode(state.hGroup1, br);
HuffmanTreeGroup.Decode(state.hGroup2, br);
state.contextMapSlice = 0;
state.distContextMapSlice = 0;
state.contextLookupOffset1 = Context.LOOKUP_OFFSETS[state.contextModes[0]];
state.contextLookupOffset2 = Context.LOOKUP_OFFSETS[state.contextModes[0] + 1];
state.literalTreeIndex = 0;
state.literalTree = state.hGroup0.trees[0];
state.treeCommandOffset = state.hGroup1.trees[0]; // TODO: == 0?
state.blockTypeRb[0] = state.blockTypeRb[2] = state.blockTypeRb[4] = 1;
state.blockTypeRb[1] = state.blockTypeRb[3] = state.blockTypeRb[5] = 0;
}
public static void CopyUncompressedData(State state)
{
BitReader br = state.br;
byte[] ringBuffer = state.ringBuffer;
int ringBufferMask = state.ringBufferSize - 1;
while (state.metaBlockLength > 0)
{
BitReader.ReadMoreInput(br);
// Optimize
ringBuffer[state.pos & ringBufferMask] = (byte)(BitReader.ReadBits(br, 8));
state.metaBlockLength--;
if ((state.pos++ & ringBufferMask) == ringBufferMask)
{
state.nextRunningState = RunningStage.COPY_UNCOMPRESSED;
state.bytesToWrite = state.ringBufferSize;
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
return;
}
}
state.runningState = RunningStage.BLOCK_START;
}
public static bool WriteRingBuffer(State state)
{
/* Ignore custom dictionary bytes. */
if (state.bytesToIgnore != 0)
{
state.bytesWritten += state.bytesToIgnore;
state.bytesToIgnore = 0;
}
int toWrite = Math.Min(state.outputLength - state.outputUsed,
state.bytesToWrite - state.bytesWritten);
if (toWrite != 0)
{
Array.Copy(state.ringBuffer, state.bytesWritten, state.output,
state.outputOffset + state.outputUsed, toWrite);
state.outputUsed += toWrite;
state.bytesWritten += toWrite;
}
return state.outputUsed < state.outputLength;
}
public static void SetCustomDictionary(State state, byte[] data)
{
state.customDictionary = (data == null) ? new byte[0] : data;
}
public static void Decompress(State state)
{
if (state.runningState == RunningStage.UNINITIALIZED)
{
throw new InvalidOperationException("Can't decompress until initialized");
}
if (state.runningState == RunningStage.CLOSED)
{
throw new InvalidOperationException("Can't decompress after close");
}
BitReader br = state.br;
int ringBufferMask = state.ringBufferSize - 1;
byte[] ringBuffer = state.ringBuffer;
while (state.runningState != RunningStage.FINISHED)
{
// TODO: extract cases to methods for the better readability.
switch (state.runningState)
{
case RunningStage.BLOCK_START:
if (state.metaBlockLength < 0)
{
throw new BrotliRuntimeException("Invalid metablock length");
}
ReadMeablockInfo(state);
/* Ring-buffer would be reallocated here. */
ringBufferMask = state.ringBufferSize - 1;
ringBuffer = state.ringBuffer;
continue;
case RunningStage.COMPRESSED_BLOCK_START:
ReadMetablockHuffmanCodesAndContextMaps(state);
state.runningState = RunningStage.MAIN_LOOP;
goto case RunningStage.MAIN_LOOP;
// Fall through
case RunningStage.MAIN_LOOP:
if (state.metaBlockLength <= 0)
{
// Protect pos from overflow, wrap it around at every GB of input data.
state.pos &= 0x3fffffff;
state.runningState = RunningStage.BLOCK_START;
continue;
}
BitReader.ReadMoreInput(br);
if (state.blockLength[1] == 0)
{
DecodeCommandBlockSwitch(state);
}
state.blockLength[1]--;
int cmdCode = ReadSymbol(state.hGroup1.codes, state.treeCommandOffset, br);
int rangeIdx = (int)((uint)cmdCode >> 6);
state.distanceCode = 0;
if (rangeIdx >= 2)
{
rangeIdx -= 2;
state.distanceCode = -1;
}
int insertCode = Prefix.INSERT_RANGE_LUT[rangeIdx] + ((int)((uint)cmdCode >> 3) & 7);
int copyCode = Prefix.COPY_RANGE_LUT[rangeIdx] + (cmdCode & 7);
state.insertLength = Prefix.INSERT_LENGTH_OFFSET[insertCode] + BitReader
.ReadBits(br, Prefix.INSERT_LENGTH_N_BITS[insertCode]);
state.copyLength = Prefix.COPY_LENGTH_OFFSET[copyCode] + BitReader
.ReadBits(br, Prefix.COPY_LENGTH_N_BITS[copyCode]);
state.j = 0;
state.runningState = RunningStage.INSERT_LOOP;
goto case RunningStage.INSERT_LOOP;
// Fall through
case RunningStage.INSERT_LOOP:
if (state.trivialLiteralContext)
{
while (state.j < state.insertLength)
{
BitReader.ReadMoreInput(br);
if (state.blockLength[0] == 0)
{
DecodeLiteralBlockSwitch(state);
}
state.blockLength[0]--;
ringBuffer[state.pos & ringBufferMask] = (byte)ReadSymbol(
state.hGroup0.codes, state.literalTree, br);
state.j++;
if ((state.pos++ & ringBufferMask) == ringBufferMask)
{
state.nextRunningState = RunningStage.INSERT_LOOP;
state.bytesToWrite = state.ringBufferSize;
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
break;
}
}
}
else
{
int prevByte1 = ringBuffer[(state.pos - 1) & ringBufferMask] & 0xFF;
int prevByte2 = ringBuffer[(state.pos - 2) & ringBufferMask] & 0xFF;
while (state.j < state.insertLength)
{
BitReader.ReadMoreInput(br);
if (state.blockLength[0] == 0)
{
DecodeLiteralBlockSwitch(state);
}
int literalTreeIndex = state.contextMap[state.contextMapSlice
+ (Context.LOOKUP[state.contextLookupOffset1 + prevByte1]
| Context.LOOKUP[state.contextLookupOffset2 + prevByte2])] & 0xFF;
state.blockLength[0]--;
prevByte2 = prevByte1;
prevByte1 = ReadSymbol(
state.hGroup0.codes, state.hGroup0.trees[literalTreeIndex], br);
ringBuffer[state.pos & ringBufferMask] = (byte)prevByte1;
state.j++;
if ((state.pos++ & ringBufferMask) == ringBufferMask)
{
state.nextRunningState = RunningStage.INSERT_LOOP;
state.bytesToWrite = state.ringBufferSize;
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
break;
}
}
}
if (state.runningState != RunningStage.INSERT_LOOP)
{
continue;
}
state.metaBlockLength -= state.insertLength;
if (state.metaBlockLength <= 0)
{
state.runningState = RunningStage.MAIN_LOOP;
continue;
}
if (state.distanceCode < 0)
{
BitReader.ReadMoreInput(br);
if (state.blockLength[2] == 0)
{
DecodeDistanceBlockSwitch(state);
}
state.blockLength[2]--;
state.distanceCode = ReadSymbol(state.hGroup2.codes, state.hGroup2.trees[
state.distContextMap[state.distContextMapSlice
+ (state.copyLength > 4 ? 3 : state.copyLength - 2)] & 0xFF], br);
if (state.distanceCode >= state.numDirectDistanceCodes)
{
state.distanceCode -= state.numDirectDistanceCodes;
int postfix = state.distanceCode & state.distancePostfixMask;
//state.distanceCode >>>= state.distancePostfixBits;
state.distanceCode = (int)((uint)state.distanceCode >> state.distancePostfixBits);
int n = (int)((uint)state.distanceCode >> 1) + 1;
int offset = ((2 + (state.distanceCode & 1)) << n) - 4;
state.distanceCode = state.numDirectDistanceCodes + postfix
+ ((offset + BitReader.ReadBits(br, n)) << state.distancePostfixBits);
}
}
// Convert the distance code to the actual distance by possibly looking up past distances
// from the ringBuffer.
state.distance = TranslateShortCodes(state.distanceCode, state.distRb, state.distRbIdx);
if (state.distance < 0)
{
throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE
}
if (state.pos < state.maxBackwardDistance
&& state.maxDistance != state.maxBackwardDistance)
{
state.maxDistance = state.pos;
}
else
{
state.maxDistance = state.maxBackwardDistance;
}
state.copyDst = state.pos & ringBufferMask;
if (state.distance > state.maxDistance)
{
state.runningState = RunningStage.TRANSFORM;
continue;
}
if (state.distanceCode > 0)
{
state.distRb[state.distRbIdx & 3] = state.distance;
state.distRbIdx++;
}
if (state.copyLength > state.metaBlockLength)
{
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
state.j = 0;
state.runningState = RunningStage.COPY_LOOP;
goto case RunningStage.COPY_LOOP;
// fall through
case RunningStage.COPY_LOOP:
for (; state.j < state.copyLength;)
{
ringBuffer[state.pos & ringBufferMask] =
ringBuffer[(state.pos - state.distance) & ringBufferMask];
// TODO: condense
state.metaBlockLength--;
state.j++;
if ((state.pos++ & ringBufferMask) == ringBufferMask)
{
state.nextRunningState = RunningStage.COPY_LOOP;
state.bytesToWrite = state.ringBufferSize;
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
break;
}
}
if (state.runningState == RunningStage.COPY_LOOP)
{
state.runningState = RunningStage.MAIN_LOOP;
}
continue;
case RunningStage.TRANSFORM:
if (state.copyLength >= Dictionary.MIN_WORD_LENGTH
&& state.copyLength <= Dictionary.MAX_WORD_LENGTH)
{
int offset = Dictionary.OFFSETS_BY_LENGTH[state.copyLength];
int wordId = state.distance - state.maxDistance - 1;
int shift = Dictionary.SIZE_BITS_BY_LENGTH[state.copyLength];
int mask = (1 << shift) - 1;
int wordIdx = wordId & mask;
int transformIdx = (int)((uint)wordId >> shift);
offset += wordIdx * state.copyLength;
if (transformIdx < Transform.TRANSFORMS.Length)
{
int len = Transform.TransformDictionaryWord(ringBuffer, state.copyDst,
Dictionary.GetData(), offset, state.copyLength,
Transform.TRANSFORMS[transformIdx]);
state.copyDst += len;
state.pos += len;
state.metaBlockLength -= len;
if (state.copyDst >= state.ringBufferSize)
{
state.nextRunningState = RunningStage.COPY_WRAP_BUFFER;
state.bytesToWrite = state.ringBufferSize;
state.bytesWritten = 0;
state.runningState = RunningStage.WRITE;
continue;
}
}
else
{
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
}
else
{
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
state.runningState = RunningStage.MAIN_LOOP;
continue;
case RunningStage.COPY_WRAP_BUFFER:
Array.Copy(ringBuffer, state.ringBufferSize, ringBuffer, 0,
state.copyDst - state.ringBufferSize);
state.runningState = RunningStage.MAIN_LOOP;
continue;
case RunningStage.READ_METADATA:
while (state.metaBlockLength > 0)
{
BitReader.ReadMoreInput(br);
// Optimize
BitReader.ReadBits(br, 8);
state.metaBlockLength--;
}
state.runningState = RunningStage.BLOCK_START;
continue;
case RunningStage.COPY_UNCOMPRESSED:
CopyUncompressedData(state);
continue;
case RunningStage.WRITE:
if (!WriteRingBuffer(state))
{
// Output buffer is full.
return;
}
state.runningState = state.nextRunningState;
continue;
default:
throw new BrotliRuntimeException("Unexpected state " + state.runningState);
}
}
if (state.runningState == RunningStage.FINISHED)
{
if (state.metaBlockLength < 0)
{
throw new BrotliRuntimeException("Invalid metablock length");
}
BitReader.JumpToByteBoundry(br);
BitReader.CheckHealth(state.br);
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,147 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Utilities for building Huffman decoding tables.
/// </summary>
public sealed class Huffman
{
/// <summary>
/// Maximum possible Huffman table size for an alphabet size of 704, max code length 15 and root
/// table bits 8.
/// </summary>
public const int HUFFMAN_MAX_TABLE_SIZE = 1080;
private const int MAX_LENGTH = 15;
/// <summary>
/// Returns reverse(reverse(key, len) + 1, len).
/// reverse(key, len) is the bit-wise reversal of the len least significant bits of key.
/// </summary>
private static int GetNextKey(int key, int len)
{
int step = 1 << (len - 1);
while ((key & step) != 0)
{
step >>= 1;
}
return (key & (step - 1)) + step;
}
/// <summary>
/// Stores item in table[0], table[step], table[2 * step] .., table[end]}.
/// Assumes that end is an integer multiple of step.
/// </summary>
private static void ReplicateValue(int[] table, int offset, int step, int end, int item)
{
do
{
end -= step;
table[offset + end] = item;
} while (end > 0);
}
/// <param name="count">histogram of bit lengths for the remaining symbols</param>
/// <param name="len">code length of the next processed symbol.</param>
/// <returns>width of the next 2nd level table</returns>
private static int NextTableBitSize(int[] count, int len, int rootBits)
{
int left = 1 << (len - rootBits);
while (len < MAX_LENGTH)
{
left -= count[len];
if (left <= 0)
{
break;
}
len++;
left <<= 1;
}
return len - rootBits;
}
/// <summary>
/// Builds Huffman lookup table assuming code lengths are in symbol order.
/// </summary>
public static void BuildHuffmanTable(int[] rootTable, int tableOffset, int rootBits,
int[] codeLengths, int codeLengthsSize)
{
int key; // Reversed prefix code.
int[] sorted = new int[codeLengthsSize]; // Symbols sorted by code length.
// TODO: fill with zeroes?
int[] count = new int[MAX_LENGTH + 1]; // Number of codes of each length.
int[] offset = new int[MAX_LENGTH + 1]; // Offsets in sorted table for each length.
int symbol;
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; symbol++)
{
count[codeLengths[symbol]]++;
}
// Generate offsets into sorted symbol table by code length.
offset[1] = 0;
for (int len = 1; len < MAX_LENGTH; len++)
{
offset[len + 1] = offset[len] + count[len];
}
// Sort symbols by length, by symbol order within each length.
for (symbol = 0; symbol < codeLengthsSize; symbol++)
{
if (codeLengths[symbol] != 0)
{
sorted[offset[codeLengths[symbol]]++] = symbol;
}
}
int tableBits = rootBits;
int tableSize = 1 << tableBits;
int totalSize = tableSize;
// Special case code with only one value.
if (offset[MAX_LENGTH] == 1)
{
for (key = 0; key < totalSize; key++)
{
rootTable[tableOffset + key] = sorted[0];
}
return;
}
// Fill in root table.
key = 0;
symbol = 0;
for (int len = 1, step = 2; len <= rootBits; len++, step <<= 1)
{
for (; count[len] > 0; count[len]--)
{
ReplicateValue(rootTable, tableOffset + key, step, tableSize, len << 16 | sorted[symbol++]);
key = GetNextKey(key, len);
}
}
// Fill in 2nd level tables and add pointers to root table.
int mask = totalSize - 1;
int low = -1;
int currentOffset = tableOffset;
for (int len = rootBits + 1, step = 2; len <= MAX_LENGTH; len++, step <<= 1)
{
for (; count[len] > 0; count[len]--)
{
if ((key & mask) != low)
{
currentOffset += tableSize;
tableBits = NextTableBitSize(count, len, rootBits);
tableSize = 1 << tableBits;
totalSize += tableSize;
low = key & mask;
rootTable[tableOffset + low] =
(tableBits + rootBits) << 16 | (currentOffset - tableOffset - low);
}
ReplicateValue(rootTable, currentOffset + (key >> rootBits), step, tableSize,
(len - rootBits) << 16 | sorted[symbol++]);
key = GetNextKey(key, len);
}
}
}
}
}

View File

@ -0,0 +1,54 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Contains a collection of huffman trees with the same alphabet size.
/// </summary>
public class HuffmanTreeGroup
{
/// <summary>
/// The maximal alphabet size in this group.
/// </summary>
private int alphabetSize;
/// <summary>
/// Storage for Huffman lookup tables.
/// </summary>
//public int[] Codes { get { return _codes; } set { _codes = value; } }
public int[] codes;
/// <summary>
/// Offsets of distinct lookup tables in <see cref="codes"/> storage.
/// </summary>
public int[] trees;
/// <summary>
/// Initializes the Huffman tree group.
/// </summary>
/// <param name="group">POJO to be initialised</param>
/// <param name="alphabetSize">the maximal alphabet size in this group</param>
/// <param name="n">number of Huffman codes</param>
public static void Init(HuffmanTreeGroup group, int alphabetSize, int n)
{
group.alphabetSize = alphabetSize;
group.codes = new int[n * Huffman.HUFFMAN_MAX_TABLE_SIZE];
group.trees = new int[n];
}
/// <summary>
/// Decodes Huffman trees from input stream and constructs lookup tables.
/// </summary>
/// <param name="group">target POJO</param>
/// <param name="br">data source</param>
public static void Decode(HuffmanTreeGroup group, BitReader br)
{
int next = 0;
int n = group.trees.Length;
for (int i = 0; i < n; i++)
{
group.trees[i] = next;
CSharpBrotli.Decode.Decode.ReadHuffmanCode(group.alphabetSize, group.codes, next, br);
next += Huffman.HUFFMAN_MAX_TABLE_SIZE;
}
}
}
}

View File

@ -0,0 +1,36 @@
using System.IO;
namespace CSharpBrotli.Decode
{
public class IntBufferReader
{
/// <summary>
/// Int32 Position
/// </summary>
public long Position
{
get
{
//Divide 4 when get Int32 position from byte position
return reader.BaseStream.Position / 4;
}
set
{
//Multiple 4 when set Int32 position to byte position
reader.BaseStream.Position = value * 4;
}
}
BinaryReader reader;
public IntBufferReader(Stream input)
{
this.reader = new BinaryReader(input);
}
public int ReadInt32()
{
return reader.ReadInt32();
}
}
}

View File

@ -0,0 +1,62 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Lookup tables to map prefix codes to value ranges.
/// <para>This is used during decoding of the block lengths, literal insertion lengths and copy lengths.</para>
/// <para>Range represents values: [offset, offset + 2 ^ n_bits)</para>
/// </summary>
public sealed class Prefix
{
public static readonly int[] BLOCK_LENGTH_OFFSET =
{
1, 5, 9, 13, 17, 25, 33, 41, 49, 65,
81, 97, 113, 145, 177, 209, 241, 305, 369, 497,
753, 1265, 2289, 4337, 8433, 16625
};
public static readonly int[] BLOCK_LENGTH_N_BITS =
{
2, 2, 2, 2, 3, 3, 3, 3, 4, 4,
4, 4, 5, 5, 5, 5, 6, 6, 7, 8,
9, 10, 11, 12, 13, 24
};
public static int[] INSERT_LENGTH_OFFSET =
{
0, 1, 2, 3, 4, 5, 6, 8, 10, 14,
18, 26, 34, 50, 66, 98, 130, 194, 322, 578,
1090, 2114, 6210, 22594
};
public static int[] INSERT_LENGTH_N_BITS =
{
0, 0, 0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 7, 8, 9,
10, 12, 14, 24
};
public static int[] COPY_LENGTH_OFFSET =
{
2, 3, 4, 5, 6, 7, 8, 9, 10, 12,
14, 18, 22, 30, 38, 54, 70, 102, 134, 198,
326, 582, 1094, 2118
};
public static int[] COPY_LENGTH_N_BITS =
{
0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
2, 2, 3, 3, 4, 4, 5, 5, 6, 7,
8, 9, 10, 24
};
public static int[] INSERT_RANGE_LUT =
{
0, 0, 8, 8, 0, 16, 8, 16, 16
};
public static int[] COPY_RANGE_LUT =
{
0, 8, 0, 8, 16, 0, 16, 8, 16
};
}
}

View File

@ -0,0 +1,22 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Enumeration of decoding state-machine.
/// </summary>
public enum RunningStage
{
UNINITIALIZED,
BLOCK_START,
COMPRESSED_BLOCK_START,
MAIN_LOOP,
READ_METADATA,
COPY_UNCOMPRESSED,
INSERT_LOOP,
COPY_LOOP,
COPY_WRAP_BUFFER,
TRANSFORM,
FINISHED,
CLOSED,
WRITE
}
}

View File

@ -0,0 +1,122 @@
using System;
using System.IO;
namespace CSharpBrotli.Decode
{
public sealed class State
{
public RunningStage runningState = RunningStage.UNINITIALIZED;
public RunningStage nextRunningState;
public readonly BitReader br = new BitReader();
public byte[] ringBuffer;
public readonly int[] blockTypeTrees = new int[3 * Huffman.HUFFMAN_MAX_TABLE_SIZE];
public readonly int[] blockLenTrees = new int[3 * Huffman.HUFFMAN_MAX_TABLE_SIZE];
// Current meta-block header information.
public int metaBlockLength;
public bool inputEnd;
public bool isUncompressed;
public bool isMetadata;
public readonly HuffmanTreeGroup hGroup0 = new HuffmanTreeGroup();
public readonly HuffmanTreeGroup hGroup1 = new HuffmanTreeGroup();
public readonly HuffmanTreeGroup hGroup2 = new HuffmanTreeGroup();
public readonly int[] blockLength = new int[3];
public readonly int[] numBlockTypes = new int[3];
public readonly int[] blockTypeRb = new int[6];
public readonly int[] distRb = { 16, 15, 11, 4 };
public int pos = 0;
public int maxDistance = 0;
public int distRbIdx = 0;
public bool trivialLiteralContext = false;
public int literalTreeIndex = 0;
public int literalTree;
public int j;
public int insertLength;
public byte[] contextModes;
public byte[] contextMap;
public int contextMapSlice;
public int distContextMapSlice;
public int contextLookupOffset1;
public int contextLookupOffset2;
public int treeCommandOffset;
public int distanceCode;
public byte[] distContextMap;
public int numDirectDistanceCodes;
public int distancePostfixMask;
public int distancePostfixBits;
public int distance;
public int copyLength;
public int copyDst;
public int maxBackwardDistance;
public int maxRingBufferSize;
public int ringBufferSize = 0;
public long expectedTotalSize = 0;
public byte[] customDictionary = new byte[0];
public int bytesToIgnore = 0;
public int outputOffset;
public int outputLength;
public int outputUsed;
public int bytesWritten;
public int bytesToWrite;
public byte[] output;
private static int DecodeWindowBits(BitReader br)
{
if (BitReader.ReadBits(br, 1) == 0)
{
return 16;
}
int n = BitReader.ReadBits(br, 3);
if (n != 0)
{
return 17 + n;
}
n = BitReader.ReadBits(br, 3);
if (n != 0)
{
return 8 + n;
}
return 17;
}
public static void SetInput(State state, Stream input)
{
if (state.runningState != RunningStage.UNINITIALIZED)
{
throw new InvalidOperationException("State MUST be uninitialized");
}
BitReader.Init(state.br, input);
int windowBits = DecodeWindowBits(state.br);
if (windowBits == 9)
{ /* Reserved case for future expansion. */
throw new BrotliRuntimeException("Invalid 'windowBits' code");
}
state.maxRingBufferSize = 1 << windowBits;
state.maxBackwardDistance = state.maxRingBufferSize - 16;
state.runningState = RunningStage.BLOCK_START;
}
public static void Close(State state)
{
try
{
if (state.runningState == RunningStage.UNINITIALIZED)
{
throw new InvalidOperationException("State MUST be initialized");
}
if (state.runningState == RunningStage.CLOSED)
{
return;
}
state.runningState = RunningStage.CLOSED;
BitReader.Close(state.br);
}
catch(IOException ex)
{
throw ex;
}
}
}
}

View File

@ -0,0 +1,254 @@
namespace CSharpBrotli.Decode
{
/// <summary>
/// Transformations on dictionary words.
/// </summary>
public sealed class Transform
{
private readonly byte[] prefix;
private readonly WordTransformType type;
private readonly byte[] suffix;
public Transform(string prefix, WordTransformType type, string suffix)
{
this.prefix = ReadUniBytes(prefix);
this.type = type;
this.suffix = ReadUniBytes(suffix);
}
public static byte[] ReadUniBytes(string uniBytes)
{
byte[] result = new byte[uniBytes.Length];
for(int i = 0; i < result.Length; i++)
{
result[i] = (byte)uniBytes[i];
}
return result;
}
public static readonly Transform[] TRANSFORMS =
{
new Transform("", IDENTITY, ""),
new Transform("", IDENTITY, " "),
new Transform(" ", IDENTITY, " "),
new Transform("", OMIT_FIRST_1, ""),
new Transform("", UPPERCASE_FIRST, " "),
new Transform("", IDENTITY, " the "),
new Transform(" ", IDENTITY, ""),
new Transform("s ", IDENTITY, " "),
new Transform("", IDENTITY, " of "),
new Transform("", UPPERCASE_FIRST, ""),
new Transform("", IDENTITY, " and "),
new Transform("", OMIT_FIRST_2, ""),
new Transform("", OMIT_LAST_1, ""),
new Transform(", ", IDENTITY, " "),
new Transform("", IDENTITY, ", "),
new Transform(" ", UPPERCASE_FIRST, " "),
new Transform("", IDENTITY, " in "),
new Transform("", IDENTITY, " to "),
new Transform("e ", IDENTITY, " "),
new Transform("", IDENTITY, "\""),
new Transform("", IDENTITY, "."),
new Transform("", IDENTITY, "\">"),
new Transform("", IDENTITY, "\n"),
new Transform("", OMIT_LAST_3, ""),
new Transform("", IDENTITY, "]"),
new Transform("", IDENTITY, " for "),
new Transform("", OMIT_FIRST_3, ""),
new Transform("", OMIT_LAST_2, ""),
new Transform("", IDENTITY, " a "),
new Transform("", IDENTITY, " that "),
new Transform(" ", UPPERCASE_FIRST, ""),
new Transform("", IDENTITY, ". "),
new Transform(".", IDENTITY, ""),
new Transform(" ", IDENTITY, ", "),
new Transform("", OMIT_FIRST_4, ""),
new Transform("", IDENTITY, " with "),
new Transform("", IDENTITY, "'"),
new Transform("", IDENTITY, " from "),
new Transform("", IDENTITY, " by "),
new Transform("", OMIT_FIRST_5, ""),
new Transform("", OMIT_FIRST_6, ""),
new Transform(" the ", IDENTITY, ""),
new Transform("", OMIT_LAST_4, ""),
new Transform("", IDENTITY, ". The "),
new Transform("", UPPERCASE_ALL, ""),
new Transform("", IDENTITY, " on "),
new Transform("", IDENTITY, " as "),
new Transform("", IDENTITY, " is "),
new Transform("", OMIT_LAST_7, ""),
new Transform("", OMIT_LAST_1, "ing "),
new Transform("", IDENTITY, "\n\t"),
new Transform("", IDENTITY, ":"),
new Transform(" ", IDENTITY, ". "),
new Transform("", IDENTITY, "ed "),
new Transform("", OMIT_FIRST_9, ""),
new Transform("", OMIT_FIRST_7, ""),
new Transform("", OMIT_LAST_6, ""),
new Transform("", IDENTITY, "("),
new Transform("", UPPERCASE_FIRST, ", "),
new Transform("", OMIT_LAST_8, ""),
new Transform("", IDENTITY, " at "),
new Transform("", IDENTITY, "ly "),
new Transform(" the ", IDENTITY, " of "),
new Transform("", OMIT_LAST_5, ""),
new Transform("", OMIT_LAST_9, ""),
new Transform(" ", UPPERCASE_FIRST, ", "),
new Transform("", UPPERCASE_FIRST, "\""),
new Transform(".", IDENTITY, "("),
new Transform("", UPPERCASE_ALL, " "),
new Transform("", UPPERCASE_FIRST, "\">"),
new Transform("", IDENTITY, "=\""),
new Transform(" ", IDENTITY, "."),
new Transform(".com/", IDENTITY, ""),
new Transform(" the ", IDENTITY, " of the "),
new Transform("", UPPERCASE_FIRST, "'"),
new Transform("", IDENTITY, ". This "),
new Transform("", IDENTITY, ","),
new Transform(".", IDENTITY, " "),
new Transform("", UPPERCASE_FIRST, "("),
new Transform("", UPPERCASE_FIRST, "."),
new Transform("", IDENTITY, " not "),
new Transform(" ", IDENTITY, "=\""),
new Transform("", IDENTITY, "er "),
new Transform(" ", UPPERCASE_ALL, " "),
new Transform("", IDENTITY, "al "),
new Transform(" ", UPPERCASE_ALL, ""),
new Transform("", IDENTITY, "='"),
new Transform("", UPPERCASE_ALL, "\""),
new Transform("", UPPERCASE_FIRST, ". "),
new Transform(" ", IDENTITY, "("),
new Transform("", IDENTITY, "ful "),
new Transform(" ", UPPERCASE_FIRST, ". "),
new Transform("", IDENTITY, "ive "),
new Transform("", IDENTITY, "less "),
new Transform("", UPPERCASE_ALL, "'"),
new Transform("", IDENTITY, "est "),
new Transform(" ", UPPERCASE_FIRST, "."),
new Transform("", UPPERCASE_ALL, "\">"),
new Transform(" ", IDENTITY, "='"),
new Transform("", UPPERCASE_FIRST, ","),
new Transform("", IDENTITY, "ize "),
new Transform("", UPPERCASE_ALL, "."),
new Transform("\u00c2\u00a0", IDENTITY, ""),
new Transform(" ", IDENTITY, ","),
new Transform("", UPPERCASE_FIRST, "=\""),
new Transform("", UPPERCASE_ALL, "=\""),
new Transform("", IDENTITY, "ous "),
new Transform("", UPPERCASE_ALL, ", "),
new Transform("", UPPERCASE_FIRST, "='"),
new Transform(" ", UPPERCASE_FIRST, ","),
new Transform(" ", UPPERCASE_ALL, "=\""),
new Transform(" ", UPPERCASE_ALL, ", "),
new Transform("", UPPERCASE_ALL, ","),
new Transform("", UPPERCASE_ALL, "("),
new Transform("", UPPERCASE_ALL, ". "),
new Transform(" ", UPPERCASE_ALL, "."),
new Transform("", UPPERCASE_ALL, "='"),
new Transform(" ", UPPERCASE_ALL, ". "),
new Transform(" ", UPPERCASE_FIRST, "=\""),
new Transform(" ", UPPERCASE_ALL, "='"),
new Transform(" ", UPPERCASE_FIRST, "='")
};
private const WordTransformType IDENTITY = WordTransformType.IDENTITY;
private const WordTransformType OMIT_LAST_1 = WordTransformType.OMIT_LAST_1;
private const WordTransformType OMIT_LAST_2 = WordTransformType.OMIT_LAST_2;
private const WordTransformType OMIT_LAST_3 = WordTransformType.OMIT_LAST_3;
private const WordTransformType OMIT_LAST_4 = WordTransformType.OMIT_LAST_4;
private const WordTransformType OMIT_LAST_5 = WordTransformType.OMIT_LAST_5;
private const WordTransformType OMIT_LAST_6 = WordTransformType.OMIT_LAST_6;
private const WordTransformType OMIT_LAST_7 = WordTransformType.OMIT_LAST_7;
private const WordTransformType OMIT_LAST_8 = WordTransformType.OMIT_LAST_8;
private const WordTransformType OMIT_LAST_9 = WordTransformType.OMIT_LAST_9;
private const WordTransformType UPPERCASE_FIRST = WordTransformType.UPPERCASE_FIRST;
private const WordTransformType UPPERCASE_ALL = WordTransformType.UPPERCASE_ALL;
private const WordTransformType OMIT_FIRST_1 = WordTransformType.OMIT_FIRST_1;
private const WordTransformType OMIT_FIRST_2 = WordTransformType.OMIT_FIRST_2;
private const WordTransformType OMIT_FIRST_3 = WordTransformType.OMIT_FIRST_3;
private const WordTransformType OMIT_FIRST_4 = WordTransformType.OMIT_FIRST_4;
private const WordTransformType OMIT_FIRST_5 = WordTransformType.OMIT_FIRST_5;
private const WordTransformType OMIT_FIRST_6 = WordTransformType.OMIT_FIRST_6;
private const WordTransformType OMIT_FIRST_7 = WordTransformType.OMIT_FIRST_7;
private const WordTransformType OMIT_FIRST_8 = WordTransformType.OMIT_FIRST_8;
private const WordTransformType OMIT_FIRST_9 = WordTransformType.OMIT_FIRST_9;
public static int TransformDictionaryWord(byte[] dest, int dstOffset, byte[] word, int wordOffset,
int len, Transform transform)
{
int offset = dstOffset;
// Copy prefix.
byte[] str = transform.prefix;
int tmp = str.Length;
int i = 0;
// In most cases tmp < 10 -> no benefits from System.arrayCopy
while (i < tmp)
{
dest[offset++] = str[i++];
}
// Copy trimmed word.
WordTransformType op = transform.type;
tmp = TransformType.GetOmitFirst(op);// op.omitFirst;
if (tmp > len)
{
tmp = len;
}
wordOffset += tmp;
len -= tmp;
len -= TransformType.GetOmitLast(op);//op.omitLast;
i = len;
while (i > 0)
{
dest[offset++] = word[wordOffset++];
i--;
}
if (op == UPPERCASE_ALL || op == UPPERCASE_FIRST)
{
int uppercaseOffset = offset - len;
if (op == UPPERCASE_FIRST)
{
len = 1;
}
while (len > 0)
{
tmp = dest[uppercaseOffset] & 0xFF;
if (tmp < 0xc0)
{
if (tmp >= 'a' && tmp <= 'z')
{
dest[uppercaseOffset] ^= (byte)32;
}
uppercaseOffset += 1;
len -= 1;
}
else if (tmp < 0xe0)
{
dest[uppercaseOffset + 1] ^= (byte)32;
uppercaseOffset += 2;
len -= 2;
}
else
{
dest[uppercaseOffset + 2] ^= (byte)5;
uppercaseOffset += 3;
len -= 3;
}
}
}
// Copy suffix.
str = transform.suffix;
tmp = str.Length;
i = 0;
while (i < tmp)
{
dest[offset++] = str[i++];
}
return offset - dstOffset;
}
}
}

View File

@ -0,0 +1,48 @@
using System;
namespace CSharpBrotli.Decode
{
public sealed class Utils
{
private static readonly byte[] BYTE_ZEROES = new byte[1024];
private static readonly int[] INT_ZEROES = new int[1024];
/// <summary>
/// Fills byte array with zeroes.
/// <para>Current implementation uses <see cref="Array.Copy(Array, int, Array, int, int)"/>, so it should be used for
/// length not less than 16.</para>
/// </summary>
/// <param name="dest">array to fill with zeroes</param>
/// <param name="offset">the first byte to fill</param>
/// <param name="length">length number of bytes to change</param>
public static void FillWithZeroes(byte[] dest, int offset, int length)
{
int cursor = 0;
while (cursor < length)
{
int step = Math.Min(cursor + 1024, length) - cursor;
Array.Copy(BYTE_ZEROES, 0, dest, offset + cursor, step);
cursor += step;
}
}
/// <summary>
/// Fills int array with zeroes.
/// <para>Current implementation uses <see cref="Array.Copy(Array, int, Array, int, int)"/>, so it should be used for length not
/// less than 16.</para>
/// </summary>
/// <param name="dest">array to fill with zeroes</param>
/// <param name="offset">the first item to fill</param>
/// <param name="length">number of item to change</param>
public static void FillWithZeroes(int[] dest, int offset, int length)
{
int cursor = 0;
while (cursor < length)
{
int step = Math.Min(cursor + 1024, length) - cursor;
Array.Copy(INT_ZEROES, 0, dest, offset + cursor, step);
cursor += step;
}
}
}
}

View File

@ -0,0 +1,52 @@
namespace CSharpBrotli.Decode
{
public enum WordTransformType
{
IDENTITY,
OMIT_LAST_1,
OMIT_LAST_2,
OMIT_LAST_3,
OMIT_LAST_4,
OMIT_LAST_5,
OMIT_LAST_6,
OMIT_LAST_7,
OMIT_LAST_8,
OMIT_LAST_9,
UPPERCASE_FIRST,
UPPERCASE_ALL,
OMIT_FIRST_1,
OMIT_FIRST_2,
OMIT_FIRST_3,
OMIT_FIRST_4,
OMIT_FIRST_5,
OMIT_FIRST_6,
OMIT_FIRST_7,
/*
* brotli specification doesn't use OMIT_FIRST_8(8, 0) transform.
* Probably, it would be used in future format extensions.
*/
OMIT_FIRST_8,
OMIT_FIRST_9
}
internal class TransformType
{
public static int GetOmitFirst(WordTransformType type)
{
if(type>=WordTransformType.OMIT_FIRST_1 && type<= WordTransformType.OMIT_FIRST_9)
{
return (type - WordTransformType.OMIT_FIRST_1) + 1;
}
return 0;
}
public static int GetOmitLast(WordTransformType type)
{
if(type >= WordTransformType.OMIT_LAST_1 && type<= WordTransformType.OMIT_LAST_9)
{
return (type - WordTransformType.OMIT_LAST_1) + 1;
}
return 0;
}
}
}

View File

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CSharpBrotli")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CSharpBrotli")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]