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