mirror of
https://github.com/google/brotli.git
synced 2024-11-21 19:20:09 +00:00
convert brotli java decoder to c# library
This commit is contained in:
parent
9fa1ad5a91
commit
d55f9a11a9
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal 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
|
34
csharp/CSharpBrotli/CSharpBrotli.sln
Normal file
34
csharp/CSharpBrotli/CSharpBrotli.sln
Normal 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
|
65
csharp/CSharpBrotli/CSharpBrotli/CSharpBrotli.csproj
Normal file
65
csharp/CSharpBrotli/CSharpBrotli/CSharpBrotli.csproj
Normal 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>
|
212
csharp/CSharpBrotli/CSharpBrotli/Decode/BitReader.cs
Normal file
212
csharp/CSharpBrotli/CSharpBrotli/Decode/BitReader.cs
Normal 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 ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
199
csharp/CSharpBrotli/CSharpBrotli/Decode/BritliInputStream.cs
Normal file
199
csharp/CSharpBrotli/CSharpBrotli/Decode/BritliInputStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) { }
|
||||
}
|
||||
}
|
159
csharp/CSharpBrotli/CSharpBrotli/Decode/Context.cs
Normal file
159
csharp/CSharpBrotli/CSharpBrotli/Decode/Context.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
969
csharp/CSharpBrotli/CSharpBrotli/Decode/Decode.cs
Normal file
969
csharp/CSharpBrotli/CSharpBrotli/Decode/Decode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
csharp/CSharpBrotli/CSharpBrotli/Decode/Dictionary.cs
Normal file
68
csharp/CSharpBrotli/CSharpBrotli/Decode/Dictionary.cs
Normal file
File diff suppressed because one or more lines are too long
147
csharp/CSharpBrotli/CSharpBrotli/Decode/Huffman.cs
Normal file
147
csharp/CSharpBrotli/CSharpBrotli/Decode/Huffman.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
csharp/CSharpBrotli/CSharpBrotli/Decode/HuffmanTreeGroup.cs
Normal file
54
csharp/CSharpBrotli/CSharpBrotli/Decode/HuffmanTreeGroup.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
csharp/CSharpBrotli/CSharpBrotli/Decode/IntBufferReader.cs
Normal file
36
csharp/CSharpBrotli/CSharpBrotli/Decode/IntBufferReader.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
62
csharp/CSharpBrotli/CSharpBrotli/Decode/Prefix.cs
Normal file
62
csharp/CSharpBrotli/CSharpBrotli/Decode/Prefix.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
22
csharp/CSharpBrotli/CSharpBrotli/Decode/RunningStage.cs
Normal file
22
csharp/CSharpBrotli/CSharpBrotli/Decode/RunningStage.cs
Normal 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
|
||||
}
|
||||
}
|
122
csharp/CSharpBrotli/CSharpBrotli/Decode/State.cs
Normal file
122
csharp/CSharpBrotli/CSharpBrotli/Decode/State.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
254
csharp/CSharpBrotli/CSharpBrotli/Decode/Transform.cs
Normal file
254
csharp/CSharpBrotli/CSharpBrotli/Decode/Transform.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
48
csharp/CSharpBrotli/CSharpBrotli/Decode/Utils.cs
Normal file
48
csharp/CSharpBrotli/CSharpBrotli/Decode/Utils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
csharp/CSharpBrotli/CSharpBrotli/Decode/WordTransformType.cs
Normal file
52
csharp/CSharpBrotli/CSharpBrotli/Decode/WordTransformType.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
30
csharp/CSharpBrotli/CSharpBrotli/Properties/AssemblyInfo.cs
Normal file
30
csharp/CSharpBrotli/CSharpBrotli/Properties/AssemblyInfo.cs
Normal 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")]
|
Loading…
Reference in New Issue
Block a user