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

Proposed by Notabilis
Status: Merged
Merged at revision: 8503
Proposed branch: lp:~widelands-dev/widelands/net-uuid
Merge into: lp:widelands
Diff against target: 844 lines (+265/-165)
13 files modified
src/CMakeLists.txt (+1/-0)
src/network/CMakeLists.txt (+3/-0)
src/network/crypto.cc (+25/-0)
src/network/crypto.h (+36/-0)
src/network/internet_gaming.cc (+77/-80)
src/network/internet_gaming.h (+23/-2)
src/network/internet_gaming_protocol.h (+53/-80)
src/random/random.cc (+10/-0)
src/random/random.h (+6/-0)
src/ui_fsmenu/CMakeLists.txt (+1/-0)
src/ui_fsmenu/internet_lobby.cc (+5/-2)
src/ui_fsmenu/multiplayer.cc (+5/-1)
src/wlapplication.cc (+20/-0)
To merge this branch: bzr merge lp:~widelands-dev/widelands/net-uuid
Reviewer Review Type Date Requested Status
SirVer Approve
GunChleoc Approve
Review via email: mp+332264@code.launchpad.net

Commit message

Improved the relogin code by adding semi-constant - 12 hour valid - user ids.
Also improved doubled logins with registered accounts.

Description of the change

Improved the relogin code by adding semi-constant user ids.

Reconnecting to the same name on the metaserver should now work for most cases, even when the complete client computer crashes. The id is used to recognize the user and assign his old name to him instead of giving him the requested name. When the user does a clean logout the reservation is removed.

If logging in with a registered account a second time, a new name will be assigned to the new client automatically instead of displaying an error message.

This branch should be merged at the same time as deploying the related branch[1] of the metaserver, since compatibility with the current metaserver version breaks.

[1] https://github.com/widelands/widelands_metaserver/pull/13

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

Continuous integration builds have changed state:

Travis build 2704. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/287726199.
Appveyor build 2519. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_uuid-2519.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Looks mostly OK - I have added some nits.

We should test to make sure that 2 Widelands instances can be run from the same computer and join the same game. I need to figure out how to run a metaserver.

Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the review! And for the grammar errors... ehem.

After some testing I am no longer so sure about the trimming of chat messages. Currently it is possible to send space-only messages to everyone or as a whisper message to a single player. So we should probably remove trailing (and leading?) space from all messages. Shall I do so? (A slight disadvantage would be that I can no longer explain the @whisper syntax by prepending it with a space.)

The first NOCOM is related to the fact that the SendMessage m is written to but the SendMessage s is send. It looks pretty broken to me, but has no-one ever tried to send an announcement and discovered this bug? I would just like to have a short confirmation that this is a bug (looking at the code is good enough for me).

As far as I understand it (language-wise), sent_uuid is correct. The variable is a boolean specifying whether the uuid should be sent, not the uuid itself. (The checkbox in the login dialog.)

I can't reproduce the bug of the second NOCOM, probably it was something unrelated to the code. Maybe on layer 8.

All on my local computer: Starting a metaserver and three clients. All Clients try to login with the name "Notabilis", second and third clients receive the names "Notabilis1" and "Notabilis2" respectively. First client opens a game, other two join it. As far as I can tell, everything worked fine.
If this answers your test, fine. But feel free to do some tests on your own. If you want me to setup a metaserver for it, just say so. (Hint: There is a --metaserver=IP command line switch in the game. Took me much too long to notice it.)

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2747. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/296944647.
Appveyor build 2559. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_uuid-2559.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I opened a new issue over at the metaserver, because I don't know how to run one. So, I'll just rely on your testing here.

As to the 2 NOCOMs, best open new bugs for them and note that they need investigating whether they are bugs.

As to the trimming, you could use it for an .empty() check only to prevent sending empty messages. The font renderer does some trimming on its own, so it shouldn't be an issue when the messages are displayed. For explaining the @whisper command, you could also rephrase it to have the explanation start with "Use @whisper at the beginning of the line to... "

Feel free to merge and deploy this once your'e ready.

review: Approve
Revision history for this message
Notabilis (notabilis27) wrote :

The NOCOM-bug is really a bug and has been fixed. I fear I introduced it myself a few month ago.
Trimming is also added now. Space is removed in front and at the end of messages. When this results in an empty message, it is dropped. This is also done with the message-part of the @whisper, /motd and /announcement commands.

As far as I am concerned, this can be merged as soon as the metaserver is ready.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Changes LGTM :)

Revision history for this message
SirVer (sirver) wrote :

Reviewing now. Sorry that this waited so long for me.

1) third_party/crypto is under "public domain" which is not a license in most parts of the world. I expect issues with debian about this. Can we ask the author to relicense under Apache 2 or MIT? Thes licenses are mostly what the author intended with public domain, but more formal. We can merge this before that happens of course.

2) I am not a fan of 'send_uuid' as a variable name - its semantics are unclear. I do not understand if it means: 'client expects server to send a uuid' or 'server expects client to send a uuid'. Maybe that becomes clearer while I read on, but a better name would be appreciated.

3) internet_gaming_protocol.h: remove all mentions of the nounce implementation? e.g. you write that the UUID replaces the nounce. But the nounce was only an interim version of the metaserver that we no longer support - so why keep this historic backage in the documentation?

I added some consts, and a few clarifying comments. After addressing these changes this lgtm.

Will review the go code next.

review: Needs Fixing
Revision history for this message
Notabilis (notabilis27) wrote :

Thanks for the review!

1) I replaced the third party crypto code with a call to a boost function. This means that instead of SHA-3 it uses SHA-1 now. This is less secure (not that it matters in this case) but SHA-1 will most likely be needed in a future branch of mine anyway. A slight problem with the boost function: It is part of a ::detail:: namespace so it might become removed at some time.

2) I renamed send_uuid to use_permanent_uuid. Not sure if this is better. What is an improvement is that I removed the variable from the InternetGaming class. It does not need to now whether it is a temporary or permanent ID and calling classes can handle this themselves.

3) Done.

Another point: I replaced my primitive "random string" code with boost::uuid. And then I realized that a simple random number would be just as good. So we can remove the random_string() function if we want to.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2836. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/306952777.
Appveyor build 2646. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_uuid-2646.

Revision history for this message
SirVer (sirver) wrote :

1) SHA1 is sufficient for our crypto needs for sure. And using a boost function is preferable for now too. We deal with it going away once it happens. Thanks!

2) I was thinking about this a bit and I suggest removing the option and always use the "permanent", i.e. 24h valid UUID. We could reduce the 24 hours to 2h or 5h or so to make sure the UUID shuffles daily and does not become personally identifiable information.

Its only use is to make reconnecting possible with the same nickname, for this we do not need the uuid to stay alive long - it must basically survive the crash and restart of Widelands.

The option is very confusing for users - they need to know what a UUID is and understand how it is used inside of the metaserver. That is an impossible ask. Also, relogin into the same ID is expected behavior for users, so even those that would opt out would still not be happy that they get a new ID assigned on relogin.

So: Get rid of the option and make the behavior default?

3) Yes please! I like uuid() much better and it conveys the semantic better than random_string(). I also like that we use a true uuid in the places where we call our variables uuid. Much better!

Revision history for this message
Notabilis (notabilis27) wrote :

2) Would be fine by me. The "temporary"-solution got in before the 24h lifetime was suggested by GunChleoc and I never reconsidered it. So it can go now. Reducing the lifetime can be done, too, but 2 hours is probably not enough. The timestamp is stored when starting the game so when I have the game running for over 2 hours and it crashes then a new UUID will be generated. So maybe something like 12 hours?

3) I don't really understand what you mean here. I am now using boost::uuid() which seems to be fine. I am not sure whether something else should be changed. Do you want me to drop the string/uuid-string and use a simple integer? Or rename random_string() to generate_uuid()? Or... ?

Revision history for this message
SirVer (sirver) wrote :

2) 12h sounds good to me. We could go more fancy and rewrite the last_started_timestamp every few real world seconds, but just doing 12h is fine IMHO.

3) Sorry, that was unclear from me. I suggest renaming the function. I did this real quick while thinking how it should be named and investigating if the boost generator could be static in the function. I missed that during review - c++ global variables should be avoided like the plague. https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables

Revision history for this message
Notabilis (notabilis27) wrote :

The option to use a temporary UUID has been removed and the interval for the "permanent" ones reduced to 12 hours. So I guess this branch is ready for merge now?

Thanks for the link, will be an interesting read. Is that the style guide Widelands (or you?) are trying to follow?

Revision history for this message
SirVer (sirver) wrote :

> The option to use a temporary UUID has been removed and the interval for the "permanent" ones reduced to 12 hours. So I guess this branch is ready for merge now?

Will have another quick read through in a minute, but yes, I think so. I will merge this and also merge the metaserver branch as soon as this is in. Thanks for working on this! Now on to the even more important relay stuff :).

> Thanks for the link, will be an interesting read. Is that the style guide Widelands (or you?) are trying to follow?

Unfortunately, Widelands predates the Google style guide. Therefore it is not really enforcable in our code base anymore. Also, the style guide mixes style (indent, whitespace, curly braces positions) with good practices (no globals, no non-const references as arguments). Generally it is the best guideline for writing good c++ that I know and I really like the thorough explanations that come with each point.

Having worked at Google for 5 years, I also came to appreciate how many bugs following the style guide avoids. So I usually adhere to it whenever I write new code.

So I'd say, having a read through will give you a lot of food for thought.

Revision history for this message
SirVer (sirver) wrote :

Looks great! Thanks again.

@bunnybot merge

review: Approve
Revision history for this message
SirVer (sirver) wrote :

r8472 has messed up the export to GitHub and the git repository state in the related git branch. Travis will not run again for this branch and merging this into trunk will likely mess up the trunk states in git as well. I will do some surgery on this branch and only retain the diff, throwing away all revisions.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.

Travis build 2841. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/307141215.

Revision history for this message
SirVer (sirver) wrote :

For the record, this was the surgery I did:

In the net-uuid branch:
$ bzr diff --diff-options="-u" -r ancestor:lp:widelands > /tmp/diff.patch

Then get a fresh trunk branched:

$ bzr branch lp:widelands
$ patch -p0 < /tmp/diff.patch
$ bzr commit -m "All changes in one clean diff on master." --author "Notabilis <email address hidden>"
$ bzr push --overwrite lp:~widelands-dev/widelands/net-uuid

Unfortunately that made bunnybot unhappy and I had to manually intervene and fix some state. Next time, we should just close the merge request and reopen a new one with the clean patch.

@Notabilis: sorry for loosing your history, but at least the work is still credited to you in full.

@bunnybot merge

Revision history for this message
Notabilis (notabilis27) wrote :

No problem. As long as we have one commit for the merge the single steps aren't that important.

Seems like I am quite talented with breaking bunnybot though, sorry. So the problem was merging with the same branch? I would have thought that this is a quite normal operation for bzr and git, isn't it? What would have been the bunnybot-friendly way to merge(?) the diverged branch heads?

Revision history for this message
SirVer (sirver) wrote :

> Seems like I am quite talented with breaking bunnybot though, sorry.

Hardly your fault :/. The whole setup with launchpad -> bzr -> git -> github is kinda fragile by design.

> So the problem was merging with the same branch?

Yes, that was fine for bzr. But in the git import it deleted all the files except for the ones changed in this branch.

> What would have been the bunnybot-friendly way to merge(?) the diverged branch heads?

I think a rebase would have been better. Unfortunately rebase is not part of the core bzr tools, but requires a bzr plugin to be installed: https://launchpad.net/bzr-rewrite. Then a bzr rebase does the job. You are right that merging the diverged branch is the bzr way to do it - it is a bummer that this apparently does not work in our setup.

Revision history for this message
SirVer (sirver) wrote :

@bunnybot merge

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2851. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/307441242.
Appveyor build 2661. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_net_uuid-2661.

Revision history for this message
Notabilis (notabilis27) wrote :

This branch has been merged and the metaserver has been updated, too. This means that previous versions of trunk can no longer connect to the metaserver. Branches based on older trunk are also affected, build 19 should not be affected.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/CMakeLists.txt'
2--- src/CMakeLists.txt 2017-11-05 20:06:36 +0000
3+++ src/CMakeLists.txt 2017-11-26 10:34:25 +0000
4@@ -121,6 +121,7 @@
5 map_io_map_loader
6 network
7 profile
8+ random
9 sound
10 ui_basic
11 ui_fsmenu
12
13=== modified file 'src/network/CMakeLists.txt'
14--- src/network/CMakeLists.txt 2017-11-05 20:06:36 +0000
15+++ src/network/CMakeLists.txt 2017-11-26 10:34:25 +0000
16@@ -1,6 +1,8 @@
17 wl_library(network
18 SRCS
19 constants.h
20+ crypto.h
21+ crypto.cc
22 internet_gaming.cc
23 internet_gaming.h
24 internet_gaming_messages.cc
25@@ -46,6 +48,7 @@
26 logic_game_settings
27 map_io_map_loader
28 profile
29+ random
30 scripting_lua_interface
31 scripting_lua_table
32 ui_basic
33
34=== added file 'src/network/crypto.cc'
35--- src/network/crypto.cc 1970-01-01 00:00:00 +0000
36+++ src/network/crypto.cc 2017-11-26 10:34:25 +0000
37@@ -0,0 +1,25 @@
38+#include "network/crypto.h"
39+
40+#include <boost/uuid/sha1.hpp>
41+
42+namespace crypto {
43+
44+std::string sha1(const std::string& input) {
45+
46+ // Hash the input
47+ boost::uuids::detail::sha1 sha;
48+ sha.process_bytes(input.data(), input.size());
49+ uint32_t digest[5];
50+ sha.get_digest(digest);
51+
52+ // Back to string
53+ char result[41] = {0};
54+
55+ for (int i = 0; i < 5; i++) {
56+ std::sprintf(result + (i << 3), "%08x", digest[i]);
57+ }
58+
59+ return std::string(result);
60+}
61+
62+}
63
64=== added file 'src/network/crypto.h'
65--- src/network/crypto.h 1970-01-01 00:00:00 +0000
66+++ src/network/crypto.h 2017-11-26 10:34:25 +0000
67@@ -0,0 +1,36 @@
68+/*
69+ * Copyright (C) 2006-2017 by the Widelands Development Team
70+ *
71+ * This program is free software; you can redistribute it and/or
72+ * modify it under the terms of the GNU General Public License
73+ * as published by the Free Software Foundation; either version 2
74+ * of the License, or (at your option) any later version.
75+ *
76+ * This program is distributed in the hope that it will be useful,
77+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
78+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
79+ * GNU General Public License for more details.
80+ *
81+ * You should have received a copy of the GNU General Public License
82+ * along with this program; if not, write to the Free Software
83+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
84+ *
85+ */
86+
87+#ifndef WL_NETWORK_CRYPTO_H
88+#define WL_NETWORK_CRYPTO_H
89+
90+#include <string>
91+
92+namespace crypto {
93+
94+/**
95+ Hashes the given input string with SHA-1 and returns the hash.
96+ @param input A string to calculate the hash of.
97+ @return The hash as hex-string.
98+ */
99+std::string sha1(const std::string& input);
100+
101+}
102+
103+#endif // end of include guard: WL_NETWORK_CRYPTO_H
104
105=== modified file 'src/network/internet_gaming.cc'
106--- src/network/internet_gaming.cc 2017-11-11 14:27:22 +0000
107+++ src/network/internet_gaming.cc 2017-11-26 10:34:25 +0000
108@@ -20,8 +20,8 @@
109 #include "network/internet_gaming.h"
110
111 #include <memory>
112-#include <random>
113
114+#include <boost/algorithm/string.hpp>
115 #include <boost/format.hpp>
116 #include <boost/lexical_cast.hpp>
117
118@@ -32,7 +32,9 @@
119 #include "io/fileread.h"
120 #include "io/filesystem/layered_filesystem.h"
121 #include "network/constants.h"
122+#include "network/crypto.h"
123 #include "network/internet_gaming_messages.h"
124+#include "random/random.h"
125
126 /// Private constructor by purpose: NEVER call directly. Always call InternetGaming::ref(), this
127 /// will ensure
128@@ -63,7 +65,7 @@
129 void InternetGaming::reset() {
130 net.reset();
131 state_ = OFFLINE;
132- pwd_ = "";
133+ authenticator_ = "";
134 reg_ = false;
135 meta_ = INTERNET_GAMING_METASERVER;
136 port_ = INTERNET_GAMING_PORT;
137@@ -120,32 +122,29 @@
138
139 /// Login to metaserver
140 bool InternetGaming::login(const std::string& nick,
141- const std::string& pwd,
142- bool reg,
143+ const std::string& authenticator,
144+ bool registered,
145 const std::string& meta,
146 uint32_t port) {
147- assert(state_ == OFFLINE);
148
149- pwd_ = pwd;
150- reg_ = reg;
151+ clientname_ = nick;
152+ reg_ = registered;
153 meta_ = meta;
154 port_ = port;
155
156- // If we are not connecting to a registered account, create a random value
157- // to send as password. Used so the metaserver can match our IPv4 and IPv6 connections.
158- // See internet_gaming_protocol.h for more information
159- if (!reg_) {
160- // Admittedly this is a pretty stupid generator. But it should be fine for us
161- static const std::string random_chars = "0123456789ABCDEF";
162- pwd_ = "";
163- std::random_device rd;
164- std::mt19937 gen(rd());
165- std::uniform_int_distribution<> dist(0, random_chars.length() - 1);
166- while (pwd_.length() < 8) {
167- pwd_.push_back(random_chars[dist(gen)]);
168- }
169+ if (registered) {
170+ authenticator_ = authenticator;
171+ } else {
172+ authenticator_ = crypto::sha1(nick + authenticator);
173 }
174
175+ assert(!authenticator_.empty());
176+
177+ return do_login();
178+}
179+
180+bool InternetGaming::do_login(bool should_relogin) {
181+
182 initialize_connection();
183
184 // If we are here, a connection was established and we can send our login package through the
185@@ -154,10 +153,10 @@
186 SendPacket s;
187 s.string(IGPCMD_LOGIN);
188 s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
189- s.string(nick);
190+ s.string(clientname_);
191 s.string(build_id());
192 s.string(bool2str(reg_));
193- s.string(pwd_);
194+ s.string(authenticator_);
195 net->send(s);
196
197 // Now let's see, whether the metaserver is answering
198@@ -169,9 +168,11 @@
199 // paperwork, so we put our feet up and just return. ;)
200 if (state_ != CONNECTING) {
201 if (state_ == LOBBY) {
202- format_and_add_chat(
203- "", "", true, _("For hosting a game, please take a look at the notes at:"));
204- format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming");
205+ if (!should_relogin) {
206+ format_and_add_chat(
207+ "", "", true, _("For hosting a game, please take a look at the notes at:"));
208+ format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming");
209+ }
210
211 // Try to establish a second connection to tell the metaserver about our IPv4 address
212 create_second_connection();
213@@ -191,42 +192,11 @@
214 throw wexception("InternetGaming::relogin: This only makes sense if there was an error.");
215 }
216
217- initialize_connection();
218-
219- // If we are here, a connection was established and we can send our login package through the
220- // socket.
221- log("InternetGaming: Sending relogin request.\n");
222- SendPacket s;
223- s.string(IGPCMD_RELOGIN);
224- s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
225- s.string(clientname_);
226- s.string(build_id());
227- s.string(bool2str(reg_));
228- s.string(pwd_);
229- net->send(s);
230-
231- // Now let's see, whether the metaserver is answering
232- uint32_t const secs = time(nullptr);
233- state_ = CONNECTING;
234- while (INTERNET_GAMING_TIMEOUT > time(nullptr) - secs) {
235- handle_metaserver_communication();
236- // Check if we are a step further... if yes handle_packet has taken care about all the
237- // paperwork, so we put our feet up and just return. ;)
238- if (state_ != CONNECTING) {
239- if (state_ == LOBBY) {
240- break;
241- } else if (error())
242- return false;
243- }
244- }
245-
246- if (INTERNET_GAMING_TIMEOUT <= time(nullptr) - secs) {
247- log("InternetGaming: No answer from metaserver!\n");
248+ if (!do_login(true)) {
249 return false;
250 }
251
252- create_second_connection();
253-
254+ state_ = LOBBY;
255 // Client is reconnected, so let's try resend the timeouted command.
256 if (waitcmd_ == IGPCMD_GAME_CONNECT)
257 join_game(gamename_);
258@@ -238,6 +208,9 @@
259 set_game_playing();
260 }
261
262+ log("InternetGaming: Reconnected to metaserver\n");
263+ format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!"));
264+
265 return true;
266 }
267
268@@ -385,7 +358,7 @@
269 s.string(IGPCMD_TELL_IP);
270 s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
271 s.string(clientname_);
272- s.string(pwd_);
273+ s.string(authenticator_);
274 tmpNet->send(s);
275
276 // Close the connection
277@@ -416,19 +389,24 @@
278 if (state_ == CONNECTING) {
279 if (cmd == IGPCMD_LOGIN) {
280 // Clients request to login was granted
281- clientname_ = packet.string();
282+ const std::string assigned_name = packet.string();
283+ if (clientname_ != assigned_name) {
284+ format_and_add_chat("", "", true,
285+ (boost::format(
286+ _("You have been logged in as '%s' since your requested name is already in use or reserved."))
287+ % assigned_name).str());
288+ }
289+ clientname_ = assigned_name;
290 clientrights_ = packet.string();
291+ if (clientrights_ == INTERNET_CLIENT_UNREGISTERED) {
292+ // Might happen that we log in with less rights than we wanted to.
293+ // Happens when we are already logged in with another client.
294+ reg_ = false;
295+ }
296 state_ = LOBBY;
297 log("InternetGaming: Client %s logged in.\n", clientname_.c_str());
298 return;
299
300- } else if (cmd == IGPCMD_RELOGIN) {
301- // Clients request to relogin was granted
302- state_ = LOBBY;
303- log("InternetGaming: Client %s relogged in.\n", clientname_.c_str());
304- format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!"));
305- return;
306-
307 } else if (cmd == IGPCMD_ERROR) {
308 std::string errortype = packet.string();
309 if (errortype != "LOGIN" && errortype != "RELOGIN") {
310@@ -451,9 +429,8 @@
311 cmd.c_str());
312 }
313 }
314-
315 try {
316- if (cmd == IGPCMD_LOGIN || cmd == IGPCMD_RELOGIN) {
317+ if (cmd == IGPCMD_LOGIN) {
318 // Login specific commands but not in CONNECTING state...
319 log("InternetGaming: Received %s cmd although client is not in CONNECTING state.\n",
320 cmd.c_str());
321@@ -766,16 +743,28 @@
322
323 /// ChatProvider: sends a message via the metaserver.
324 void InternetGaming::send(const std::string& msg) {
325+ // TODO(Notabilis): Messages can get lost when we are temporarily disconnected from the metaserver,
326+ // even when we reconnect again. "Answered" messages like IGPCMD_GAME_CONNECT are resent but chat
327+ // messages are not. Resend them after some time when we did not receive the matching IGPCMD_CHAT
328+ // command from the server? For global/public messages we could wait for the returned IGPCMD_CHAT
329+ // from the metaserver, similar to other commands. What about private messages? Maybe modify the
330+ // metaserver to send them back, too?
331 if (!logged_in()) {
332 format_and_add_chat(
333 "", "", true, _("Message could not be sent: You are not connected to the metaserver!"));
334 return;
335 }
336
337+ std::string trimmed = boost::algorithm::trim_copy(msg);
338+ if (trimmed.empty()) {
339+ // Message is empty or only space characters. We don't want it either way
340+ return;
341+ }
342+
343 SendPacket s;
344 s.string(IGPCMD_CHAT);
345
346- if (msg.size() && *msg.begin() == '@') {
347+ if (*msg.begin() == '@') {
348 // Format a personal message
349 std::string::size_type const space = msg.find(' ');
350 if (space >= msg.size() - 1) {
351@@ -784,12 +773,19 @@
352 _("Message could not be sent: Was this supposed to be a private message?"));
353 return;
354 }
355- s.string(msg.substr(space + 1)); // message
356+ trimmed = boost::algorithm::trim_copy(msg.substr(space + 1));
357+ if (trimmed.empty()) {
358+ format_and_add_chat(
359+ "", "", true,
360+ _("Message could not be sent: Was this supposed to be a private message?"));
361+ return;
362+ }
363+ s.string(trimmed); // message
364 s.string(msg.substr(1, space - 1)); // recipient
365
366 format_and_add_chat(clientname_, msg.substr(1, space - 1), false, msg.substr(space + 1));
367
368- } else if (clientrights_ == INTERNET_CLIENT_SUPERUSER && msg.size() && *msg.begin() == '/') {
369+ } else if (clientrights_ == INTERNET_CLIENT_SUPERUSER && *msg.begin() == '/') {
370 // This is either a /me command, a super user command, or well... just a chat message
371 // beginning
372 // with a "/" - let's see...
373@@ -798,15 +794,16 @@
374 std::string cmd, arg;
375 std::string temp = msg.substr(1); // cut off '/'
376 std::string::size_type const space = temp.find(' ');
377- if (space > temp.size())
378+ if (space > temp.size()) {
379 // no argument
380 goto normal;
381+ }
382
383 // get the cmd and the arg
384 cmd = temp.substr(0, space);
385- arg = temp.substr(space + 1);
386+ arg = boost::algorithm::trim_copy(temp.substr(space + 1));
387
388- if (cmd == "motd") {
389+ if (!arg.empty() && cmd == "motd") {
390 SendPacket m;
391 m.string(IGPCMD_MOTD);
392 // Check whether motd is attached or should be loaded from a file
393@@ -827,19 +824,19 @@
394 }
395 // send the request to change the motd
396 m.string(arg);
397- net->send(s);
398+ net->send(m);
399 return;
400- } else if (cmd == "announcement") {
401+ } else if (!arg.empty() && cmd == "announcement") {
402 // send the request to change the motd
403 SendPacket m;
404 m.string(IGPCMD_ANNOUNCEMENT);
405 m.string(arg);
406- net->send(s);
407+ net->send(m);
408 return;
409- } else
410+ } else {
411 // let everything else pass
412 goto normal;
413-
414+ }
415 } else {
416 normal:
417 s.string(msg);
418
419=== modified file 'src/network/internet_gaming.h'
420--- src/network/internet_gaming.h 2017-07-01 08:22:54 +0000
421+++ src/network/internet_gaming.h 2017-11-26 10:34:25 +0000
422@@ -51,15 +51,28 @@
423 * The InternetGaming struct.
424 */
425 struct InternetGaming : public ChatProvider {
426+
427 /// The only instance of InternetGaming -> the constructor is private by purpose!
428 static InternetGaming& ref();
429
430 void reset();
431
432 // Login and logout
433+
434 void initialize_connection();
435+
436+ /**
437+ * Try to login on the metaserver.
438+ * @param nick The preferred username. Another username might be chosen by the metaserver if
439+ * the requested one is already in use.
440+ * @param authenticator If \c registered is \c true, this is the password. Otherwise, it is some unique
441+ * id the server can use to identify the user.
442+ * @param metaserver The hostname of the metaserver.
443+ * @param port The port number of the metaserver.
444+ * @return Whether the login was successful.
445+ */
446 bool login(const std::string& nick,
447- const std::string& pwd,
448+ const std::string& authenticator,
449 bool registered,
450 const std::string& metaserver,
451 uint32_t port);
452@@ -180,6 +193,13 @@
453 bool system,
454 const std::string& msg);
455
456+ /**
457+ * Does the real work of the login.
458+ * \param relogin Whether this is a relogin. Only difference is that
459+ * on first login a greeting is shown.
460+ */
461+ bool do_login(bool relogin = false);
462+
463 /// The connection to the metaserver
464 std::unique_ptr<NetClient> net;
465
466@@ -187,8 +207,9 @@
467 enum { OFFLINE, CONNECTING, LOBBY, IN_GAME, COMMUNICATION_ERROR } state_;
468
469 /// data saved for possible relogin
470- std::string pwd_;
471+ std::string authenticator_;
472 bool reg_;
473+
474 std::string meta_;
475 uint16_t port_;
476
477
478=== modified file 'src/network/internet_gaming_protocol.h'
479--- src/network/internet_gaming_protocol.h 2017-07-01 08:22:54 +0000
480+++ src/network/internet_gaming_protocol.h 2017-11-26 10:34:25 +0000
481@@ -28,12 +28,16 @@
482
483 /**
484 * The current version of the in-game network protocol. Client and metaserver
485- * protocol versions must match.
486+ * protocol versions must match. The metaserver supports the protocol version of latest stable build
487+ * and the newest version in trunk.
488 * Used Versions:
489- * 0: Build 19 and before
490+ * 0: Build 19 and before [stable, supported]
491 * 1: Between build 19 and build 20 - IPv6 support added
492+ * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after crashes.
493+ * When logging twice with a registered account, the second connection
494+ * gets a free username assigned. Dropping RELOGIN command. [supported]
495 */
496-#define INTERNET_GAMING_PROTOCOL_VERSION 1
497+#define INTERNET_GAMING_PROTOCOL_VERSION 2
498
499 /**
500 * The default timeout time after which the client tries to resend a package or even finally closes
501@@ -91,35 +95,43 @@
502 * Every packet starts with a single-byte command code.
503 *
504 * \note ALL PAYLOADS SHALL BE STRINGS - this is for easier handling and debugging of the
505- * communication
506- * between metaserver and client. If an unsigned or signed value has to be sent, convert it
507- * with
508- * boost::lexical_cast<std::string>. Boolean values should be sent in form of "true" or
509- * "false".
510+ * communication between metaserver and client. If an unsigned or signed value has
511+ * to be sent, convert it with boost::lexical_cast<std::string>. Boolean values should
512+ be sent in form of "true" or "false".
513 */
514
515 /**
516- * The nonce:
517- *
518- * The nonce is used on the metaserver to link multiple connections by the same client. This
519- * normally
520- * happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This
521- * way,
522- * the metaserver knows that the clients supports both versions and can show games / offer his game
523+ * The UUID:
524+ *
525+ * The UUID is a semi-permanent ID stored in the configuration file of Widelands.
526+ * It has to be stored in the file since it should survive crashes of the game or computer.
527+ * If the game is not started for 24 hours, a new one is created to increase privacy.
528+ * Basically it allows the metaserver to identify the user even when multiple users try to join with
529+ * the same username. Note that the sent UUID differs from the stored one: The sent UUID is
530+ * hash(username | stored-id).
531+ * In the case of registered players, the password can be used instead of a UUID. The username
532+ * alone can not be used for this, especially not for unregistered users: The metaserver can not
533+ * differentiate between a second connection by the user and an initial login of another user
534+ * (with the same name).
535+ *
536+ * Use-cases of the UUID:
537+ *
538+ * 1) Linking connections with IPv4 and IPv6
539+ * The UUID is used on the metaserver to link multiple connections by the same client. This
540+ * normally happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This
541+ * way, the metaserver knows that the client supports both versions and can show games / offer its game
542 * of/for clients with both protocol versions.
543 *
544- * When a network client connects to the metaserver with (RE)LOGIN he also sends a nonce.
545- * When "another" netclient connects to the metaserver and sends TELL_IP containing the same nonce,
546+ * When a network client connects to the metaserver with (RE)LOGIN it also sends the UUID.
547+ * When "another" netclient connects to the metaserver and sends TELL_IP containing the same UUID,
548 * it is considered the same game client connecting with another IP. This way, two connections by
549- * IPv4 and
550- * IPv6 can be matched so the server learns both addresses of the client.
551+ * IPv4 and IPv6 can be matched so the server learns both addresses of the client.
552 *
553- * In the case of registered players, the password can be used instead of a random nonce. The
554- * username alone
555- * can not be used for this, especially not for unregistered users: The metaserver can not
556- * differentiate
557- * between a second connection by the user and an initial login of another user (with the same
558- * name).
559+ * 2) Reconnect after crash / network problems.
560+ * When Widelands breaks the connection without logging out, the server still assumes that the old
561+ * connection is active. So when the player reconnects, another name is chosen. Sending the UUID allows
562+ * to reclaim the old name, since the server recognizes that there isn't a second player trying to use
563+ * the same name.
564 */
565
566 /**
567@@ -132,15 +144,12 @@
568 * closing the connection. The receiver of this command should just close the connection.
569 *
570 * \note that either party is allowed to close the connection without sending a \ref
571- * IGPCMD_DISCONNECT
572- * command first (in any case, this can happen when the program crashes or network connection
573- * is lost).
574+ * IGPCMD_DISCONNECT command first (in any case, this can happen when the program crashes
575+ * or network connection is lost).
576 *
577 * \note If you want to change the payload of this command, change it only by appending new items.
578- * The
579- * reason is that this is the only command that can be sent by the metaserver even when the
580- * protocol
581- * versions differ.
582+ * The reason is that this is the only command that can be sent by the metaserver even when the
583+ * protocol versions differ.
584 *
585 */
586 static const std::string IGPCMD_DISCONNECT = "DISCONNECT";
587@@ -153,62 +162,28 @@
588 * \li string: protocol version
589 * \li string: client name
590 * \li string: build_id of the client
591- * \li string: whether the client wants to login in to a registered account ("true" or "false" as
592- * string)
593+ * \li string: whether the client wants to login in to a registered account
594+ * ("true" or "false" as string)
595 * \li string: for registered accounts: password in clear text
596- * for unregistered users a random nonce to recognized the matching IPv4 and IPv6
597- * connections.
598- * for an explanation of the nonce, see above.
599+ * for unregistered users the UUID to recognize the matching IPv4 and IPv6
600+ * connections or to reclaim the username after a unintended disconnect.
601+ * For an explanation of the UUID, see above.
602 *
603 * If the metaserver accepts, it replies with a LOGIN command with the following payload:
604 * \li string: client name (might be different to the previously chosen one, if the client did
605 * NOT login to a registered account and either the chosen is registered or already
606- * used.)
607+ * used.)
608 * \li string: clients rights (see client rights section above)
609 *
610 * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will again try to login
611 * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not
612 * answer"
613 *
614- * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_REJECTED
615+ * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_ERROR
616 */
617 static const std::string IGPCMD_LOGIN = "LOGIN";
618
619 /**
620- * Reinitiate a connection.
621- *
622- * Basically the difference to \ref IGPCMD_LOGIN is, that the metaserver allows the user to go on at
623- * the
624- * place it was before - so if the user was in a running game and only lost connection to the
625- * metaserver,
626- * but not to the game itself, statistics for that game can still be sent and saved.
627- * To ensure everything is fine, the login information will still be checked + if a client with the
628- * name
629- * is still listed as online, the metaserver will ping the client and if the client does not answer
630- * intime
631- * it will be replaced by the user requesting the relogin
632- *
633- * sent by the client, with the following payload:
634- * \li string: protocol version
635- * \li string: client name - the one the metaserver replied at the first login
636- * \li string: build_id of the client
637- * \li string: whether the client wants to login in to a registered account ("false", "true")
638- * \li string: for registered accounts: password in clear text
639- * for unregistered users a random nonce to recognized the matching IPv4 and IPv6
640- * connections.
641- * for an explanation of the nonce, see above.
642- *
643- * If the metaserver accepts, it replies with a RELOGIN command without any payload.
644- *
645- * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will try to relogin
646- * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not
647- * answer"
648- *
649- * For the case, that the metaserver does not accept the login, it sends a \ref IGPCMD_ERROR "LOGIN"
650- */
651-static const std::string IGPCMD_RELOGIN = "RELOGIN";
652-
653-/**
654 * Tells the metaserver about a secondary IP address.
655 *
656 * Assuming the client already has a connection over IPv6 and tries to establish a secondary
657@@ -220,8 +195,8 @@
658 * \li string: protocol version
659 * \li string: client name - the one the metaserver replied at the first login
660 * \li string: for registered accounts: password in clear text
661- * for unregistered users the random nonce used on login
662- * for an explanation of the nonce, see above.
663+ * for unregistered users the UUID used on login
664+ * for an explanation of the UUID, see above.
665 */
666 static const std::string IGPCMD_TELL_IP = "TELL_IP";
667
668@@ -291,15 +266,13 @@
669 * The metaserver will echo the message if the client is allowed to send chat messages.
670 *
671 * The metaserver either broadcasts a chat message to all clients or sends it to the pm recipient
672- * with the
673- * following payload:
674+ * with the following payload:
675 * \li string: sender (may be empty if it is a system message)
676 * \li string: the message
677 * \li string: type ("public", "private", "system")
678 *
679- * \note system messages are the motd (Sent by the metaserver to the client, after login (but not
680- * relogin)
681- * and after the motd got changed) and announcements by superusers.
682+ * \note system messages are the motd (Sent by the metaserver to the client, after login
683+ * (but not relogin) and after the motd got changed) and announcements by superusers.
684 */
685 static const std::string IGPCMD_CHAT = "CHAT";
686
687
688=== modified file 'src/random/random.cc'
689--- src/random/random.cc 2017-01-25 18:55:59 +0000
690+++ src/random/random.cc 2017-11-26 10:34:25 +0000
691@@ -20,6 +20,11 @@
692 #include "random/random.h"
693
694 #include <cstdio>
695+#include <random>
696+
697+#include <boost/uuid/uuid.hpp>
698+#include <boost/uuid/uuid_generators.hpp>
699+#include <boost/uuid/uuid_io.hpp>
700
701 #include "base/wexception.h"
702 #include "io/streamread.h"
703@@ -98,3 +103,8 @@
704 sw.unsigned_32(state0);
705 sw.unsigned_32(state1);
706 }
707+
708+std::string generate_random_uuid() {
709+ static boost::uuids::random_generator gen = boost::uuids::random_generator();
710+ return boost::uuids::to_string(gen());
711+}
712
713=== modified file 'src/random/random.h'
714--- src/random/random.h 2017-01-25 18:55:59 +0000
715+++ src/random/random.h 2017-11-26 10:34:25 +0000
716@@ -21,6 +21,7 @@
717 #define WL_RANDOM_RANDOM_H
718
719 #include <stdint.h>
720+#include <string>
721
722 extern const uint32_t rng_sbox[256];
723
724@@ -44,4 +45,9 @@
725
726 #define SIMPLE_RAND(x) (((x) >> 8) ^ rng_sbox[(x)&0xff])
727
728+/// Generates a random UUID, looking like "550e8400-e29b-11d4-a716-446655440000".
729+/// This does not use logic_rand(), but instead a thread local random number
730+/// generator, so do not use in logic code - it will desync.
731+std::string generate_random_uuid();
732+
733 #endif // end of include guard: WL_RANDOM_RANDOM_H
734
735=== modified file 'src/ui_fsmenu/CMakeLists.txt'
736--- src/ui_fsmenu/CMakeLists.txt 2017-11-05 20:09:44 +0000
737+++ src/ui_fsmenu/CMakeLists.txt 2017-11-26 10:34:25 +0000
738@@ -79,6 +79,7 @@
739 map_io_map_loader
740 network
741 profile
742+ random
743 scripting_coroutine
744 scripting_lua_interface
745 scripting_lua_table
746
747=== modified file 'src/ui_fsmenu/internet_lobby.cc'
748--- src/ui_fsmenu/internet_lobby.cc 2017-06-20 19:55:32 +0000
749+++ src/ui_fsmenu/internet_lobby.cc 2017-11-26 10:34:25 +0000
750@@ -27,10 +27,12 @@
751 #include "base/macros.h"
752 #include "graphic/graphic.h"
753 #include "graphic/text_constants.h"
754+#include "network/crypto.h"
755 #include "network/gameclient.h"
756 #include "network/gamehost.h"
757 #include "network/internet_gaming.h"
758 #include "profile/profile.h"
759+#include "random/random.h"
760 #include "ui_basic/messagebox.h"
761
762 FullscreenMenuInternetLobby::FullscreenMenuInternetLobby(char const* const nick,
763@@ -192,8 +194,9 @@
764 Section& s = g_options.pull_section("global");
765 const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
766 uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);
767-
768- InternetGaming::ref().login(nickname_, password_, is_registered_, metaserver, port);
769+ std::string auth = is_registered_ ? password_ : s.get_string("uuid");
770+ assert(!auth.empty());
771+ InternetGaming::ref().login(nickname_, auth, is_registered_, metaserver, port);
772 }
773
774 /// fills the server list
775
776=== modified file 'src/ui_fsmenu/multiplayer.cc'
777--- src/ui_fsmenu/multiplayer.cc 2017-02-25 13:27:40 +0000
778+++ src/ui_fsmenu/multiplayer.cc 2017-11-26 10:34:25 +0000
779@@ -21,8 +21,10 @@
780
781 #include "base/i18n.h"
782 #include "graphic/text_constants.h"
783+#include "network/crypto.h"
784 #include "network/internet_gaming.h"
785 #include "profile/profile.h"
786+#include "random/random.h"
787 #include "ui_basic/messagebox.h"
788 #include "wui/login_box.h"
789
790@@ -122,7 +124,9 @@
791 // Try to connect to the metaserver
792 const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
793 uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);
794- InternetGaming::ref().login(nickname_, password_, register_, meta, port);
795+ std::string auth = register_ ? password_ : s.get_string("uuid");
796+ assert(!auth.empty());
797+ InternetGaming::ref().login(nickname_, auth, register_, meta, port);
798
799 // Check whether metaserver send some data
800 if (InternetGaming::ref().logged_in())
801
802=== modified file 'src/wlapplication.cc'
803--- src/wlapplication.cc 2017-11-24 09:19:52 +0000
804+++ src/wlapplication.cc 2017-11-26 10:34:25 +0000
805@@ -75,6 +75,7 @@
806 #include "network/gamehost.h"
807 #include "network/internet_gaming.h"
808 #include "profile/profile.h"
809+#include "random/random.h"
810 #include "sound/sound_handler.h"
811 #include "ui_basic/messagebox.h"
812 #include "ui_basic/progresswindow.h"
813@@ -743,6 +744,7 @@
814 s.get_bool("write_syncstreams");
815 s.get_bool("sound_at_message");
816 s.get_bool("transparent_chat");
817+ s.get_string("uuid");
818 s.get_string("registered");
819 s.get_string("nickname");
820 s.get_string("password");
821@@ -755,6 +757,24 @@
822 s.get_natural("metaserverport");
823 // KLUDGE!
824
825+ long int last_start = s.get_int("last_start", 0);
826+ if (last_start + 12 * 60 * 60 < time(nullptr)) {
827+ // First start of the game or not started for 12 hours. Create a (new) UUID.
828+ // For the use of the UUID, see network/internet_gaming_protocol.h
829+ s.set_string("uuid", generate_random_uuid());
830+ }
831+ s.set_int("last_start", time(nullptr));
832+
833+ // Save configuration now. Otherwise, the UUID is not saved
834+ // when the game crashes, loosing part of its advantage
835+ try {
836+ g_options.write("config", false);
837+ } catch (const std::exception& e) {
838+ log("WARNING: could not save configuration: %s\n", e.what());
839+ } catch (...) {
840+ log("WARNING: could not save configuration");
841+ }
842+
843 return true;
844 }
845

Subscribers

People subscribed via source and target branches

to status/vote changes: