Merge lp:~widelands-dev/widelands/net-relay into lp:widelands

Proposed by Notabilis
Status: Superseded
Proposed branch: lp:~widelands-dev/widelands/net-relay
Merge into: lp:widelands
Diff against target: 2211 lines (+1645/-130)
24 files modified
src/network/CMakeLists.txt (+9/-0)
src/network/gameclient.cc (+15/-4)
src/network/gameclient.h (+2/-2)
src/network/gamehost.cc (+32/-16)
src/network/gamehost.h (+2/-2)
src/network/internet_gaming.cc (+21/-3)
src/network/internet_gaming.h (+7/-0)
src/network/internet_gaming_protocol.h (+17/-6)
src/network/netclient.h (+7/-32)
src/network/netclient_interface.h (+73/-0)
src/network/netclientproxy.cc (+133/-0)
src/network/netclientproxy.h (+74/-0)
src/network/nethost.cc (+6/-0)
src/network/nethost.h (+17/-49)
src/network/nethost_interface.h (+93/-0)
src/network/nethostproxy.cc (+258/-0)
src/network/nethostproxy.h (+104/-0)
src/network/netrelayconnection.cc (+299/-0)
src/network/netrelayconnection.h (+200/-0)
src/network/network.cc (+13/-0)
src/network/network.h (+4/-0)
src/network/relay_protocol.h (+222/-0)
src/ui_fsmenu/internet_lobby.cc (+36/-16)
src/ui_fsmenu/internet_lobby.h (+1/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/net-relay
Reviewer Review Type Date Requested Status
Widelands Developers Pending
Review via email: mp+327491@code.launchpad.net

This proposal has been superseded by a proposal from 2017-10-17.

Description of the change

This branch is not ready for merge yet!
As of now, this merge request only exists so we can discuss the file src/network/relay_protocol.h for the planned internet gaming relay. Feel free to join the discussion.

The file contains documentation for a communication protocol between server/client and the relay. The protocol is really basic and only deals with simply relaying of packets for a single game. Features like better reconnects or late joins are not supported. When the basics work, we can decide on how to support multiple games and how to add advanced features.

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2443. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/254133988.
Appveyor build 2269. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2269.

Revision history for this message
GunChleoc (gunchleoc) wrote :

The protocol looks good to me.

There is no description of how the relay server get shut down yet, either in game end or when host and clients disappear without telling the relay about it.

I'd add a note to RLYCMD_TO_HOST that it's identical to RLYCMD_TO_CLIENT etc., to make the documentation easier to read.

There are also a bunch of commented out braces.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for looking over it!

A command to shutdown the server is really missing, I haven't thought about that one. I think there should be a (long) timeout until automated shutdown when all players disconnect, just in case the shutdown-message does not arrive. Since the game can't work without the host, we could simply interpret the (planned) disconnect message of the host as a shutdown command.

With "identical" you mean the same enum-value? That is actually a bug, good catch. Is fixed in my local file now.
Assuming with the braces you mean the ///\{ and ///\} : This is intentional. With doxygen this leads to grouping of methods. The /** command */ in front of them is the description of the group.

Revision history for this message
SirVer (sirver) :
Revision history for this message
GunChleoc (gunchleoc) wrote :

1 comment regarding naming conventions.

I'd also have some minor phrasing/English language nits, but I'll keep those for the final code review when the branch is done.

Revision history for this message
SirVer (sirver) wrote :

Notabilis, could you give us an update on your work? I am quite curious. Are you blocked in any way?

Revision history for this message
Notabilis (notabilis27) wrote :

Sorry for the long silence, I should have written much earlier. My primary problem is lack of time, but when I can work on it its going well enough. I fear I won't be able to work on it in the next two weeks but hopefully I have more time afterwards.

Thanks for the comments regarding the protocol, it should be all fixed now. I also changed some more things while working on the implementation.
First (alpha-) versions of the relay server and the game connections have been implemented. The current state is pushed here and on GitHub. I haven't tested it yet but it compiles and the general design should be recognizable. If you want to comment on it, go ahead. My next step will probably be to try to run a game over the relay (and fix all the bugs that come up...).

As a high-level overview:

- The relay server is one process which creates a Game object for each running game. New connections are all to the same port. Based on their first message (containing the game name) they are distributed between the running games.

- I extracted a NetHostInterface/NetClientInterface from the NetHost/NetClient classes. These interfaces are also implemented by NetHostProxy/NetHostClient. For LAN games the old NetHost/NetClient classes are instantiated, for internet games the *Proxy classes are used. These pack all packets and actions (e.g. (dis-)connects) as described in relay_protocol.h and sent them over a single connection to the relay server. There they are forwarded to the other participants of the respective game. This way, the GameHost/GameClient classes do not have to care whether it is an internet or a local game.

What is currently missing is the integration with the metaserver. When a player wants to start a new game the metaserver has to order the relay to create the respective game and tell the host about ip/port/host-password of it. After the host connected, the metaserver can announce the game in the lobby for other clients to join.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2557. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/265326001.
Appveyor build 2269. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2269.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Good to hear that it's going well whenever you have the time :)

Revision history for this message
SirVer (sirver) wrote :

Thanks for the update! I just was curious if you are blocked. Do you want a review now? Otherwise I'd defer reviewing until you have something mergable.

Also, if some of the refactorings you did can be done beforhand in a smaller merge request, that would be very useful.

Revision history for this message
Notabilis (notabilis27) wrote :

If you want to say something about the design, go ahead. Looking at the details isn't worthwhile yet.

Okay, I will try to create a smaller merge when I am done.

Revision history for this message
Notabilis (notabilis27) wrote :

I just pushed the first "working" version of the relay server. Working as in: Everything hardcoded (IPs, name of the game, ...), but relaying game packets works and I was able to run a game.

One question where I would like some opinion: Currently the metaserver tells the client where to connect to (i.e. the IP and port of the relay). Do we need/want this flexibility or do we simply use hardcoded values in the client? It will probably always (?) be the IP of the metaserver and some fixed port number.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2667. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/275219903.
Appveyor build 2488. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_relay-2488.

Revision history for this message
GunChleoc (gunchleoc) wrote :

As a matter of principle, I am against hard coding where it can be avoided without too much trouble. There could conceivably be multiple game servers in the future.

Revision history for this message
Notabilis (notabilis27) wrote :

Okay, thanks. Than I will implement the dynamic version.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/network/CMakeLists.txt'
2--- src/network/CMakeLists.txt 2017-06-26 11:35:50 +0000
3+++ src/network/CMakeLists.txt 2017-10-17 19:31:56 +0000
4@@ -10,10 +10,18 @@
5 gameclient.h
6 gamehost.cc
7 gamehost.h
8+ netclient_interface.h
9 netclient.cc
10 netclient.h
11+ netclientproxy.cc
12+ netclientproxy.h
13+ nethost_interface.h
14 nethost.cc
15 nethost.h
16+ nethostproxy.cc
17+ nethostproxy.h
18+ netrelayconnection.cc
19+ netrelayconnection.h
20 network.cc
21 network.h
22 network_gaming_messages.cc
23@@ -23,6 +31,7 @@
24 network_player_settings_backend.cc
25 network_player_settings_backend.h
26 network_protocol.h
27+ relay_protocol.h
28 DEPENDS
29 ai
30 base_exceptions
31
32=== modified file 'src/network/gameclient.cc'
33--- src/network/gameclient.cc 2017-08-18 10:23:45 +0000
34+++ src/network/gameclient.cc 2017-10-17 19:31:56 +0000
35@@ -40,6 +40,8 @@
36 #include "logic/playersmanager.h"
37 #include "map_io/widelands_map_loader.h"
38 #include "network/internet_gaming.h"
39+#include "network/netclient.h"
40+#include "network/netclientproxy.h"
41 #include "network/network_gaming_messages.h"
42 #include "network/network_protocol.h"
43 #include "scripting/lua_interface.h"
44@@ -57,7 +59,7 @@
45
46 std::string localplayername;
47
48- std::unique_ptr<NetClient> net;
49+ std::unique_ptr<NetClientInterface> net;
50
51 /// Currently active modal panel. Receives an end_modal on disconnect
52 UI::Panel* modal;
53@@ -90,13 +92,22 @@
54
55 GameClient::GameClient(const std::pair<NetAddress, NetAddress>& host,
56 const std::string& playername,
57- bool internet)
58+ bool internet, const std::string& gamename)
59 : d(new GameClientImpl), internet_(internet) {
60
61- d->net = NetClient::connect(host.first);
62+ if (internet) {
63+ assert(!gamename.empty());
64+ d->net = NetClientProxy::connect(host.first, gamename);
65+ } else {
66+ d->net = NetClient::connect(host.first);
67+ }
68 if ((!d->net || !d->net->is_connected()) && host.second.is_valid()) {
69 // First IP did not work? Try the second IP
70- d->net = NetClient::connect(host.second);
71+ if (internet) {
72+ d->net = NetClientProxy::connect(host.first, gamename);
73+ } else {
74+ d->net = NetClient::connect(host.second);
75+ }
76 }
77 if (!d->net || !d->net->is_connected()) {
78 throw WLWarning(_("Could not establish connection to host"),
79
80=== modified file 'src/network/gameclient.h'
81--- src/network/gameclient.h 2017-08-11 15:30:42 +0000
82+++ src/network/gameclient.h 2017-10-17 19:31:56 +0000
83@@ -24,7 +24,7 @@
84 #include "logic/game_controller.h"
85 #include "logic/game_settings.h"
86 #include "logic/player_end_result.h"
87-#include "network/netclient.h"
88+#include "network/netclient_interface.h"
89
90 struct GameClientImpl;
91
92@@ -39,7 +39,7 @@
93 struct GameClient : public GameController, public GameSettingsProvider, public ChatProvider {
94 GameClient(const std::pair<NetAddress, NetAddress>& host,
95 const std::string& playername,
96- bool internet = false);
97+ bool internet = false, const std::string& gamename = "");
98
99 virtual ~GameClient();
100
101
102=== modified file 'src/network/gamehost.cc'
103--- src/network/gamehost.cc 2017-09-20 21:27:25 +0000
104+++ src/network/gamehost.cc 2017-10-17 19:31:56 +0000
105@@ -50,6 +50,8 @@
106 #include "map_io/widelands_map_loader.h"
107 #include "network/constants.h"
108 #include "network/internet_gaming.h"
109+#include "network/nethost.h"
110+#include "network/nethostproxy.h"
111 #include "network/network_gaming_messages.h"
112 #include "network/network_lan_promotion.h"
113 #include "network/network_player_settings_backend.h"
114@@ -387,7 +389,7 @@
115 };
116
117 struct Client {
118- NetHost::ConnectionId sock_id;
119+ NetHostInterface::ConnectionId sock_id;
120 uint8_t playernum;
121 int16_t usernum;
122 std::string build_id;
123@@ -411,7 +413,7 @@
124 NetworkPlayerSettingsBackend npsb;
125
126 LanGamePromoter* promoter;
127- std::unique_ptr<NetHost> net;
128+ std::unique_ptr<NetHostInterface> net;
129
130 /// List of connected clients. Note that clients are not in the same
131 /// order as players. In fact, a client must not be assigned to a player.
132@@ -478,21 +480,30 @@
133 : d(new GameHostImpl(this)), internet_(internet), forced_pause_(false) {
134 log("[Host]: starting up.\n");
135
136- if (internet) {
137- InternetGaming::ref().open_game();
138- }
139-
140 d->localplayername = playername;
141
142 // create a listening socket
143- d->net = NetHost::listen(WIDELANDS_PORT);
144- if (d->net == nullptr) {
145- // This might happen when the widelands socket is already in use
146- throw WLWarning(_("Failed to start the server!"),
147- _("Widelands could not start a server.\n"
148- "Probably some other process is already running a server on our port."));
149+ if (internet) {
150+ // No real listening socket. Instead, connect to the relay server
151+ d->net = NetHostProxy::connect(InternetGaming::ref().ips(),
152+ InternetGaming::ref().get_local_servername(), InternetGaming::ref().relay_password());
153+ if (d->net == nullptr) {
154+ // Some kind of problem with the relay server. Bad luck :(
155+ throw WLWarning(_("Failed to host the server!"),
156+ _("Widelands could not start hosting a server.\n"
157+ "This should not happen and is unfortunately most likely "
158+ "a bug of the metaserver. There is nothing you can do."));
159+ }
160+ } else {
161+ d->net = NetHost::listen(WIDELANDS_PORT);
162+ if (d->net == nullptr) {
163+ // This might happen when the widelands socket is already in use
164+ throw WLWarning(_("Failed to start the server!"),
165+ _("Widelands could not start a server.\n"
166+ "Probably some other process is already running a server on our port."));
167+ }
168+ d->promoter = new LanGamePromoter();
169 }
170- d->promoter = new LanGamePromoter();
171 d->game = nullptr;
172 d->pseudo_networktime = 0;
173 d->waiting = true;
174@@ -523,7 +534,10 @@
175
176 // close all open sockets
177 d->net.reset();
178- delete d->promoter;
179+ if (d->promoter != nullptr) {
180+ delete d->promoter;
181+ d->promoter = nullptr;
182+ }
183 delete d;
184 delete file_;
185 }
186@@ -1420,12 +1434,14 @@
187
188 // Send the packet to all properly connected clients
189 void GameHost::broadcast(SendPacket& packet) {
190+ std::vector<NetHostInterface::ConnectionId> receivers;
191 for (const Client& client : d->clients) {
192 if (client.playernum != UserSettings::not_connected()) {
193 assert(client.sock_id > 0);
194- d->net->send(client.sock_id, packet);
195+ receivers.push_back(client.sock_id);
196 }
197 }
198+ d->net->send(receivers, packet);
199 }
200
201 void GameHost::write_setting_map(SendPacket& packet) {
202@@ -2195,7 +2211,7 @@
203 }
204 }
205
206-void GameHost::send_file_part(NetHost::ConnectionId csock_id, uint32_t part) {
207+void GameHost::send_file_part(NetHostInterface::ConnectionId csock_id, uint32_t part) {
208 assert(part < file_->parts.size());
209
210 uint32_t left = file_->bytes - NETFILEPARTSIZE * part;
211
212=== modified file 'src/network/gamehost.h'
213--- src/network/gamehost.h 2017-08-11 15:30:42 +0000
214+++ src/network/gamehost.h 2017-10-17 19:31:56 +0000
215@@ -24,7 +24,7 @@
216 #include "logic/game_settings.h"
217 #include "logic/player_end_result.h"
218 #include "logic/widelands.h"
219-#include "network/nethost.h"
220+#include "network/nethost_interface.h"
221 #include "network/network.h"
222
223 struct ChatMessage;
224@@ -127,7 +127,7 @@
225
226 void handle_packet(uint32_t i, RecvPacket&);
227 void handle_network();
228- void send_file_part(NetHost::ConnectionId client_sock_id, uint32_t part);
229+ void send_file_part(NetHostInterface::ConnectionId client_sock_id, uint32_t part);
230
231 void check_hung_clients();
232 void broadcast_real_speed(uint32_t speed);
233
234=== modified file 'src/network/internet_gaming.cc'
235--- src/network/internet_gaming.cc 2017-08-16 04:31:56 +0000
236+++ src/network/internet_gaming.cc 2017-10-17 19:31:56 +0000
237@@ -603,6 +603,15 @@
238 if (waitcmd_ == IGPCMD_GAME_OPEN) {
239 waitcmd_ = "";
240 }
241+ // Save the received IP(s), so the client can connect to the game
242+ NetAddress::parse_ip(&gameips_.first, packet.string(), INTERNET_RELAY_PORT);
243+ // If the next value is true, a secondary IP follows
244+ if (packet.string() == bool2str(true)) {
245+ NetAddress::parse_ip(&gameips_.second, packet.string(), INTERNET_RELAY_PORT);
246+ }
247+ log("InternetGaming: Received ips of the relay to host: %s %s.\n",
248+ gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
249+ state_ = IN_GAME;
250 }
251
252 else if (cmd == IGPCMD_GAME_CONNECT) {
253@@ -610,10 +619,10 @@
254 assert(waitcmd_ == IGPCMD_GAME_CONNECT);
255 waitcmd_ = "";
256 // Save the received IP(s), so the client can connect to the game
257- NetAddress::parse_ip(&gameips_.first, packet.string(), WIDELANDS_PORT);
258+ NetAddress::parse_ip(&gameips_.first, packet.string(), INTERNET_RELAY_PORT);
259 // If the next value is true, a secondary IP follows
260 if (packet.string() == bool2str(true)) {
261- NetAddress::parse_ip(&gameips_.second, packet.string(), WIDELANDS_PORT);
262+ NetAddress::parse_ip(&gameips_.second, packet.string(), INTERNET_RELAY_PORT);
263 }
264 log("InternetGaming: Received ips of the game to join: %s %s.\n",
265 gameips_.first.ip.to_string().c_str(), gameips_.second.ip.to_string().c_str());
266@@ -670,11 +679,18 @@
267 return gameips_;
268 }
269
270+const std::string InternetGaming::relay_password() {
271+ return pwd_;
272+}
273+
274 /// called by a client to join the game \arg gamename
275 void InternetGaming::join_game(const std::string& gamename) {
276 if (!logged_in())
277 return;
278
279+ // Reset the game ips, we should receive new ones shortly
280+ gameips_ = std::make_pair(NetAddress(), NetAddress());
281+
282 SendPacket s;
283 s.string(IGPCMD_GAME_CONNECT);
284 s.string(gamename);
285@@ -693,13 +709,15 @@
286 if (!logged_in())
287 return;
288
289+ // Reset the game ips, we should receive new ones shortly
290+ gameips_ = std::make_pair(NetAddress(), NetAddress());
291+
292 SendPacket s;
293 s.string(IGPCMD_GAME_OPEN);
294 s.string(gamename_);
295 s.string("1024"); // Used to be maxclients, no longer used.
296 net->send(s);
297 log("InternetGaming: Client opened a game with the name %s.\n", gamename_.c_str());
298- state_ = IN_GAME;
299
300 // From now on we wait for a reply from the metaserver
301 waitcmd_ = IGPCMD_GAME_OPEN;
302
303=== modified file 'src/network/internet_gaming.h'
304--- src/network/internet_gaming.h 2017-07-01 08:22:54 +0000
305+++ src/network/internet_gaming.h 2017-10-17 19:31:56 +0000
306@@ -87,10 +87,17 @@
307 * Contains two addresses when the host supports IPv4 and IPv6, one address when the host
308 * only supports one of the protocols, no addresses when no join-request was sent to
309 * the metaserver. "No address" means a default constructed address.
310+ * Also returns the IPs of the relay server when trying to host a game.
311 * Use NetAddress::is_valid() to check whether a NetAddress has been default constructed.
312 * @return The addresses.
313 */
314 const std::pair<NetAddress, NetAddress>& ips();
315+
316+ /**
317+ * Returns the password required to connect to the relay server as host.
318+ */
319+ const std::string relay_password();
320+
321 void join_game(const std::string& gamename);
322 void open_game();
323 void set_game_playing();
324
325=== modified file 'src/network/internet_gaming_protocol.h'
326--- src/network/internet_gaming_protocol.h 2017-07-01 08:22:54 +0000
327+++ src/network/internet_gaming_protocol.h 2017-10-17 19:31:56 +0000
328@@ -32,6 +32,8 @@
329 * Used Versions:
330 * 0: Build 19 and before
331 * 1: Between build 19 and build 20 - IPv6 support added
332+ * NOCOM(Notabilis): Update this comment and the protocol version after merging net-uuid
333+ * 3: Between build 19 and build 20 - Added network relay[version supported]
334 */
335 #define INTERNET_GAMING_PROTOCOL_VERSION 1
336
337@@ -66,7 +68,15 @@
338
339 /// Metaserver connection details
340 static const std::string INTERNET_GAMING_METASERVER = "widelands.org";
341+// Default port for connecting to the metaserver
342 #define INTERNET_GAMING_PORT 7395
343+// Default port for connecting to the relay
344+#define INTERNET_RELAY_PORT 7397
345+// The following ones are only used between metaserver and relay
346+// Port used by the metaserver to contact the relay
347+// INTERNET_RELAY_RPC_PORT 7398
348+// Port used by the relay to contact the metaserver
349+// INTERNET_GAMING_RPC_PORT 7399
350
351 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
352 * CLIENT RIGHTS *
353@@ -356,12 +366,13 @@
354 * \li string: number of maximal clients
355 * \note build_id is not necessary, as this is in every way the build_id of the hosting client.
356 *
357- * Sent by the metaserver to acknowledge the startup of a new game without payload. The metaserver
358- * will
359- * list the new game, but set it as not connectable and recheck the connectability for
360- * INTERNET_GAMING_TIMEOUT ms.
361- * If the game gets connectable in time, the metaserver lists the game as connectable, else it
362- * removes the game from the list of games.
363+ * Sent by the metaserver to acknowledge the startup of a new game with the following payload:
364+ * \li string: primary ip of relay server for the game.
365+ * \li string: whether a secondary ip for the relay follows ("true" or "false" as string)
366+ * \li string: secondary ip of the relay - only valid if previous was true
367+ * The metaserver will list the new game, but set it as not connectable.
368+ * When the client connects to the relay within INTERNET_GAMING_TIMEOUT milliseconds,
369+ * the metaserver lists the game as connectable, else it removes the game from the list of games.
370 */
371 static const std::string IGPCMD_GAME_OPEN = "GAME_OPEN";
372
373
374=== modified file 'src/network/netclient.h'
375--- src/network/netclient.h 2017-07-01 08:22:54 +0000
376+++ src/network/netclient.h 2017-10-17 19:31:56 +0000
377@@ -22,6 +22,7 @@
378
379 #include <memory>
380
381+#include "network/netclient_interface.h"
382 #include "network/network.h"
383
384 /**
385@@ -30,7 +31,7 @@
386 * This class only tries to create a single socket, either for IPv4 and IPv6.
387 * Which is used depends on what kind of address is given on call to connect().
388 */
389-class NetClient {
390+class NetClient : public NetClientInterface {
391 public:
392 /**
393 * Tries to establish a connection to the given host.
394@@ -39,10 +40,6 @@
395 */
396 static std::unique_ptr<NetClient> connect(const NetAddress& host);
397
398- /**
399- * Closes the connection.
400- * If you want to send a goodbye-message to the host, do so before freeing the object.
401- */
402 ~NetClient();
403
404 /**
405@@ -53,33 +50,11 @@
406 */
407 bool get_remote_address(NetAddress* addr) const;
408
409- /**
410- * Returns whether the client is connected.
411- * \return \c true if the connection is open, \c false otherwise.
412- */
413- bool is_connected() const;
414-
415- /**
416- * Closes the connection.
417- * If you want to send a goodbye-message to the host, do so before calling this.
418- */
419- void close();
420-
421- /**
422- * Tries to receive a packet.
423- * \param packet A packet that should be overwritten with the received data.
424- * \return \c true if a packet is available, \c false otherwise.
425- * The given packet is only modified when \c true is returned.
426- * Calling this on a closed connection will return false.
427- */
428- bool try_receive(RecvPacket* packet);
429-
430- /**
431- * Sends a packet.
432- * Calling this on a closed connection will silently fail.
433- * \param packet The packet to send.
434- */
435- void send(const SendPacket& packet);
436+ // Inherited from NetClientInterface
437+ bool is_connected() const override;
438+ void close() override;
439+ bool try_receive(RecvPacket* packet) override;
440+ void send(const SendPacket& packet) override;
441
442 private:
443 /**
444
445=== added file 'src/network/netclient_interface.h'
446--- src/network/netclient_interface.h 1970-01-01 00:00:00 +0000
447+++ src/network/netclient_interface.h 2017-10-17 19:31:56 +0000
448@@ -0,0 +1,73 @@
449+/*
450+ * Copyright (C) 2008-2017 by the Widelands Development Team
451+ *
452+ * This program is free software; you can redistribute it and/or
453+ * modify it under the terms of the GNU General Public License
454+ * as published by the Free Software Foundation; either version 2
455+ * of the License, or (at your option) any later version.
456+ *
457+ * This program is distributed in the hope that it will be useful,
458+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
459+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
460+ * GNU General Public License for more details.
461+ *
462+ * You should have received a copy of the GNU General Public License
463+ * along with this program; if not, write to the Free Software
464+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
465+ *
466+ */
467+
468+#ifndef WL_NETWORK_NETCLIENT_INTERFACE_H
469+#define WL_NETWORK_NETCLIENT_INTERFACE_H
470+
471+#include <memory>
472+
473+#include "network/network.h"
474+
475+/**
476+ * NetClient manages the network connection for a network game in which this computer
477+ * participates as a client.
478+ * This class provides the interface all NetClient implementation have to follow.
479+ * Currently two implementations exists: A "real" NetClient for local games and a
480+ * NetClientProxy which relays commands over a relay server.
481+ */
482+class NetClientInterface {
483+public:
484+
485+ /**
486+ * Closes the connection.
487+ * If you want to send a goodbye-message to the host, do so before freeing the object.
488+ */
489+ virtual ~NetClientInterface() {
490+ }
491+
492+ /**
493+ * Returns whether the client is connected.
494+ * \return \c true if the connection is open, \c false otherwise.
495+ */
496+ virtual bool is_connected() const = 0;
497+
498+ /**
499+ * Closes the connection.
500+ * If you want to send a goodbye-message to the host, do so before calling this.
501+ */
502+ virtual void close() = 0;
503+
504+ /**
505+ * Tries to receive a packet.
506+ * \param packet A packet that should be overwritten with the received data.
507+ * \return \c true if a packet is available, \c false otherwise.
508+ * The given packet is only modified when \c true is returned.
509+ * Calling this on a closed connection will return false.
510+ */
511+ virtual bool try_receive(RecvPacket* packet) = 0;
512+
513+ /**
514+ * Sends a packet.
515+ * Calling this on a closed connection will silently fail.
516+ * \param packet The packet to send.
517+ */
518+ virtual void send(const SendPacket& packet) = 0;
519+};
520+
521+#endif // end of include guard: WL_NETWORK_NETCLIENT_INTERFACE_H
522
523=== added file 'src/network/netclientproxy.cc'
524--- src/network/netclientproxy.cc 1970-01-01 00:00:00 +0000
525+++ src/network/netclientproxy.cc 2017-10-17 19:31:56 +0000
526@@ -0,0 +1,133 @@
527+#include "network/netclientproxy.h"
528+
529+#include <memory>
530+
531+#include "base/log.h"
532+#include "network/relay_protocol.h"
533+
534+std::unique_ptr<NetClientProxy> NetClientProxy::connect(const NetAddress& address, const std::string& name) {
535+ std::unique_ptr<NetClientProxy> ptr(new NetClientProxy(address, name));
536+ if (ptr->conn_ != nullptr && ptr->conn_->is_connected()) {
537+ return ptr;
538+ } else {
539+ ptr.reset();
540+ return ptr;
541+ }
542+}
543+
544+NetClientProxy::~NetClientProxy() {
545+ close();
546+}
547+
548+
549+bool NetClientProxy::is_connected() const {
550+ return conn_ && conn_->is_connected();
551+}
552+
553+void NetClientProxy::close() {
554+ if (conn_ && conn_->is_connected()) {
555+ conn_->close();
556+ }
557+}
558+
559+bool NetClientProxy::try_receive(RecvPacket* packet) {
560+ receive_commands();
561+
562+ // Now check whether there is data
563+ if (received_.empty())
564+ return false;
565+
566+ *packet = std::move(received_.front());
567+ received_.pop();
568+ return true;
569+}
570+
571+void NetClientProxy::send(const SendPacket& packet) {
572+ conn_->send(RelayCommand::kToHost);
573+ conn_->send(packet);
574+}
575+
576+NetClientProxy::NetClientProxy(const NetAddress& address, const std::string& name)
577+ : conn_(NetRelayConnection::connect(address)) {
578+
579+ if (conn_ == nullptr || !conn_->is_connected()) {
580+ return;
581+ }
582+
583+ conn_->send(RelayCommand::kHello);
584+ conn_->send(kRelayProtocolVersion);
585+ conn_->send(name);
586+ conn_->send("client");
587+
588+ // Wait for answer
589+ // Don't like it.
590+ while (!conn_->peek_cmd());
591+
592+ RelayCommand cmd;
593+ conn_->receive(&cmd);
594+
595+ if (cmd != RelayCommand::kWelcome) {
596+ conn_->close();
597+ conn_.reset();
598+ return;
599+ }
600+
601+ // Check version
602+ while (!conn_->peek_uint8_t());
603+ uint8_t relay_proto_version;
604+ conn_->receive(&relay_proto_version);
605+ if (relay_proto_version != kRelayProtocolVersion) {
606+ conn_->close();
607+ conn_.reset();
608+ }
609+
610+ // Check game name
611+ while (!conn_->peek_string());
612+ std::string game_name;
613+ conn_->receive(&game_name);
614+ if (game_name != name) {
615+ conn_->close();
616+ conn_.reset();
617+ return;
618+ }
619+}
620+
621+void NetClientProxy::receive_commands() {
622+ if (!conn_->is_connected()) {
623+ return;
624+ }
625+
626+ // Receive all available commands
627+ RelayCommand cmd;
628+ conn_->peek_reset();
629+ if (!conn_->peek_cmd(&cmd)) {
630+ // No command to receive
631+ return;
632+ }
633+ switch (cmd) {
634+ case RelayCommand::kDisconnect:
635+ if (conn_->peek_string()) {
636+ // Command is completely in the buffer, handle it
637+ conn_->receive(&cmd);
638+ std::string reason;
639+ conn_->receive(&reason);
640+ // TODO(Notabilis): Handle the reason for the disconnect
641+ conn_->close();
642+ }
643+ break;
644+ case RelayCommand::kFromHost:
645+ if (conn_->peek_recvpacket()) {
646+ conn_->receive(&cmd);
647+ RecvPacket packet;
648+ conn_->receive(&packet);
649+ received_.push(std::move(packet));
650+ }
651+ break;
652+ default:
653+ // Other commands should not be possible.
654+ // Then is either something wrong with the protocol or there is an implementation mistake
655+ log("Received command code %i from relay server, do not know what to do with it\n",
656+ static_cast<uint8_t>(cmd));
657+ NEVER_HERE();
658+ }
659+}
660
661=== added file 'src/network/netclientproxy.h'
662--- src/network/netclientproxy.h 1970-01-01 00:00:00 +0000
663+++ src/network/netclientproxy.h 2017-10-17 19:31:56 +0000
664@@ -0,0 +1,74 @@
665+/*
666+ * Copyright (C) 2008-2017 by the Widelands Development Team
667+ *
668+ * This program is free software; you can redistribute it and/or
669+ * modify it under the terms of the GNU General Public License
670+ * as published by the Free Software Foundation; either version 2
671+ * of the License, or (at your option) any later version.
672+ *
673+ * This program is distributed in the hope that it will be useful,
674+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
675+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
676+ * GNU General Public License for more details.
677+ *
678+ * You should have received a copy of the GNU General Public License
679+ * along with this program; if not, write to the Free Software
680+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
681+ *
682+ */
683+
684+#ifndef WL_NETWORK_NETCLIENTPROXY_H
685+#define WL_NETWORK_NETCLIENTPROXY_H
686+
687+#include <map>
688+#include <memory>
689+
690+#include "network/netclient_interface.h"
691+#include "network/netrelayconnection.h"
692+
693+/**
694+ * NetClientProxy manages is a NetHostInterface implementation communicating to
695+ * the game clients about a relay server.
696+ */
697+class NetClientProxy : public NetClientInterface {
698+public:
699+
700+ /**
701+ * Tries to connect to the relay at the given address.
702+ * \param address The address to connect to.
703+ * \param name The name of the game.
704+ * \return A pointer to a ready \c NetClientProxy object or a nullptr if the connection failed.
705+ */
706+ static std::unique_ptr<NetClientProxy> connect(const NetAddress& address, const std::string& name);
707+
708+ /**
709+ * Closes the server.
710+ */
711+ ~NetClientProxy();
712+
713+
714+ // Inherited from NetClientInterface
715+ bool is_connected() const override;
716+ void close() override;
717+ bool try_receive(RecvPacket* packet) override;
718+ void send(const SendPacket& packet) override;
719+
720+private:
721+
722+ /**
723+ * Tries to connect to the relay at the given address.
724+ * If it fails, is_connected() will return \c false.
725+ * \param address The address to connect to.
726+ * \param name The name of the game.
727+ */
728+ explicit NetClientProxy(const NetAddress& address, const std::string& name);
729+
730+ void receive_commands();
731+
732+ std::unique_ptr<NetRelayConnection> conn_;
733+
734+ /// For each connected client, the packages that have been received from him.
735+ std::queue<RecvPacket> received_;
736+};
737+
738+#endif // end of include guard: WL_NETWORK_NETCLIENTPROXY_H
739
740=== modified file 'src/network/nethost.cc'
741--- src/network/nethost.cc 2017-06-19 18:32:06 +0000
742+++ src/network/nethost.cc 2017-10-17 19:31:56 +0000
743@@ -191,6 +191,12 @@
744 }
745 }
746
747+void NetHost::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) {
748+ for (ConnectionId id : ids) {
749+ send(id, packet);
750+ }
751+}
752+
753 NetHost::NetHost(const uint16_t port)
754 : clients_(), next_id_(1), io_service_(), acceptor_v4_(io_service_), acceptor_v6_(io_service_) {
755
756
757=== modified file 'src/network/nethost.h'
758--- src/network/nethost.h 2017-06-24 13:44:38 +0000
759+++ src/network/nethost.h 2017-10-17 19:31:56 +0000
760@@ -23,17 +23,15 @@
761 #include <map>
762 #include <memory>
763
764-#include "network/network.h"
765+#include "network/nethost_interface.h"
766
767 /**
768 * NetHost manages the client connections of a network game in which this computer
769 * participates as a server.
770- * This class tries to create sockets for IPv4 and IPv6.
771+ * This class tries to create sockets for IPv4 and IPv6 for gaming in the local network.
772 */
773-class NetHost {
774+class NetHost : public NetHostInterface {
775 public:
776- /// IDs used to enumerate the clients.
777- using ConnectionId = uint32_t;
778
779 /**
780 * Tries to listen on the given port.
781@@ -47,60 +45,30 @@
782 */
783 ~NetHost();
784
785+ // Inherited from NetHostInterface
786+ bool is_connected(ConnectionId id) const override;
787+ void close(ConnectionId id) override;
788+ bool try_accept(ConnectionId* new_id) override;
789+ bool try_receive(ConnectionId id, RecvPacket* packet) override;
790+ void send(ConnectionId id, const SendPacket& packet) override;
791+ void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
792+
793+private:
794+
795 /**
796 * Returns whether the server is started and is listening.
797 * \return \c true if the server is listening, \c false otherwise.
798 */
799+ // Feel free to make this method public if you need it
800 bool is_listening() const;
801
802 /**
803- * Returns whether the given client is connected.
804- * \param The id of the client to check.
805- * \return \c true if the connection is open, \c false otherwise.
806- */
807- bool is_connected(ConnectionId id) const;
808-
809- /**
810 * Stops listening for connections.
811 */
812+ // Feel free to make this method public if you need it
813 void stop_listening();
814
815 /**
816- * Closes the connection to the given client.
817- * \param id The id of the client to close the connection to.
818- */
819- void close(ConnectionId id);
820-
821- /**
822- * Tries to accept a new client.
823- * \param new_id The connection id of the new client will be stored here.
824- * \return \c true if a client has connected, \c false otherwise.
825- * The given id is only modified when \c true is returned.
826- * Calling this on a closed server will return false.
827- * The returned id is always greater than 0.
828- */
829- bool try_accept(ConnectionId* new_id);
830-
831- /**
832- * Tries to receive a packet.
833- * \param id The connection id of the client that should be received.
834- * \param packet A packet that should be overwritten with the received data.
835- * \return \c true if a packet is available, \c false otherwise.
836- * The given packet is only modified when \c true is returned.
837- * Calling this on a closed connection will return false.
838- */
839- bool try_receive(ConnectionId id, RecvPacket* packet);
840-
841- /**
842- * Sends a packet.
843- * Calling this on a closed connection will silently fail.
844- * \param id The connection id of the client that should be sent to.
845- * \param packet The packet to send.
846- */
847- void send(ConnectionId id, const SendPacket& packet);
848-
849-private:
850- /**
851 * Tries to listen on the given port.
852 * If it fails, is_listening() will return \c false.
853 * \param port The port to listen on.
854@@ -129,9 +97,9 @@
855
856 /// A map linking client ids to the respective data about the clients.
857 /// Client ids not in this map should be considered invalid.
858- std::map<NetHost::ConnectionId, Client> clients_;
859+ std::map<NetHostInterface::ConnectionId, Client> clients_;
860 /// The next client id that will be used
861- NetHost::ConnectionId next_id_;
862+ NetHostInterface::ConnectionId next_id_;
863 /// An io_service needed by boost.asio. Primary needed for async operations.
864 boost::asio::io_service io_service_;
865 /// The acceptor we get IPv4 connection requests to.
866
867=== added file 'src/network/nethost_interface.h'
868--- src/network/nethost_interface.h 1970-01-01 00:00:00 +0000
869+++ src/network/nethost_interface.h 2017-10-17 19:31:56 +0000
870@@ -0,0 +1,93 @@
871+/*
872+ * Copyright (C) 2008-2017 by the Widelands Development Team
873+ *
874+ * This program is free software; you can redistribute it and/or
875+ * modify it under the terms of the GNU General Public License
876+ * as published by the Free Software Foundation; either version 2
877+ * of the License, or (at your option) any later version.
878+ *
879+ * This program is distributed in the hope that it will be useful,
880+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
881+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
882+ * GNU General Public License for more details.
883+ *
884+ * You should have received a copy of the GNU General Public License
885+ * along with this program; if not, write to the Free Software
886+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
887+ *
888+ */
889+
890+#ifndef WL_NETWORK_NETHOST_INTERFACE_H
891+#define WL_NETWORK_NETHOST_INTERFACE_H
892+
893+#include "network/network.h"
894+
895+/**
896+ * A NetHost manages the client connections of a network game in
897+ * which this computer participates as a server.
898+ * This class provides the interface all NetHost implementation have to follow.
899+ * Currently two implementations exists: A "real" NetHost for local games and a
900+ * NetHostProxy which relays commands over a relay server.
901+ */
902+class NetHostInterface {
903+public:
904+ /// IDs used to enumerate the clients.
905+ using ConnectionId = uint8_t;
906+
907+ /**
908+ * Closes the server.
909+ */
910+ virtual ~NetHostInterface() {
911+ }
912+
913+ /**
914+ * Returns whether the given client is connected.
915+ * \param The id of the client to check.
916+ * \return \c true if the connection is open, \c false otherwise.
917+ */
918+ virtual bool is_connected(ConnectionId id) const = 0;
919+
920+ /**
921+ * Closes the connection to the given client.
922+ * \param id The id of the client to close the connection to.
923+ */
924+ virtual void close(ConnectionId id) = 0;
925+
926+ /**
927+ * Tries to accept a new client.
928+ * \param new_id The connection id of the new client will be stored here.
929+ * \return \c true if a client has connected, \c false otherwise.
930+ * The given id is only modified when \c true is returned.
931+ * Calling this on a closed server will return false.
932+ * The returned id is always greater than 0.
933+ */
934+ virtual bool try_accept(ConnectionId* new_id) = 0;
935+
936+ /**
937+ * Tries to receive a packet.
938+ * \param id The connection id of the client that should be received.
939+ * \param packet A packet that should be overwritten with the received data.
940+ * \return \c true if a packet is available, \c false otherwise.
941+ * The given packet is only modified when \c true is returned.
942+ * Calling this on a closed connection will return false.
943+ */
944+ virtual bool try_receive(ConnectionId id, RecvPacket* packet) = 0;
945+
946+ /**
947+ * Sends a packet.
948+ * Calling this on a closed connection will silently fail.
949+ * \param id The connection id of the client that should be sent to.
950+ * \param packet The packet to send.
951+ */
952+ virtual void send(ConnectionId id, const SendPacket& packet) = 0;
953+
954+ /**
955+ * Sends a packet to a group of clients.
956+ * Calling this on a closed connection will silently fail.
957+ * \param ids The connection ids of the clients that should be sent to.
958+ * \param packet The packet to send.
959+ */
960+ virtual void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) = 0;
961+};
962+
963+#endif // end of include guard: WL_NETWORK_NETHOST_INTERFACE_H
964
965=== added file 'src/network/nethostproxy.cc'
966--- src/network/nethostproxy.cc 1970-01-01 00:00:00 +0000
967+++ src/network/nethostproxy.cc 2017-10-17 19:31:56 +0000
968@@ -0,0 +1,258 @@
969+#include "network/nethostproxy.h"
970+
971+#include <memory>
972+
973+#include "base/log.h"
974+#include "network/relay_protocol.h"
975+
976+std::unique_ptr<NetHostProxy> NetHostProxy::connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password) {
977+ std::unique_ptr<NetHostProxy> ptr(new NetHostProxy(addresses, name, password));
978+ if (ptr->conn_ != nullptr && ptr->conn_->is_connected()) {
979+ return ptr;
980+ } else {
981+ ptr.reset();
982+ return ptr;
983+ }
984+}
985+
986+NetHostProxy::~NetHostProxy() {
987+ if (conn_ && conn_->is_connected()) {
988+ while (!clients_.empty()) {
989+ close(clients_.begin()->first);
990+ clients_.erase(clients_.begin());
991+ }
992+ conn_->close();
993+ }
994+}
995+
996+bool NetHostProxy::is_connected(const ConnectionId id) const {
997+ return clients_.count(id) > 0 && clients_.at(id).state_ == Client::State::kConnected;
998+}
999+
1000+void NetHostProxy::close(const ConnectionId id) {
1001+ auto iter_client = clients_.find(id);
1002+ if (iter_client == clients_.end()) {
1003+ // Not connected anyway
1004+ return;
1005+ }
1006+ conn_->send(RelayCommand::kDisconnectClient);
1007+ conn_->send(id);
1008+ if (iter_client->second.received_.empty()) {
1009+ // No pending messages, remove the client
1010+ clients_.erase(iter_client);
1011+ } else {
1012+ // Still messages pending. Keep the structure so the host can receive them
1013+ iter_client->second.state_ = Client::State::kDisconnected;
1014+ }
1015+}
1016+
1017+bool NetHostProxy::try_accept(ConnectionId* new_id) {
1018+ // Always read all available data into buffers
1019+ receive_commands();
1020+
1021+ for (auto& entry : clients_) {
1022+ if (entry.second.state_ == Client::State::kConnecting) {
1023+ *new_id = entry.first;
1024+ entry.second.state_ = Client::State::kConnected;
1025+ return true;
1026+ }
1027+ }
1028+ return false;
1029+}
1030+
1031+bool NetHostProxy::try_receive(const ConnectionId id, RecvPacket* packet) {
1032+ receive_commands();
1033+
1034+ // Check whether client is not (yet) connected
1035+ if (clients_.count(id) == 0 || clients_.at(id).state_ == Client::State::kConnecting)
1036+ return false;
1037+
1038+ std::queue<RecvPacket>& packet_list = clients_.at(id).received_;
1039+
1040+ // Now check whether there is data for the requested client
1041+ if (packet_list.empty()) {
1042+ // If the client is already disconnected it should not be in the map anymore
1043+ assert(clients_.at(id).state_ == Client::State::kConnected);
1044+ return false;
1045+ }
1046+
1047+ *packet = std::move(packet_list.front());
1048+ packet_list.pop();
1049+ if (packet_list.empty() && clients_.at(id).state_ == Client::State::kDisconnected) {
1050+ // If the receive buffer is empty now, remove client
1051+ clients_.erase(id);
1052+ }
1053+ return true;
1054+}
1055+
1056+void NetHostProxy::send(const ConnectionId id, const SendPacket& packet) {
1057+ std::vector<ConnectionId> vec;
1058+ vec.push_back(id);
1059+ send(vec, packet);
1060+}
1061+
1062+void NetHostProxy::send(const std::vector<ConnectionId>& ids, const SendPacket& packet) {
1063+ if (ids.empty()) {
1064+ return;
1065+ }
1066+
1067+ receive_commands();
1068+
1069+ conn_->send(RelayCommand::kToClients);
1070+ for (ConnectionId id : ids) {
1071+ if (is_connected(id)) {
1072+ // This should be but is not always the case. It can happen that we receive a client disconnect
1073+ // on receive_commands() above and the GameHost did not have the chance to react to it yet.
1074+ conn_->send(id);
1075+ }
1076+ }
1077+ conn_->send(0);
1078+ conn_->send(packet);
1079+}
1080+
1081+NetHostProxy::NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password)
1082+ : conn_(NetRelayConnection::connect(addresses.first)) {
1083+
1084+ if ((conn_ == nullptr || !conn_->is_connected()) && addresses.second.is_valid()) {
1085+ conn_ = NetRelayConnection::connect(addresses.second);
1086+ }
1087+
1088+ if (conn_ == nullptr || !conn_->is_connected()) {
1089+ return;
1090+ }
1091+
1092+ conn_->send(RelayCommand::kHello);
1093+ conn_->send(kRelayProtocolVersion);
1094+ conn_->send(name);
1095+ conn_->send(password);
1096+ conn_->send(password);
1097+
1098+ // Wait 10 seconds for an answer
1099+ uint32_t endtime = time(nullptr) + 10;
1100+ while (!conn_->peek_cmd()) {
1101+ if (time(nullptr) > endtime) {
1102+ // No message received in time
1103+ conn_->close();
1104+ conn_.reset();
1105+ return;
1106+ }
1107+ }
1108+
1109+ RelayCommand cmd;
1110+ conn_->receive(&cmd);
1111+
1112+ if (cmd != RelayCommand::kWelcome) {
1113+ conn_->close();
1114+ conn_.reset();
1115+ return;
1116+ }
1117+
1118+ // Check version
1119+ endtime = time(nullptr) + 10;
1120+ while (!conn_->peek_uint8_t()) {
1121+ if (time(nullptr) > endtime) {
1122+ // No message received in time
1123+ conn_->close();
1124+ conn_.reset();
1125+ return;
1126+ }
1127+ }
1128+ uint8_t relay_proto_version;
1129+ conn_->receive(&relay_proto_version);
1130+ if (relay_proto_version != kRelayProtocolVersion) {
1131+ conn_->close();
1132+ conn_.reset();
1133+ return;
1134+ }
1135+
1136+ // Check game name
1137+ endtime = time(nullptr) + 10;
1138+ while (!conn_->peek_string()) {
1139+ if (time(nullptr) > endtime) {
1140+ // No message received in time
1141+ conn_->close();
1142+ conn_.reset();
1143+ return;
1144+ }
1145+ }
1146+ std::string game_name;
1147+ conn_->receive(&game_name);
1148+ if (game_name != name) {
1149+ conn_->close();
1150+ conn_.reset();
1151+ return;
1152+ }
1153+}
1154+
1155+void NetHostProxy::receive_commands() {
1156+ if (!conn_->is_connected()) {
1157+ return;
1158+ }
1159+
1160+ // Receive all available commands
1161+ RelayCommand cmd;
1162+ conn_->peek_reset();
1163+ if (!conn_->peek_cmd(&cmd)) {
1164+ // No command to receive
1165+ return;
1166+ }
1167+ switch (cmd) {
1168+ case RelayCommand::kDisconnect:
1169+ if (conn_->peek_string()) {
1170+ // Command is completely in the buffer, handle it
1171+ conn_->receive(&cmd);
1172+ std::string reason;
1173+ conn_->receive(&reason);
1174+ conn_->close();
1175+ // Set all clients to offline
1176+ for (auto& entry : clients_) {
1177+ entry.second.state_ = Client::State::kDisconnected;
1178+ }
1179+ }
1180+ break;
1181+ case RelayCommand::kConnectClient:
1182+ if (conn_->peek_uint8_t()) {
1183+ conn_->receive(&cmd);
1184+ uint8_t id;
1185+ conn_->receive(&id);
1186+ // Kind of hacky, but create a new Client object inplace.
1187+ // insert() and emplace() do not work since they call the (deleted) copy constructor
1188+ // (operator[] returns the object when it exists, otherwise a new one is created)
1189+ assert(clients_.count(id) == 0);
1190+ clients_[id];
1191+ assert(clients_.count(id) == 1);
1192+ }
1193+ break;
1194+ case RelayCommand::kDisconnectClient:
1195+ if (conn_->peek_uint8_t()) {
1196+ conn_->receive(&cmd);
1197+ uint8_t id;
1198+ conn_->receive(&id);
1199+ assert(clients_.count(id));
1200+ clients_.at(id).state_ = Client::State::kDisconnected;
1201+ }
1202+ break;
1203+ case RelayCommand::kFromClient:
1204+ if (conn_->peek_uint8_t() && conn_->peek_recvpacket()) {
1205+ conn_->receive(&cmd);
1206+ uint8_t id;
1207+ conn_->receive(&id);
1208+ RecvPacket packet;
1209+ conn_->receive(&packet);
1210+ assert(clients_.count(id));
1211+ clients_.at(id).received_.push(std::move(packet));
1212+ }
1213+ break;
1214+ case RelayCommand::kPing:
1215+ conn_->receive(&cmd);
1216+ // Reply with a pong
1217+ conn_->send(RelayCommand::kPong);
1218+ break;
1219+ default:
1220+ // Other commands should not be possible.
1221+ // Then is either something wrong with the protocol or there is an implementation mistake
1222+ log("Received command code %i from relay server, do not know what to do with it\n",
1223+ static_cast<uint8_t>(cmd));
1224+ NEVER_HERE();
1225+ }
1226+}
1227
1228=== added file 'src/network/nethostproxy.h'
1229--- src/network/nethostproxy.h 1970-01-01 00:00:00 +0000
1230+++ src/network/nethostproxy.h 2017-10-17 19:31:56 +0000
1231@@ -0,0 +1,104 @@
1232+/*
1233+ * Copyright (C) 2008-2017 by the Widelands Development Team
1234+ *
1235+ * This program is free software; you can redistribute it and/or
1236+ * modify it under the terms of the GNU General Public License
1237+ * as published by the Free Software Foundation; either version 2
1238+ * of the License, or (at your option) any later version.
1239+ *
1240+ * This program is distributed in the hope that it will be useful,
1241+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1242+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1243+ * GNU General Public License for more details.
1244+ *
1245+ * You should have received a copy of the GNU General Public License
1246+ * along with this program; if not, write to the Free Software
1247+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1248+ *
1249+ */
1250+
1251+#ifndef WL_NETWORK_NETHOSTPROXY_H
1252+#define WL_NETWORK_NETHOSTPROXY_H
1253+
1254+#include <map>
1255+#include <memory>
1256+
1257+#include "network/nethost_interface.h"
1258+#include "network/netrelayconnection.h"
1259+
1260+/**
1261+ * NetHostProxy manages is a NetHostInterface implementation communicating to
1262+ * the game clients about a relay server.
1263+ */
1264+class NetHostProxy : public NetHostInterface {
1265+public:
1266+
1267+ /**
1268+ * Tries to connect to the relay at the given address.
1269+ * \param address The address to connect to.
1270+ * \param name The name of the game.
1271+ * \param password The password for connecting as host.
1272+ * \return A pointer to a ready \c NetHostProxy object or a nullptr if the connection failed.
1273+ */
1274+ static std::unique_ptr<NetHostProxy> connect(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
1275+
1276+ /**
1277+ * Closes the server.
1278+ */
1279+ ~NetHostProxy();
1280+
1281+ // Inherited from NetHostInterface
1282+ bool is_connected(ConnectionId id) const override;
1283+ void close(ConnectionId id) override;
1284+ bool try_accept(ConnectionId* new_id) override;
1285+ bool try_receive(ConnectionId id, RecvPacket* packet) override;
1286+ void send(ConnectionId id, const SendPacket& packet) override;
1287+ void send(const std::vector<ConnectionId>& ids, const SendPacket& packet) override;
1288+
1289+private:
1290+
1291+ /**
1292+ * Tries to connect to the relay at the given address.
1293+ * If it fails, is_connected() will return \c false.
1294+ * \param addresses Two possible addresses to connect to.
1295+ * \param name The name of the game.
1296+ * \param password The password for connecting as host.
1297+ */
1298+ explicit NetHostProxy(const std::pair<NetAddress, NetAddress>& addresses, const std::string& name, const std::string& password);
1299+
1300+ void receive_commands();
1301+
1302+ std::unique_ptr<NetRelayConnection> conn_;
1303+
1304+ /// A list of clients which want to connect.
1305+ std::queue<ConnectionId> accept_;
1306+
1307+ /// The clients connected through the relay
1308+ struct Client {
1309+ /// The state of the client
1310+ enum class State {
1311+ /// The relay introduced the client but try_accept() hasn't been called for it yet
1312+ kConnecting,
1313+ /// A normally connected client
1314+ kConnected,
1315+ /// The relay told us that the client disconnected but there are still packages in the buffer
1316+ kDisconnected
1317+ };
1318+
1319+ Client()
1320+ : state_(State::kConnecting), received_() {
1321+ }
1322+
1323+ // deleted since RecvPacket does not offer a copy constructor
1324+ Client(const Client& other) = delete;
1325+
1326+ /// The current connection state
1327+ State state_;
1328+ /// The packages that have been received
1329+ std::queue<RecvPacket> received_;
1330+ };
1331+ /// The connected clients
1332+ std::map<ConnectionId, Client> clients_;
1333+};
1334+
1335+#endif // end of include guard: WL_NETWORK_NETHOSTPROXY_H
1336
1337=== added file 'src/network/netrelayconnection.cc'
1338--- src/network/netrelayconnection.cc 1970-01-01 00:00:00 +0000
1339+++ src/network/netrelayconnection.cc 2017-10-17 19:31:56 +0000
1340@@ -0,0 +1,299 @@
1341+#include "network/netrelayconnection.h"
1342+
1343+#include <memory>
1344+
1345+#include "base/log.h"
1346+
1347+// Not so great: Quite some duplicated code between this class and NetClient.
1348+
1349+std::unique_ptr<NetRelayConnection> NetRelayConnection::connect(const NetAddress& host) {
1350+
1351+ std::unique_ptr<NetRelayConnection> ptr(new NetRelayConnection(host));
1352+ if (ptr->is_connected()) {
1353+ return ptr;
1354+ } else {
1355+ ptr.reset();
1356+ return ptr;
1357+ }
1358+}
1359+
1360+NetRelayConnection::~NetRelayConnection() {
1361+ if (is_connected()) {
1362+ close();
1363+ }
1364+}
1365+
1366+bool NetRelayConnection::is_connected() const {
1367+ return socket_.is_open();
1368+}
1369+
1370+void NetRelayConnection::close() {
1371+ if (!is_connected()) {
1372+ return;
1373+ }
1374+ boost::system::error_code ec;
1375+ boost::asio::ip::tcp::endpoint remote = socket_.remote_endpoint(ec);
1376+ if (!ec) {
1377+ log("[NetRelayConnection] Closing network socket connected to %s:%i.\n",
1378+ remote.address().to_string().c_str(), remote.port());
1379+ } else {
1380+ log("[NetRelayConnection] Closing network socket.\n");
1381+ }
1382+ socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
1383+ socket_.close(ec);
1384+}
1385+
1386+bool NetRelayConnection::peek_string() {
1387+ try_network_receive();
1388+
1389+ if (buffer_.size() < peek_pointer_ + 1) {
1390+ return false;
1391+ }
1392+
1393+ // A string goes until the next \0 and might have a length of 0
1394+ for (size_t i = peek_pointer_; i < buffer_.size(); ++i) {
1395+ if (buffer_[i] == '\0') {
1396+ peek_pointer_ = i + 1;
1397+ return true;
1398+ }
1399+ }
1400+ return false;
1401+}
1402+
1403+void NetRelayConnection::receive(std::string *str) {
1404+ // Try to receive something from the network.
1405+ try_network_receive();
1406+
1407+ // Reset the peek pointer
1408+ peek_reset();
1409+ #ifdef NDEBUG
1410+ // Check if we can read a complete string
1411+ assert(peek_string());
1412+ peek_reset();
1413+ #endif // NDEBUG
1414+
1415+ // Read the string
1416+ str->clear();
1417+ // No range check needed, peek_string() takes care of that
1418+ while (buffer_.front() != '\0') {
1419+ str->push_back(buffer_.front());
1420+ buffer_.pop_front();
1421+ }
1422+ // Pop the \0
1423+ buffer_.pop_front();
1424+}
1425+
1426+bool NetRelayConnection::peek_cmd(RelayCommand *cmd) {
1427+ try_network_receive();
1428+
1429+ if (buffer_.size() > peek_pointer_) {
1430+ if (cmd != nullptr) {
1431+ *cmd = static_cast<RelayCommand>(buffer_[peek_pointer_]);
1432+ }
1433+ peek_pointer_++;
1434+ return true;
1435+ } else {
1436+ return false;
1437+ }
1438+}
1439+
1440+void NetRelayConnection::receive(RelayCommand *out) {
1441+ try_network_receive();
1442+
1443+ peek_reset();
1444+ assert(!buffer_.empty());
1445+
1446+ uint8_t i;
1447+ receive(&i);
1448+ *out = static_cast<RelayCommand>(i);
1449+}
1450+
1451+bool NetRelayConnection::peek_uint8_t() {
1452+ try_network_receive();
1453+
1454+ // If there is any byte available, we can read an uint8
1455+ if (buffer_.size() > peek_pointer_) {
1456+ peek_pointer_++;
1457+ return true;
1458+ } else {
1459+ return false;
1460+ }
1461+}
1462+
1463+void NetRelayConnection::receive(uint8_t *out) {
1464+ try_network_receive();
1465+
1466+ peek_reset();
1467+ assert(!buffer_.empty());
1468+
1469+ *out = buffer_.front();
1470+ buffer_.pop_front();
1471+}
1472+
1473+bool NetRelayConnection::peek_recvpacket() {
1474+ try_network_receive();
1475+
1476+ if (buffer_.size() < peek_pointer_ + 2) {
1477+ // Not even enough space for the size
1478+ return false;
1479+ }
1480+
1481+ // RecvPackets have their size coded in their first two bytes
1482+ const uint16_t size = buffer_[peek_pointer_ + 0] << 8 | buffer_[peek_pointer_ + 1];
1483+ assert(size >= 2);
1484+
1485+ if (buffer_.size() >= peek_pointer_ + size) {
1486+ peek_pointer_ += size;
1487+ return true;
1488+ } else {
1489+ return false;
1490+ }
1491+}
1492+
1493+void NetRelayConnection::receive(RecvPacket *packet) {
1494+ try_network_receive();
1495+
1496+ peek_reset();
1497+ #ifdef NDEBUG
1498+ assert(peek_recvpacket());
1499+ peek_reset();
1500+ #endif // NDEBUG
1501+
1502+ // Read the packet (adapted copy from Deserializer)
1503+ const uint16_t size = buffer_[0] << 8 | buffer_[1];
1504+ assert(size >= 2);
1505+ assert(buffer_.size() >= size);
1506+
1507+ packet->buffer.clear();
1508+ packet->buffer.insert(packet->buffer.end(), buffer_.begin() + 2, buffer_.begin() + size);
1509+ packet->index_ = 0;
1510+
1511+ buffer_.erase(buffer_.begin(), buffer_.begin() + size);
1512+}
1513+
1514+bool NetRelayConnection::try_network_receive() {
1515+ if (!is_connected()) {
1516+ return false;
1517+ }
1518+
1519+ unsigned char buffer[kNetworkBufferSize];
1520+ boost::system::error_code ec;
1521+ size_t length = socket_.read_some(boost::asio::buffer(buffer, kNetworkBufferSize), ec);
1522+ if (!ec) {
1523+ assert(length > 0);
1524+ assert(length <= kNetworkBufferSize);
1525+ // Has read something
1526+ for (size_t i = 0; i < length; ++i) {
1527+ buffer_.push_back(buffer[i]);
1528+ }
1529+ }
1530+
1531+ if (ec && ec != boost::asio::error::would_block) {
1532+ // Connection closed or some error, close the socket
1533+ log("[NetRelayConnection] Error when trying to receive some data: %s.\n", ec.message().c_str());
1534+ close();
1535+ return false;
1536+ }
1537+ return true;
1538+}
1539+
1540+void NetRelayConnection::peek_reset() {
1541+ peek_pointer_ = 0;
1542+}
1543+
1544+void NetRelayConnection::send(const RelayCommand data) {
1545+ send(static_cast<uint8_t>(data));
1546+}
1547+
1548+void NetRelayConnection::send(const uint8_t data) {
1549+ if (!is_connected()) {
1550+ return;
1551+ }
1552+
1553+ uint8_t buf[1];
1554+ buf[0] = data;
1555+
1556+ boost::system::error_code ec;
1557+#ifdef NDEBUG
1558+ boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec);
1559+#else
1560+ size_t written =
1561+ boost::asio::write(socket_, boost::asio::buffer(buf, 1), ec);
1562+#endif
1563+
1564+ // TODO(Notabilis): This one is an assertion of mine, I am not sure if it will hold
1565+ // If it doesn't, set the socket to blocking before writing
1566+ // If it does, remove this comment after build 20
1567+ assert(ec != boost::asio::error::would_block);
1568+ assert(written == 1 || ec);
1569+ if (ec) {
1570+ log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
1571+ close();
1572+ }
1573+}
1574+
1575+void NetRelayConnection::send(const std::string& str) {
1576+ if (!is_connected()) {
1577+ return;
1578+ }
1579+
1580+ // Append \0
1581+ std::vector<boost::asio::const_buffer> buffers;
1582+ buffers.push_back(boost::asio::buffer(str));
1583+ buffers.push_back(boost::asio::buffer("\0", 1));
1584+
1585+ boost::system::error_code ec;
1586+#ifdef NDEBUG
1587+ boost::asio::write(socket_, buffers, ec);
1588+#else
1589+ size_t written =
1590+ boost::asio::write(socket_, buffers, ec);
1591+#endif
1592+
1593+ assert(ec != boost::asio::error::would_block);
1594+ assert(written == str.length() + 1 || ec);
1595+ if (ec) {
1596+ log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
1597+ close();
1598+ }
1599+}
1600+
1601+void NetRelayConnection::send(const SendPacket& packet) {
1602+ if (!is_connected()) {
1603+ return;
1604+ }
1605+
1606+ boost::system::error_code ec;
1607+#ifdef NDEBUG
1608+ boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
1609+#else
1610+ size_t written =
1611+ boost::asio::write(socket_, boost::asio::buffer(packet.get_data(), packet.get_size()), ec);
1612+#endif
1613+
1614+ assert(ec != boost::asio::error::would_block);
1615+ assert(written == packet.get_size() || ec);
1616+ if (ec) {
1617+ log("[NetRelayConnection] Error when trying to send some data: %s.\n", ec.message().c_str());
1618+ close();
1619+ }
1620+}
1621+
1622+NetRelayConnection::NetRelayConnection(const NetAddress& host)
1623+ : io_service_(), socket_(io_service_), buffer_(), peek_pointer_(0) {
1624+
1625+ assert(host.is_valid());
1626+ const boost::asio::ip::tcp::endpoint destination(host.ip, host.port);
1627+
1628+ log("[NetRelayConnection]: Trying to connect to %s:%u ... ", host.ip.to_string().c_str(), host.port);
1629+ boost::system::error_code ec;
1630+ socket_.connect(destination, ec);
1631+ if (!ec && is_connected()) {
1632+ log("success.\n");
1633+ socket_.non_blocking(true);
1634+ } else {
1635+ log("failed.\n");
1636+ socket_.close();
1637+ assert(!is_connected());
1638+ }
1639+}
1640
1641=== added file 'src/network/netrelayconnection.h'
1642--- src/network/netrelayconnection.h 1970-01-01 00:00:00 +0000
1643+++ src/network/netrelayconnection.h 2017-10-17 19:31:56 +0000
1644@@ -0,0 +1,200 @@
1645+/*
1646+ * Copyright (C) 2008-2017 by the Widelands Development Team
1647+ *
1648+ * This program is free software; you can redistribute it and/or
1649+ * modify it under the terms of the GNU General Public License
1650+ * as published by the Free Software Foundation; either version 2
1651+ * of the License, or (at your option) any later version.
1652+ *
1653+ * This program is distributed in the hope that it will be useful,
1654+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1655+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1656+ * GNU General Public License for more details.
1657+ *
1658+ * You should have received a copy of the GNU General Public License
1659+ * along with this program; if not, write to the Free Software
1660+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1661+ *
1662+ */
1663+
1664+#ifndef WL_NETWORK_NETRELAYCONNECTION_H
1665+#define WL_NETWORK_NETRELAYCONNECTION_H
1666+
1667+#include <memory>
1668+
1669+#include "network/network.h"
1670+#include "network/relay_protocol.h"
1671+
1672+/**
1673+ * A wrapper around a network connection to the netrelay.
1674+ * Does not contain logic but provides a buffer to read
1675+ * uint8_t / std::string / SendPacket from the network stream.
1676+ *
1677+ * Additionally, the peek_* methods allow to check the contents of the buffer. They will not remove any bytes,
1678+ * but will check whether the required value can be read. After checking, an internal peek-pointer will be
1679+ * incremented. The next call of a peek_* method will then start reading after the previous value.
1680+ * Calling peek_reset() will restore the pointer to the start of the buffer. Only a successful peek will
1681+ * move the pointer.
1682+ * Note that a successful peek does not mean that the received bytes really are of the requested type, it only
1683+ * means they could be interpreted that way. Whether the type matches is in the responsibility of the caller.
1684+ * The idea of this methods is that the caller can check whether a complete relay command can be received
1685+ * before starting to remove data from the buffer. Otherwise, the caller would have to maintain an own buffer.
1686+ */
1687+class NetRelayConnection {
1688+public:
1689+ /**
1690+ * Tries to establish a connection to the given relay.
1691+ * \param host The host to connect to.
1692+ * \return A pointer to a connected \c NetRelayConnection object or a \c nullptr.
1693+ */
1694+ static std::unique_ptr<NetRelayConnection> connect(const NetAddress& host);
1695+
1696+ /**
1697+ * Closes the connection.
1698+ * If you want to send a goodbye-message to the relay, do so before freeing the object.
1699+ */
1700+ ~NetRelayConnection();
1701+
1702+ /**
1703+ * Returns whether the relay is connected.
1704+ * \return \c true if the connection is open, \c false otherwise.
1705+ */
1706+ bool is_connected() const;
1707+
1708+ /**
1709+ * Closes the connection.
1710+ * If you want to send a goodbye-message to the relay, do so before calling this.
1711+ */
1712+ void close();
1713+
1714+ /**
1715+ * Checks whether a relay command can be read from the buffer.
1716+ * This method does not modify the buffer contents but increases the peek-pointer.
1717+ * \param cmd The command that will be returned next. It will not be removed from the input queue!
1718+ * Can be nullptr.
1719+ * \return \c True if the value can be read, \c false if not enough data has been received.
1720+ */
1721+ bool peek_cmd(RelayCommand *cmd = nullptr);
1722+
1723+ /**
1724+ * Checks whether an uint8_t can be read from the buffer.
1725+ * This method does not modify the buffer contents but increases the peek-pointer.
1726+ * \return \c True if the value can be read, \c false if not enough data has been received.
1727+ */
1728+ bool peek_uint8_t();
1729+
1730+ /**
1731+ * Checks whether a std::string can be read from the buffer.
1732+ * This method does not modify the buffer contents but increases the peek-pointer.
1733+ * \return \c True if the value can be read, \c false if not enough data has been received.
1734+ */
1735+ bool peek_string();
1736+
1737+ /**
1738+ * Checks whether a RecvPacket can be read from the buffer.
1739+ * This method does not modify the buffer contents but increases the peek-pointer.
1740+ * \return \c True if the value can be read, \c false if not enough data has been received.
1741+ */
1742+ bool peek_recvpacket();
1743+
1744+ /**
1745+ * Resets the peek pointer to the beginning of the buffer.
1746+ */
1747+ void peek_reset();
1748+
1749+ /**
1750+ * Receive a command.
1751+ * \warning Calling this method is only safe when peek_cmd() returned \c true.
1752+ * Otherwise the behavior of this method is undefined.
1753+ *
1754+ * Calling this methods resets the peek-pointer.
1755+ * \param out The variable to write the value to.
1756+ */
1757+ void receive(RelayCommand *out);
1758+
1759+ /**
1760+ * Receive an uint8_t.
1761+ * \warning Calling this method is only safe when peek_uint8_t() returned \c true.
1762+ * Otherwise the behavior of this method is undefined.
1763+ *
1764+ * Calling this methods resets the peek-pointer.
1765+ * \param out The variable to write the value to.
1766+ */
1767+ void receive(uint8_t *out);
1768+
1769+ /**
1770+ * Receive a string.
1771+ * \warning Calling this method is only safe when peek_string() returned \c true.
1772+ * Otherwise the behavior of this method is undefined.
1773+ *
1774+ * Calling this methods resets the peek-pointer.
1775+ * \param out The variable to write the value to.
1776+ */
1777+ void receive(std::string *out);
1778+
1779+ /**
1780+ * Receive a RecvPacket.
1781+ * \warning Calling this method is only safe when peek_recvpacket() returned \c true.
1782+ * Otherwise the behavior of this method is undefined.
1783+ *
1784+ * Calling this methods resets the peek-pointer.
1785+ * \param out The variable to write the value to.
1786+ */
1787+ void receive(RecvPacket *out);
1788+
1789+ /**
1790+ * Sends a relay command.
1791+ * Calling this on a closed connection will silently fail.
1792+ * \param data The data to send.
1793+ */
1794+ void send(RelayCommand data);
1795+
1796+ /**
1797+ * Sends an uint8_t.
1798+ * Calling this on a closed connection will silently fail.
1799+ * \param data The data to send.
1800+ */
1801+ void send(uint8_t data);
1802+
1803+ /**
1804+ * Sends a string.
1805+ * Calling this on a closed connection will silently fail.
1806+ * \param data The data to send.
1807+ */
1808+ void send(const std::string& data);
1809+
1810+ /**
1811+ * Sends a packet.
1812+ * Calling this on a closed connection will silently fail.
1813+ * \param data The data to send.
1814+ */
1815+ void send(const SendPacket& data);
1816+
1817+private:
1818+ /**
1819+ * Tries to establish a connection to the given host.
1820+ * If the connection attempt failed, is_connected() will return \c false.
1821+ * \param host The host to connect to.
1822+ */
1823+ explicit NetRelayConnection(const NetAddress& host);
1824+
1825+ /**
1826+ * Reads data from network.
1827+ * \return \c False if an error occurred.
1828+ */
1829+ bool try_network_receive();
1830+
1831+ /// An io_service needed by boost.asio. Primarily needed for asynchronous operations.
1832+ boost::asio::io_service io_service_;
1833+
1834+ /// The socket that connects us to the relay.
1835+ boost::asio::ip::tcp::socket socket_;
1836+
1837+ /// Buffer for arriving data. We need to store it until we have enough to return the required type.
1838+ std::deque<unsigned char> buffer_;
1839+
1840+ /// The position of the next peek.
1841+ size_t peek_pointer_;
1842+};
1843+
1844+#endif // end of include guard: WL_NETWORK_NETRELAYCONNECTION_H
1845
1846=== modified file 'src/network/network.cc'
1847--- src/network/network.cc 2017-07-05 19:21:57 +0000
1848+++ src/network/network.cc 2017-10-17 19:31:56 +0000
1849@@ -171,6 +171,8 @@
1850 if (buffer.empty()) {
1851 buffer.push_back(0); // this will finally be the length of the packet
1852 buffer.push_back(0);
1853+ // Attention! These bytes are also used by the network relay protocol.
1854+ // So if they are removed the protocol has to be updated
1855 }
1856
1857 for (size_t idx = 0; idx < size; ++idx)
1858@@ -200,6 +202,17 @@
1859
1860 /*** class RecvPacket ***/
1861
1862+RecvPacket::RecvPacket(RecvPacket&& other) {
1863+ index_ = other.index_;
1864+ buffer.swap(other.buffer);
1865+}
1866+
1867+RecvPacket& RecvPacket::operator=(RecvPacket&& other) {
1868+ index_ = other.index_;
1869+ buffer.swap(other.buffer);
1870+ return *this;
1871+}
1872+
1873 size_t RecvPacket::data(void* const packet_data, size_t const bufsize) {
1874 if (index_ + bufsize > buffer.size())
1875 throw wexception("Packet too short");
1876
1877=== modified file 'src/network/network.h'
1878--- src/network/network.h 2017-08-09 17:55:34 +0000
1879+++ src/network/network.h 2017-10-17 19:31:56 +0000
1880@@ -166,11 +166,15 @@
1881 */
1882 struct RecvPacket : public StreamRead {
1883 public:
1884+ RecvPacket() = default;
1885+ RecvPacket(RecvPacket&& other);
1886+ RecvPacket& operator=(RecvPacket&& other);
1887 size_t data(void* data, size_t bufsize) override;
1888 bool end_of_file() const override;
1889
1890 private:
1891 friend class Deserializer;
1892+ friend class NetRelayConnection;
1893 std::vector<uint8_t> buffer;
1894 size_t index_ = 0U;
1895 };
1896
1897=== added file 'src/network/relay_protocol.h'
1898--- src/network/relay_protocol.h 1970-01-01 00:00:00 +0000
1899+++ src/network/relay_protocol.h 2017-10-17 19:31:56 +0000
1900@@ -0,0 +1,222 @@
1901+/*
1902+ * Copyright (C) 2012-2017 by the Widelands Development Team
1903+ *
1904+ * This program is free software; you can redistribute it and/or
1905+ * modify it under the terms of the GNU General Public License
1906+ * as published by the Free Software Foundation; either version 2
1907+ * of the License, or (at your option) any later version.
1908+ *
1909+ * This program is distributed in the hope that it will be useful,
1910+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1911+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1912+ * GNU General Public License for more details.
1913+ *
1914+ * You should have received a copy of the GNU General Public License
1915+ * along with this program; if not, write to the Free Software
1916+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1917+ *
1918+ */
1919+
1920+#ifndef WL_NETWORK_RELAY_PROTOCOL_H
1921+#define WL_NETWORK_RELAY_PROTOCOL_H
1922+
1923+/**
1924+ * Before the internet gaming relay was added, (a) NetHost and (multiple) NetClient established a direct
1925+ * connection and exchanged data packets created and processed by the GameHost/GameClient. With the
1926+ * introduction of the relay this changes: GameHost/GameClient still create and process the data packets.
1927+ * For LAN games, they still pass them to the NetHost/NetClient.
1928+ * For internet games, the traffic is relayed by the metaserver. GameHost/GameClient pass their packets to
1929+ * the new classes NetHostProxy/NetClientProxy. Those no longer have a direct connection but are both
1930+ * connected to the relay on the metaserver. When they want to exchange messages, they send their packets
1931+ * to the relay which forwards them to the intended recipient.
1932+ * The relay only transport the packets, it does not run any game logic. The idea of this is that the
1933+ * relay runs on an globally reachable computer (i.e. the one of the metaserver) and simplifies
1934+ * connectivity for the users (i.e. no more port forwarding required).
1935+ *
1936+ * Below are the command codes used in the relay protocol. They are used on the connection
1937+ * between NetHostProxy/NetClientProxy and relay.
1938+ *
1939+ * An example of a typical session:
1940+ * 1) A user wants to start an internet game. The Widelands instance tells the metaserver about it.
1941+ * 2) A relay instance is started by the metaserver. On startup of the relay, the nonce of the player is set
1942+ * as password for the game-host position in the new game. Additionally, the name of the hosted game
1943+ * is stored. The IP address of the relay is send to the Widelands instance by the metaserver.
1944+ * 3) The Widelands instance of the user connects to the relay.
1945+ * 4) Now there is a reachable relay/game running somewhere. The open game can be listed in the lobby.
1946+ * 5) Clients get the address and port of the relay by the metaserver and can connect to the game.
1947+ * When enough clients have connected, the GameHost can start the game.
1948+ * 6) When a client wants to send a packet (e.g. user input) to the GameHost, its GameClient packs the packet,
1949+ * passes it to the local NetClientProxy which sends it to the relay. The relay relays the packet to the
1950+ * NetHostProxy which passes it to the GameHost.
1951+ * 7) When the GameHost wants to send a packet to one or all clients, it also packs it and passes it to its
1952+ * NetHostProxy which sends it to the relay, where it is forwarded to the client(s).
1953+ */
1954+
1955+
1956+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1957+ * CONSTANTS
1958+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1959+
1960+/**
1961+ * The current version of the network protocol.
1962+ * Protocol versions must match on all systems.
1963+ * Used versions:
1964+ * 1: Initial version between build 19 and build 20
1965+ */
1966+constexpr uint8_t kRelayProtocolVersion = 1;
1967+
1968+/**
1969+ * The commands used by the relay.
1970+ *
1971+ * If a command is followed by parameters, they are listed in the description.
1972+ * Most of these commands do not require any reaction by the receiver.
1973+ * Exceptions are described at the respective commands.
1974+ *
1975+ * Parameter types:
1976+ *
1977+ * Strings are NULL-terminated.
1978+ *
1979+ * Transmitting data packets:
1980+ *
1981+ * The main work of the relay consists of passing unstructured data packets between GameHost
1982+ * and GameClients. They send SendPacket's over the network and expect to receive RecvPacket's
1983+ * (see network.h). Those packets basically consists of an array of uint8_t with the first two bytes
1984+ * coding the length of the packet.
1985+ * When in the following documentation the \c packet datatype is listed as payload data, such a packet
1986+ * is meant. Since they are already containing their own length, no explicit length field or separator
1987+ * is required to pack them. When sending they are just written out. When receiving, the Deserializer
1988+ * has to read the length first and read the appropriate amount of bytes afterwards.
1989+ */
1990+/**
1991+ * This protocol is a binary protocol (as in: not human readable). The receiver reads only the first byte
1992+ * of the received data to determine which command has been send. Based on that, it knows whether to expect
1993+ * a string / an uint8 / a SendPacket. Based on this, it can read until NULL / one byte / as much
1994+ * as the next two bytes specify. When all parameters have been read the next command starts
1995+ * without any separator.
1996+ */
1997+// If anyone removes a command: Please leave a comment which command with which value was removed
1998+enum class RelayCommand : uint8_t {
1999+ // Value 0 should not be used
2000+
2001+ /**
2002+ * Commands send by/to all participants.
2003+ */
2004+ /// \{
2005+ /**
2006+ * Sent by the NetHostProxy or NetClientProxy on connect to the relay.
2007+ * Has the following payload:
2008+ * \li unsigned_8: Protocol version.
2009+ * \li string: Game name.
2010+ * \li string: For the host: Password that was set on start of the relay.
2011+ * For clients/observers: String "client".
2012+ *
2013+ * Is answered by kWelcome or kDisconnect (if a parameter is wrong/unknown).
2014+ */
2015+ kHello = 1,
2016+
2017+ /**
2018+ * Send by the relay to answer a kHello command.
2019+ * Confirms the successful connection with the relay. In the case of a connecting NetClientProxy,
2020+ * this does not mean that it can participate on the game. This decision is up to the GameHost as
2021+ * soon as it learns about the new client.
2022+ * Payload:
2023+ * \li unsigned_8: The protocol version. Should be the same as the one send in the kHello.
2024+ * \li string: The name of the hosted game as shown in the internet lobby.
2025+ * The client might want to check this.
2026+ *
2027+ * This command and its parameters are not really necessary. But it might be nice to have at least some
2028+ * confirmation that we have a connection to a (and the right) relay and not to some other server.
2029+ */
2030+ kWelcome = 2,
2031+
2032+ /**
2033+ * Can be sent by any participant.
2034+ * Might be the result of a protocol violation, an invalid password on connect of the NetHostProxy
2035+ * or a regular disconnect (e.g. the game is over).
2036+ * After sending or receiving this command, the TCP connection should be closed.
2037+ * \note When the game host sends its kDisconnect message, the relay will shut down.
2038+ * Payload:
2039+ * \li string: An error code describing the reason of the disconnect. Valid values:
2040+ * NORMAL: Regular disconnect (game has ended, client leaves, ...);
2041+ * RELAY_SHUTDOWN: Relay shuts down for some reason;
2042+ * PROTOCOL_VIOLATION: Some protocol error (unknown command, invalid parameters, ...);
2043+ * WRONG_VERSION: The version in the kHello packet is not supported;
2044+ * GAME_UNKNOWN: Game name provided in kHello packet is unknown;
2045+ * NO_HOST: No host is connected to the relay yet;
2046+ * INVALID_CLIENT: Host tried to send a message to a non-existing client
2047+ */
2048+ kDisconnect = 3,
2049+ /// \}
2050+
2051+ /**
2052+ * Communication between relay and NetHostProxy.
2053+ */
2054+ /// \{
2055+
2056+ /**
2057+ * Send by the relay to the NetHostProxy to inform that a client established a connection to the relay.
2058+ * Payload:
2059+ * \li unsigned_8: An id to represent the new client.
2060+ */
2061+ kConnectClient = 11,
2062+
2063+ /**
2064+ * If send by the NetHostProxy, tells the relay to close the connection to a client.
2065+ * If send by the relay, informs the NetHostProxy that the connection to a client has been lost.
2066+ * Payload:
2067+ * \li unsigned_8: The id of the client.
2068+ */
2069+ kDisconnectClient = 12,
2070+
2071+ /**
2072+ * The NetHostProxy sends a message to a connected client over the relay.
2073+ * Payload:
2074+ * \li NULL terminated list of unsigned_8: The ids of the clients.
2075+ * \li packet: The SendPacket to relay.
2076+ */
2077+ kToClients = 13,
2078+
2079+ /**
2080+ * The relay transmits a packet from a client to the NetHostProxy.
2081+ * Payload:
2082+ * \li unsigned_8: The id of the client.
2083+ * \li packet: The SendPacket to relay.
2084+ */
2085+ kFromClient = 14,
2086+
2087+ /**
2088+ * The relay sends this message to check for the presence of the NetHostProxy.
2089+ * Any message is acceptable as response.
2090+ */
2091+ kPing = 15,
2092+
2093+ /**
2094+ * The NetHostProxy replies with this message to a kPing when it has nothing
2095+ * else to talk about.
2096+ */
2097+ kPong = 16,
2098+ /// \}
2099+
2100+ /**
2101+ * Communication between relay and NetHostProxy.
2102+ */
2103+ /// \{
2104+
2105+ /**
2106+ * The NetClientProxy sends a message to the NetHostProxy over the relay.
2107+ * Direct communication between clients is not supported.
2108+ * Payload:
2109+ * \li packet: The SendPacket to relay.
2110+ */
2111+ kToHost = 21,
2112+
2113+ /**
2114+ * The relay transmits a packet from a NetHostProxy to the NetClientProxy.
2115+ * Payload:
2116+ * \li packet: The SendPacket to relay.
2117+ */
2118+ kFromHost = 22
2119+ /// \}
2120+};
2121+
2122+#endif // end of include guard: WL_NETWORK_RELAY_PROTOCOL_H
2123
2124=== modified file 'src/ui_fsmenu/internet_lobby.cc'
2125--- src/ui_fsmenu/internet_lobby.cc 2017-06-20 19:55:32 +0000
2126+++ src/ui_fsmenu/internet_lobby.cc 2017-10-17 19:31:56 +0000
2127@@ -349,30 +349,40 @@
2128 }
2129 }
2130
2131+bool FullscreenMenuInternetLobby::wait_for_ip() {
2132+ // Wait until the metaserver provided us with an IP address
2133+ uint32_t const secs = time(nullptr);
2134+ while (!InternetGaming::ref().ips().first.is_valid()) {
2135+ InternetGaming::ref().handle_metaserver_communication();
2136+ // give some time for the answer + for a relogin, if a problem occurs.
2137+ if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) {
2138+ // Show a popup warning message
2139+ const std::string warning(
2140+ _("Widelands was unable to get the IP address of the server in time. "
2141+ "There seems to be a network problem, either on your side or on the side "
2142+ "of the server.\n"));
2143+ UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
2144+ UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
2145+ mmb.run<UI::Panel::Returncodes>();
2146+ InternetGaming::ref().set_error();
2147+ return false;
2148+ }
2149+ }
2150+ return true;
2151+}
2152+
2153 /// called when the 'join game' button was clicked
2154 void FullscreenMenuInternetLobby::clicked_joingame() {
2155 if (opengames_list_.has_selection()) {
2156 InternetGaming::ref().join_game(opengames_list_.get_selected().name);
2157
2158- uint32_t const secs = time(nullptr);
2159- while (!InternetGaming::ref().ips().first.is_valid()) {
2160- InternetGaming::ref().handle_metaserver_communication();
2161- // give some time for the answer + for a relogin, if a problem occurs.
2162- if ((INTERNET_GAMING_TIMEOUT * 5 / 3) < time(nullptr) - secs) {
2163- // Show a popup warning message
2164- const std::string warning(
2165- _("Widelands was unable to get the IP address of the server in time.\n"
2166- "There seems to be a network problem, either on your side or on the side\n"
2167- "of the server.\n"));
2168- UI::WLMessageBox mmb(this, _("Connection timed out"), warning,
2169- UI::WLMessageBox::MBoxType::kOk, UI::Align::kLeft);
2170- mmb.run<UI::Panel::Returncodes>();
2171- return InternetGaming::ref().set_error();
2172- }
2173+ if (!wait_for_ip()) {
2174+ return;
2175 }
2176 const std::pair<NetAddress, NetAddress>& ips = InternetGaming::ref().ips();
2177
2178- GameClient netgame(ips, InternetGaming::ref().get_local_clientname(), true);
2179+ GameClient netgame(ips, InternetGaming::ref().get_local_clientname(),
2180+ true, opengames_list_.get_selected().name);
2181 netgame.run();
2182 } else
2183 throw wexception("No server selected! That should not happen!");
2184@@ -396,6 +406,16 @@
2185
2186 // Start the game
2187 try {
2188+
2189+ // Tell the metaserver about it
2190+ InternetGaming::ref().open_game();
2191+
2192+ // Wait for his response with the IPs of the relay server
2193+ if (!wait_for_ip()) {
2194+ return;
2195+ }
2196+
2197+ // Start our relay host
2198 GameHost netgame(InternetGaming::ref().get_local_clientname(), true);
2199 netgame.run();
2200 } catch (...) {
2201
2202=== modified file 'src/ui_fsmenu/internet_lobby.h'
2203--- src/ui_fsmenu/internet_lobby.h 2017-01-25 18:55:59 +0000
2204+++ src/ui_fsmenu/internet_lobby.h 2017-10-17 19:31:56 +0000
2205@@ -75,6 +75,7 @@
2206 void server_doubleclicked();
2207
2208 void change_servername();
2209+ bool wait_for_ip();
2210 void clicked_joingame();
2211 void clicked_hostgame();
2212

Subscribers

People subscribed via source and target branches

to status/vote changes: