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
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!
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);
56 int rd
= (count
> buf
.length ?
cast(int)buf
.length
: cast(int)count
);
57 st
.rawReadExact(buf
[0..rd
]);
64 ubyte len
= st
.readNum
!ubyte;
68 void skipType (ubyte tp
, int count
=1) {
70 case NCEntryType
.Bool
:
71 case NCEntryType
.Char
:
73 case NCEntryType
.Uint
:
74 case NCEntryType
.Float
:
75 skip(count
*(tp
&0x0f)); // size
77 case NCEntryType
.Struct
:
78 if ((tp
&0x0f) != 0) throw new Exception("invalid struct type");
80 skipStr(); // struct name
83 tp
= st
.readNum
!ubyte; // name length
84 if (tp
== NCEntryType
.End
) break;
86 tp
= st
.readNum
!ubyte; // data type
91 case NCEntryType
.Array
:
92 if ((tp
&0x0f) != 0) throw new Exception("invalid array type");
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;
101 //foreach (immutable _; 0..len) skipType(tp);
102 if (len
<= int.max
) {
105 foreach (immutable _
; 0..len
) skipType(tp
);
108 foreach (immutable _
; 0..len
) readDim(dcleft
-1);
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
) {
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
) {
139 void[] rawRead (void[] buf
) {
140 auto rd
= st
.rawRead(buf
);
141 if (rd
.length
) data
~= cast(const(ubyte)[])rd
;
145 wrapStream(CopyStream()).ncskip
;
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
) {
171 fl
.rawReadExact((&b
)[0..1]);
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
) () {
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
));
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
[]);
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
)) {
218 void writeMArray(AT
) (AT arr
) {
219 fl
.writeXInt(arr
.length
);
220 static if (isMultiDimArray
!AT
) {
221 foreach (const a2
; arr
) writeMArray(a2
);
223 // write POD arrays in one chunk
224 static if (isSimpleType
!UT
) {
225 fl
.rawWriteExact(arr
[]);
227 foreach (const ref it
; arr
) serData(it
);
232 } else static if (is(T
: V
[K
], K
, V
)) {
234 fl
.writeXInt(v
.length
);
235 foreach (const kv
; v
.byKeyValue
) {
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
);
255 static assert(0, "can't serialize type '"~T
.stringof
~"'");
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)`);
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)`);
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)`);
310 Unqual
!(typeof(arr
[0]))[] narr
;
313 static if (isMultiDimArray
!AT
) {
314 foreach (ref a2
; narr
) readMArray(a2
);
316 alias ET
= arrayElementType
!AT
;
317 // read byte arrays in one chunk
318 static if (isSimpleType
!ET
) {
319 fl
.rawReadExact(narr
[]);
321 foreach (ref it
; narr
) unserData(it
);
324 static if (!__traits(isStaticArray
, AT
)) arr
= cast(AT
)narr
;
327 } else static if (is(T
: V
[K
], K
, V
)) {
330 foreach (immutable _
; 0..fl
.readXInt
!usize
) {
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
);
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
));
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)");
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;
397 enum isMultiDimArray
= false;
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
;
416 enum dimensionCount
= 1;
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
));
433 alias arrayElementType
= Unqual
!T
;
436 static assert(is(arrayElementType
!string
== char));
439 // ////////////////////////////////////////////////////////////////////////// //
440 version(ncserial_test
) unittest {
443 // ////////////////////////////////////////////////////////////////////////// //
444 static struct AssemblyInfo
{
447 @NCIgnore uint ignoreme
;
450 static struct ReplyAsmInfo
{
451 @NCName("command") @NCName("xcommand") ubyte cmd
;
452 @NCName("values") AssemblyInfo
[][2] list
;
459 // ////////////////////////////////////////////////////////////////////////// //
463 ri
.list
[0] ~= AssemblyInfo(666, "hell");
464 ri
.list
[1] ~= AssemblyInfo(69, "fuck");
466 ri
.dict
["boo"] = 666;
470 auto fl
= VFile("z00.bin", "w");
475 auto fl
= VFile("z00.bin");
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");
497 ri
.list
[0] ~= AssemblyInfo(666, "hell");
498 ri
.list
[1] ~= AssemblyInfo(69, "fuck");
500 ri
.dict
["boo"] = 666;
503 auto mem
= wrapMemoryRW(null);
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");
526 assert(mem
.tell
== mem
.size
);
528 auto m2
= mem
.ncread
;
529 assert(mem
.tell
== mem
.size
);
530 assert(m2
.tell
== mem
.size
);
535 // ////////////////////////////////////////////////////////////////////////// //
537 private import std
.traits
;
541 public struct NCRPCEPName
{ string name
; } // RPC endpoint name, can be used instead of NCRPCEP
543 public enum RPCommand
: ushort {
551 // ////////////////////////////////////////////////////////////////////////// //
552 private alias Id(alias T
) = T
;
554 private struct RPCEndPoint
{
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
;
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
;
603 void put (const(void)[] buf
) {
604 sha
.put(cast(const(ubyte)[])buf
);
608 //put(nodots(fullyQualifiedName!func.stringof));
609 put(ReturnType
!func
.stringof
);
611 foreach (immutable par
; Parameters
!func
) {
619 // ////////////////////////////////////////////////////////////////////////// //
620 private string
BuildCall(alias func
) () {
621 string res
= "func(";
622 foreach (immutable idx
, immutable par
; Parameters
!func
) {
623 import std
.conv
: to
;
625 res
~= idx
.to
!string
;
632 // ////////////////////////////////////////////////////////////////////////// //
633 private static mixin template BuildRPCArgs (alias func
) {
634 private import std
.traits
;
635 private static string
buildIt(alias func
) () {
637 alias defs
= ParameterDefaults
!func
;
638 foreach (immutable idx
, immutable par
; Parameters
!func
) {
639 import std
.conv
: to
;
642 res
~= to
!string(idx
);
643 static if (!is(defs
[idx
] == void)) res
~= " = "~defs
[idx
].stringof
;
652 // ////////////////////////////////////////////////////////////////////////// //
653 public struct RPCCallHeader
{
659 // ////////////////////////////////////////////////////////////////////////// //
660 private void fcopy (VFile to
, VFile from
) {
661 ubyte[64] buf
= void;
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));
680 static struct RPCMarshalArgs
{ mixin BuildRPCArgs
!func
; }
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;");
690 static if (name
.length
> 0) {
691 hdr
.name
= prefix
~name
;
693 hdr
.name
= prefix
~nodots(fullyQualifiedName
!func
.stringof
);
695 hdr
.hash
= rpchash
!func
;
697 chan
.writeNum
!ushort(RPCommand
.Call
);
701 auto replyCode
= chan
.readNum
!ushort;
702 if (replyCode
== RPCommand
.Err
) {
705 throw new Exception("RPC ERROR: "~msg
);
707 if (replyCode
== RPCommand
.RetRes
) {
708 static if (!is(ReturnType
!func
== void)) {
710 ReturnType
!func rval
;
717 } else if (replyCode
== RPCommand
.RetVoid
) {
719 static if (!is(ReturnType
!func
== void)) {
720 return ReturnType
!func
.init
;
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
) {
737 foreach (immutable idx
, const tp
; A
) {
738 import std
.conv
: to
;
741 res
~= to
!string(idx
);
746 static struct RPCMarshalArgs
{ mixin(BuildIt
); /*pragma(msg, BuildIt);*/ }
748 foreach (immutable idx
, ref arg
; args
) {
749 import std
.conv
: to
;
750 mixin("mr.a"~to
!string(idx
)~" = arg;");
753 hdr
.name
= cast(string
)name
; // it is safe to cast it here
756 chan
.writeNum
!ushort(RPCommand
.Call
);
760 auto replyCode
= chan
.readNum
!ushort;
761 if (replyCode
== RPCommand
.Err
) {
764 throw new Exception("RPC ERROR: "~msg
);
766 if (replyCode
== RPCommand
.RetRes
) {
767 static if (!is(RT
== void)) {
776 } else if (replyCode
== RPCommand
.RetVoid
) {
778 static if (!is(RT
== void)) {
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
;
795 ep
.name
= prefix
.idup
~name
.idup
;
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
) {
803 static struct RPCMarshalArgs
{ mixin BuildRPCArgs
!func
; }
806 auto fo
= wrapMemoryRW(null);
807 static if (is(ReturnType
!func
== void)) {
808 mixin(BuildCall
!func
~";");
810 mixin("fo.ncser("~BuildCall
!func
~");");
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
) {
825 auto epp
= hdr
.name
in endpoints
;
828 chan
.writeNum
!ushort(RPCommand
.Err
);
829 chan
.ncser("unknown function '"~hdr
.name
~"'");
832 foreach (ubyte b
; hdr
.hash
) {
834 if (epp
.hash
!= hdr
.hash
) {
836 chan
.writeNum
!ushort(RPCommand
.Err
);
837 chan
.ncser("invalid signature for function '"~hdr
.name
~"'");
843 auto rdf
= chan
.ncread
;
847 } catch (Exception e
) {
848 chan
.writeNum
!ushort(RPCommand
.Err
);
849 chan
.ncser("EXCEPTION: "~e
.msg
);
853 chan
.writeNum
!ushort(RPCommand
.RetRes
);
854 ubyte[512] buf
= void;
856 auto rd
= rf
.rawRead(buf
[]);
857 if (rd
.length
== 0) break;
858 chan
.rawWriteExact(rd
[]);
861 chan
.writeNum
!ushort(RPCommand
.RetVoid
);