egra: post rebuild event on minimisation (because minimised windows won't do it)
[iv.d.git] / _obsolete_dont_use / ncserial.d
blob1743cc58a93b6950278451e49fe69f6ca821e491
1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // very simple serializer
17 // WARNING! do not use for disk and other sensitive serialization,
18 // as format may change without notice! at least version it!
19 // there is also very simple RPC system based on this serializer
20 module iv.ncserial;
21 private:
23 import iv.vfs;
24 //version(rdmd) import iv.strex;
27 // ////////////////////////////////////////////////////////////////////////// //
28 public enum NCIgnore; // ignore this field
29 public struct NCName { string name; } // rename this field
32 enum NCEntryType : ubyte {
33 End = 0x00, // WARNING! SHOULD BE ZERO!
34 Bool = 0x10,
35 Char = 0x20,
36 Int = 0x30,
37 Uint = 0x40,
38 Float = 0x50,
39 Struct = 0x60,
40 Array = 0x70,
41 Dict = 0x80,
45 // ////////////////////////////////////////////////////////////////////////// //
46 // skip serialized data block
47 public void ncskip(ST) (auto ref ST st) if (isReadableStream!ST) {
48 void skip (uint count) {
49 if (count == 0) return;
51 static if (isSeekableStream!ST) {
52 st.seek(count, Seek.Cur);
53 } else*/ {
54 ubyte[64] buf = void;
55 while (count > 0) {
56 int rd = (count > buf.length ? cast(int)buf.length : cast(int)count);
57 st.rawReadExact(buf[0..rd]);
58 count -= rd;
63 void skipStr () {
64 ubyte len = st.readNum!ubyte;
65 skip(len);
68 void skipType (ubyte tp, int count=1) {
69 switch (tp&0xf0) {
70 case NCEntryType.Bool:
71 case NCEntryType.Char:
72 case NCEntryType.Int:
73 case NCEntryType.Uint:
74 case NCEntryType.Float:
75 skip(count*(tp&0x0f)); // size
76 break;
77 case NCEntryType.Struct:
78 if ((tp&0x0f) != 0) throw new Exception("invalid struct type");
79 while (count-- > 0) {
80 skipStr(); // struct name
81 // fields
82 for (;;) {
83 tp = st.readNum!ubyte; // name length
84 if (tp == NCEntryType.End) break;
85 skip(tp); // name
86 tp = st.readNum!ubyte; // data type
87 skipType(tp);
90 break;
91 case NCEntryType.Array:
92 if ((tp&0x0f) != 0) throw new Exception("invalid array type");
93 while (count-- > 0) {
94 ubyte dimc = st.readNum!ubyte; // dimension count
95 if (dimc == 0) throw new Exception("invalid array type");
96 tp = st.readNum!ubyte; // data type
98 void readDim (int dcleft) {
99 auto len = st.readXInt!uint;
100 if (dcleft == 1) {
101 //foreach (immutable _; 0..len) skipType(tp);
102 if (len <= int.max) {
103 skipType(tp, len);
104 } else {
105 foreach (immutable _; 0..len) skipType(tp);
107 } else {
108 foreach (immutable _; 0..len) readDim(dcleft-1);
111 readDim(dimc);
113 break;
114 case NCEntryType.Dict:
115 if ((tp&0x0f) != 0) throw new Exception("invalid dict type");
116 while (count-- > 0) {
117 ubyte kt = st.readNum!ubyte; // key type
118 ubyte vt = st.readNum!ubyte; // value type
119 foreach (immutable _; 0..st.readXInt!usize) {
120 skipType(kt);
121 skipType(vt);
124 break;
125 default: throw new Exception("invalid data type");
129 skipType(st.readNum!ubyte);
133 // ////////////////////////////////////////////////////////////////////////// //
134 // read serialized data block to buffer, return wrapped memory
135 public ubyte[] ncreadBytes(ST) (auto ref ST st) if (isReadableStream!ST) {
136 ubyte[] data;
138 struct CopyStream {
139 void[] rawRead (void[] buf) {
140 auto rd = st.rawRead(buf);
141 if (rd.length) data ~= cast(const(ubyte)[])rd;
142 return rd;
145 wrapStream(CopyStream()).ncskip;
146 return data;
150 // read serialized data block to buffer, return wrapped memory
151 public VFile ncread(ST) (auto ref ST st) if (isReadableStream!ST) {
152 return st.ncreadBytes.wrapMemoryRO;
156 // ////////////////////////////////////////////////////////////////////////// //
157 template isSimpleType(T) {
158 private import std.traits : Unqual;
159 private alias UT = Unqual!T;
160 enum isSimpleType = __traits(isIntegral, UT) || __traits(isFloating, UT) || is(UT == bool);
164 void ncWriteUbyte(ST) (auto ref ST fl, ubyte b) {
165 fl.rawWriteExact((&b)[0..1]);
169 ubyte ncReadUbyte(ST) (auto ref ST fl) {
170 ubyte b;
171 fl.rawReadExact((&b)[0..1]);
172 return b;
176 // ////////////////////////////////////////////////////////////////////////// //
177 public void ncser(T, ST) (auto ref ST fl, in auto ref T v) if (!is(T == class) && isWriteableStream!ST) {
178 import std.traits : Unqual;
180 void writeTypeHeader(T) () {
181 alias UT = Unqual!T;
182 static if (is(UT : V[], V)) {
183 enum dc = dimensionCount!UT;
184 static assert(dc <= 255, "too many array dimenstions");
185 fl.ncWriteUbyte(NCEntryType.Array);
186 fl.ncWriteUbyte(cast(ubyte)dc);
187 writeTypeHeader!(arrayElementType!UT);
188 } else static if (is(UT : K[V], K, V)) {
189 fl.ncWriteUbyte(NCEntryType.Dict);
190 writeTypeHeader!(Unqual!K);
191 writeTypeHeader!(Unqual!V);
192 } else static if (is(UT == bool)) {
193 fl.ncWriteUbyte(cast(ubyte)(NCEntryType.Bool|bool.sizeof));
194 } else static if (is(UT == char) || is(UT == wchar) || is(UT == dchar)) {
195 fl.ncWriteUbyte(cast(ubyte)(NCEntryType.Char|UT.sizeof));
196 } else static if (__traits(isIntegral, UT)) {
197 static if (__traits(isUnsigned, UT)) {
198 fl.ncWriteUbyte(cast(ubyte)(NCEntryType.Uint|UT.sizeof));
199 } else {
200 fl.ncWriteUbyte(cast(ubyte)(NCEntryType.Int|UT.sizeof));
202 } else static if (__traits(isFloating, UT)) {
203 fl.ncWriteUbyte(cast(ubyte)(NCEntryType.Float|UT.sizeof));
204 } else static if (is(UT == struct)) {
205 static assert(UT.stringof.length <= 255, "struct name too long: "~UT.stringof);
206 fl.ncWriteUbyte(NCEntryType.Struct);
207 fl.ncWriteUbyte(cast(ubyte)UT.stringof.length);
208 fl.rawWriteExact(UT.stringof[]);
209 } else {
210 static assert(0, "can't serialize type '"~T.stringof~"'");
214 void serData(T) (in ref T v) {
215 alias UT = arrayElementType!T;
216 static if (is(T : V[], V)) {
217 // array
218 void writeMArray(AT) (AT arr) {
219 fl.writeXInt(arr.length);
220 static if (isMultiDimArray!AT) {
221 foreach (const a2; arr) writeMArray(a2);
222 } else {
223 // write POD arrays in one chunk
224 static if (isSimpleType!UT) {
225 fl.rawWriteExact(arr[]);
226 } else {
227 foreach (const ref it; arr) serData(it);
231 writeMArray(v);
232 } else static if (is(T : V[K], K, V)) {
233 // associative array
234 fl.writeXInt(v.length);
235 foreach (const kv; v.byKeyValue) {
236 serData(kv.key);
237 serData(kv.value);
239 } else static if (isSimpleType!UT) {
240 fl.rawWriteExact((&v)[0..1]);
241 } else static if (is(UT == struct)) {
242 import std.traits : FieldNameTuple, getUDAs, hasUDA;
243 foreach (string fldname; FieldNameTuple!UT) {
244 static if (!hasUDA!(__traits(getMember, UT, fldname), NCIgnore)) {
245 enum names = getUDAs!(__traits(getMember, UT, fldname), NCName);
246 static if (names.length) enum xname = names[0].name; else enum xname = fldname;
247 static assert(xname.length <= 255, "struct '"~UT.stringof~"': field name too long: "~xname);
248 fl.ncWriteUbyte(cast(ubyte)xname.length);
249 fl.rawWriteExact(xname[]);
250 fl.ncser(__traits(getMember, v, fldname));
253 fl.ncWriteUbyte(NCEntryType.End);
254 } else {
255 static assert(0, "can't serialize type '"~T.stringof~"'");
259 writeTypeHeader!T;
260 serData(v);
264 // ////////////////////////////////////////////////////////////////////////// //
265 public void ncunser(T, ST) (auto ref ST fl, out T v) if (!is(T == class) && isReadableStream!ST) {
266 import std.traits : Unqual;
268 void checkTypeId(T) () {
269 static if (is(T : V[], V)) {
270 if (fl.ncReadUbyte != NCEntryType.Array) throw new Exception(`invalid stream (array expected)`);
271 if (fl.ncReadUbyte != dimensionCount!T) throw new Exception(`invalid stream (dimension count)`);
272 checkTypeId!(arrayElementType!T);
273 } else static if (is(T : K[V], K, V)) {
274 if (fl.ncReadUbyte != NCEntryType.Dict) throw new Exception(`invalid stream (dict expected)`);
275 checkTypeId!(Unqual!K);
276 checkTypeId!(Unqual!V);
277 } else static if (is(T == bool)) {
278 if (fl.ncReadUbyte != (NCEntryType.Bool|bool.sizeof)) throw new Exception(`invalid stream (bool expected)`);
279 } else static if (is(T == char) || is(T == wchar) || is(T == dchar)) {
280 if (fl.ncReadUbyte != (NCEntryType.Char|T.sizeof)) throw new Exception(`invalid stream (char expected)`);
281 } else static if (__traits(isIntegral, T)) {
282 static if (__traits(isUnsigned, T)) {
283 if (fl.ncReadUbyte != (NCEntryType.Uint|T.sizeof)) throw new Exception(`invalid stream (int expected)`);
284 } else {
285 if (fl.ncReadUbyte != (NCEntryType.Int|T.sizeof)) throw new Exception(`invalid stream (int expected)`);
287 } else static if (__traits(isFloating, T)) {
288 if (fl.ncReadUbyte != (NCEntryType.Float|T.sizeof)) throw new Exception(`invalid stream (float expected)`);
289 } else static if (is(T == struct)) {
290 char[255] cbuf = void;
291 static assert(T.stringof.length <= 255, "struct name too long: "~T.stringof);
292 if (fl.ncReadUbyte != NCEntryType.Struct) throw new Exception(`invalid stream (struct expected)`);
293 if (fl.ncReadUbyte != T.stringof.length) throw new Exception(`invalid stream (struct name length)`);
294 fl.rawReadExact(cbuf[0..T.stringof.length]);
295 if (cbuf[0..T.stringof.length] != T.stringof) throw new Exception(`invalid stream (struct name)`);
296 } else {
297 static assert(0, "can't unserialize type '"~T.stringof~"'");
301 void unserData(T) (out T v) {
302 static if (is(T : V[], V)) {
303 void readMArray(AT) (out AT arr) {
304 auto llen = fl.readXInt!usize;
305 if (llen == 0) return;
306 static if (__traits(isStaticArray, AT)) {
307 if (arr.length != llen) throw new Exception(`invalid stream (array size)`);
308 alias narr = arr;
309 } else {
310 Unqual!(typeof(arr[0]))[] narr;
311 narr.length = llen;
313 static if (isMultiDimArray!AT) {
314 foreach (ref a2; narr) readMArray(a2);
315 } else {
316 alias ET = arrayElementType!AT;
317 // read byte arrays in one chunk
318 static if (isSimpleType!ET) {
319 fl.rawReadExact(narr[]);
320 } else {
321 foreach (ref it; narr) unserData(it);
324 static if (!__traits(isStaticArray, AT)) arr = cast(AT)narr;
326 readMArray(v);
327 } else static if (is(T : V[K], K, V)) {
328 K key = void;
329 V value = void;
330 foreach (immutable _; 0..fl.readXInt!usize) {
331 unserData(key);
332 unserData(value);
333 v[key] = value;
335 } else static if (isSimpleType!T) {
336 fl.rawReadExact((&v)[0..1]);
337 } else static if (is(T == struct)) {
338 import std.traits : FieldNameTuple, getUDAs, hasUDA;
340 ulong[(FieldNameTuple!T.length+ulong.sizeof-1)/ulong.sizeof] fldseen = 0;
342 bool tryField(uint idx, string fldname) (const(char)[] name) {
343 static if (hasUDA!(__traits(getMember, T, fldname), NCName)) {
344 enum names = getUDAs!(__traits(getMember, T, fldname), NCName);
345 } else {
346 alias tuple(T...) = T;
347 enum names = tuple!(NCName(fldname));
349 foreach (immutable xname; names) {
350 if (xname.name == name) {
351 if (fldseen[idx/8]&(1UL<<(idx%8))) throw new Exception(`duplicate field value for '`~fldname~`'`);
352 fldseen[idx/8] |= 1UL<<(idx%8);
353 fl.ncunser(__traits(getMember, v, fldname));
354 return true;
357 return false;
360 void tryAllFields (const(char)[] name) {
361 foreach (immutable idx, string fldname; FieldNameTuple!T) {
362 static if (!hasUDA!(__traits(getMember, T, fldname), NCIgnore)) {
363 if (tryField!(idx, fldname)(name)) return;
366 throw new Exception("unknown field '"~name.idup~"'");
369 char[255] cbuf = void;
370 // let's hope that fields are in order
371 foreach (immutable idx, string fldname; FieldNameTuple!T) {
372 static if (!hasUDA!(__traits(getMember, T, fldname), NCIgnore)) {
373 auto nlen = fl.ncReadUbyte;
374 if (nlen == NCEntryType.End) throw new Exception("invalid stream (out of fields)");
375 fl.rawReadExact(cbuf[0..nlen]);
376 if (!tryField!(idx, fldname)(cbuf[0..nlen])) tryAllFields(cbuf[0..nlen]);
379 if (fl.ncReadUbyte != NCEntryType.End) throw new Exception("invalid stream (extra fields)");
383 checkTypeId!T;
384 unserData(v);
388 // ////////////////////////////////////////////////////////////////////////// //
389 template isMultiDimArray(T) {
390 private import std.range.primitives : hasLength;
391 private import std.traits : isArray, isNarrowString;
392 static if (isArray!T) {
393 alias DT = typeof(T.init[0]);
394 static if (hasLength!DT || isNarrowString!DT) {
395 enum isMultiDimArray = true;
396 } else {
397 enum isMultiDimArray = false;
399 } else {
400 enum isMultiDimArray = false;
403 static assert(isMultiDimArray!(string[]) == true);
404 static assert(isMultiDimArray!string == false);
405 static assert(isMultiDimArray!(int[int]) == false);
408 template dimensionCount(T) {
409 private import std.range.primitives : hasLength;
410 private import std.traits : isArray, isNarrowString;
411 static if (isArray!T) {
412 alias DT = typeof(T.init[0]);
413 static if (hasLength!DT || isNarrowString!DT) {
414 enum dimensionCount = 1+dimensionCount!DT;
415 } else {
416 enum dimensionCount = 1;
418 } else {
419 enum dimensionCount = 0;
422 static assert(dimensionCount!string == 1);
423 static assert(dimensionCount!(int[int]) == 0);
426 template arrayElementType(T) {
427 private import std.traits : isArray, Unqual;
428 static if (isArray!T) {
429 alias arrayElementType = arrayElementType!(typeof(T.init[0]));
430 } else static if (is(typeof(T))) {
431 alias arrayElementType = Unqual!(typeof(T));
432 } else {
433 alias arrayElementType = Unqual!T;
436 static assert(is(arrayElementType!string == char));
439 // ////////////////////////////////////////////////////////////////////////// //
440 version(ncserial_test) unittest {
441 import iv.vfs;
443 // ////////////////////////////////////////////////////////////////////////// //
444 static struct AssemblyInfo {
445 uint id;
446 string name;
447 @NCIgnore uint ignoreme;
450 static struct ReplyAsmInfo {
451 @NCName("command") @NCName("xcommand") ubyte cmd;
452 @NCName("values") AssemblyInfo[][2] list;
453 uint[string] dict;
454 bool fbool;
455 char[3] ext;
459 // ////////////////////////////////////////////////////////////////////////// //
460 void test0 () {
461 ReplyAsmInfo ri;
462 ri.cmd = 42;
463 ri.list[0] ~= AssemblyInfo(666, "hell");
464 ri.list[1] ~= AssemblyInfo(69, "fuck");
465 ri.dict["foo"] = 42;
466 ri.dict["boo"] = 666;
467 ri.fbool = true;
468 ri.ext = "elf";
470 auto fl = VFile("z00.bin", "w");
471 fl.ncser(ri);
474 ReplyAsmInfo xf;
475 auto fl = VFile("z00.bin");
476 fl.ncunser(xf);
477 assert(fl.tell == fl.size);
478 assert(xf.cmd == 42);
479 assert(xf.list.length == 2);
480 assert(xf.list[0].length == 1);
481 assert(xf.list[1].length == 1);
482 assert(xf.list[0][0].id == 666);
483 assert(xf.list[0][0].name == "hell");
484 assert(xf.list[1][0].id == 69);
485 assert(xf.list[1][0].name == "fuck");
486 assert(xf.dict.length == 2);
487 assert(xf.dict["foo"] == 42);
488 assert(xf.dict["boo"] == 666);
489 assert(xf.fbool == true);
490 assert(xf.ext == "elf");
494 void test1 () {
495 ReplyAsmInfo ri;
496 ri.cmd = 42;
497 ri.list[0] ~= AssemblyInfo(666, "hell");
498 ri.list[1] ~= AssemblyInfo(69, "fuck");
499 ri.dict["foo"] = 42;
500 ri.dict["boo"] = 666;
501 ri.fbool = true;
502 ri.ext = "elf";
503 auto mem = wrapMemoryRW(null);
504 mem.ncser(ri);
506 mem.seek(0);
507 ReplyAsmInfo xf;
508 mem.ncunser(xf);
509 assert(mem.tell == mem.size);
510 assert(xf.cmd == 42);
511 assert(xf.list.length == 2);
512 assert(xf.list[0].length == 1);
513 assert(xf.list[1].length == 1);
514 assert(xf.list[0][0].id == 666);
515 assert(xf.list[0][0].name == "hell");
516 assert(xf.list[1][0].id == 69);
517 assert(xf.list[1][0].name == "fuck");
518 assert(xf.dict.length == 2);
519 assert(xf.dict["foo"] == 42);
520 assert(xf.dict["boo"] == 666);
521 assert(xf.fbool == true);
522 assert(xf.ext == "elf");
524 mem.seek(0);
525 mem.ncskip;
526 assert(mem.tell == mem.size);
528 auto m2 = mem.ncread;
529 assert(mem.tell == mem.size);
530 assert(m2.tell == mem.size);
535 // ////////////////////////////////////////////////////////////////////////// //
536 // simple RPC system
537 private import std.traits;
540 public enum NCRPCEP;
541 public struct NCRPCEPName { string name; } // RPC endpoint name, can be used instead of NCRPCEP
543 public enum RPCommand : ushort {
544 Call = 0x29a,
545 RetVoid,
546 RetRes,
547 Err,
551 // ////////////////////////////////////////////////////////////////////////// //
552 private alias Id(alias T) = T;
554 private struct RPCEndPoint {
555 string name;
556 ubyte[32] hash;
557 bool isFunction;
558 VFile delegate (VFile fi) dg; // read args, do call, write result; throws on error; returns serialized res
562 private RPCEndPoint[string] endpoints;
565 // ////////////////////////////////////////////////////////////////////////// //
566 private string nodots (string s) {
567 if (s.length > 2 && s[0] == '"' && s[$-1] == '"') s = s[1..$-1];
568 usize pos = s.length;
569 while (pos > 0 && s[pos-1] != '.') --pos;
570 return s[pos..$];
574 // ////////////////////////////////////////////////////////////////////////// //
575 public string[] rpcEndpointNames () { return endpoints.keys; }
578 // ////////////////////////////////////////////////////////////////////////// //
579 // look over the whole module for exported functions
580 public void rpcRegisterModuleEndpoints(alias mod) (const(char)[] prefix=null) {
581 foreach (string memberName; __traits(allMembers, mod)) {
582 static if (is(typeof(__traits(getMember, mod, memberName)))) {
583 alias member = Id!(__traits(getMember, mod, memberName));
584 // is it a function marked with export?
585 static if (is(typeof(member) == function) /*&& __traits(getProtection, member) == "export"*/) {
586 static if (hasUDA!(member, NCRPCEPName)) {
587 //pragma(msg, memberName);
588 rpcRegisterEndpoint!member(null, getUDAs!(member, NCRPCEPName)[0].name);
589 } else static if (hasUDA!(member, NCRPCEP)) {
590 rpcRegisterEndpoint!member(prefix);
598 // ////////////////////////////////////////////////////////////////////////// //
599 public static ubyte[32] rpchash(alias func) () if (isCallable!func) {
600 import std.digest.sha;
601 SHA256 sha;
603 void put (const(void)[] buf) {
604 sha.put(cast(const(ubyte)[])buf);
607 sha.start();
608 //put(nodots(fullyQualifiedName!func.stringof));
609 put(ReturnType!func.stringof);
610 put(",");
611 foreach (immutable par; Parameters!func) {
612 put(par.stringof);
613 put(",");
615 return sha.finish();
619 // ////////////////////////////////////////////////////////////////////////// //
620 private string BuildCall(alias func) () {
621 string res = "func(";
622 foreach (immutable idx, immutable par; Parameters!func) {
623 import std.conv : to;
624 res ~= "mr.a";
625 res ~= idx.to!string;
626 res ~= ",";
628 return res~")";
632 // ////////////////////////////////////////////////////////////////////////// //
633 private static mixin template BuildRPCArgs (alias func) {
634 private import std.traits;
635 private static string buildIt(alias func) () {
636 string res;
637 alias defs = ParameterDefaults!func;
638 foreach (immutable idx, immutable par; Parameters!func) {
639 import std.conv : to;
640 res ~= par.stringof;
641 res ~= " a";
642 res ~= to!string(idx);
643 static if (!is(defs[idx] == void)) res ~= " = "~defs[idx].stringof;
644 res ~= ";\n";
646 return res;
648 mixin(buildIt!func);
652 // ////////////////////////////////////////////////////////////////////////// //
653 public struct RPCCallHeader {
654 string name; // fqn
655 ubyte[32] hash;
659 // ////////////////////////////////////////////////////////////////////////// //
660 private void fcopy (VFile to, VFile from) {
661 ubyte[64] buf = void;
662 for (;;) {
663 auto rd = from.rawRead(buf[]);
664 if (rd.length == 0) break;
665 to.rawWriteExact(rd[]);
670 // ////////////////////////////////////////////////////////////////////////// //
671 // client will use this
672 // it will send RPCommand.Call
673 // throws on fatal stream error
674 public static auto rpcall(alias func, string prefix=null, string name=null, ST, A...) (auto ref ST chan, A args)
675 if (isRWStream!ST && is(typeof(func) == function) /*&& __traits(getProtection, func) == "export"*/)
677 //pragma(msg, "type: ", typeof(func));
678 //pragma(msg, "prot: ", __traits(getProtection, func));
679 import std.traits;
680 static struct RPCMarshalArgs { mixin BuildRPCArgs!func; }
681 RPCMarshalArgs mr;
682 alias defs = ParameterDefaults!func;
683 static assert(A.length <= defs.length, "too many arguments");
684 static if (A.length < defs.length) static assert(!is(defs[A.length] == void), "not enough default argument values");
685 foreach (immutable idx, ref arg; args) {
686 import std.conv : to;
687 mixin("mr.a"~to!string(idx)~" = arg;");
689 RPCCallHeader hdr;
690 static if (name.length > 0) {
691 hdr.name = prefix~name;
692 } else {
693 hdr.name = prefix~nodots(fullyQualifiedName!func.stringof);
695 hdr.hash = rpchash!func;
696 // call
697 chan.writeNum!ushort(RPCommand.Call);
698 chan.ncser(hdr);
699 chan.ncser(mr);
700 // result
701 auto replyCode = chan.readNum!ushort;
702 if (replyCode == RPCommand.Err) {
703 string msg;
704 chan.ncunser(msg);
705 throw new Exception("RPC ERROR: "~msg);
707 if (replyCode == RPCommand.RetRes) {
708 static if (!is(ReturnType!func == void)) {
709 // read result
710 ReturnType!func rval;
711 chan.ncunser(rval);
712 return rval;
713 } else {
714 chan.ncskip;
715 return;
717 } else if (replyCode == RPCommand.RetVoid) {
718 // got reply, wow
719 static if (!is(ReturnType!func == void)) {
720 return ReturnType!func.init;
721 } else {
722 return;
725 throw new Exception("invalid RPC reply");
729 // ////////////////////////////////////////////////////////////////////////// //
730 // client will use this
731 // it will send RPCommand.Call
732 // throws on fatal stream error
733 public static RT rpcallany(RT, ST, A...) (auto ref ST chan, const(char)[] name, A args) if (isRWStream!ST) {
734 import std.traits;
735 string BuildIt () {
736 string res;
737 foreach (immutable idx, const tp; A) {
738 import std.conv : to;
739 res ~= tp.stringof;
740 res ~= " a";
741 res ~= to!string(idx);
742 res ~= ";\n";
744 return res;
746 static struct RPCMarshalArgs { mixin(BuildIt); /*pragma(msg, BuildIt);*/ }
747 RPCMarshalArgs mr;
748 foreach (immutable idx, ref arg; args) {
749 import std.conv : to;
750 mixin("mr.a"~to!string(idx)~" = arg;");
752 RPCCallHeader hdr;
753 hdr.name = cast(string)name; // it is safe to cast it here
754 hdr.hash[] = 0;
755 // call
756 chan.writeNum!ushort(RPCommand.Call);
757 chan.ncser(hdr);
758 chan.ncser(mr);
759 // result
760 auto replyCode = chan.readNum!ushort;
761 if (replyCode == RPCommand.Err) {
762 string msg;
763 chan.ncunser(msg);
764 throw new Exception("RPC ERROR: "~msg);
766 if (replyCode == RPCommand.RetRes) {
767 static if (!is(RT == void)) {
768 // read result
769 RT rval;
770 chan.ncunser(rval);
771 return rval;
772 } else {
773 chan.ncskip;
774 return;
776 } else if (replyCode == RPCommand.RetVoid) {
777 // got reply, wow
778 static if (!is(RT == void)) {
779 return RT.init;
780 } else {
781 return;
784 throw new Exception("invalid RPC reply");
788 // ////////////////////////////////////////////////////////////////////////// //
789 // register RPC endpoint (server-side)
790 // if you'll specify only prefix, it will be added to func name
791 public static void rpcRegisterEndpoint(alias func) (const(char)[] prefix=null, const(char)[] name=null) if (is(typeof(func) == function)) {
792 import std.digest.sha;
793 RPCEndPoint ep;
794 if (name.length) {
795 ep.name = prefix.idup~name.idup;
796 } else {
797 ep.name = prefix.idup~nodots(fullyQualifiedName!func.stringof);
798 //{ import std.stdio; stderr.writeln("name: [", ep.name, "]"); }
800 ep.hash = rpchash!func;
801 ep.dg = delegate (VFile fi) {
802 // parse and call
803 static struct RPCMarshalArgs { mixin BuildRPCArgs!func; }
804 RPCMarshalArgs mr;
805 fi.ncunser(mr);
806 auto fo = wrapMemoryRW(null);
807 static if (is(ReturnType!func == void)) {
808 mixin(BuildCall!func~";");
809 } else {
810 mixin("fo.ncser("~BuildCall!func~");");
812 fo.seek(0);
813 return fo;
815 endpoints[ep.name] = ep;
819 // ////////////////////////////////////////////////////////////////////////// //
820 // server will use this; RPCommand.Call already read
821 // throws on unrecoverable stream error
822 public static bool rpcProcessCall(ST) (auto ref ST chan) if (isRWStream!ST) {
823 RPCCallHeader hdr;
824 chan.ncunser(hdr);
825 auto epp = hdr.name in endpoints;
826 if (epp is null) {
827 chan.ncskip;
828 chan.writeNum!ushort(RPCommand.Err);
829 chan.ncser("unknown function '"~hdr.name~"'");
830 return false;
832 foreach (ubyte b; hdr.hash) {
833 if (b != 0) {
834 if (epp.hash != hdr.hash) {
835 chan.ncskip;
836 chan.writeNum!ushort(RPCommand.Err);
837 chan.ncser("invalid signature for function '"~hdr.name~"'");
838 return false;
840 break;
843 auto rdf = chan.ncread;
844 VFile rf;
845 try {
846 rf = epp.dg(rdf);
847 } catch (Exception e) {
848 chan.writeNum!ushort(RPCommand.Err);
849 chan.ncser("EXCEPTION: "~e.msg);
850 return false;
852 if (rf.size > 0) {
853 chan.writeNum!ushort(RPCommand.RetRes);
854 ubyte[512] buf = void;
855 for (;;) {
856 auto rd = rf.rawRead(buf[]);
857 if (rd.length == 0) break;
858 chan.rawWriteExact(rd[]);
860 } else {
861 chan.writeNum!ushort(RPCommand.RetVoid);
863 return true;