fixes to network and client ids; it should work with more than two peers now ;-)
[wootedit.git] / wootnet.d
blob1733fbbe07dc18edabaf3407dfa82da109f93deb
1 // WOOT network protocol
2 //
3 // coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
4 // Understanding is not required. Only obedience.
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 // THE SOFTWARE.
24 // WARNING! crypto here is TOTALLY INVALID! don't do it like that!
25 module wootnet;
27 //version = woot_net_chatty;
30 // ////////////////////////////////////////////////////////////////////////// //
31 import core.time;
33 import std.conv : to;
34 import std.digest.sha;
35 import std.socket;
37 import iv.chachasimple;
38 import iv.cmdcon;
39 import iv.prng.seeder;
40 import iv.prng.pcg32;
41 import iv.unarray;
43 import wootext;
46 // ////////////////////////////////////////////////////////////////////////// //
47 version(Windows) {
48 import core.sys.windows.winsock2;
49 enum MSG_NOSIGNAL = 0;
50 } else {
51 import core.sys.posix.netinet.in_;
52 import core.sys.posix.sys.select;
53 import core.sys.posix.sys.socket;
57 // ////////////////////////////////////////////////////////////////////////// //
58 public string toString() (in auto ref sockaddr_in it) {
59 import std.format : format;
60 return
61 "%u.%u.%u.%u:%u".format(
62 it.sin_addr.s_addr&0xff, (it.sin_addr.s_addr>>8)&0xff, (it.sin_addr.s_addr>>16)&0xff, (it.sin_addr.s_addr>>24)&0xff,
63 ntohs(it.sin_port));
67 // ////////////////////////////////////////////////////////////////////////// //
68 align(1) struct WootNetOp {
69 align(1):
70 public:
71 string toString () const { return op.toString; }
73 pure nothrow @safe @nogc:
74 public:
75 ubyte[ChaCha20.IVSize] nonce;
76 WStrOp op;
78 WStrOp.OpType opType = WStrOp.OpType.None;
79 UUID id; // the id assigned at creation-time that this character keeps forever
80 uint opid;
81 dchar ch; // the user-visible character that this WChar represents; uint.max means "invisible"
82 // as per the algorithm outlines in the document, each character specifies which two characters it belongs between
83 // these are the ids of the chars that must go somewhere before and somewhere after this character respectively
84 UUID prev, next;
85 uint popid, nopid;
90 // ////////////////////////////////////////////////////////////////////////// //
91 // we will use UUIDs to identify clients in the network,
94 * WOOT network packet:
95 * uint pktseq;
96 * 4 bytes: client id (self for send, remote for recv)
97 * ubyte operation (see PktType)
98 * hello:
99 * insert:
100 * 4 bytes: dchar
101 * 8 bytes: char id (UUID, opid)
102 * 8 bytes: prev id (UUID, opid)
103 * 8 bytes: next id (UUID, opid)
104 * delete:
105 * 8 bytes: char id (UUID, opid)
106 * ping:
107 * uint pingid
108 * pong:
109 * uint pingid
110 * ack:
111 * pktseq is used as acked packet id
112 * peer:
113 * uint ip
114 * ushort port
117 class WootNet {
118 private:
119 enum MTU = 1472;
121 private:
122 enum PktType : ubyte {
123 Hello,
124 Insert,
125 Delete,
126 Ping,
127 Ack,
128 Peer,
131 private:
132 static struct Packet {
133 uint seq;
134 bool ack;
135 ubyte[] data;
137 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (seq != 0 && data.length >= ChaCha20.IVSize); }
139 void clear () { delete data; seq = 0; ack = false; }
141 void setup (uint aseqid, bool aack=false) {
142 seq = aseqid;
143 ack = aack;
144 data.unsafeArraySetLength(ChaCha20.IVSize);
145 put!uint(aseqid);
148 void put(T) (T v) if (__traits(isIntegral, T)) {
149 auto dp = cast(const(ubyte)*)&v;
150 foreach (ubyte b; dp[0..T.sizeof]) data.unsafeArrayAppend(b);
154 void put(T:UUID) (in auto ref T v) {
155 foreach (ubyte b; v.data[]) data.unsafeArrayAppend(b);
160 static struct Peer {
161 uint cid; // client id [1..]
162 Packet[] queue; // send queue
163 sockaddr_in addr;
164 bool netvalid;
165 MonoTime lastActivity;
166 bool pingSent;
168 void markDead () {
169 foreach (ref pkt; queue) pkt.clear();
170 delete queue;
171 netvalid = false;
175 ubyte[ChaCha20.KeySize] mKey;
176 PCG32 prng;
177 int sk = -1;
178 uint pktseq;
179 Peer[] mPeers;
180 uint[uint] cid2peerMap;
181 public uint mMyCId;
182 public sockaddr_in myaddr;
185 static struct HelloQueue {
186 sockaddr_in addr;
187 Packet pkt;
189 HelloQueue[] mHelloQ;
191 int xsend = -1; // <0: hellos; >=0: peers
193 private:
194 Packet buildHello (ref HelloQueue hq, out sockaddr_in aout) {
195 if (sk < 0) return Packet();
196 if (hq.addr.sin_family != AF_INET) return Packet();
197 if (hq.addr.sin_port == 0) return Packet();
198 if (hq.addr.sin_addr.s_addr == 0 || hq.addr.sin_addr.s_addr == uint.max) return Packet();
199 // send hello packet
200 if (!hq.pkt.valid) {
201 initPacket(hq.pkt);
202 // generate packet
203 hq.pkt.put(mMyCId);
204 hq.pkt.put(cast(ubyte)PktType.Hello);
205 //hq.pkt.put(mPort);
206 encodePacket(hq.pkt);
208 //return (sendto(sk, hq.pkt.data.ptr, hq.pkt.data.length, MSG_NOSIGNAL, cast(sockaddr*)&hq.addr, hq.addr.sizeof) == hq.pkt.data.length);
209 aout = hq.addr;
210 return hq.pkt;
213 void gotPacketFrom (in ref sockaddr_in addr) {
214 foreach (immutable idx, const ref hq; mHelloQ) {
215 if (hq.addr.sin_addr.s_addr == addr.sin_addr.s_addr && hq.addr.sin_port == addr.sin_port) {
216 mHelloQ.unsafeArrayRemove(cast(int)idx);
217 return;
222 void advanceXSend () {
223 if (xsend < 0) {
224 --xsend;
225 if (-xsend <= mHelloQ.length) return;
226 xsend = 0;
227 } else {
228 ++xsend;
230 for (;;) {
231 if (xsend >= mPeers.length) break;
232 if (mPeers[xsend].netvalid && mPeers[xsend].queue.length) return;
233 ++xsend;
235 xsend = -1;
238 private:
239 void encodePacket (ref Packet pkt) {
240 if (!pkt.valid) assert(0, "internal error");
242 // generate nonce
243 foreach (ref ubyte b; pkt.data[0..ChaCha20.IVSize]) {
244 b = cast(ubyte)prng.front;
245 prng.popFront();
248 // encrypt packet
249 auto cyph = ChaCha20(mKey, pkt.data[0..ChaCha20.IVSize]);
250 cyph.processBuf(pkt.data[ChaCha20.IVSize..$]);
253 void initPacket (ref Packet pkt) {
254 if (pktseq == 0) ++pktseq;
255 pkt.setup(pktseq++);
258 Packet encodePacketOp (in ref WootNetOp op) {
259 Packet pkt;
261 if (op.op.opType == WStrOp.OpType.None || op.op.opType < WStrOp.OpType.min || op.op.opType > WStrOp.OpType.max) return pkt;
263 initPacket(pkt);
265 // generate packet
266 pkt.put(mMyCId);
268 final switch (op.op.opType) {
269 case WStrOp.OpType.Insert:
270 pkt.put(cast(ubyte)PktType.Insert);
271 pkt.put(cast(uint)op.op.ch.ch);
272 pkt.put(op.op.ch.id.uid);
273 pkt.put(op.op.ch.prev.uid);
274 pkt.put(op.op.ch.next.uid);
275 break;
276 case WStrOp.OpType.Delete:
277 pkt.put(cast(ubyte)PktType.Delete);
278 pkt.put(op.op.ch.id.uid);
279 break;
280 case WStrOp.OpType.None: assert(0);
283 encodePacket(pkt);
284 return pkt;
287 Packet encodePacketPeer() (in auto ref Peer peer) {
288 Packet pkt;
289 if (!peer.netvalid) return pkt;
291 initPacket(pkt);
293 // generate packet
294 pkt.put(mMyCId);
295 pkt.put(cast(ubyte)PktType.Peer);
296 pkt.put(peer.addr.sin_addr.s_addr);
297 pkt.put(peer.addr.sin_port);
299 //conprintfln("sending peer %s %s", peer.uuid, peer.addr.sin_port);
300 encodePacket(pkt);
301 return pkt;
304 Packet encodePacketAck() (uint seqn) {
305 Packet pkt;
306 if (seqn == 0) return pkt;
308 pkt.setup(seqn, true);
310 // generate packet
311 pkt.put(mMyCId);
312 pkt.put(cast(ubyte)PktType.Ack);
314 encodePacket(pkt);
315 return pkt;
318 Packet encodePacketPing () {
319 Packet pkt;
321 initPacket(pkt);
323 // generate packet
324 pkt.put(mMyCId);
325 pkt.put(cast(ubyte)PktType.Ping);
327 encodePacket(pkt);
328 return pkt;
331 void peerAck (ref Peer peer, uint seqn) {
332 if (!peer.netvalid || seqn == 0 || !peer.netvalid) return;
333 // check if we already have this ack
334 foreach (immutable idx, ref pkt; peer.queue) {
335 if (!pkt.ack) {
336 // insert ack
337 auto pk = encodePacketAck(seqn);
338 if (pk.valid) peer.queue.unsafeArrayInsertBefore(cast(int)idx, pk);
339 return;
341 if (pkt.seq == seqn) return;
343 auto pk = encodePacketAck(seqn);
344 if (pk.valid) peer.queue.unsafeArrayInsertBefore(0, pk);
347 private:
348 Peer* newPeer() (uint peercid) {
349 if (peercid == 0) return null;
350 if (peercid == mMyCId) return null;
351 if (auto pip = peercid in cid2peerMap) return &mPeers[*pip];
352 mPeers.unsafeArrayAppend(Peer(peercid));
353 cid2peerMap[peercid] = cast(uint)(mPeers.length-1);
354 mPeers[$-1].lastActivity = MonoTime.currTime;
355 return &mPeers[$-1];
358 Peer* findPeer() (uint peercid) {
359 if (auto pip = peercid in cid2peerMap) return &mPeers[*pip];
360 return null;
363 Peer* findPeerByAddr() (in auto ref sockaddr_in addr) {
364 foreach (immutable idx, ref peer; mPeers) {
365 if (!peer.netvalid) continue;
366 if (peer.addr.sin_port != addr.sin_port) continue;
367 if (peer.addr.sin_addr.s_addr != addr.sin_addr.s_addr) continue;
368 return &mPeers[idx];
370 return null;
373 public:
374 this (const(char)[] akey, ushort aport) {
375 import core.stdc.string : memset;
376 memset(&myaddr, 0, myaddr.sizeof);
378 // init crypto key
379 mKey = sha256Of(akey);
380 prng.seed(getUlongSeed, getUlongSeed);
382 // add initial peer
383 foreach (ref ubyte b; (cast(ubyte*)&mMyCId)[0..mMyCId.sizeof]) {
384 b = cast(ubyte)prng.front;
385 prng.popFront();
388 // prepare socket
389 sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
390 if (sk < -1) throw new Exception("cannot create socket");
391 myaddr.sin_family = AF_INET;
392 myaddr.sin_port = htons(aport);
393 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
394 if (bind(sk, cast(sockaddr*)&myaddr, myaddr.sizeof) == -1) {
395 closeSocket();
396 throw new Exception("cannot bind socket");
400 ~this () { closeSocket(); }
402 final private void closeSocket () nothrow @trusted @nogc {
403 import core.stdc.string : memset;
404 if (sk >= 0) {
405 version(Windows) {
406 } else {
407 import core.sys.posix.unistd : close;
408 close(sk);
410 sk = -1;
412 memset(&myaddr, 0, myaddr.sizeof);
415 void addPeer (const(char)[] host, const(char)[] port) {
416 import std.socket, std.conv : to;
417 auto nport = port.to!ushort;
418 auto addr = new InternetAddress(host, nport);
419 scope(exit) delete addr;
420 addPeer(cast(sockaddr_in)*addr.name);
423 void addPeer() (in auto ref sockaddr_in sin) {
424 if (sk < 0) return;
425 if (sin.sin_port == 0 || sin.sin_addr.s_addr == 0 || sin.sin_addr.s_addr == uint.max) return;
426 foreach (const ref hq; mHelloQ) {
427 if (hq.addr.sin_addr.s_addr == sin.sin_addr.s_addr && hq.addr.sin_port == sin.sin_port) return; // known peer
429 // new peer
430 HelloQueue hq;
431 hq.addr = sin;
432 hq.addr.sin_family = AF_INET;
433 mHelloQ.unsafeArrayAppend(hq);
436 private final Packet createOpPacket() (in auto ref WStrOp op) {
437 if (op.empty) return Packet();
438 WootNetOp netop;
439 netop.op = op;
440 return encodePacketOp(netop);
443 final void queue (const(WStrOp)[] ops...) {
444 if (mPeers.length == 0) return;
445 foreach (const ref op; ops[]) {
446 if (op.empty) continue;
447 auto pk = createOpPacket(op);
448 if (!pk.valid) continue;
449 // don't send to ourself
450 foreach (ref peer; mPeers[]) {
451 if (!peer.netvalid) continue;
452 peer.queue.unsafeArrayAppend(pk);
457 final void processSends () {
458 import core.stdc.string : memset;
459 if (sk < 0) return;
460 fd_set wrset;
461 timeval to;
462 foreach (immutable _; 0..mHelloQ.length+mPeers.length) {
463 // check if we can send something
464 memset(&to, 0, to.sizeof);
465 FD_ZERO(&wrset);
466 FD_SET(sk, &wrset);
467 if (select(sk+1, null, &wrset, null, &to) < 1) return;
468 if (!FD_ISSET(sk, &wrset)) return;
469 //conwriteln("can send! xsend=", xsend);
470 // build packet data
471 ubyte[MTU] pkt = 0;
472 int pktpos = 0;
473 sockaddr_in aout;
474 advanceXSend();
475 // send something
476 if (xsend < 0) {
477 if (-xsend <= mHelloQ.length) {
478 auto pk = buildHello(mHelloQ[-xsend-1], aout);
479 if (pk.valid) {
480 pkt[pktpos..pktpos+pk.data.length] = pk.data[];
481 pktpos += cast(int)pk.data.length;
484 } else if (xsend >= 0 && xsend < mPeers.length && mPeers[xsend].queue.length && mPeers[xsend].netvalid) {
485 auto peer = &mPeers[xsend];
486 version(woot_net_chatty) conwriteln("sending packet to peer ", peer.cid);
487 aout = peer.addr;
488 int cnum = 0;
489 while (cnum < peer.queue.length) {
490 if (pkt.length-pktpos <= peer.queue[cnum].data.length) break;
491 pkt[pktpos..pktpos+peer.queue[cnum].data.length] = peer.queue[cnum].data;
492 pktpos += cast(int)peer.queue[cnum].data.length;
493 // remove ack packets (peers will resend non-acked packets anyway)
494 if (peer.queue[cnum].ack) {
495 peer.queue[cnum].clear();
496 peer.queue.unsafeArrayRemove(cnum);
497 } else {
498 ++cnum;
502 if (pktpos > 0) {
503 version(woot_net_chatty) conwriteln("NET: sending packet to ", aout.toString);
504 if (sendto(sk, pkt.ptr, pktpos, MSG_NOSIGNAL, cast(sockaddr*)&aout, aout.sizeof) != pktpos) return;
509 final void checkTimeouts () {
510 auto ct = MonoTime.currTime;
511 foreach (ref peer; mPeers[]) {
512 if (!peer.netvalid) continue;
513 // pinged?
514 if (peer.pingSent) {
515 if ((ct-peer.lastActivity).total!"msecs" > 1000) {
516 // this peer is dead
517 conwriteln("NET: disconnected ", peer.cid);
518 peer.markDead();
520 continue;
522 // need to ping?
523 if ((ct-peer.lastActivity).total!"msecs" > 500) {
524 auto pk = encodePacketPing();
525 if (pk.valid) {
526 peer.pingSent = true;
527 peer.queue.unsafeArrayInsertBefore(0, pk);
529 continue;
534 final void recv (WString wstr, void delegate (in WStrOp op) cb) {
535 import core.stdc.string : memset;
537 if (sk < 0) return;
538 assert(wstr !is null);
539 assert(cb !is null);
541 for (;;) {
542 fd_set rdset;
543 timeval to;
544 FD_ZERO(&rdset);
545 FD_SET(sk, &rdset);
546 if (select(sk+1, &rdset, null, null, &to) < 1) return;
547 if (!FD_ISSET(sk, &rdset)) return;
549 sockaddr_in it = void;
550 socklen_t itlen = it.sizeof;
551 memset(&it, 0, it.sizeof);
553 // read packet
554 ubyte[MTU] pkt = 0;
555 auto rd = recvfrom(sk, pkt.ptr, pkt.length, 0/*MSG_DONTWAIT*/, cast(sockaddr*)&it, &itlen);
556 if (rd < 0) return;
557 gotPacketFrom(it); // clear hellos
559 version(woot_net_chatty) conwriteln("NET: packet from ", it.toString);
561 int ccpktpos = 0;
562 datagram: while (ccpktpos < rd && rd-ccpktpos >= ChaCha20.IVSize) {
563 ubyte[MTU] xdata = void;
564 xdata[0..rd-ccpktpos] = pkt[ccpktpos..rd];
566 // decrypt packet
567 auto cyph = ChaCha20(mKey, xdata[0..ChaCha20.IVSize]);
568 ccpktpos += ChaCha20.IVSize;
569 auto data = xdata[ChaCha20.IVSize..rd];
570 cyph.processBuf(data[]);
572 if (data.length < 4+4+1) break;
574 T get(T) () if (__traits(isIntegral, T)) {
575 if (data.length < T.sizeof) throw new Exception("invalid packed received");
576 ccpktpos += cast(int)T.sizeof;
577 T res = *(cast(T*)data.ptr);
578 data = data[T.sizeof..$];
579 return res;
582 try {
583 uint rseq = get!uint;
584 uint ruuid = get!uint;
585 ubyte opcode = get!ubyte;
587 if (opcode < PktType.min || opcode > PktType.max) break; // alas
589 auto peer = findPeer(ruuid);
590 if (peer is null) {
591 // new peer
592 version(woot_net_chatty) conwriteln("NET: new peer comes: ", ruuid);
593 peer = newPeer(ruuid);
594 if (peer is null) break; // oops
595 version(woot_net_chatty) conwriteln(" ", peer.cid, " : ", (peer.cid == ruuid));
598 version(woot_net_chatty) conprintfln("NET: msg from %u.%u.%u.%u:%u",
599 it.sin_addr.s_addr&0xff, (it.sin_addr.s_addr>>8)&0xff, (it.sin_addr.s_addr>>16)&0xff, (it.sin_addr.s_addr>>24)&0xff,
600 ntohs(it.sin_port));
602 it.sin_family = AF_INET;
603 peer.addr = it;
604 peer.netvalid = true;
605 peer.lastActivity = MonoTime.currTime;
606 peer.pingSent = false;
608 uint xid;
609 switch (opcode) {
610 case PktType.Hello:
611 // new client arrived
612 version(woot_net_chatty) conwriteln("NET: HELLO, port=", htons(peer.addr.sin_port));
613 // ack this packet
614 peerAck(*peer, rseq);
615 // send all known peers to it
616 foreach (ref pr; mPeers[]) {
617 if (pr.cid != peer.cid) {
618 auto pk = encodePacketPeer(pr);
619 if (pk.valid) peer.queue.unsafeArrayAppend(pk);
622 // send all the text to it
623 if (wstr !is null) {
624 foreach (immutable idx; 0..wstr.length) {
625 auto ch = wstr[idx];
626 if (ch.visible) {
627 auto op = WStrOp(WStrOp.OpType.Insert, wstr[idx]);
628 auto pk = createOpPacket(op);
629 if (pk.valid) peer.queue.unsafeArrayAppend(pk);
630 } else {
631 ch = WChar(ch.id, ' ', ch.prev, ch.next);
632 auto op = WStrOp(WStrOp.OpType.Insert, ch);
633 auto pk = createOpPacket(op);
634 if (pk.valid) peer.queue.unsafeArrayAppend(pk);
635 op = WStrOp(WStrOp.OpType.Delete, wstr[idx]);
636 pk = createOpPacket(op);
637 if (pk.valid) peer.queue.unsafeArrayAppend(pk);
641 break;
642 case PktType.Insert:
643 version(woot_net_chatty) conwriteln("NET: INSERT");
644 // ack this packet
645 peerAck(*peer, rseq);
646 // char
647 dchar ch = cast(dchar)get!uint;
648 // char id
649 auto chid = WCharId(get!ulong);
650 // prev id
651 auto previd = WCharId(get!ulong);
652 // next id
653 auto nextid = WCharId(get!ulong);
654 auto wc = WChar(chid, ch, previd, nextid);
655 cb(WStrOp(WStrOp.OpType.Insert, wc));
656 break;
657 case PktType.Delete:
658 version(woot_net_chatty) conwriteln("NET: DELETE");
659 // ack this packet
660 peerAck(*peer, rseq);
661 // char id
662 auto chid = WCharId(get!ulong);
663 auto wc = WChar(chid, 0);
664 cb(WStrOp(WStrOp.OpType.Delete, wc));
665 break;
666 case PktType.Ping:
667 // ping request comes
668 version(woot_net_chatty) conwriteln("NET: PING");
669 // ack this packet
670 peerAck(*peer, rseq);
671 break;
672 case PktType.Ack:
673 // ack comes
674 version(woot_net_chatty) conwriteln("NET: ACK");
675 // check if this is "hello" ack
676 foreach (immutable c, ref hq; mHelloQ) {
677 if (hq.pkt.valid && hq.pkt.seq == rseq) {
678 version(woot_net_chatty) conwriteln(" hello acked!");
679 hq.pkt.clear();
680 mHelloQ.unsafeArrayRemove(cast(int)c);
681 break;
684 // check peer packets
685 foreach (immutable c, ref pk; peer.queue) {
686 if (pk.seq == rseq) {
687 peer.queue.unsafeArrayRemove(cast(int)c);
688 break;
691 break;
692 case PktType.Peer:
693 // ack this packet
694 peerAck(*peer, rseq);
695 it.sin_family = AF_INET;
696 it.sin_addr.s_addr = get!uint;
697 it.sin_port = get!ushort;
698 if (it.sin_port == myaddr.sin_port && it.sin_addr.s_addr == myaddr.sin_addr.s_addr) break;
699 version(woot_net_chatty) conwriteln("NET: PEER ", it.toString);
700 peer = findPeerByAddr(it);
701 if (peer is null) addPeer(it);
702 break;
703 default: throw new Exception("invalid packed received");
705 } catch (Exception e) {
706 // sorry
707 break datagram;
709 } // one received datagram
713 public:
714 final @property uint mycid () const pure nothrow @safe @nogc { pragma(inline, true); return mMyCId; }
718 version(Windows) {
719 shared static this () {
720 WSADATA wsa;
721 if (WSAStartup(0x0202, &wsa) != 0) assert(0, "cannot initialize shitdoze sockets");