1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  *
24  *
25  * DSFML is based on SFML (Copyright Laurent Gomila)
26  */
27 
28 /**
29  * TCP is a connected protocol, which means that a TCP socket can only
30  * communicate with the host it is connected to.
31  *
32  * It can't send or receive anything if it is not connected.
33  *
34  * The TCP protocol is reliable but adds a slight overhead. It ensures that your
35  * data will always be received in order and without errors (no data corrupted,
36  * lost or duplicated).
37  *
38  * When a socket is connected to a remote host, you can retrieve informations
39  * about this host with the `getRemoteAddress` and `getRemotePort` functions.
40  *
41  * You can also get the local port to which the socket is bound (which is
42  * automatically chosen when the socket is connected), with the `getLocalPort`
43  * function.
44  *
45  * Sending and receiving data can use either the low-level or the high-level
46  * functions. The low-level functions process a raw sequence of bytes, and
47  * cannot ensure that one call to Send will exactly match one call to Receive at
48  * the other end of the socket.
49  *
50  * The high-level interface uses packets (see $(PACKET_LINK)), which are easier
51  * to use and provide more safety regarding the data that is exchanged. You can
52  * look at the $(PACKET_LINK) class to get more details about how they work.
53  *
54  * The socket is automatically disconnected when it is destroyed, but if you
55  * want to explicitely close the connection while the socket instance is still
56  * alive, you can call disconnect.
57  *
58  * Example:
59  * ---
60  * // ----- The client -----
61  *
62  * // Create a socket and connect it to 192.168.1.50 on port 55001
63  * auto socket = new TcpSocket();
64  * socket.connect("192.168.1.50", 55001);
65  *
66  * // Send a message to the connected host
67  * string message = "Hi, I am a client";
68  * socket.send(message);
69  *
70  * // Receive an answer from the server
71  * char[1024] buffer;
72  * size_t received = 0;
73  * socket.receive(buffer, received);
74  * writeln("The server said: ", buffer[0 .. received]);
75  *
76  * // ----- The server -----
77  *
78  * // Create a listener to wait for incoming connections on port 55001
79  * auto listener = TcpListener();
80  * listener.listen(55001);
81  *
82  * // Wait for a connection
83  * auto socket = new TcpSocket();
84  * listener.accept(socket);
85  * writeln("New client connected: ", socket.getRemoteAddress());
86  *
87  * // Receive a message from the client
88  * char[1024] buffer;
89  * size_t received = 0;
90  * socket.receive(buffer, received);
91  * writeln("The client said: ", buffer[0 .. received]);
92  *
93  * // Send an answer
94  * string message = "Welcome, client";
95  * socket.send(message);
96  * ---
97  *
98  * See_Also:
99  * $(SOCKET_LINK), $(UDPSOCKET_LINK), $(PACKET_LINK)
100  */
101 module nudsfml.network.tcpsocket;
102 
103 public import nudsfml.system.time;
104 
105 import nudsfml.network.ipaddress;
106 import nudsfml.network.packet;
107 import nudsfml.network.socket;
108 
109 import nudsfml.system.err;
110 
111 /**
112  * Specialized socket using the TCP protocol.
113  */
114 class TcpSocket:Socket
115 {
116     package sfTcpSocket* sfPtr;
117 
118     /// Default constructor.
119     this()
120     {
121         sfPtr = sfTcpSocket_create();
122     }
123 
124     /// Destructor.
125     ~this()
126     {
127         import nudsfml.system.config;
128         mixin(destructorOutput);
129         sfTcpSocket_destroy(sfPtr);
130     }
131 
132     /**
133      * Get the port to which the socket is bound locally.
134      *
135      * If the socket is not connected, this function returns 0.
136      *
137      * Returns: Port to which the socket is bound.
138      */
139     ushort getLocalPort() const
140     {
141         return sfTcpSocket_getLocalPort(sfPtr);
142     }
143 
144     /**
145      * Get the address of the connected peer.
146      *
147      * It the socket is not connected, this function returns `IpAddress.None`.
148      *
149      * Returns: Address of the remote peer.
150      */
151     IpAddress getRemoteAddress() const
152     {
153         IpAddress temp;
154 
155         sfTcpSocket_getRemoteAddress(sfPtr,&temp);
156 
157         return temp;
158     }
159 
160     /**
161      * Get the port of the connected peer to which the socket is connected.
162      *
163      * If the socket is not connected, this function returns 0.
164      *
165      * Returns: Remote port to which the socket is connected.
166      */
167     ushort getRemotePort() const
168     {
169         return sfTcpSocket_getRemotePort(sfPtr);
170     }
171 
172     /**
173      * Set the blocking state of the socket.
174      *
175      * In blocking mode, calls will not return until they have completed their
176      * task. For example, a call to `receive` in blocking mode won't return
177      * until some data was actually received.
178      *
179      * In non-blocking mode, calls will always return immediately, using the
180      * return code to signal whether there was data available or not. By
181      * default, all sockets are blocking.
182      *
183      * Params:
184      *  blocking = true to set the socket as blocking, false for non-blocking
185      */
186     void setBlocking(bool blocking)
187     {
188         sfTcpSocket_setBlocking(sfPtr, blocking);
189     }
190 
191     /**
192      * Connect the socket to a remote peer.
193      *
194      * In blocking mode, this function may take a while, especially if the
195      * remote peer is not reachable. The last parameter allows you to stop
196      * trying to connect after a given timeout.
197      *
198      * If the socket was previously connected, it is first disconnected.
199      *
200      * Params:
201      *  host    = Address of the remote peer
202      * 	port    = Port of the remote peer
203      * 	timeout = Optional maximum time to wait
204      *
205      * Returns: Status code.
206      */
207     Status connect(IpAddress host, ushort port, Time timeout = Time.Zero)
208     {
209         return sfTcpSocket_connect(sfPtr, &host, port, timeout.asMicroseconds());
210     }
211 
212     /**
213      * Disconnect the socket from its remote peer.
214      *
215      * This function gracefully closes the connection. If the socket is not
216      * connected, this function has no effect.
217      */
218     void disconnect()
219     {
220         sfTcpSocket_disconnect(sfPtr);
221     }
222 
223     /**
224      * Tell whether the socket is in blocking or non-blocking mode.
225      *
226      * Returns: true if the socket is blocking, false otherwise.
227      */
228     bool isBlocking() const
229     {
230         return (sfTcpSocket_isBlocking(sfPtr));
231     }
232 
233     /**
234      * Send raw data to the remote peer.
235      *
236      * This function will fail if the socket is not connected.
237      *
238      * Params:
239      * 	data = Sequence of bytes to send
240      *
241      * Returns: Status code.
242      */
243     Status send(const(void)[] data)
244     {
245         return sfTcpSocket_send(sfPtr, data.ptr, data.length);
246     }
247 
248     /**
249      * Send a formatted packet of data to the remote peer.
250      *
251      * This function will fail if the socket is not connected.
252      *
253      * Params:
254      * 	packet = Packet to send
255      *
256      * Returns: Status code.
257      */
258     Status send(Packet packet)
259     {
260         import std.stdio;
261         //temporary packet to be removed on function exit
262         scope SfPacket temp = new SfPacket();
263 
264         //getting packet's "to send" data
265         temp.append(packet.onSend());
266 
267         //send the data
268         return sfTcpSocket_sendPacket(sfPtr, temp.sfPtr);
269     }
270 
271     /**
272      * Receive raw data from the remote peer.
273      *
274      * In blocking mode, this function will wait until some bytes are actually
275      * received. This function will fail if the socket is not connected.
276      *
277      * Params:
278      * 	data = Array to fill with the received bytes
279      * 	sizeReceived = This variable is filled with the actual number of bytes
280                        received
281      *
282      * Returns: Status code.
283      */
284     Status receive(void[] data , out size_t sizeReceived)
285     {
286         return sfTcpSocket_receive(sfPtr, data.ptr, data.length, &sizeReceived);
287     }
288 
289     /**
290      * Receive a formatted packet of data from the remote peer.
291      *
292      * In blocking mode, this function will wait until the whole packet has been
293      * received. This function will fail if the socket is not connected.
294      *
295      * Params:
296      * 	packet = Packet to fill with the received data
297      *
298      * Returns: Status code.
299      */
300     Status receive(Packet packet)
301     {
302         //temporary packet to be removed on function exit
303         scope SfPacket temp = new SfPacket();
304 
305         //get the sent data
306         Status status = sfTcpSocket_receivePacket(sfPtr, temp.sfPtr);
307 
308         //put data into the packet so that it can process it first if it wants.
309         packet.onRecieve(temp.getData());
310 
311         return status;
312     }
313 }
314 
315 unittest
316 {
317     //TODO: Expand to use more methods in TcpSocket
318     version(DSFML_Unittest_Network)
319     {
320         import std.stdio;
321         import nudsfml.network.tcplistener;
322 
323         writeln("Unittest for Tcp Socket");
324 
325         //socket connecting to server
326         auto clientSocket = new TcpSocket();
327 
328         //listener looking for new sockets
329         auto listener = new TcpListener();
330         listener.listen(55003);
331 
332         //get our client socket to connect to the server
333         clientSocket.connect(IpAddress.LocalHost, 55003);
334 
335         //packet to send data
336         auto sendPacket = new Packet();
337 
338         //Packet to receive data
339         auto receivePacket = new Packet();
340 
341         //socket on the server side connected to the client's socket
342         auto serverSocket = new TcpSocket();
343 
344         //accepts a new connection and binds it to the socket in the parameter
345         listener.accept(serverSocket);
346 
347         string temp = "I'm sending you stuff!";
348 
349         //Let's greet the server!
350         //sendPacket.writeString("Hello, I'm a client!");
351         //clientSocket.send(sendPacket);
352 
353         clientSocket.send(temp);
354 
355         //And get the data on the server side
356         //serverSocket.receive(receivePacket);
357 
358         char[1024] temp2;
359         size_t received;
360 
361         serverSocket.receive(temp2, received);
362 
363         //What did we get from the client?
364         writeln("Gotten from client: ", cast(string)temp2[0..received]);
365 
366         //clear the packets to send/get new information
367         sendPacket.clear();
368         receivePacket.clear();
369 
370         //Respond back to the client
371         sendPacket.write("Hello, I'm your server.");
372 
373         serverSocket.send(sendPacket);
374 
375         clientSocket.receive(receivePacket);
376 
377         string message;
378         receivePacket.read!string(message);
379         writeln("Gotten from server: ", message);
380 
381         clientSocket.disconnect();
382         writeln();
383     }
384 }
385 
386 package extern(C):
387 
388 struct sfTcpSocket;
389 
390 //Create a new TCP socket
391 sfTcpSocket* sfTcpSocket_create();
392 
393 //Destroy a TCP socket
394 void sfTcpSocket_destroy(sfTcpSocket* socket);
395 
396 //Set the blocking state of a TCP listener
397 void sfTcpSocket_setBlocking(sfTcpSocket* socket, bool blocking);
398 
399 //Tell whether a TCP socket is in blocking or non-blocking mode
400 bool sfTcpSocket_isBlocking(const(sfTcpSocket)* socket);
401 
402 //Get the port to which a TCP socket is bound locally
403 ushort sfTcpSocket_getLocalPort(const(sfTcpSocket)* socket);
404 
405 //Get the address of the connected peer of a TCP socket
406 void sfTcpSocket_getRemoteAddress(const(sfTcpSocket)* socket, IpAddress* ipAddress);
407 
408 //Get the port of the connected peer to which a TCP socket is connected
409 ushort sfTcpSocket_getRemotePort(const(sfTcpSocket)* socket);
410 
411 //Connect a TCP socket to a remote peer
412 Socket.Status sfTcpSocket_connect(sfTcpSocket* socket, IpAddress* host, ushort port, long timeout);
413 
414 //Disconnect a TCP socket from its remote peer
415 void sfTcpSocket_disconnect(sfTcpSocket* socket);
416 
417 //Send raw data to the remote peer of a TCP socket
418 Socket.Status sfTcpSocket_send(sfTcpSocket* socket, const void* data, size_t size);
419 
420 //Receive raw data from the remote peer of a TCP socket
421 Socket.Status sfTcpSocket_receive(sfTcpSocket* socket, void* data, size_t maxSize, size_t* sizeReceived);
422 
423 //Send a formatted packet of data to the remote peer of a TCP socket
424 Socket.Status sfTcpSocket_sendPacket(sfTcpSocket* socket, sfPacket* packet);
425 
426 //Receive a formatted packet of data from the remote peer
427 Socket.Status sfTcpSocket_receivePacket(sfTcpSocket* socket, sfPacket* packet);