1 /**
2 This module implements functions for encoding various D types into ubyte arrays. Note that the encoding is not
3 cross platform and results will be different across platforms depending on CPU architecture.
4 
5 ────────────────────────────────────────────────────────────────────────────────
6 
7 Copyright (C) 2021 pillager86.rf.gd
8 
9 This program is free software: you can redistribute it and/or modify it under 
10 the terms of the GNU General Public License as published by the Free Software 
11 Foundation, either version 3 of the License, or (at your option) any later 
12 version.
13 
14 This program is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License along with 
19 this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21 module mildew.util.encode;
22 
23 import std.traits: isBasicType;
24 
25 debug import std.stdio;
26 
27 /** 
28  * Encodes a value as an ubyte array. Note that only simple types, and arrays of simple types can be encoded.
29  * Params:
30  *  value = The value to be encoded.
31  * Returns:
32  *  The value stored as an ubyte[]
33  */
34 ubyte[] encode(T)(T value)
35 {
36 	ubyte[] encoding;
37 	static if(isBasicType!T)
38 	{
39 		encoding ~= (cast(ubyte*)&value)[0..T.sizeof];
40 	}
41 	else static if(is(T == E[], E))
42 	{
43         static assert(isBasicType!E, "Only arrays of basic types are supported");
44 		size_t size = value.length;
45 		encoding ~= (cast(ubyte*)&size)[0..size.sizeof];
46         foreach(item ; value)
47             encoding ~= encode(item);
48 	}
49 	else
50 	{
51 		static assert(false, "Unable to encode type " ~ T.stringof);
52 	}
53 	return encoding;
54 }
55 
56 /** 
57  * decode a value from an ubyte range.
58  * Params:
59  *  data = An ubyte[] that should be large enough to contain the size of T otherwise an exception is thrown.
60  * Returns:
61  *  The decoded piece of data described by type T.
62  */
63 T decode(T)(const ubyte[] data)
64 {
65     static if(isBasicType!T)
66     {
67         if(data.length < T.sizeof)
68             throw new EncodeException("Data length is too short for type " ~ T.stringof);
69         return *cast(T*)(data.ptr);
70     }
71     else static if(is(T==E[], E))
72     {
73         static assert(isBasicType!E, "Only arrays of basic types are supported");
74         if(data.length < size_t.sizeof)
75             throw new EncodeException("Data length is shorter than size_t for " ~ T.stringof);
76         size_t size = *cast(size_t*)(data.ptr);
77         if(data.length < size_t.sizeof + E.sizeof * size)
78             throw new EncodeException("Data length is too short for array elements " ~ E.stringof);
79         T array = new T(size);
80         static if(is(E==ubyte))
81         {
82             for(size_t i = 0; i < size; ++i)
83                 array[i] = cast(E)data[size_t.sizeof + i];
84         }
85         else
86         {
87             for(size_t i = 0; i < size; ++i)
88                 array[i] = cast(E)decode!E(data[size_t.sizeof + i * E.sizeof..$]);
89         }
90         return array;
91     }
92     else static assert(false, "Unable to decode type " ~ T.stringof);
93 }
94 
95 /// Thrown when decoding
96 class EncodeException : Exception
97 {
98     /// ctor
99     this(string msg, string file = __FILE__, size_t line = __LINE__)
100     {
101         super(msg, file, line);
102     }
103 }
104 
105 unittest
106 {
107     import std.format: format;
108     auto testInts = [1, 5, 9];
109     auto encoded = encode(testInts);
110     auto decoded = decode!(int[])(encoded[0..$]);
111     assert(decoded.length == 3);
112     assert(decoded[0] == 1, "Value is actually " ~ format("%x", decoded[0]));
113     assert(decoded[1] == 5);
114     assert(decoded[2] == 9);
115 }