1 module varint;
2 import utils;
3 //          Copyright Stefan Koch 2015 - 2018.
4 // Distributed under the Boost Software License, Version 1.0.
5 //    (See accompanying file LICENSE.md or copy at
6 //          http://www.boost.org/LICENSE_1_0.txt)
7 
8 enum unrolled = true;
9 
10 struct VarInt {
11 	pure nothrow @safe @nogc :
12 	this(BigEndian!long value) {
13 		//FIXME the constructor does not work for value bigger then uint.max;
14 		auto len = lengthInVarInt(value);
15 		ubyte[9] tmp;
16 		long beValue = value.asBigEndian;
17 
18 		auto _len = len;
19 		while(_len--) {
20 			tmp[_len] = (beValue & 0x7f) | 0x80;
21 			beValue >>= 7;
22 		}
23 		tmp[len-1] &= 0x7f;
24 
25 		byteArray = tmp[0 .. len];
26 	}
27 
28 	const ubyte[] byteArray;
29 
30 	alias toBeLong this;
31 	alias toBeLong = toBeLongImpl;
32 
33 	//TODO FIXME toBeLong does not correctly convert negative Numbers
34 
35 	@property BigEndian!long toBeLongImpl() {
36 		long tmp;
37 		static if (unrolled) {
38 		uint v3 = 0; 
39 
40 		if ( byteArray[0] & 0x80)
41 		{
42 			v3 = 1;
43 			if ( byteArray[1] & 0x80)
44 			{
45 				v3 = 2;
46 				if ( byteArray[2] & 0x80)
47 				{
48 					v3 = 3;
49 					if ( byteArray[3] & 0x80)
50 					{
51 						v3 = 4;
52 						if ( byteArray[4] & 0x80)
53 						{
54 							v3 = 5;
55 							if ( byteArray[5] & 0x80)
56 							{
57 								v3 = 6;
58 								if ( byteArray[6] & 0x80)
59 								{
60 									v3 = 7;
61 									if ( byteArray[7] & 0x80)
62 										v3 = 8;
63 								}
64 							}
65 						}
66 					}
67 				}
68 			}
69 		} else {
70 			BigEndian!long result;
71 
72 			version (LittleEndian) {
73 				result.asNative = (cast(long)byteArray[0] << 56);
74 			} else {
75 				result.asNative = (cast(long)byteArray[0]);
76 			}
77 			return result;
78 		}
79 	
80 		
81 		} else {
82 		uint length = length();
83 		}
84 
85 		BigEndian!long result;
86 		
87 		// The hottest loop in the whole program!
88 		//TODO There must be a way to speed it up!
89 		static if (unrolled) {
90 		switch(v3) {
91 			//	uint counter;
92 			case 8 :
93 				tmp |= ((cast(long)byteArray[8]) << 7 * (v3 - 8));
94 				goto case 7;
95 			case 7 :
96 				tmp |= ((cast(long)byteArray[7] & 0x7FUL) << 7 * (v3 - 7));
97 				goto case 6;
98 			case 6 :
99 				tmp |= ((cast(long)byteArray[6] & 0x7FUL) << 7 * (v3 - 6));
100 				goto case 5;
101 			case 5 :
102 				tmp |= ((cast(long)byteArray[5] & 0x7FUL) << 7 * (v3 - 5));
103 				goto case 4;
104 			case 4 :
105 				tmp |= ((cast(long)byteArray[4] & 0x7FUL) << 7 * (v3 - 4));
106 				goto case 3;
107 			case 3 :
108 				tmp |= ((cast(long)byteArray[3] & 0x7FUL) << 7 * (v3 - 3));
109 				goto case 2;
110 			case 2 :
111 				tmp |= ((cast(long)byteArray[2] & 0x7FUL) << 7 * (v3 - 2));
112 				goto case 1;
113 			case 1 :
114 				tmp |= ((cast(long)byteArray[1] & 0x7FUL) << 7 * (v3 - 1));
115 				tmp |= ((cast(long)byteArray[0] & 0x7FUL) << 7 * (v3 - 0));
116 			break;
117 			default : assert(0);
118 
119 		}
120 		} else 
121 
122 		{
123 		foreach(idx;0..length) {
124 			ubyte val = byteArray[idx];
125 			long maskedVal = (cast(long)val & 0x7fUL); // mask 8th bit
126 			long shiftBy = (length-idx-1UL)*7UL;
127 				if(idx < 8) {
128 					tmp |=  (maskedVal << shiftBy);
129 						} else {
130 					tmp |=  (cast(long)val << 63UL);
131 				}
132 		}
133 		}
134 		//this evokes swapIfNeeded
135 		result = tmp;
136 
137 
138 		return result;
139 	}
140 	
141 	this (const ubyte[] _arr) {
142 		this.byteArray = _arr;
143 	}
144 
145 	static int lengthInVarInt(BigEndian!long value) {
146 		if (value > 1L<<56 || value < 0) {
147 			return 9;
148 		} else if (value < 1<<7) {
149 			return 1;
150 		} else if (value < 1<<14) {
151 			return 2;
152 		} else if (value < 1<<21) {
153 			return 3;
154 		} else if (value < 1<<28) {
155 			return 4;
156 		} else if (value < 1L<<35) {
157 			return 5;
158 		} else if (value < 1L<<42) {
159 			return 6;
160 		} else if (value < 1L<<49) {
161 			return 7;
162 		} else if (value < 1L<<56) {
163 			return 8;
164 		}
165 		assert(0, "We should never get here");
166 	}
167 
168 	@property uint length ()  {
169 		return _length(byteArray);
170 	}
171 
172 	static uint _length(const ubyte[] arr) {
173 
174 		foreach(idx;0..9) {
175 			if(arr[idx] & (1 << 7)) {
176 				continue;
177 			} else {
178 				return idx+1;
179 			}
180 			assert(0, "we should never get here");
181 		}
182 		return 9;
183 	}
184 
185 	static assert(_length((cast(ubyte[])[0x6d,0x00])) == 1);
186 	static assert(_length((cast(ubyte[])[0x7f,0x00])) == 1);
187 	static assert(_length((cast(ubyte[])[0x82,0x12])) == 2);
188 	static assert(_length((cast(ubyte[])[0xfe,0xfe,0x00])) == 3);
189 	static assert(VarInt((cast(ubyte[])[0x81,0x01])).toBeLong == 129);
190 	static assert(VarInt((cast(ubyte[])[0x81,0x00])).toBeLong == 0x0080);
191 	static assert(VarInt((cast(ubyte[])[0x82,0x00])).toBeLong == 0x0100); // should be 0x0100
192 	static assert(_length((cast(ubyte[])[0x82,0x80,0x00])) == 3);
193 	static assert(VarInt((cast(ubyte[])[0x84,0x60,0x00])).toBeLong == 608);
194 	//FIXME make this work!
195 //	static assert(VarInt(cast(ubyte[])[0xFF, 0xFF, 0xFF, 0XFF, 0xFF, 0XFF, 0xFF, 0xFF, 0xEA]).toBeLong == -22);
196 	static assert(VarInt(bigEndian!long(265)).toBeLong == 265);
197 	static assert(VarInt(bigEndian!long(6421)).toBeLong == 6421);
198 	static assert(VarInt(bigEndian!long(22)).toBeLong == 22);
199 	static assert(VarInt.lengthInVarInt(BigEndian!long(-22)) == 9);
200 	static assert(VarInt(bigEndian!long(uint.max)).toBeLong == uint.max);
201 	static assert(VarInt(cast(ubyte[])[0xFF, 0xFF, 0xFF, 0XFF, 0xFF, 0XFF, 0xFF, 0xFF, 0xEA]) == VarInt(bigEndian(-22L)));
202 	//static assert (VarInt().lengthInVarInt(608) == 2);
203 	static assert(VarInt((cast(ubyte[])[0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89])).toBeLong != 0);
204 }