1 /*******************************************************************************
2 
3         Standards: rfc3548, rfc4648
4 
5         Copyright:
6             Copyright (c) 2010 Ulrik Mikaelsson.
7             Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
8             All rights reserved.
9 
10         License:
11             Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
12             See LICENSE_TANGO.txt for details.
13 
14         Authors: Ulrik Mikaelsson
15 
16 *******************************************************************************/
17 
18 /*******************************************************************************
19 
20     This module is used to decode and encode base32 char[] arrays.
21 
22     Example:
23     ---
24     char[] blah = "Hello there, my name is Jeff.";
25 
26     scope encodebuf = new char[allocateEncodeSize(cast(ubyte[])blah)];
27     char[] encoded = encode(cast(ubyte[])blah, encodebuf);
28 
29     scope decodebuf = new ubyte[encoded.length];
30     if (cast(char[])decode(encoded, decodebuf) == "Hello there, my name is Jeff.")
31         Stdout("yay").newline;
32     ---
33 
34     Since v1.0
35 
36 *******************************************************************************/
37 
38 module ocean.util.encode.Base32;
39 
40 import ocean.meta.types.Qualifiers;
41 import ocean.core.Verify;
42 
43 version (unittest) import ocean.core.Test;
44 
45 /*******************************************************************************
46 
47     calculates and returns the size needed to encode the length of the
48     array passed.
49 
50     Params:
51     data = An array that will be encoded
52 
53 *******************************************************************************/
54 
55 
56 size_t allocateEncodeSize(ubyte[] data)
57 {
58     return allocateEncodeSize(data.length);
59 }
60 
61 /*******************************************************************************
62 
63     calculates and returns the size needed to encode the length passed.
64 
65     Params:
66     length = Number of bytes to be encoded
67 
68 *******************************************************************************/
69 
70 size_t allocateEncodeSize(size_t length)
71 {
72     auto inputbits = length * 8;
73     auto inputquantas = (inputbits + 39) / 40; // Round upwards
74     return inputquantas * 8;
75 }
76 
77 
78 /*******************************************************************************
79 
80     encodes data and returns as an ASCII base32 string.
81 
82     Params:
83     data = what is to be encoded
84     buff = buffer large enough to hold encoded data
85     pad  = Whether to pad ascii output with '='-chars
86 
87     Example:
88     ---
89     char[512] encodebuf;
90     char[] myEncodedString = encode(cast(ubyte[])"Hello, how are you today?", encodebuf);
91     Stdout(myEncodedString).newline; // JBSWY3DPFQQGQ33XEBQXEZJAPFXXKIDUN5SGC6J7
92     ---
93 
94 
95 *******************************************************************************/
96 
97 char[] encode(ubyte[] data, char[] buff, bool pad=true)
98 {
99     verify(data !is null);
100     verify(buff.length >= allocateEncodeSize(data));
101 
102     uint i = 0;
103     ushort remainder; // Carries overflow bits to next char
104     byte remainlen;  // Tracks bits in remainder
105     foreach (ubyte j; data)
106     {
107         remainder = cast(ushort) ((remainder<<8) | j);
108         remainlen += 8;
109         do {
110             remainlen -= 5;
111             buff[i++] = _encodeTable[(remainder>>remainlen)&0b11111];
112         } while (remainlen > 5);
113     }
114     if (remainlen)
115         buff[i++] = _encodeTable[(remainder<<(5-remainlen))&0b11111];
116     if (pad) {
117         for (ubyte padCount= cast(ubyte) (-i%8);padCount > 0; padCount--)
118             buff[i++] = base32_PAD;
119     }
120 
121     return buff[0..i];
122 }
123 
124 /*******************************************************************************
125 
126     encodes data and returns as an ASCII base32 string.
127 
128     Params:
129     data = what is to be encoded
130     pad = whether to pad output with '='-chars
131 
132     Example:
133     ---
134     char[] myEncodedString = encode(cast(ubyte[])"Hello, how are you today?");
135     Stdout(myEncodedString).newline; // JBSWY3DPFQQGQ33XEBQXEZJAPFXXKIDUN5SGC6J7
136     ---
137 
138 
139 *******************************************************************************/
140 
141 
142 char[] encode(ubyte[] data, bool pad=true)
143 {
144     verify(data !is null);
145 
146     auto rtn = new char[allocateEncodeSize(data)];
147     return encode(data, rtn, pad);
148 }
149 
150 /*******************************************************************************
151 
152     decodes an ASCII base32 string and returns it as ubyte[] data. Pre-allocates
153     the size of the array.
154 
155     This decoder will ignore non-base32 characters. So:
156     SGVsbG8sIGhvd
157     yBhcmUgeW91IH
158     RvZGF5Pw==
159 
160     Is valid.
161 
162     Params:
163     data = what is to be decoded
164 
165     Example:
166     ---
167     char[] myDecodedString = cast(char[])decode("JBSWY3DPFQQGQ33XEBQXEZJAPFXXKIDUN5SGC6J7");
168     Stdout(myDecodeString).newline; // Hello, how are you today?
169     ---
170 
171 *******************************************************************************/
172 
173 ubyte[] decode(cstring data)
174 {
175     verify(data !is null);
176 
177     auto rtn = new ubyte[data.length];
178     return decode(data, rtn);
179 }
180 
181 /*******************************************************************************
182 
183     decodes an ASCII base32 string and returns it as ubyte[] data.
184 
185     This decoder will ignore non-base32 characters. So:
186     SGVsbG8sIGhvd
187     yBhcmUgeW91IH
188     RvZGF5Pw==
189 
190     Is valid.
191 
192     Params:
193     data = what is to be decoded
194     buff = a big enough array to hold the decoded data
195 
196     Example:
197     ---
198     ubyte[512] decodebuf;
199     char[] myDecodedString = cast(char[])decode("JBSWY3DPFQQGQ33XEBQXEZJAPFXXKIDUN5SGC6J7", decodebuf);
200     Stdout(myDecodeString).newline; // Hello, how are you today?
201     ---
202 
203 *******************************************************************************/
204 ubyte[] decode(cstring data, ubyte[] buff)
205 {
206     verify(data !is null);
207 
208     ushort remainder;
209     byte remainlen;
210     size_t oIndex;
211     foreach (c; data)
212     {
213         auto dec = _decodeTable[c];
214         if (dec & 0b1000_0000)
215             continue;
216         remainder = cast(ushort) ((remainder<<5) | dec);
217         for (remainlen += 5; remainlen >= 8; remainlen -= 8)
218             buff[oIndex++] = cast(ubyte) (remainder >> (remainlen-8));
219     }
220 
221     return buff[0..oIndex];
222 }
223 
224 unittest
225 {
226     static istring[] testBytes = [
227         "",
228         "foo",
229         "foob",
230         "fooba",
231         "foobar",
232         "Hello, how are you today?",
233     ];
234     static istring[] testChars = [
235         "",
236         "MZXW6===",
237         "MZXW6YQ=",
238         "MZXW6YTB",
239         "MZXW6YTBOI======",
240         "JBSWY3DPFQQGQ33XEBQXEZJAPFXXKIDUN5SGC6J7",
241     ];
242 
243     for (uint i; i < testBytes.length; i++) {
244         auto resultChars = encode(cast(ubyte[])testBytes[i]);
245         test(resultChars == testChars[i],
246                 testBytes[i]~": ("~resultChars~") != ("~testChars[i]~")");
247 
248         auto resultBytes = decode(testChars[i]);
249         test(resultBytes == cast(ubyte[])testBytes[i],
250                 testChars[i]~": ("~cast(char[])resultBytes~") != ("~testBytes[i]~")");
251     }
252 }
253 
254 private:
255 
256 /*
257     Static immutable tables used for fast lookups to
258     encode and decode data.
259 */
260 static immutable ubyte base32_PAD = '=';
261 static istring _encodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
262 
263 static const(ubyte)[] _decodeTable = [
264     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
265     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
266     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
267     0xFF,0xFF,0x1A,0x1B, 0x1C,0x1D,0x1E,0x1F, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
268     0xFF,0x00,0x01,0x02, 0x03,0x04,0x05,0x06, 0x07,0x08,0x09,0x0A, 0x0B,0x0C,0x0D,0x0E,
269     0x0F,0x10,0x11,0x12, 0x13,0x14,0x15,0x16, 0x17,0x18,0x19,0xFF, 0xFF,0xFF,0xFF,0xFF,
270     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
271     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
272     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
273     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
274     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
275     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
276     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
277     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
278     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
279     0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,
280 ];