1 /// Provides the Host, Server, and Client classes.
2 
3 module gfm.enet.host;
4 
5 import std.algorithm,
6        std.typecons;
7 
8 import derelict.enet.enet;
9 
10 import gfm.enet.address,
11        gfm.enet.event,
12        gfm.enet.enet,
13        gfm.enet.packet,
14        gfm.enet.peer;
15 
16 /**
17  * A subclass of Host that exists to concisely create a client-oriented Host
18  *
19  * The Host superclass' address is set to null which disallows other peers from
20  * connecting to the Host.
21  *
22  * See_Also: Host, Client
23  */
24 final class Client : Host
25 {
26     /**
27      * Creates a new Client, a subclass of Host
28      *
29      * Params:
30      *  enet              = Library object.
31      *  peerCount         = The maximum number of peers that should be allocated
32      *                      for the host, for clients this is useful for
33      *                      connecting to multiple servers.
34      *  channelLimit      = The maximum number of channels allowed; if 0, then
35      *                      this is equivalent to
36      *                      ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
37      *  incomingBandwidth = downstream bandwidth of the host in bytes/second; if
38      *                      0, ENet will assume unlimited bandwidth
39      *  outgoingBandwidth = upstream bandwidth of the host in bytes/second; if
40      *                      0, ENet will assume unlimited bandwidth
41      *
42      * Throws: ENetException if enet_create_host fails
43      */
44     this(ENet enet,
45          size_t peerCount,
46          size_t channelLimit,
47          uint incomingBandwidth=0,
48          uint outgoingBandwidth=0)
49     {
50         super(enet,
51               null,
52               peerCount,
53               channelLimit,
54               incomingBandwidth,
55               outgoingBandwidth);
56     }
57 
58 
59 
60     /**
61      * Creates a new Client, a subclass of Host
62      *
63      * This constructor sets peerCount to 1, meaning it can only connect to a
64      * singular Peer.
65      *
66      * Params:
67      *  enet              = Library object.
68      *  channelLimit      = The maximum number of channels allowed; if 0, then
69      *                      this is equivalent to
70      *                      ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
71      *  bandwidthLimits   = downstream and upstream bandwidth of the host in
72      *                      bytes/second; if 0, ENet will assume unlimited
73      *                      bandwidth
74      *
75      * Throws: ENetException if enet_create_host fails
76      */
77     this(ENet enet, size_t channelLimit, uint[2] bandwidthLimits=[0, 0])
78     {
79         this(enet, 1, channelLimit, bandwidthLimits[0], bandwidthLimits[1]);
80     }
81 
82 }
83 
84 /**
85  * A subclass of Host that exists to concisely create a server-oriented Host
86  *
87  * The Host superclass' address is set to ENET_HOST_ANY which binds the server
88  * to the default localhost.
89  *
90  * See_Also: Host, Client
91  */
92 class Server : Host
93 {
94     /**
95      * Creates a new Server, which is a type of Host.
96      *
97      * Params:
98      *  enet              = Library object.
99      *  port              = Port that the server binds to
100      *  peerCount         = The maximum number of peers that should be allocated
101      *                      for the host
102      *  channelLimit      = The maximum number of channels allowed; if 0, then
103      *                      this is equivalent to
104      *                      ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
105      *  incomingBandwidth = downstream bandwidth of the host in bytes/second; if
106      *                      0, ENet will assume unlimited bandwidth
107      *  outgoingBandwidth = upstream bandwidth of the host in bytes/second; if
108      *                      0, ENet will assume unlimited bandwidth
109      *
110      * Throws: ENetException if enet_address_set_host fails
111      * Throws: ENetException if enet_create_host fails
112      */
113     this(ENet enet,
114          ushort port,
115          size_t peerCount,
116          size_t channelLimit = 0,
117          uint incomingBandwidth = 0,
118          uint outgoingBandwidth = 0)
119     {
120         Address serverAddress = Address(ENET_HOST_ANY, port);
121         super(enet,
122               &serverAddress,
123               peerCount,
124               channelLimit,
125               incomingBandwidth,
126               outgoingBandwidth);
127     }
128 }
129 
130 /**
131  * Encompasses an ENetHost structure with an object-oriented wrapper
132  *
133  * See_Also: Client, Server
134  */
135 class Host
136 {
137     protected ENetHost *_handle;
138     protected ENet _enet;
139     private
140     {
141         Peer[] _peers;
142         bool _usingRangeCoder;
143     }
144 
145     /**
146      * Creates a new Host
147      *
148      * Params:
149      *  enet              = Library object.
150      *  address           = The address at which other peers may connect to this
151      *                      host; if null, then no peers may connect to the host
152      *  peerCount         = The maximum number of peers that should be allocated
153      *                      for the host
154      *  channelLimit      = The maximum number of channels allowed; if 0, then
155      *                      this is equivalent to
156      *                      ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
157      *  incomingBandwidth = downstream bandwidth of the host in bytes/second; if
158      *                      0, ENet will assume unlimited bandwidth
159      *  outgoingBandwidth = upstream bandwidth of the host in bytes/second; if
160      *                      0, ENet will assume unlimited bandwidth
161      *
162      * Throws: ENetException if enet_create_host fails
163      */
164     this(ENet enet,
165          Address* address,
166          size_t peerCount,
167          size_t channelLimit,
168          uint incomingBandwidth=0,
169          uint outgoingBandwidth=0)
170     {
171         _enet = enet;
172 
173         _handle = enet_host_create(&address.address,
174                                    peerCount,
175                                    channelLimit,
176                                    incomingBandwidth,
177                                    outgoingBandwidth);
178 
179         if(_handle is null)
180             throw new ENetException("enet_host_create failed");
181         _usingRangeCoder = false;
182     }
183 
184     /// Cleans up any resources used by the Host
185     ~this()
186     {
187         if(_handle !is null)
188         {
189             debug ensureNotInGC("Host");
190             enet_host_destroy(_handle);
191             _handle = null;
192         }
193     }
194 
195     /**
196      * Initiates a connection to a foreign host
197      *
198      * Params:
199      *  foreignAddress = Destination for the connection
200      *  channelCount   = Number of channels to allocate
201      *  data           = User data supplied to the receiving host
202      *
203      * Throws: ENetException if enet_host_connect fails
204      */
205     Peer connect(Address foreignAddress, size_t channelCount, uint data=0)
206     {
207         ENetPeer *peer = enet_host_connect(_handle,
208                                            &foreignAddress.address,
209                                            channelCount,
210                                            data);
211 
212         if(peer is null)
213             throw new ENetException("enet_host_connect failed");
214 
215         return new Peer(_enet, peer);
216     }
217 
218     /**
219      * Initiates a connection to a foreign host
220      *
221      * Params:
222      *  hostName     = Destination hostname for the connection
223      *  port         = Destination port for the connection
224      *  channelCount = Number of channels to allocate
225      *  data         = User data supplied to the receiving host
226      *
227      * Throws: ENetException if enet_host_connect fails
228      * See_Also: connect
229      */
230     Peer connect(string hostName, ushort port, size_t channelCount, uint data=0)
231     {
232         Address foreignAddress = Address(hostName, port);
233         return connect(foreignAddress, channelCount, data);
234     }
235 
236     /**
237      * Queues a packet to be sent to all peers associated with the host
238      *
239      * Dirties the sent packet, meaning it cannot be changed.
240      *
241      * Params:
242      *  packet    = Packet to broadcast to all associated peers
243      *  channelID = Channel to broadcast the packet on
244      */
245     void broadcast(Packet packet, ubyte channelID=0)
246     {
247         // If there are no references to the packet, ENet destroys it
248         if(packet._handle.referenceCount == 0)
249             packet._destroyed = true;
250         enet_host_broadcast(_handle, channelID, packet._handle);
251         packet._dirty = true;
252     }
253 
254     /// Sets the packet compressor the host should use to the default range
255     /// coder
256     void compressWithRangeCoder()
257     {
258         if(!_usingRangeCoder)
259         {
260             auto errCode = enet_host_compress_with_range_coder(_handle);
261             if(errCode < 0)
262             {
263                 string msg = "enet_host_compress_with_range_coder failed";
264                 throw new ENetException(msg);
265             }
266             _usingRangeCoder = true;
267         }
268     }
269 
270     /// Turns off the range coder packet compressor
271     void disableRangeCoder()
272     {
273         if(_usingRangeCoder)
274             enet_host_compress(_handle, null);
275         _usingRangeCoder = false;
276     }
277 
278     /**
279      * Sends any queued packets on the host specified to its designated peers
280      *
281      * This is not necessary if you are using service, which will also send
282      * queued packets to its designated peers.
283      *
284      * See_Also: service
285      */
286     void flush()
287     {
288         enet_host_flush(_handle);
289     }
290 
291     /**
292      * Checks for any queued events on the host and dispatches one if available
293      *
294      * Returns:
295      *  The event which occurred if one happened or null if no event happened.
296      *
297      * Throws: ENetException if enet_host_check_events fails
298      *
299      * See_Also: service
300      */
301     Event checkEvents()
302     {
303         ENetEvent event;
304         auto result = enet_host_check_events(_handle, &event);
305         if(result == 0)
306             return null;
307         else if(result > 0)
308         {
309             Event wrappedEvent = new Event(_enet, &event);
310             processPeers(wrappedEvent);
311             return wrappedEvent;
312         }
313         else
314             throw new ENetException("enet_host_check_events failed");
315     }
316 
317     /**
318      * Waits for events on the host and shuttles packets between the host and
319      * its peers
320      *
321      * Params:
322      *  timeout = number of milliseconds that ENet should wait for events
323      *
324      * Returns:
325      *  The event which occurred if one happened or null if no event happened.
326      *
327      * Throws: ENetException if enet_host_service fails
328      *
329      * See_Also: checkEvents
330      */
331     Event service(uint timeout=0)
332     {
333         ENetEvent event;
334         auto result = enet_host_service(_handle, &event, timeout);
335         if(result == 0)
336             return null;
337         else if(result > 0)
338         {
339             Event wrappedEvent = new Event(_enet, &event);
340             processPeers(wrappedEvent);
341             return wrappedEvent;
342         }
343         else
344             throw new ENetException("enet_host_service failed");
345     }
346 
347     private void processPeers(Event event)
348     {
349         if(event.type() == ENET_EVENT_TYPE_CONNECT)
350         {
351             _peers.length++;
352             _peers[$-1] = event.peer();
353         }
354         else if(event.type() == ENET_EVENT_TYPE_DISCONNECT)
355         {
356             _peers = _peers.remove(_peers.countUntil(event.peer()));
357         }
358     }
359 
360     @property
361     {
362         ENetChecksumCallback checksum()
363         {
364             return _handle.checksum;
365         }
366 
367         void checksum(ENetChecksumCallback callback)
368         {
369             _handle.checksum = callback;
370         }
371     }
372 
373     @property
374     {
375         ENetCompressor compressor()
376         {
377             return _handle.compressor;
378         }
379 
380         void compressor(ENetCompressor compressor)
381         {
382             if(_usingRangeCoder)
383                 disableRangeCoder();
384             _handle.compressor = compressor;
385         }
386 
387         bool compress()
388         {
389             return _usingRangeCoder;
390         }
391 
392         void compress(bool shouldCompress)
393         {
394             if(shouldCompress == true)
395                 compressWithRangeCoder();
396             else
397                 disableRangeCoder();
398         }
399     }
400 
401     @property
402     {
403         ENetInterceptCallback intercept()
404         {
405             return _handle.intercept;
406         }
407 
408         void intercept(ENetInterceptCallback callback)
409         {
410             _handle.intercept = callback;
411         }
412     }
413 
414     @property
415     {
416         size_t channelLimit()
417         {
418             return _handle.channelLimit;
419         }
420 
421         void channelLimit(size_t channelLimit)
422         {
423             enet_host_channel_limit(_handle, channelLimit);
424         }
425     }
426 
427     @property
428     {
429         uint[2] bandwidthLimits()
430         {
431             return [ _handle.incomingBandwidth, _handle.outgoingBandwidth ];
432         }
433 
434         uint incomingBandwidth()
435         {
436             return _handle.incomingBandwidth;
437         }
438 
439         uint outgoingBandwidth()
440         {
441             return _handle.outgoingBandwidth;
442         }
443 
444         void bandwidthLimits(uint[2] limits)
445         {
446             enet_host_bandwidth_limit(_handle, limits[0], limits[1]);
447         }
448 
449         void incomingBandwidth(uint limit)
450         {
451             enet_host_bandwidth_limit(_handle, limit, _handle.outgoingBandwidth);
452         }
453 
454         void outgoingBandwidth(uint limit)
455         {
456             enet_host_bandwidth_limit(_handle, _handle.incomingBandwidth, limit);
457         }
458     }
459 
460     @property
461     {
462         size_t duplicatePeers()
463         {
464             return _handle.duplicatePeers;
465         }
466 
467         void duplicatePeers(size_t maxDuplicatePeers)
468         {
469             _handle.duplicatePeers = maxDuplicatePeers;
470         }
471     }
472 
473     // Getters
474     @property
475     {
476         Address address() { return Address(_handle.address); }
477 
478         Address receivedAddress() { return Address(_handle.receivedAddress); }
479         Peer[] peers() { return _peers; }
480 
481         // _ENetHost structure properties
482         ENetSocket socket() { return _handle.socket; }
483         uint bandwidthThrottleEpoch() { return _handle.bandwidthThrottleEpoch; }
484         uint mtu() { return _handle.mtu; }
485         uint randomSeed() { return _handle.randomSeed; }
486         int recalculateBandwidthLimits() { return _handle.recalculateBandwidthLimits; }
487         size_t peerCount() { return _handle.peerCount; }
488         uint serviceTime() { return _handle.serviceTime; }
489         ENetList dispatchQueue() { return _handle.dispatchQueue; }
490         int continueSending() { return _handle.continueSending; }
491         size_t packetSize() { return _handle.packetSize; }
492         ushort headerFlags() { return _handle.headerFlags; }
493         ENetProtocol[] commands() { return _handle.commands; }
494         size_t commandCount() { return _handle.commandCount; }
495         ENetBuffer[] buffers() { return _handle.buffers; }
496         size_t bufferCount() { return _handle.bufferCount; }
497         ubyte[ENET_PROTOCOL_MAXIMUM_MTU][2] packetData() { return _handle.packetData; }
498         ubyte *receivedData() { return _handle.receivedData; }
499         size_t receivedDataLength() { return _handle.receivedDataLength; }
500         uint totalSentData() { return _handle.totalSentData; }
501         uint totalSentPackets() { return _handle.totalSentPackets; }
502         uint totalReceivedData() { return _handle.totalReceivedData; }
503         uint totalReceivedPackets() { return _handle.totalReceivedPackets; }
504         size_t connectedPeers() { return _handle.connectedPeers; }
505         size_t bandwidthLimitedPeers() { return _handle.bandwidthLimitedPeers; }
506     }
507 }
508 
509 
510 // Tests
511 
512 // Creates a Client with 2 peers and 4 channels maximum
513 unittest
514 {
515     auto enet = scoped!ENet();
516     auto client = scoped!Client(enet, 2, 4);
517     assert(client.peerCount == 2);
518     assert(client.channelLimit == 4);
519     assert(client.bandwidthLimits == [0, 0]);
520     assert(client.incomingBandwidth == 0);
521     assert(client.outgoingBandwidth == 0);
522 }
523 
524 // Similar to before, only with a downstream and upstream bandwidth limit
525 unittest
526 {
527     auto enet = scoped!ENet();
528     auto maxPeers = 32;
529     auto maxChannels = 16;
530     auto inLimit = (1*10^^6)/8; // 1mbps
531     auto outLimit = (512*10^^5)/8; // 512kbps
532     auto client = scoped!Client(enet, maxPeers, maxChannels, inLimit, outLimit);
533     assert(client.peerCount == 32);
534     assert(client.channelLimit == 16);
535     assert(client.bandwidthLimits == [(1*10^^6)/8, (512*10^^5)/8]);
536     assert(client.incomingBandwidth == (1*10^^6)/8);
537     assert(client.outgoingBandwidth == (512*10^^5)/8);
538 }
539 
540 // Creates a client with 1 channel maximum, no bandwidth limits
541 unittest
542 {
543     auto enet = scoped!ENet();
544     auto client = scoped!Client(enet, 1);
545     assert(client.peerCount == 1);
546     assert(client.channelLimit == 1);
547     assert(client.bandwidthLimits == [0, 0]);
548     assert(client.incomingBandwidth == 0);
549     assert(client.outgoingBandwidth == 0);
550 }
551 
552 // 4 channels, 768kbps downstream, 128kbps upstream
553 unittest
554 {
555     auto enet = scoped!ENet();
556     auto inLimit = (768*10^^5)/8; // 768kbps
557     auto outLimit = (128*10^^5)/8; // 128kbps
558     auto client = scoped!Client(enet, 1, 4, inLimit, outLimit);
559     assert(client.peerCount == 1);
560     assert(client.channelLimit == 4);
561     assert(client.bandwidthLimits == [inLimit, outLimit]);
562     assert(client.incomingBandwidth == inLimit);
563     assert(client.outgoingBandwidth == outLimit);
564 }
565 
566 // Creates a server on port 27777, with 32 max peers and 8 max channels, with bandwidth limits
567 unittest
568 {
569     auto enet = scoped!ENet();
570     auto inLimit = (20*10^^6)/8; // 20mbps
571     auto outLimit = (10*10^^6)/8; // 10mbps
572     auto server = new Server(enet, 32807, 32, 8, inLimit, outLimit);
573     scope(exit) server.destroy();
574     assert(server.peerCount == 32);
575     assert(server.channelLimit == 8);
576     assert(server.bandwidthLimits == [inLimit, outLimit]);
577     assert(server.incomingBandwidth == inLimit);
578     assert(server.outgoingBandwidth == outLimit);
579 }
580 
581 // Creates Host for purposes of being a server
582 unittest
583 {
584     auto enet = scoped!ENet();
585     auto maxPeers = 32;
586     auto maxChans = 16;
587     auto inLimit = (2*10^^6)/8;
588     auto outLimit = (2*10^^6)/8;
589     // Create an address which refers to localhost address and port 32807
590     auto hostAddr = new Address(ENET_HOST_ANY, 32807);
591     auto host = scoped!Host(enet, hostAddr, maxPeers, maxChans, inLimit, outLimit);
592     assert(host.peerCount == 32);
593     assert(host.channelLimit == 16);
594     assert(host.bandwidthLimits == [inLimit, outLimit]);
595     assert(host.incomingBandwidth == inLimit);
596     assert(host.outgoingBandwidth == outLimit);
597 }
598 
599 // Creates Host for purposes of being a client
600 unittest
601 {
602     auto enet = scoped!ENet();
603     // Bandwidth defaults to 0, which means unlimited
604     auto maxPeers = 32;
605     auto maxChannels = 16;
606     // A null Address disallows other peers from connecting
607     auto host = scoped!Host(enet, null, maxPeers, maxChannels);
608     assert(host.peerCount == 32);
609     assert(host.channelLimit == 16);
610     assert(host.bandwidthLimits == [0, 0]);
611     assert(host.incomingBandwidth == 0);
612     assert(host.outgoingBandwidth == 0);
613 }
614 
615 // Creates and destroys a host
616 unittest
617 {
618     auto enet = scoped!ENet();
619     auto host = scoped!Host(enet, null, 1, 1);
620 }
621 
622 // Creates 2 hosts and connect them together
623 unittest
624 {
625     auto enet = scoped!ENet();
626     ushort port = 25403;
627     auto maxPeers = 1;
628     auto maxChannels = 1;
629     auto server = scoped!Server(enet, port, maxPeers, maxChannels);
630     auto client = scoped!Client(enet, maxPeers, maxChannels);
631     client.connect(server.address, maxChannels);
632 }
633 
634 // Creates 2 hosts and connect them together
635 unittest
636 {
637     auto enet = scoped!ENet();
638     ushort port = 25403;
639     auto maxPeers = 1;
640     auto maxChannels = 1;
641     auto server = scoped!Server(enet, port, maxPeers, maxChannels);
642     auto client = scoped!Client(enet, maxPeers, maxChannels);
643     client.connect("localhost", port, maxChannels);
644 }
645 
646 // Broadcasts a packet to several peers
647 unittest
648 {
649     auto enet = scoped!ENet();
650     ushort port = 25403;
651     auto maxPeers = 2;
652     auto maxChannels = 1;
653     auto server = scoped!Server(enet, port, maxPeers, maxChannels);
654     auto client1 = scoped!Client(enet, 1, maxChannels);
655     auto client2 = scoped!Client(enet, 1, maxChannels);
656     auto packet = scoped!Packet(enet, [0, 1, 2, 3, 4]);
657     client1.connect(server.address, maxChannels);
658     client2.connect(server.address, maxChannels);
659     server.broadcast(packet);
660 }
661 
662 // Makes a server and client host which use range coder
663 unittest
664 {
665     import std.stdio;
666     auto enet = scoped!ENet();
667     ushort port = 25403;
668     auto maxPeers = 1;
669     auto maxChannels = 1;
670     auto server = scoped!Server(enet, port, maxPeers, maxChannels);
671     auto client = scoped!Client(enet, maxPeers, maxChannels);
672     // Both hosts must be using range coder
673     server.compressWithRangeCoder();
674     client.compressWithRangeCoder();
675     auto serverAddress = Address("localhost", port);
676     client.connect(serverAddress, port);
677 }
678 
679 // Makes a server, enables the range coder, then disables it
680 unittest
681 {
682     auto enet = scoped!ENet();
683     auto server = new Server(enet, 25403, 1, 1);
684     scope(exit) server.destroy();
685     server.compressWithRangeCoder(); // Enable
686     server.disableRangeCoder(); // Disable
687 }