1 module lz4;
2 /**
3  * CTFEable LZ4 decompressor
4  * Copyright © 2016 Stefan Koch
5  * All rights reserved
6  */
7 enum Endianess
8 {
9 	BigEndian,
10 	LittleEndian
11 }
12 /// JUST TO DEMONSTRATE THAT IT IS UNLIKELY :)
13 auto unlikely(T)(T expressionValue)
14 {
15 	return expressionValue;
16 }
17 
18 T fromBytes(T, Endianess endianess = Endianess.LittleEndian)(const ubyte[] _data)
19 {
20 	static assert(is(T : long)); // poor man's isIntegral
21 	T result;
22 	
23 	foreach (i; 0 .. T.sizeof)
24 	{
25 		static if (endianess == Endianess.LittleEndian)
26 		{
27 			result |= (_data[i] << i * 8);
28 		}
29 		else
30 		{
31 			result |= (_data[i] << (T.sizeof - 1 - i) * 8);
32 		}
33 	}
34 	return result;
35 }
36 
37 struct LZ4Header
38 {
39 	//TODO: finish this! ("parsing" LZ4 Frame format header)
40 	int end = 7;
41 	ubyte flags;
42 
43 	bool hasBlockIndependence;
44 	bool hasBlockChecksum;
45 	bool hasContentChecksum;
46 
47 	ulong contentSize;
48 
49 	this(const ubyte[] data) pure
50 	{
51 		assert(((data[0] >> 6) & 0b11) == 0b01, "Format can not be read");
52 
53 		hasBlockIndependence = ((data[0] >> 5) & 0b1);
54 		hasBlockChecksum = ((data[0] >> 4) & 0b1);
55 
56 		bool hasContentSize = ((data[0] >> 3) & 0b1);
57 
58 		hasContentChecksum = ((data[0] >> 2) & 0b1);
59 
60 		if (hasContentSize)
61 		{
62 			contentSize = fromBytes!ulong(data[2 .. 2 + ulong.sizeof]);
63 			assert(contentSize);
64 			end = 11;
65 		}
66 	}
67 }
68 
69 ubyte[] decodeLZ4File(const ubyte[] data) pure in {
70 	assert(data.length > 11, "Any valid LZ4 File has ti be longer then 11 bytes");
71 } body {
72 	assert(data[0 .. 4] == [0x04, 0x22, 0x4d, 0x18], "not a valid LZ4 file");
73 	auto lz4Header = LZ4Header(data[5 .. $]);
74 	uint length = fromBytes!uint(data[lz4Header.end .. lz4Header.end + uint.sizeof]);
75 
76 	return decodeLZ4Block(data[lz4Header.end + uint.sizeof .. $], length);
77 }
78 
79 ubyte[] decodeLZ4Block(const ubyte[] input, uint blockLength) pure in {
80 	assert(input.length > 5, "empty or too short input passed to decodeLZ4Block");
81 } body {
82 	uint coffset;
83 	ubyte[] output;
84 	
85 	while (true)
86 	{
87 		auto bitfield = input[coffset++];
88 		auto highBits = (bitfield >> 4);
89 		auto lowBits = bitfield & 0xF;
90 
91 		if (highBits)
92 		{
93 			uint literalsLength = 0xF;
94 
95 			if (highBits != 0xF)
96 			{
97 				literalsLength = highBits;
98 			}
99 			else
100 			{
101 				while (input[coffset++] == 0xFF)
102 				{
103 					literalsLength += 0xFF;
104 				}
105 				literalsLength += input[coffset - 1];
106 			}
107 
108 			output ~= input[coffset .. coffset + literalsLength];
109 			coffset += literalsLength;
110 		}
111 
112 		if (coffset >= blockLength)
113 			return output;
114 
115 		uint matchLength = 0xF + 4;
116 		ushort offset = (input[coffset++] | (input[coffset++] << 8));
117 
118 		if (lowBits != 0xF)
119 		{
120 			matchLength = lowBits + 4;
121 		}
122 		else
123 		{
124 			while (input[coffset++] == 0xFF)
125 			{
126 				matchLength += 0xFF;
127 			}
128 			matchLength += input[coffset - 1];
129 		}
130 
131 		if (unlikely(offset < matchLength))
132 		{
133 			uint startMatch = cast(uint) output.length - offset;
134 
135 			// this works for now. Maybe it's even more complicated...
136 			// e.g. lz4 widens the offset as the match gets longer
137 			// but the docs seem to suggest that the following code is indeed correct
138 
139 			while (unlikely(offset < matchLength))
140 			{ // TODO: IS IT REALLY _unlikely_ or could be _likely_ ?
141 				output ~= output[startMatch .. startMatch + offset];
142 				matchLength -= offset;
143 			}
144 
145 			output ~= output[startMatch .. startMatch + matchLength];
146 		}
147 		else
148 		{
149 			output ~= output[$ - offset .. ($ - offset) + matchLength];
150 		}
151 	}
152 }