1 /*
2  *  XDR - A D language implementation of the External Data Representation
3  *  Copyright (C) 2015 Paul O'Neil
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 //! Provides an implementation of RFC 4506.
20 module xdr;
21 
22 import std.algorithm : copy, map;
23 import std.bitmanip;
24 import std.range;
25 import std.traits;
26 
27 version (unittest)
28 {
29     import std.exception : assertThrown;
30 }
31 
32 void put(T, Output)(ref Output output, T val)
33     if (isOutputRange!(Output, ubyte)
34         && T.sizeof % 4 == 0 && (isIntegral!T || isFloatingPoint!T))
35 {
36     std.range.put(output, nativeToBigEndian(val)[]);
37 }
38 
39 void put(T: bool, Output)(ref Output output, bool val)
40     if (isOutputRange!(Output, ubyte))
41 {
42     if (val)
43     {
44         output.put!int(1);
45     }
46     else
47     {
48         output.put!int(0);
49     }
50 }
51 // The existence of this method seems to solve const/overload problems
52 void put(Output)(ref Output output, bool val) if (isOutputRange!(Output, ubyte))
53 {
54     output.put!bool(val);
55 }
56 
57 void put(ulong len, Output)(ref Output output, in ubyte[len] data)
58     if (isOutputRange!(Output, ubyte) && len % 4 == 0)
59 {
60     std.range.put(output, data[]);
61 }
62 
63 void put(ulong len, Output)(ref Output output, in ubyte[len] data)
64     if (isOutputRange!(Output, ubyte) && len % 4 != 0)
65 {
66     std.range.put(output, data[]);
67     enum padding = 4 - (len % 4);
68     std.range.put(output, [0, 0, 0][0 .. padding]);
69 }
70 
71 void put(Array, Output)(ref Output output, in Array data)
72     if (isOutputRange!(Output, ubyte) && isStaticArray!Array)
73 {
74     foreach (const ref elem; data)
75     {
76         output.put(elem);
77     }
78 }
79 
80 void put(Array, Output)(ref Output output, in Array data)
81     if (isOutputRange!(Output, ubyte)
82         && isDynamicArray!Array && !is(Array == ubyte[]) && !is(Array == string)
83         && __traits(compiles, output.put!(ElementType!Array)(data[0])))
84 in {
85     assert(data.length <= uint.max);
86 }
87 body {
88     output.put!uint(cast(uint)data.length);
89     foreach (const ref elem; data)
90     {
91         output.put(elem);
92     }
93 }
94 
95 void put(T: ubyte[], Output)(ref Output output, in T data)
96     if (isOutputRange!(Output, ubyte))
97 in {
98     assert(data.length <= uint.max);
99 }
100 body {
101     output.put!uint(cast(uint)data.length);
102     std.range.put(output, data);
103     if (data.length % 4 > 0)
104     {
105         immutable pad_length = 4 - (data.length % 4);
106         ubyte[] padding = [0, 0, 0];
107         std.range.put(output, padding[0 .. pad_length]);
108     }
109 }
110 
111 // FIXME combine with ubyte[]
112 void put(T: string, Output)(ref Output output, in T data)
113     if (isOutputRange!(Output, ubyte))
114 in {
115     assert(data.length <= uint.max);
116 }
117 body {
118     output.put!uint(cast(uint)data.length);
119     std.range.put(output, data);
120     if (data.length % 4 > 0)
121     {
122         immutable pad_length = 4 - (data.length % 4);
123         ubyte[] padding = [0, 0, 0];
124         std.range.put(output, padding[0 .. pad_length]);
125     }
126 }
127 
128 void put(T, Output)(ref Output output, in T data)
129     if (isOutputRange!(Output, ubyte)
130         && isAggregateType!T && !hasIndirections!T)
131 {
132     foreach (elem; data.tupleof)
133     {
134         output.put(elem);
135     }
136 }
137 
138 unittest
139 {
140     ubyte[] serializer;
141 
142     assert(__traits(compiles, serializer.put!char(2)) == false);
143     assert(__traits(compiles, serializer.put!dchar(2)) == false);
144     assert(__traits(compiles, serializer.put!wchar(2)) == false);
145 
146     assert(__traits(compiles, serializer.put!byte(2)) == false);
147     assert(__traits(compiles, serializer.put!ubyte(2)) == false);
148     assert(__traits(compiles, serializer.put!short(2)) == false);
149     assert(__traits(compiles, serializer.put!ushort(2)) == false);
150 
151     assert(__traits(compiles, serializer.put!int(2)) == true);
152     assert(__traits(compiles, serializer.put!uint(2)) == true);
153     assert(__traits(compiles, serializer.put!long(2)) == true);
154     assert(__traits(compiles, serializer.put!ulong(2)) == true);
155 
156     assert(__traits(compiles, serializer.put!bool(true)) == true);
157 
158     assert(__traits(compiles, serializer.put!float(1.0)) == true);
159     assert(__traits(compiles, serializer.put!double(1.0)) == true);
160 
161     assert(__traits(compiles, serializer.put!(ubyte[])([])) == true);
162     assert(__traits(compiles, serializer.put!(string)("")) == true);
163 
164     assert(__traits(compiles, serializer.put!(short[])([])) == false);
165     assert(__traits(compiles, serializer.put!(ushort[])([])) == false);
166 
167     assert(__traits(compiles, serializer.put!(short[2])([1, 2])) == false);
168 
169     assert(__traits(compiles, serializer.put!(int[2])([1, 2])) == true);
170     assert(__traits(compiles, serializer.put!(uint[2])([1, 2])) == true);
171     assert(__traits(compiles, serializer.put!(long[2])([1, 2])) == true);
172     assert(__traits(compiles, serializer.put!(ulong[2])([1, 2])) == true);
173     // Commented out pending std.bitmanip.EndianSwap stuff
174     // From std.bitmanip.d:2210
175     // private union EndianSwapper(T)
176     //     if(canSwapEndianness!T)
177     // {
178     //     Unqual!T value;
179     //     ubyte[T.sizeof] array;
180     //
181     //     static if(is(FloatingPointTypeOf!T == float))
182     //         uint  intValue;
183     //     else static if(is(FloatingPointTypeOf!T == double))
184     //         ulong intValue;
185     //
186     // }
187     // The static ifs fail because FloatingPointTypeOf!(const(float)) == const(float), not float
188     // assert(__traits(compiles, serializer.put!(float[2])([1, 2])) == true);
189     // assert(__traits(compiles, serializer.put!(double[2])([1, 2])) == true);
190     assert(__traits(compiles, serializer.put!(bool[2])([true, false])) == true);
191 }
192 
193 unittest
194 {
195     ubyte[] outBuffer = new ubyte[4];
196     ubyte[] movingBuffer = outBuffer[];
197 
198     movingBuffer.put!int(4);
199     assert(outBuffer == [0, 0, 0, 4]);
200 }
201 
202 unittest
203 {
204     ubyte[] outBuffer = new ubyte[4];
205     ubyte[] movingBuffer = outBuffer[];
206 
207     movingBuffer.put!bool(true);
208     assert(outBuffer == [0, 0, 0, 1]);
209 }
210 
211 unittest
212 {
213     ubyte[] outBuffer = new ubyte[8];
214     ubyte[] movingBuffer = outBuffer[];
215 
216     movingBuffer.put!long(4);
217     assert(outBuffer == [0, 0, 0, 0, 0, 0, 0, 4]);
218 }
219 
220 unittest
221 {
222     ubyte[] outBuffer = new ubyte[8];
223     ubyte[] movingBuffer = outBuffer[];
224 
225     ubyte[] data = [1, 2, 3, 4];
226     movingBuffer.put(data);
227     assert(outBuffer == [0, 0, 0, 4, 1, 2, 3, 4]);
228 }
229 unittest
230 {
231     ubyte[] outBuffer = new ubyte[8];
232     ubyte[] movingBuffer = outBuffer[];
233 
234     ubyte[] data = [1, 2, 3];
235     movingBuffer.put(data);
236     assert(outBuffer == [0, 0, 0, 3, 1, 2, 3, 0]);
237 }
238 
239 unittest
240 {
241     ubyte[] outBuffer = new ubyte[12];
242     ubyte[] movingBuffer = outBuffer[];
243 
244     string data = "hello";
245     movingBuffer.put(data);
246     assert(outBuffer == [0, 0, 0, 5, 'h', 'e', 'l', 'l', 'o', 0, 0, 0]);
247 }
248 
249 unittest
250 {
251     ubyte[] outBuffer = new ubyte[8];
252     ubyte[] movingBuffer = outBuffer[];
253 
254     int[2] data = [1, 2];
255     movingBuffer.put(data);
256     assert(outBuffer == [0, 0, 0, 1, 0, 0, 0, 2]);
257 }
258 
259 unittest
260 {
261     ubyte[] outBuffer = new ubyte[8];
262     ubyte[] movingBuffer = outBuffer[];
263 
264     struct AB
265     {
266         int a;
267         int b;
268     }
269 
270     AB ab = {1, 2};
271     movingBuffer.put(ab);
272     assert(outBuffer == [0, 0, 0, 1, 0, 0, 0, 2]);
273 }
274 
275 class EndOfInput : Exception
276 {
277     this(string file = __FILE__, size_t line = __LINE__)
278     {
279         super("Reached end of input while extracting data.", file, line);
280     }
281 }
282 
283 class NotABool : Exception
284 {
285     this(int intVal, string file = __FILE__, size_t line = __LINE__)
286     {
287         import std.conv : to;
288         super("Tried to decode into a bool, but " ~ std.conv.to!string(intVal) ~ " is not a valid XDR bool.", file, line);
289     }
290 }
291 
292 // Uses popFrontN, so it may read the end of input without
293 // doing anything useful with it
294 T get(T, Input)(ref Input input)
295     if ((isInputRange!Input && is(ElementType!Input == ubyte))
296         && (T.sizeof % 4 == 0 && (isIntegral!T || isFloatingPoint!T)))
297 {
298     ubyte[T.sizeof] buffer;
299     ubyte[] remaining = copy(input.take(T.sizeof), buffer[]);
300     if (remaining.length != 0)
301     {
302         throw new EndOfInput();
303     }
304     // Only need to pop front here if the input.take() does not.
305     // For ubyte[], take does not popFront, but maybe for InputRanges
306     // that are not sliceable it does?
307     input.popFrontExactly(T.sizeof);
308     return bigEndianToNative!T(buffer);
309 }
310 
311 bool get(T: bool, Input)(ref Input input)
312     if (isInputRange!Input && is(ElementType!Input == ubyte))
313 {
314     immutable intVal = input.get!int();
315     if (intVal == 0)
316     {
317         return false;
318     }
319     else if (intVal == 1)
320     {
321         return true;
322     }
323     else
324     {
325         throw new NotABool(intVal);
326     }
327 }
328 
329 auto get(T, Input)(ref Input input)
330     if (isInputRange!Input && is(ElementType!Input == ubyte)
331         && is(T == ubyte[length], ulong length)
332            && isStaticArray!T)
333 {
334     static if (T.length % 4 != 0)
335     {
336         enum pad_length = 4 - (T.length % 4);
337     }
338     else
339     {
340         enum pad_length = 0;
341     }
342 
343     auto result = input.take(T.length);
344     input.popFrontExactly(T.length + pad_length);
345     return result;
346 }
347 
348 Array get(Array, Input)(ref Input input)
349     if (isInputRange!Input && is(ElementType!Input == ubyte)
350         && is(Array == Element[length], Element, ulong length)
351             && !is(ElementType!Array == ubyte)
352             && isStaticArray!Array)
353 {
354     alias Element = ElementType!Array;
355     enum length = Array.length;
356     static if (Element.sizeof % 4 == 0)
357     {
358         enum elementSize = Element.sizeof;
359     }
360     else
361     {
362         enum elementSize = Element.sizeof + 4 - (Element.sizeof % 4);
363     }
364 
365     Array result;
366     // FIXME check on whether I need to pop front afterwards
367     copy(input.take(elementSize * length).chunks(elementSize).map!((chunk)=> chunk.get!Element()), result[]);
368     input.popFrontExactly(elementSize * length);
369     return result;
370 }
371 
372 auto get(Array, Input)(ref Input input)
373     if (isInputRange!Input && is(ElementType!Input == ubyte)
374         && isDynamicArray!Array && !is(Array == ubyte[]) && !is(Array == string))
375 {
376     alias Element = ElementType!Array;
377 
378     uint length = input.get!uint();
379     static if (Element.sizeof % 4 == 0)
380     {
381         enum elementSize = Element.sizeof;
382     }
383     else
384     {
385         enum elementSize = Element.sizeof + 4 - (Element.sizeof % 4);
386     }
387 
388     Array result = new Element[length];
389     // FIXME check on whether I need to pop front afterwards
390     copy(input.take(elementSize * length).chunks(elementSize).map!((chunk)=> chunk.get!Element()), result[]);
391     input.popFrontExactly(elementSize * length);
392     return result;
393 }
394 
395 auto get(Array, Input)(ref Input input)
396     if (isInputRange!Input && is(ElementType!Input == ubyte)
397         && isDynamicArray!Array && (is(Array == ubyte[]) || is(Array == string)))
398 {
399     uint length = input.get!uint();
400     auto result = input.take(length);
401 
402     input.popFrontExactly(length);
403     if (length % 4 > 0)
404     {
405         input.popFrontExactly(4 - (length % 4));
406     }
407     return result;
408 }
409 
410 T get(T, Input)(ref Input input)
411     if (isInputRange!Input && is(ElementType!Input == ubyte)
412         && isAggregateType!T && !hasIndirections!T)
413 {
414     T result;
415     foreach (ref elem; result.tupleof)
416     {
417         elem = input.get!(typeof(elem));
418     }
419 
420     return result;
421 }
422 
423 unittest
424 {
425     ubyte[] inBuffer = [0, 0, 0, 4];
426 
427     assert(inBuffer.get!int() == 4);
428 }
429 unittest
430 {
431     ubyte[] inBuffer = [0, 0, 0, 4, 0, 0, 0, 12];
432 
433     assert(inBuffer.get!int() == 4);
434     assert(inBuffer.get!int() == 12);
435 
436     assertThrown!EndOfInput(inBuffer.get!int());
437 }
438 
439 unittest
440 {
441     ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
442 
443     assert(inBuffer.get!bool() == true);
444     assert(inBuffer.get!bool() == false);
445     assertThrown!NotABool(inBuffer.get!bool());
446     assertThrown!EndOfInput(inBuffer.get!bool());
447 }
448 
449 unittest
450 {
451     ubyte[] inBuffer = [0, 0, 0, 0, 0, 0, 0, 4];
452 
453     assert(inBuffer.get!long() == 4);
454     assertThrown!EndOfInput(inBuffer.get!long());
455 }
456 
457 unittest
458 {
459     ubyte[] inBuffer = [0, 0, 0, 4, 1, 2, 3, 4];
460 
461     assert(inBuffer.get!(ubyte[])() == [1, 2, 3, 4]);
462     assertThrown!EndOfInput(inBuffer.get!(ubyte[])());
463 }
464 
465 unittest
466 {
467     ubyte[] inBuffer = [0, 0, 0, 3, 1, 2, 3, 0];
468 
469     assert(inBuffer.get!(ubyte[])() == [1, 2, 3]);
470     assertThrown!EndOfInput(inBuffer.get!int());
471 }
472 
473 unittest
474 {
475     ubyte[] inBuffer = [0, 0, 0, 5, 'h', 'e', 'l', 'l', 'o', 0, 0, 0];
476 
477     assert(inBuffer.get!string() == "hello");
478     assertThrown!EndOfInput(inBuffer.get!int());
479 }
480 
481 unittest
482 {
483     ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 2];
484 
485     assert(inBuffer.get!(int[2])() == [1, 2]);
486     assertThrown!EndOfInput(inBuffer.get!int());
487 }
488 
489 unittest
490 {
491     ubyte[] inBuffer = [0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2];
492 
493     assert(inBuffer.get!(int[])() == [1, 2]);
494     assertThrown!EndOfInput(inBuffer.get!int());
495 }
496 
497 unittest
498 {
499     ubyte[] inBuffer = [1, 2, 0, 0];
500 
501     assert(inBuffer.get!(ubyte[2])() == [1, 2]);
502     assertThrown!EndOfInput(inBuffer.get!int());
503 }
504 
505 unittest
506 {
507     ubyte[] inBuffer = [1, 2, 3, 4];
508 
509     assert(inBuffer.get!(ubyte[4])() == [1, 2, 3, 4]);
510     assertThrown!EndOfInput(inBuffer.get!int());
511 }
512 
513 unittest
514 {
515     ubyte[] inBuffer = [0, 0, 0, 1, 0, 0, 0, 2];
516 
517     struct AB
518     {
519         int a;
520         int b;
521     }
522 
523     AB ab = {1, 2};
524     assert(inBuffer.get!AB() == ab);
525     assertThrown!EndOfInput(inBuffer.get!int());
526 }