Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
LZ4Codec.cs
Go to the documentation of this file.
1 #region license
2 
3 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
4 // This file is distributed under BSD 2-Clause License. See LICENSE.md for details.
5 /*
6 Copyright (c) 2013, Milosz Krajewski
7 All rights reserved.
8 
9 Redistribution and use in source and binary forms, with or without modification, are permitted provided
10 that the following conditions are met:
11 
12 * Redistributions of source code must retain the above copyright notice, this list of conditions
13  and the following disclaimer.
14 
15 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
16  and the following disclaimer in the documentation and/or other materials provided with the distribution.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
19 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
25 IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28 #endregion
29 
30 using System;
31 using System.Runtime.CompilerServices;
32 using System.Text;
33 
34 using SiliconStudio.Core.LZ4.Services;
35 
36 namespace SiliconStudio.Core.LZ4
37 {
38  /// <summary>
39  /// LZ$ codec selecting best implementation depending on platform.
40  /// </summary>
41  public static class LZ4Codec
42  {
43  #region fields
44 
45  /// <summary>Encoding service.</summary>
46  private static readonly ILZ4Service encoder;
47 
48  /// <summary>Encoding service for HC algorithm.</summary>
49  private static readonly ILZ4Service encoderHC;
50 
51  /// <summary>Decoding service.</summary>
52  private static readonly ILZ4Service decoder;
53 
54  // ReSharper disable InconsistentNaming
55 
56  // mixed mode
57  private static ILZ4Service _service_Native32;
58  private static ILZ4Service _service_Native64;
59 
60  // ReSharper restore InconsistentNaming
61 
62  #endregion
63 
64  #region initialization
65 
66  /// <summary>Initializes the <see cref="LZ4Codec"/> class.</summary>
67  static LZ4Codec()
68  {
69  // NOTE: this method exploits the fact that assemblies are loaded first time they
70  // are needed so we can safely try load and handle if not loaded
71  // I may change in future versions of .NET
72 
73  Try(InitializeLZ4Native);
74 
75  // refer to: http://lz4net.codeplex.com/wikipage?title=Performance%20Testing
76  // for explanation about this order
77  // feel free to change preferred order, just don't do it willy-nilly
78  // back it up with some evidence
79 
80  if (IntPtr.Size == 4)
81  {
82  encoder =
83  _service_Native32 ??
84  _service_Native64;
85 
86  decoder =
87  _service_Native32 ??
88  _service_Native64;
89  encoderHC =
90  _service_Native32 ??
91  _service_Native64;
92  }
93  else
94  {
95  encoder =
96  _service_Native64 ??
97  _service_Native32;
98  decoder =
99  _service_Native64 ??
100  _service_Native32;
101  encoderHC =
102  _service_Native64 ??
103  _service_Native32;
104  }
105 
106  if (encoder == null || decoder == null)
107  {
108  throw new NotSupportedException("No LZ4 compression service found");
109  }
110  }
111 
112  /// <summary>Tries to execute specified action. Ignores exception if it failed.</summary>
113  /// <param name="method">The method.</param>
114  private static void Try(Action method)
115  {
116  try
117  {
118  method();
119  }
120  // ReSharper disable EmptyGeneralCatchClause
121  catch
122  {
123  // ignore exception
124  }
125  // ReSharper restore EmptyGeneralCatchClause
126  }
127 
128  /// <summary>Tries to create a specified <seealso cref="ILZ4Service"/> and tests it.</summary>
129  /// <typeparam name="T">Concrete <seealso cref="ILZ4Service"/> type.</typeparam>
130  /// <returns>A service if succeeded or <c>null</c> if it failed.</returns>
131  private static ILZ4Service Try<T>()
132  where T: ILZ4Service, new()
133  {
134  try
135  {
136  return AutoTest(new T());
137  }
138  catch
139  {
140  return null;
141  }
142  }
143 
144  /// <summary>Perofrms the quick auto-test on given compression service.</summary>
145  /// <param name="service">The service.</param>
146  /// <returns>A service or <c>null</c> if it failed.</returns>
147  private static ILZ4Service AutoTest(ILZ4Service service)
148  {
149  const string loremIpsum =
150  "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut " +
151  "labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
152  "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
153  "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " +
154  "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
155 
156  // generate some well-known array of bytes
157  const string inputText = loremIpsum + loremIpsum + loremIpsum + loremIpsum + loremIpsum;
158  var original = Encoding.UTF8.GetBytes(inputText);
159 
160  // LZ4 test
161  {
162  // compress it
163  var encoded = new byte[MaximumOutputLength(original.Length)];
164  var encodedLength = service.Encode(original, 0, original.Length, encoded, 0, encoded.Length);
165  if (encodedLength < 0) return null;
166 
167  // decompress it (knowing original length)
168  var decoded = new byte[original.Length];
169  var decodedLength1 = service.Decode(encoded, 0, encodedLength, decoded, 0, decoded.Length, true);
170  if (decodedLength1 != original.Length) return null;
171  var outputText1 = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
172  if (outputText1 != inputText) return null;
173 
174  // decompress it (not knowing original length)
175  var decodedLength2 = service.Decode(encoded, 0, encodedLength, decoded, 0, decoded.Length, false);
176  if (decodedLength2 != original.Length) return null;
177  var outputText2 = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
178  if (outputText2 != inputText) return null;
179  }
180 
181  // LZ4HC
182  {
183  // compress it
184  var encoded = new byte[MaximumOutputLength(original.Length)];
185  var encodedLength = service.EncodeHC(original, 0, original.Length, encoded, 0, encoded.Length);
186  if (encodedLength < 0) return null;
187 
188  // decompress it (knowing original length)
189  var decoded = new byte[original.Length];
190  var decodedLength1 = service.Decode(encoded, 0, encodedLength, decoded, 0, decoded.Length, true);
191  if (decodedLength1 != original.Length) return null;
192  var outputText1 = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
193  if (outputText1 != inputText) return null;
194 
195  // decompress it (not knowing original length)
196  var decodedLength2 = service.Decode(encoded, 0, encodedLength, decoded, 0, decoded.Length, false);
197  if (decodedLength2 != original.Length) return null;
198  var outputText2 = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
199  if (outputText2 != inputText) return null;
200  }
201 
202  return service;
203  }
204 
205  // ReSharper disable InconsistentNaming
206 
207  /// <summary>Initializes codecs from LZ4 native.</summary>
208  [MethodImpl(MethodImplOptions.NoInlining)]
209  private static void InitializeLZ4Native()
210  {
211  _service_Native32 = Try<Native32LZ4Service>();
212  _service_Native64 = Try<Native64LZ4Service>();
213  }
214 
215  // ReSharper restore InconsistentNaming
216 
217  #endregion
218 
219  #region public interface
220 
221  /// <summary>Gets the name of selected codec(s).</summary>
222  /// <value>The name of the codec.</value>
223  public static string CodecName
224  {
225  get
226  {
227  return string.Format(
228  "{0}/{1}/{2}HC",
229  encoder == null ? "<none>" : encoder.CodecName,
230  decoder == null ? "<none>" : decoder.CodecName,
231  encoderHC == null ? "<none>" : encoderHC.CodecName);
232  }
233  }
234 
235  /// <summary>Get maximum output length.</summary>
236  /// <param name="inputLength">Input length.</param>
237  /// <returns>Output length.</returns>
238  public static int MaximumOutputLength(int inputLength)
239  {
240  return inputLength + (inputLength/255) + 16;
241  }
242 
243  #region Encode
244 
245  /// <summary>Encodes the specified input.</summary>
246  /// <param name="input">The input.</param>
247  /// <param name="inputOffset">The input offset.</param>
248  /// <param name="inputLength">Length of the input.</param>
249  /// <param name="output">The output.</param>
250  /// <param name="outputOffset">The output offset.</param>
251  /// <param name="outputLength">Length of the output.</param>
252  /// <returns>Number of bytes written.</returns>
253  public static int Encode(
254  byte[] input,
255  int inputOffset,
256  int inputLength,
257  byte[] output,
258  int outputOffset,
259  int outputLength)
260  {
261  return encoder.Encode(input, inputOffset, inputLength, output, outputOffset, outputLength);
262  }
263 
264  /// <summary>Encodes the specified input.</summary>
265  /// <param name="input">The input.</param>
266  /// <param name="inputOffset">The input offset.</param>
267  /// <param name="inputLength">Length of the input.</param>
268  /// <returns>Compressed buffer.</returns>
269  public static byte[] Encode(byte[] input, int inputOffset, int inputLength)
270  {
271  if (inputLength < 0) inputLength = input.Length - inputOffset;
272 
273  if (input == null) throw new ArgumentNullException("input");
274  if (inputOffset < 0 || inputOffset + inputLength > input.Length)
275  throw new ArgumentException("inputOffset and inputLength are invalid for given input");
276 
277  var result = new byte[MaximumOutputLength(inputLength)];
278  var length = Encode(input, inputOffset, inputLength, result, 0, result.Length);
279 
280  if (length != result.Length)
281  {
282  if (length < 0)
283  throw new InvalidOperationException("Compression has been corrupted");
284  var buffer = new byte[length];
285  Buffer.BlockCopy(result, 0, buffer, 0, length);
286  return buffer;
287  }
288  return result;
289  }
290 
291  /// <summary>Encodes the specified input.</summary>
292  /// <param name="input">The input.</param>
293  /// <param name="inputOffset">The input offset.</param>
294  /// <param name="inputLength">Length of the input.</param>
295  /// <param name="output">The output.</param>
296  /// <param name="outputOffset">The output offset.</param>
297  /// <param name="outputLength">Length of the output.</param>
298  /// <returns>Number of bytes written.</returns>
299  public static int EncodeHC(
300  byte[] input,
301  int inputOffset,
302  int inputLength,
303  byte[] output,
304  int outputOffset,
305  int outputLength)
306  {
307  return (encoderHC ?? encoder)
308  .EncodeHC(input, inputOffset, inputLength, output, outputOffset, outputLength);
309  }
310 
311  /// <summary>Encodes the specified input.</summary>
312  /// <param name="input">The input.</param>
313  /// <param name="inputOffset">The input offset.</param>
314  /// <param name="inputLength">Length of the input.</param>
315  /// <returns>Compressed buffer.</returns>
316  public static byte[] EncodeHC(byte[] input, int inputOffset, int inputLength)
317  {
318  if (inputLength < 0) inputLength = input.Length - inputOffset;
319 
320  if (input == null) throw new ArgumentNullException("input");
321  if (inputOffset < 0 || inputOffset + inputLength > input.Length)
322  throw new ArgumentException("inputOffset and inputLength are invalid for given input");
323 
324  var result = new byte[MaximumOutputLength(inputLength)];
325  var length = EncodeHC(input, inputOffset, inputLength, result, 0, result.Length);
326 
327  if (length != result.Length)
328  {
329  if (length < 0)
330  throw new InvalidOperationException("Compression has been corrupted");
331  var buffer = new byte[length];
332  Buffer.BlockCopy(result, 0, buffer, 0, length);
333  return buffer;
334  }
335  return result;
336  }
337 
338  #endregion
339 
340  #region Decode
341 
342  /// <summary>Decodes the specified input.</summary>
343  /// <param name="input">The input.</param>
344  /// <param name="inputOffset">The input offset.</param>
345  /// <param name="inputLength">Length of the input.</param>
346  /// <param name="output">The output.</param>
347  /// <param name="outputOffset">The output offset.</param>
348  /// <param name="outputLength">Length of the output.</param>
349  /// <param name="knownOutputLength">Set it to <c>true</c> if output length is known.</param>
350  /// <returns>Number of bytes written.</returns>
351  public static int Decode(
352  byte[] input,
353  int inputOffset,
354  int inputLength,
355  byte[] output,
356  int outputOffset,
357  int outputLength = 0,
358  bool knownOutputLength = false)
359  {
360  return decoder.Decode(input, inputOffset, inputLength, output, outputOffset, outputLength, knownOutputLength);
361  }
362 
363  /// <summary>Decodes the specified input.</summary>
364  /// <param name="input">The input.</param>
365  /// <param name="inputOffset">The input offset.</param>
366  /// <param name="inputLength">Length of the input.</param>
367  /// <param name="outputLength">Length of the output.</param>
368  /// <returns>Decompressed buffer.</returns>
369  public static byte[] Decode(byte[] input, int inputOffset, int inputLength, int outputLength)
370  {
371  if (inputLength < 0) inputLength = input.Length - inputOffset;
372 
373  if (input == null) throw new ArgumentNullException("input");
374  if (inputOffset < 0 || inputOffset + inputLength > input.Length)
375  throw new ArgumentException("inputOffset and inputLength are invalid for given input");
376 
377  var result = new byte[outputLength];
378  var length = Decode(input, inputOffset, inputLength, result, 0, outputLength, true);
379  if (length != outputLength)
380  throw new ArgumentException("outputLength is not valid");
381  return result;
382  }
383 
384  #endregion
385 
386  #endregion
387  }
388 }
static int Decode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength=0, bool knownOutputLength=false)
Decodes the specified input.
Definition: LZ4Codec.cs:351
LZ$ codec selecting best implementation depending on platform.
Definition: LZ4Codec.cs:41
static byte[] EncodeHC(byte[] input, int inputOffset, int inputLength)
Encodes the specified input.
Definition: LZ4Codec.cs:316
static int EncodeHC(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength)
Encodes the specified input.
Definition: LZ4Codec.cs:299
static byte[] Encode(byte[] input, int inputOffset, int inputLength)
Encodes the specified input.
Definition: LZ4Codec.cs:269
static byte[] Decode(byte[] input, int inputOffset, int inputLength, int outputLength)
Decodes the specified input.
Definition: LZ4Codec.cs:369
static int Encode(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int outputLength)
Encodes the specified input.
Definition: LZ4Codec.cs:253
static int MaximumOutputLength(int inputLength)
Get maximum output length.
Definition: LZ4Codec.cs:238