1 module tagion.betterC.communication.HiRPC;
2 
3 import std.format;
4 import std.traits : EnumMembers;
5 import tagion.basic.Types : Buffer;
6 import tagion.betterC.hibon.Document;
7 import tagion.betterC.hibon.HiBON;
8 import tagion.betterC.utils.Memory;
9 import tagion.betterC.wallet.Net;
10 import tagion.betterC.wallet.WalletRecords : GetLabel, Label;
11 import tagion.crypto.Types : Pubkey, Signature;
12 
13 struct HiRPC {
14     // import tagion.hibon.HiBONRecord;
15 
16     struct Method {
17         @Label("*", true) uint id;
18         @Label("*", true) Document params;
19         @Label("method") string name;
20 
21         this(Document doc) {
22             enum id_name = GetLabel!(id).name;
23             enum params_name = GetLabel!(params).name;
24             enum method_name = GetLabel!(name).name;
25 
26             id = doc[id_name].get!uint;
27             params = doc[params_name].get!Document;
28             name = doc[method_name].get!string;
29         }
30 
31         inout(HiBONT) toHiBON() inout {
32             auto hibon = HiBON();
33             enum id_name = GetLabel!(id).name;
34             enum params_name = GetLabel!(params).name;
35             enum method_name = GetLabel!(name).name;
36 
37             hibon[id_name] = id;
38             hibon[params_name] = params;
39             hibon[method_name] = name;
40 
41             return cast(inout) hibon;
42         }
43 
44         const(Document) toDoc() {
45             return Document(toHiBON.serialize);
46         }
47     }
48 
49     struct Response {
50         @Label("*", true) uint id;
51         Document result;
52 
53         this(Document doc) {
54             enum id_name = GetLabel!(id).name;
55             enum result_name = GetLabel!(result).name;
56 
57             id = doc[id_name].get!uint;
58             result = doc[result_name].get!Document;
59         }
60 
61         inout(HiBONT) toHiBON() inout {
62             auto hibon = HiBON();
63             enum id_name = GetLabel!(id).name;
64             enum result_name = GetLabel!(result).name;
65 
66             hibon[id_name] = id;
67             hibon[result_name] = result;
68 
69             return cast(inout) hibon;
70         }
71 
72         const(Document) toDoc() {
73             return Document(toHiBON.serialize);
74         }
75     }
76 
77     struct Error {
78         @Label("*", true) uint id;
79         @Label("*", true) Document data;
80         @Label("*", true) string message;
81         @Label("*", true) int code;
82 
83         this(Document doc) {
84             enum id_name = GetLabel!(id).name;
85             enum data_name = GetLabel!(data).name;
86             enum message_name = GetLabel!(message).name;
87             enum code_name = GetLabel!(code).name;
88 
89             id = doc[id_name].get!uint;
90             data = doc[data_name].get!Document;
91             message = doc[message_name].get!string;
92             code = doc[code_name].get!int;
93         }
94 
95         inout(HiBONT) toHiBON() inout {
96             auto hibon = HiBON();
97 
98             enum id_name = GetLabel!(id).name;
99             enum data_name = GetLabel!(data).name;
100             enum message_name = GetLabel!(message).name;
101             enum code_name = GetLabel!(code).name;
102 
103             hibon[id_name] = id;
104             hibon[data_name] = data;
105             hibon[message_name] = message;
106             hibon[code_name] = code;
107 
108             return cast(inout) hibon;
109         }
110 
111         const(Document) toDoc() {
112             return Document(toHiBON.serialize);
113         }
114 
115         static bool valid(const Document doc) {
116             enum codeName = GetLabel!(code).name;
117             enum messageName = GetLabel!(message).name;
118             enum dataName = GetLabel!(data).name;
119             return doc.hasMember(codeName) || doc.hasMember(messageName) || doc.hasMember(dataName);
120         }
121 
122     }
123 
124     enum SignedState {
125         INVALID = -1,
126         NOSIGN = 0,
127         VALID = 1
128     }
129 
130     enum Type : uint {
131         none, /// No valid Type
132         method, /// Action method
133         result, /// Respose
134         error
135     }
136 
137     enum Direction {
138         SEND,
139         RECEIVE
140     }
141 
142     struct Post(Direction DIRECTION) {
143         union Message {
144             Method method;
145             Response response;
146             Error error;
147         }
148 
149         @disable this();
150         @Label("$sign", true) immutable(ubyte)[] signature;
151         @Label("$pkey", true) immutable(ubyte)[] pubkey;
152         @Label("$msg") Document message;
153         @Label("") immutable Type type;
154 
155         this(Document doc) {
156             enum signature_name = GetLabel!(signature).name;
157             enum pubkey_name = GetLabel!(pubkey).name;
158             enum message_name = GetLabel!(message).name;
159             enum type_name = GetLabel!(type).name;
160 
161             auto received_sign = doc[signature_name].get!Buffer;
162             signature.create(received_sign.length);
163             signature = received_sign;
164 
165             auto received_pubkey = doc[pubkey_name].get!Buffer;
166             pubkey.create(received_pubkey.length);
167             pubkey = received_pubkey;
168 
169             message = doc[message_name].get!Document;
170             type = cast(Type) doc[type_name].get!uint;
171         }
172 
173         inout(HiBONT) toHiBON() inout {
174             auto hibon = HiBON();
175 
176             enum signature_name = GetLabel!(signature).name;
177             enum pubkey_name = GetLabel!(pubkey).name;
178             enum message_name = GetLabel!(message).name;
179             enum type_name = GetLabel!(type).name;
180             hibon[signature_name] = signature;
181             hibon[pubkey_name] = pubkey;
182             hibon[message_name] = message;
183             hibon[type_name] = cast(uint) type;
184 
185             return cast(inout) hibon;
186         }
187 
188         const(Document) toDoc() {
189             return Document(toHiBON.serialize);
190         }
191 
192         bool supports(T)() const {
193             import std.algorithm.searching : canFind;
194             import std.traits : isCallable;
195 
196             return (type is Type.method) &&
197                 Callers!T.canFind(method.name);
198         }
199 
200         bool verify(const Document doc) {
201             return true;
202         }
203 
204         static if (DIRECTION is Direction.RECEIVE) {
205             @Label("") protected Message _message;
206             @Label("") immutable SignedState signed;
207             enum signName = GetLabel!(signature).name;
208             enum pubkeyName = GetLabel!(pubkey).name;
209             enum messageName = GetLabel!(message).name;
210             // this(const Document doc) {
211             //     this(null, doc);
212             // }
213 
214             this(const SecureNet net, const Document doc) {
215                 enum type_name = GetLabel!(type).name;
216                 type = cast(Type) doc[type_name].get!uint;
217                 message = doc[messageName].get!Document;
218 
219                 enum signature_name = GetLabel!(signature).name;
220                 enum pubkey_name = GetLabel!(pubkey).name;
221                 auto received_sign = doc[signature_name].get!Buffer;
222                 signature.create(received_sign.length);
223                 signature = received_sign[0 .. $];
224 
225                 auto received_pubkey = doc[pubkey_name].get!Buffer;
226                 pubkey.create(received_pubkey.length);
227                 pubkey = received_pubkey[0 .. $];
228                 static SignedState verifySignature(const SecureNet net, const Document doc, const(
229                         ubyte[]) sgn, const(ubyte[]) pkey) {
230                     if (sgn.length) {
231                         //                 //immutable fingerprint=net.hashOf(msg);
232                         if (net.verify(doc.serialize, sgn, pkey)) {
233                             return SignedState.VALID;
234                         }
235                         else {
236                             return SignedState.INVALID;
237                         }
238                     }
239                     return SignedState.NOSIGN;
240                 }
241 
242                 void set_message() @trusted {
243                     with (Type) {
244                         final switch (type) {
245                         case none:
246                             break;
247                         case method:
248                             _message.method = Method(message);
249                             break;
250                         case result:
251                             _message.response = Response(message);
252                             break;
253                         case error:
254                             _message.error = Error(message);
255                         }
256                     }
257                 }
258 
259                 set_message;
260                 signed = verifySignature(net, message, signature, pubkey);
261             }
262 
263             //     this(T)(const SecureNet net, T pack) if (isHiBONRecord!T) {
264             //         this(net, pack.toDoc);
265             //     }
266 
267             @trusted const(Error) error() const pure {
268                 return _message.error;
269             }
270 
271             @trusted const(Response) response() const pure {
272                 return _message.response;
273             }
274 
275             @trusted const(Method) method() const pure {
276                 return _message.method;
277             }
278 
279             const(T) params(T, Args...)(Args args) {
280                 return T(args, method.params);
281             }
282 
283             const(T) result(T, Args...)(Args args) {
284                 return T(response.result);
285             }
286 
287             // @trusted
288             // bool isRecord(T)() const {
289             //     with (Type) {
290             //         final switch (type) {
291             //         case none, error:
292             //             return false;
293             //         case method:
294             //             return T.isRecord(_message.method.params);
295             //         case result:
296             //             return T.isRecord(_message.response.result);
297             //         }
298             //     }
299             //     assert(0);
300             // }
301         }
302         else {
303             this(T)(const SecureNet net, const T post) {
304                 message = post;
305                 // type = getType(post);
306                 auto doc = post;
307                 enum type_name = GetLabel!(type).name;
308                 type = cast(Type) doc[type_name].get!uint;
309                 // immutable signed=net.sign(message);
310                 // fingerprint=signed.message;
311 
312                 // signature = net.sign(message.serialize).signature;
313 
314                 pubkey.create(net.pubkey.length);
315                 // pubkey = net.pubkey;
316             }
317 
318             Error error() const {
319                 return Error(message);
320             }
321 
322             Response response() const {
323                 return Response(message);
324             }
325 
326             Method method() const {
327                 return Method(message);
328             }
329 
330             /++
331              Checks if the message has been signed
332              NOTE!! This does not mean that the signature is correct
333              Returns:
334              True if the message has been signed
335              +/
336             bool isSigned() const pure {
337                 return (signature.length !is 0);
338             }
339         }
340 
341     }
342 
343     alias Sender = Post!(Direction.SEND);
344     alias Receiver = Post!(Direction.RECEIVE);
345     const SecureNet net;
346 
347     const(uint) generateId() const {
348         uint id = 0;
349         // import tagion.utils.Random;
350         // import stdrnd = std.random;
351 
352         // auto rnd = Random!uint(stdrnd.unpredictableSeed);
353         do {
354             //     id = rnd.value();
355         }
356         while (id is 0 || id is uint.max);
357         return id;
358     }
359 
360     const(Sender) action(string method, const Document params, const uint id = uint.max) const {
361         Method message;
362         message.id = (id is uint.max) ? generateId : id;
363         if (!params.empty) {
364             message.params = params;
365         }
366         message.name = method;
367         message.params = params;
368         auto sender = Sender(net, message.toDoc);
369         return sender;
370     }
371 
372     const(Sender) action(string method, HiBONT params, const uint id = uint.max) const {
373         const doc = Document(params);
374         return action(method, doc, id);
375     }
376 
377     final const(Receiver) receive(Document doc) const {
378         auto receiver = Receiver(net, doc);
379         return receiver;
380     }
381 
382 }