1 module buffer.packet;
2 
3 import std.meta : AliasSeq, staticIndexOf;
4 import std.variant;
5 import std.bitmanip;
6 import std.traits;
7 import std.typecons;
8 import std.conv : to;
9 import std.exception;
10 
11 import crypto.aes;
12 import crypto.tea;
13 import crypto.rsa;
14 
15 import buffer.utils;
16 
17 /// All encryption supported.
18 enum CryptType
19 {
20     NONE            = 0,
21     XTEA            = 1,
22     AES             = 2,
23     RSA             = 3,
24     RSA_XTEA_MIXIN  = 4
25 }
26 
27 package:
28 
29 /// These two items must correspond one by one.
30 alias supportedBuiltinTypes = AliasSeq!(     byte, ubyte, short, ushort, int,  uint, long, ulong, float, double, real, bool, char, string);
31 immutable byte[] supportedBuiltinTypeNos = [ 0x01, 0x02,  0x03,  0x04,   0x05, 0x06, 0x07, 0x08,  0x20,  0x21,   0x22, 0x30, 0x40, 0x41 ];
32 
33 /// Convert Type to TypeNo.
34 template TypeNo(T)
35 {
36     enum idx = staticIndexOf!(T, supportedBuiltinTypes);
37     static assert(idx != -1, "Data types that are not supported: " ~ typeid(T));
38     enum TypeNo = supportedBuiltinTypeNos[idx];
39 }
40 
41 alias write = std.bitmanip.write;
42 
43 class Packet
44 {
45     static ubyte[] build(const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey,
46         const string name, const string method, Variant[] params)
47     {
48         assert(name.length <= 255, "Paramter name cannot be greater than 255 characters.");
49         assert(method.length <= 255, "Paramter method cannot be greater than 255 characters.");
50 
51         ubyte[] tlv;
52         BufferBuilder bb = new BufferBuilder(&tlv);
53 
54         foreach (v; params)
55         {
56             bool typeValid;
57             static foreach (T; supportedBuiltinTypes)
58             {
59                 if (v.type == typeid(T))
60                 {
61                     static if (is(T == string))
62                         bb.put!T(v.get!T, true, true, 4);
63                     else
64                         bb.put!T(v.get!T, true, false, 0);
65                     typeValid = true;
66                 }
67                 else if (v.type == typeid(ConstOf!T))
68                 {
69                     static if (is(Unique!T == string))
70                         bb.put!T(v.get!(const T), true, true, 4);
71                     else
72                         bb.put!T(v.get!(const T), true, false, 0);
73                     typeValid = true;
74                 }
75                 else if (v.type == typeid(SharedOf!T))
76                 {
77                     static if (is(Unique!T == string))
78                         bb.put!T(v.get!(shared T), true, true, 4);
79                     else
80                         bb.put!T(v.get!(shared T), true, false, 0);
81                     typeValid = true;
82                 }
83                 else if (v.type == typeid(SharedConstOf!T))
84                 {
85                     static if (is(Unique!T == string))
86                         bb.put!T(v.get!(shared const T), true, true, 4);
87                     else
88                         bb.put!T(v.get!(shared const T), true, false, 0);
89                     typeValid = true;
90                 }
91                 else if (v.type == typeid(ImmutableOf!T))
92                 {
93                     static if (is(Unique!T == string))
94                         bb.put!T(v.get!(immutable T), true, true, 4);
95                     else
96                         bb.put!T(v.get!(immutable T), true, false, 0);
97                     typeValid = true;
98                 }
99             }
100 
101             assert(typeValid, "Data types id that are not supported: " ~ v.type.toString);
102         }
103 
104         final switch (crypt)
105         {
106             case CryptType.NONE:
107                 break;
108             case CryptType.XTEA:
109                 tlv = Xtea.encrypt(tlv, key, 64, PaddingMode.PKCS5);
110                 break;
111             case CryptType.AES:
112                 tlv = AESUtils.encrypt!AES128(tlv, key, iv, PaddingMode.PKCS5);
113                 break;
114             case CryptType.RSA:
115                 tlv = RSA.encrypt(rsaKey.get, tlv);
116                 break;
117             case CryptType.RSA_XTEA_MIXIN:
118                 tlv = RSA.encrypt(rsaKey.get, tlv, true);
119         }
120 
121         ubyte[] buffer;
122         bb = new BufferBuilder(&buffer);
123         bb.put!ushort(magic, false, false, 0);
124         bb.put!int(0, false, false, 0);    // length, seize a seat.
125         bb.put!string(name, false, true, 2);
126         bb.put!string(method, false, true, 2);
127         buffer ~= tlv;
128         buffer.write!int(cast(int)(buffer.length - 2 - 4 + 2), 2);
129         buffer ~= strToByte_hex(MD5(buffer)[0 .. 4]);
130 
131         return buffer;
132     }
133 
134     static size_t parseInfo(const ubyte[] buffer, out string name, out string method)
135     {
136         enforce(buffer != null && buffer.length >= 10, "Incorrect buffer length.");
137 
138         ushort len1 = buffer.peek!ushort(6);
139         if (len1 > 0)
140         {
141             name = cast(string) buffer[8 .. 8 + len1];
142         }
143 
144         ushort len2 = buffer.peek!ushort(8 + len1);
145         if (len2 > 0)
146         {
147             method = cast(string) buffer[10 + len1 .. 10 + len1 + len2];
148         }
149 
150         return 10 + len1 + len2;
151     }
152 
153     static Variant[] parse(const ubyte[] buffer, const ushort magic, const CryptType crypt, const string key, Nullable!RSAKeyInfo rsaKey, out string name, out string method)
154     {
155         enforce(buffer != null && buffer.length >= 10, "Incorrect buffer length.");
156 
157         ushort t_magic;
158         int t_len;
159         t_magic = buffer.peek!ushort(0);
160         t_len = buffer.peek!int(2);
161 
162         if ((t_magic != magic) || (t_len > cast(int)buffer.length - 6))
163         {
164             return null;
165         }
166 
167         ubyte[] buf = cast(ubyte[]) buffer[0 .. t_len + 6];
168         if (strToByte_hex(MD5(buf[0 .. $ - 2])[0 .. 4]) != buf[$ - 2 .. $])
169         {
170             return null;
171         }
172 
173         size_t tlv_pos = parseInfo(buf, name, method);
174         buf = buf[tlv_pos .. $ - 2];
175 
176         final switch (crypt)
177         {
178             case CryptType.NONE:
179                 break;
180             case CryptType.XTEA:
181                 buf = Xtea.decrypt(buf, key, 64, PaddingMode.PKCS5);
182                 break;
183             case CryptType.AES:
184                 buf = AESUtils.decrypt!AES128(buf, key, iv, PaddingMode.PKCS5);
185                 break;
186             case CryptType.RSA:
187                 buf = RSA.decrypt(rsaKey.get, buf);
188                 break;
189             case CryptType.RSA_XTEA_MIXIN:
190                 buf = RSA.decrypt(rsaKey.get, buf, true);
191                 break;
192         }
193 
194         ubyte typeNo;
195         int pos;
196         Variant[] ret;
197 
198         while (pos < buf.length)
199         {
200             typeNo = buf[pos];
201             pos++;
202 
203             bool typeValid;
204             static foreach (idx, T; supportedBuiltinTypes)
205             {
206                 if (typeNo == supportedBuiltinTypeNos[idx])
207                 {
208                     typeValid = true;
209 
210                     static if (is(T == real))
211                     {
212                         //get!real;
213                         ret ~= Variant(ubytesToReal(buf[pos .. pos + real.sizeof]));
214                         pos += real.sizeof;
215                     }
216                     else static if (is(T == string))
217                     {
218                         immutable temp = buf.peek!int(pos);
219                         pos += 4;
220                         ret ~= Variant(cast(string) buf[pos .. pos + temp]);    // !! May be: invalid UTF-8 sequence.
221                         pos += temp;
222                     }
223                     else
224                     {
225                         ret ~= Variant(buf.peek!T(pos));
226                         pos += T.sizeof;
227                     }
228                 }
229             }
230             assert(typeValid, "Data types id that are not supported: " ~ typeNo.to!string);
231         }
232 
233         return ret;
234     }
235 
236 private:
237 
238     static ubyte[] iv = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
239 }
240 
241 class BufferBuilder
242 {
243     public ubyte[]* buffer;
244 
245     this(ubyte[]* buffer)
246     {
247         this.buffer = buffer;
248     }
249 
250     size_t put(T)(const T value, const bool isWriteTypeInfo, const bool isWriteLengthInfo, const int lengthBytes)
251     {
252         assert(lengthBytes == 0 || lengthBytes == 2 || lengthBytes == 4);
253 
254         ubyte[] buf_data;
255         size_t len;
256 
257         if (isWriteTypeInfo)
258         {
259             *buffer ~= TypeNo!T;
260         }
261 
262         static if (is(Unqual!T == string))
263         {
264             buf_data = cast(ubyte[])value;
265             len = buf_data.length;
266         }
267         else static if (is(Unqual!T == real))
268         {
269             buf_data = realToUBytes(value);
270             len = real.sizeof;
271         }
272         else
273         {
274             buf_data = new ubyte[T.sizeof];
275             buf_data.write!T(value, 0);
276             len = T.sizeof;
277         }
278 
279         if (isWriteLengthInfo && lengthBytes > 0)
280         {
281             ubyte[] buf_len = new ubyte[lengthBytes];
282             if (lengthBytes == 2)
283                 buf_len.write!ushort(cast(ushort)len, 0);
284             else
285                 buf_len.write!int(cast(int)len, 0);
286 
287             *buffer ~= buf_len;
288         }
289 
290         *buffer ~= buf_data;
291 
292         return len;
293     }
294 }