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 and RPC system
17 // WARNING! do not use for disk and other sensitive serialization,
18 // as format may change without notice! at least version it!
19 module iv
.ncrpc
/*is aliced*/;
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
;
539 public enum RPCommand
: ushort {
547 // ////////////////////////////////////////////////////////////////////////// //
548 private alias Id(alias T
) = T
;
550 private struct RPCEndPoint
{
554 VFile
delegate (VFile fi
) dg
; // read args, do call, write result; throws on error; returns serialized res
558 private RPCEndPoint
[string
] endpoints
;
561 // ////////////////////////////////////////////////////////////////////////// //
562 private string
nodots (string s
) {
563 if (s
.length
> 2 && s
[0] == '"' && s
[$-1] == '"') s
= s
[1..$-1];
564 usize pos
= s
.length
;
565 while (pos
> 0 && s
[pos
-1] != '.') --pos
;
570 // ////////////////////////////////////////////////////////////////////////// //
571 public string
[] rpcEndpointNames () { return endpoints
.keys
; }
574 // ////////////////////////////////////////////////////////////////////////// //
575 public static ubyte[32] rpchash(alias func
) () if (isCallable
!func
) {
576 import std
.digest
.sha
;
579 void put (const(void)[] buf
) {
580 sha
.put(cast(const(ubyte)[])buf
);
584 //put(nodots(fullyQualifiedName!func.stringof));
585 put(ReturnType
!func
.stringof
);
587 foreach (immutable par
; Parameters
!func
) {
595 // ////////////////////////////////////////////////////////////////////////// //
596 private string
BuildCall(alias func
) () {
597 string res
= "func(";
598 foreach (immutable idx
, immutable par
; Parameters
!func
) {
599 import std
.conv
: to
;
601 res
~= idx
.to
!string
;
608 // ////////////////////////////////////////////////////////////////////////// //
609 private static mixin template BuildRPCArgs (alias func
) {
610 private import std
.traits
;
611 private static string
buildIt(alias func
) () {
613 alias defs
= ParameterDefaults
!func
;
614 foreach (immutable idx
, immutable par
; Parameters
!func
) {
615 import std
.conv
: to
;
618 res
~= to
!string(idx
);
619 static if (!is(defs
[idx
] == void)) res
~= " = "~defs
[idx
].stringof
;
628 // ////////////////////////////////////////////////////////////////////////// //
629 public struct RPCCallHeader
{
635 // ////////////////////////////////////////////////////////////////////////// //
636 private void fcopy (VFile to
, VFile from
) {
637 ubyte[64] buf
= void;
639 auto rd
= from
.rawRead(buf
[]);
640 if (rd
.length
== 0) break;
641 to
.rawWriteExact(rd
[]);
646 // ////////////////////////////////////////////////////////////////////////// //
647 // client will use this
648 // it will send RPCommand.Call
649 // throws on fatal stream error
650 public static auto rpcall(alias func
, string prefix
=null, string name
=null, ST
, A
...) (auto ref ST chan
, A args
)
651 if (isRWStream
!ST
&& (is(typeof(func
) == function) ||
is(typeof(func
) == delegate)))
653 //pragma(msg, "type: ", typeof(func));
654 //pragma(msg, "prot: ", __traits(getProtection, func));
656 static struct RPCMarshalArgs
{ mixin BuildRPCArgs
!func
; }
658 alias defs
= ParameterDefaults
!func
;
659 static assert(A
.length
<= defs
.length
, "too many arguments");
660 static if (A
.length
< defs
.length
) static assert(!is(defs
[A
.length
] == void), "not enough default argument values");
661 foreach (immutable idx
, ref arg
; args
) {
662 import std
.conv
: to
;
663 mixin("mr.a"~to
!string(idx
)~" = arg;");
666 static if (name
.length
> 0) {
667 hdr
.name
= prefix
~name
;
669 hdr
.name
= prefix
~nodots(fullyQualifiedName
!func
.stringof
);
671 hdr
.hash
= rpchash
!func
;
673 chan
.writeNum
!ushort(RPCommand
.Call
);
677 auto replyCode
= chan
.readNum
!ushort;
678 if (replyCode
== RPCommand
.Err
) {
681 throw new Exception("RPC ERROR: "~msg
);
683 if (replyCode
== RPCommand
.RetRes
) {
684 static if (!is(ReturnType
!func
== void)) {
686 ReturnType
!func rval
;
693 } else if (replyCode
== RPCommand
.RetVoid
) {
695 static if (!is(ReturnType
!func
== void)) {
696 return ReturnType
!func
.init
;
701 throw new Exception("invalid RPC reply");
705 // ////////////////////////////////////////////////////////////////////////// //
706 // client will use this
707 // it will send RPCommand.Call
708 // throws on fatal stream error
709 public static RT
rpcallany(RT
, ST
, A
...) (auto ref ST chan
, const(char)[] name
, A args
) if (isRWStream
!ST
) {
713 foreach (immutable idx
, const tp
; A
) {
714 import std
.conv
: to
;
717 res
~= to
!string(idx
);
722 static struct RPCMarshalArgs
{ mixin(BuildIt
); /*pragma(msg, BuildIt);*/ }
724 foreach (immutable idx
, ref arg
; args
) {
725 import std
.conv
: to
;
726 mixin("mr.a"~to
!string(idx
)~" = arg;");
729 hdr
.name
= cast(string
)name
; // it is safe to cast it here
732 chan
.writeNum
!ushort(RPCommand
.Call
);
736 auto replyCode
= chan
.readNum
!ushort;
737 if (replyCode
== RPCommand
.Err
) {
740 throw new Exception("RPC ERROR: "~msg
);
742 if (replyCode
== RPCommand
.RetRes
) {
743 static if (!is(RT
== void)) {
752 } else if (replyCode
== RPCommand
.RetVoid
) {
754 static if (!is(RT
== void)) {
760 throw new Exception("invalid RPC reply");
764 // ////////////////////////////////////////////////////////////////////////// //
765 // register RPC endpoint (server-side)
766 // if you'll specify only prefix, it will be added to func name
767 public static void rpcRegisterEndpoint(alias Dg
) (typeof(Dg
) func
, const(char)[] prefix
=null, const(char)[] name
=null)
768 if (is(typeof(Dg
) == function) ||
is(typeof(Dg
) == delegate))
770 import std
.traits
: FieldNameTuple
, getUDAs
, hasUDA
;
771 import std
.digest
.sha
;
775 ep
.name
= prefix
.idup
~name
.idup
;
777 ep
.name
= prefix
.idup
~nodots(fullyQualifiedName
!Dg
.stringof
);
778 //{ import std.stdio; stderr.writeln("name: [", ep.name, "]"); }
780 ep
.hash
= rpchash
!Dg
;
781 ep
.dg
= delegate (VFile fi
) {
783 static struct RPCMarshalArgs
{ mixin BuildRPCArgs
!Dg
; }
786 auto fo
= wrapMemoryRW(null);
787 static if (is(ReturnType
!Dg
== void)) {
788 mixin(BuildCall
!Dg
~";");
790 mixin("fo.ncser("~BuildCall
!Dg
~");");
795 endpoints
[ep
.name
] = ep
;
799 // ////////////////////////////////////////////////////////////////////////// //
800 // server will use this; RPCommand.Call already read
801 // throws on unrecoverable stream error
802 public static bool rpcProcessCall(ST
) (auto ref ST chan
) if (isRWStream
!ST
) {
805 auto epp
= hdr
.name
in endpoints
;
808 chan
.writeNum
!ushort(RPCommand
.Err
);
809 chan
.ncser("unknown function '"~hdr
.name
~"'");
812 foreach (ubyte b
; hdr
.hash
) {
814 if (epp
.hash
!= hdr
.hash
) {
816 chan
.writeNum
!ushort(RPCommand
.Err
);
817 chan
.ncser("invalid signature for function '"~hdr
.name
~"'");
823 auto rdf
= chan
.ncread
;
827 } catch (Exception e
) {
828 chan
.writeNum
!ushort(RPCommand
.Err
);
829 chan
.ncser("EXCEPTION: "~e
.msg
);
833 chan
.writeNum
!ushort(RPCommand
.RetRes
);
834 ubyte[512] buf
= void;
836 auto rd
= rf
.rawRead(buf
[]);
837 if (rd
.length
== 0) break;
838 chan
.rawWriteExact(rd
[]);
841 chan
.writeNum
!ushort(RPCommand
.RetVoid
);
847 // ////////////////////////////////////////////////////////////////////////// //
848 /** unix domain socket without inode
853 * sk.create("/k8/rpc-test");
854 * auto cl = sk.accept();
862 * sk.connect("/k8/rpc-test");
865 public struct UDSocket
{
867 static struct UDSData
{
874 @disable this (this);
880 void decRef () nothrow @nogc {
882 auto uds
= cast(UDSData
*)udsp
;
884 import core
.stdc
.stdlib
: free
;
885 import core
.sys
.posix
.unistd
: close
;
886 if (!uds
.dontclose
) close(uds
.fd
);
893 this (this) nothrow @nogc { pragma(inline
, true); if (udsp
) ++(cast(UDSData
*)udsp
).rc
; } ///
894 ~this () nothrow @nogc { pragma(inline
, true); if (udsp
) close(); } ///
897 void opAssign (UDSocket sk
) {
898 pragma(inline
, true);
899 if (sk
.udsp
) ++(cast(UDSData
*)sk
.udsp
).rc
;
904 @property bool isOpen () const nothrow @trusted @nogc { pragma(inline
, true); return (udsp
!= 0); } ///
905 @property int fd () const nothrow @trusted @nogc { pragma(inline
, true); return (udsp
!= 0 ?
(cast(UDSData
*)udsp
).fd
: -1); } ///
907 void close () nothrow @nogc { pragma(inline
, true); if (udsp
) decRef(); } ///
908 void create (const(char)[] name
) { doCC
!"server"(name
); } ///
909 void connect (const(char)[] name
) { doCC
!"client"(name
); } ///
911 @property uint bytesSent () const nothrow @trusted @nogc { pragma(inline
, true); return (udsp
!= 0 ?
(cast(UDSData
*)udsp
).bytesSent
: 0); } ///
912 @property uint bytesReceived () const nothrow @trusted @nogc { pragma(inline
, true); return (udsp
!= 0 ?
(cast(UDSData
*)udsp
).bytesReceived
: 0); } ///
914 @property void resetBytesSent () nothrow @trusted @nogc { pragma(inline
, true); if (udsp
!= 0) (cast(UDSData
*)udsp
).bytesSent
= 0; } ///
915 @property void resetBytesReceived () nothrow @trusted @nogc { pragma(inline
, true); if (udsp
!= 0) (cast(UDSData
*)udsp
).bytesReceived
= 0; } ///
919 if (!udsp
) throw new Exception("can't listen on closed socket");
920 auto uds
= cast(UDSData
*)udsp
;
921 if (!uds
.didlisten
) {
922 import core
.sys
.posix
.sys
.socket
: listen
;
923 if (listen(uds
.fd
, 1) != 0) throw new Exception("listen failed");
924 uds
.didlisten
= true;
931 auto uds
= cast(UDSData
*)udsp
;
932 assert(uds
.didlisten
);
933 import core
.sys
.posix
.sys
.socket
: accept
;
934 int cfd
= accept(uds
.fd
, null, null);
935 if (cfd
== -1) throw new Exception("accept failed");
943 if (!udsp
) throw new Exception("can't detach closed socket");
944 auto uds
= cast(UDSData
*)udsp
;
946 uds
.dontclose
= true;
952 void[] rawRead (void[] buf
) {
953 import core
.sys
.posix
.sys
.socket
: recv
;
954 if (!udsp
) throw new Exception("can't read from closed socket");
955 auto uds
= cast(UDSData
*)udsp
;
956 if (buf
.length
== 0) return buf
[];
957 auto rd
= recv(uds
.fd
, buf
.ptr
, buf
.length
, 0);
958 if (rd
< 0) throw new Exception("socket read error");
959 uds
.bytesReceived
+= rd
;
964 void rawWrite (const(void)[] buf
) {
965 import core
.sys
.posix
.sys
.socket
: send
, MSG_NOSIGNAL
;
966 if (!udsp
) throw new Exception("can't write to closed socket");
967 auto uds
= cast(UDSData
*)udsp
;
968 auto dp
= cast(const(ubyte)*)buf
.ptr
;
969 auto left
= buf
.length
;
971 auto wr
= send(uds
.fd
, dp
, left
, 0);
972 if (wr
<= 0) throw new Exception("socket write error");
980 void assignFD (int fd
) {
981 import core
.stdc
.stdlib
: malloc
;
982 import core
.stdc
.string
: memset
;
985 auto uds
= cast(UDSData
*)malloc(UDSData
.sizeof
);
987 import core
.sys
.posix
.unistd
: close
;
989 throw new Exception("out of memory"); // let's hope that we can do it
991 memset(uds
, 0, (*uds
).sizeof
);
994 udsp
= cast(usize
)uds
;
998 void doCC(string mode
) (const(char)[] name
) {
999 static assert(mode
== "client" || mode
== "server", "invalid mode");
1000 import core
.stdc
.stdlib
: malloc
;
1001 import core
.stdc
.string
: memset
;
1003 int fd
= makeUADS
!mode(name
);
1004 auto uds
= cast(UDSData
*)malloc(UDSData
.sizeof
);
1006 import core
.sys
.posix
.unistd
: close
;
1008 throw new Exception("out of memory"); // let's hope that we can do it
1010 memset(uds
, 0, (*uds
).sizeof
);
1013 udsp
= cast(usize
)uds
;
1016 static int makeUADS(string mode
) (const(char)[] name
) {
1017 static assert(mode
== "client" || mode
== "server", "invalid mode");
1018 import core
.stdc
.string
: memset
;
1019 import core
.sys
.posix
.sys
.socket
;
1020 import core
.sys
.posix
.sys
.un
: sockaddr_un
;
1021 import core
.sys
.posix
.unistd
: close
;
1022 // max name length is 108, so be safe here
1023 if (name
.length
== 0 || name
.length
> 100) throw new Exception("invalid name");
1024 //{ import core.stdc.stdio; printf("[%.*s]\n", cast(uint)name.length, name.ptr); }
1025 sockaddr_un sun
= void;
1026 memset(&sun
, 0, sun
.sizeof
);
1027 sun
.sun_family
= AF_UNIX
;
1028 // create domain socket without FS inode (first byte of name buffer should be zero)
1029 sun
.sun_path
[1..1+name
.length
] = cast(byte[])name
[];
1030 int fd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
1031 if (fd
< 0) throw new Exception("can't create unix domain socket");
1032 static if (mode
== "server") {
1033 import core
.sys
.posix
.sys
.socket
: bind
;
1034 if (bind(fd
, cast(sockaddr
*)&sun
, sun
.sizeof
) != 0) { close(fd
); throw new Exception("can't bind unix domain socket"); }
1036 import core
.sys
.posix
.sys
.socket
: connect
;
1037 if (connect(fd
, cast(sockaddr
*)&sun
, sun
.sizeof
) != 0) {
1038 import core
.stdc
.errno
;
1041 //{ import std.stdio; writeln("ERRNO: ", err); }
1042 throw new Exception("can't connect to unix domain socket");
1045 //{ import core.stdc.stdio; printf("fd=%d\n", fd); }