aboutsummaryrefslogtreecommitdiff
path: root/src/unittest/test_random.cpp
blob: 14de764e1dbc110526662530ba79837e851d7a38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
 /*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "test.h"

#include <cmath>
#include "util/numeric.h"
#include "exceptions.h"
#include "noise.h"

class TestRandom : public TestBase {
public:
	TestRandom() { TestManager::registerTestModule(this); }
	const char *getName() { return "TestRandom"; }

	void runTests(IGameDef *gamedef);

	void testPseudoRandom();
	void testPseudoRandomRange();
	void testPcgRandom();
	void testPcgRandomRange();
	void testPcgRandomBytes();
	void testPcgRandomNormalDist();

	static const int expected_pseudorandom_results[256];
	static const u32 expected_pcgrandom_results[256];
	static const u8 expected_pcgrandom_bytes_result[24];
	static const u8 expected_pcgrandom_bytes_result2[24];
};

static TestRandom g_test_instance;

void TestRandom::runTests(IGameDef *gamedef)
{
	TEST(testPseudoRandom);
	TEST(testPseudoRandomRange);
	TEST(testPcgRandom);
	TEST(testPcgRandomRange);
	TEST(testPcgRandomBytes);
	TEST(testPcgRandomNormalDist);
}

////////////////////////////////////////////////////////////////////////////////

void TestRandom::testPseudoRandom()
{
	PseudoRandom pr(814538);

	for (u32 i = 0; i != 256; i++)
		UASSERTEQ(int, pr.next(), expected_pseudorandom_results[i]);
}


void TestRandom::testPseudoRandomRange()
{
	PseudoRandom pr((int)time(NULL));

	EXCEPTION_CHECK(PrngException, pr.range(2000, 6000));
	EXCEPTION_CHECK(PrngException, pr.range(5, 1));

	for (u32 i = 0; i != 32768; i++) {
		int min = (pr.next() % 3000) - 500;
		int max = (pr.next() % 3000) - 500;
		if (min > max)
			SWAP(int, min, max);

		int randval = pr.range(min, max);
		UASSERT(randval >= min);
		UASSERT(randval <= max);
	}
}


void TestRandom::testPcgRandom()
{
	PcgRandom pr(814538, 998877);

	for (u32 i = 0; i != 256; i++)
		UASSERTEQ(u32, pr.next(), expected_pcgrandom_results[i]);
}


void TestRandom::testPcgRandomRange()
{
	PcgRandom pr((int)time(NULL));

	EXCEPTION_CHECK(PrngException, pr.range(5, 1));

	// Regression test for bug 3027
	pr.range(pr.RANDOM_MIN, pr.RANDOM_MAX);

	for (u32 i = 0; i != 32768; i++) {
		int min = (pr.next() % 3000) - 500;
		int max = (pr.next() % 3000) - 500;
		if (min > max)
			SWAP(int, min, max);

		int randval = pr.range(min, max);
		UASSERT(randval >= min);
		UASSERT(randval <= max);
	}
}


void TestRandom::testPcgRandomBytes()
{
	char buf[32];
	PcgRandom r(1538, 877);

	memset(buf, 0, sizeof(buf));
	r.bytes(buf + 5, 23);
	UASSERT(memcmp(buf + 5, expected_pcgrandom_bytes_result,
		sizeof(expected_pcgrandom_bytes_result)) == 0);

	memset(buf, 0, sizeof(buf));
	r.bytes(buf, 17);
	UASSERT(memcmp(buf, expected_pcgrandom_bytes_result2,
		sizeof(expected_pcgrandom_bytes_result2)) == 0);
}


void TestRandom::testPcgRandomNormalDist()
{
	static const int max = 120;
	static const int min = -120;
	static const int num_trials = 20;
	static const u32 num_samples = 61000;
	s32 bins[max - min + 1];
	memset(bins, 0, sizeof(bins));

	PcgRandom r(486179 + (int)time(NULL));

	for (u32 i = 0; i != num_samples; i++) {
		s32 randval = r.randNormalDist(min, max, num_trials);
		UASSERT(randval <= max);
		UASSERT(randval >= min);
		bins[randval - min]++;
	}

	// Note that here we divide variance by the number of trials;
	// this is because variance is a biased estimator.
	int range      = (max - min + 1);
	float mean     = (max + min) / 2;
	float variance = ((range * range - 1) / 12) / num_trials;
	float stddev   = std::sqrt(variance);

	static const float prediction_intervals[] = {
		0.68269f, // 1.0
		0.86639f, // 1.5
		0.95450f, // 2.0
		0.98758f, // 2.5
		0.99730f, // 3.0
	};

	//// Simple normality test using the 68-95-99.7% rule
	for (u32 i = 0; i != ARRLEN(prediction_intervals); i++) {
		float deviations = i / 2.f + 1.f;
		int lbound = myround(mean - deviations * stddev);
		int ubound = myround(mean + deviations * stddev);
		UASSERT(lbound >= min);
		UASSERT(ubound <= max);

		int accum = 0;
		for (int j = lbound; j != ubound; j++)
			accum += bins[j - min];

		float actual = (float)accum / num_samples;
		UASSERT(std::fabs(actual - prediction_intervals[i]) < 0.02f);
	}
}


const int TestRandom::expected_pseudorandom_results[256] = {
	0x02fa, 0x60d5, 0x6c10, 0x606b, 0x098b, 0x5f1e, 0x4f56, 0x3fbd, 0x77af,
	0x4fe9, 0x419a, 0x6fe1, 0x177b, 0x6858, 0x36f8, 0x6d83, 0x14fc, 0x2d62,
	0x1077, 0x23e2, 0x041b, 0x7a7e, 0x5b52, 0x215d, 0x682b, 0x4716, 0x47e3,
	0x08c0, 0x1952, 0x56ae, 0x146d, 0x4b4f, 0x239f, 0x3fd0, 0x6794, 0x7796,
	0x7be2, 0x75b7, 0x5691, 0x28ee, 0x2656, 0x40c0, 0x133c, 0x63cd, 0x2aeb,
	0x518f, 0x7dbc, 0x6ad8, 0x736e, 0x5b05, 0x160b, 0x589f, 0x6f64, 0x5edc,
	0x092c, 0x0a39, 0x199e, 0x1927, 0x562b, 0x2689, 0x3ba3, 0x366f, 0x46da,
	0x4e49, 0x0abb, 0x40a1, 0x3846, 0x40db, 0x7adb, 0x6ec1, 0x6efa, 0x01cc,
	0x6335, 0x4352, 0x72fb, 0x4b2d, 0x509a, 0x257e, 0x2f7d, 0x5891, 0x2195,
	0x6107, 0x5269, 0x56e3, 0x4849, 0x38f7, 0x2791, 0x04f2, 0x4e05, 0x78ff,
	0x6bae, 0x50b3, 0x74ad, 0x31af, 0x531e, 0x7d56, 0x11c9, 0x0b5e, 0x405e,
	0x1e15, 0x7f6a, 0x5bd3, 0x6649, 0x71b4, 0x3ec2, 0x6ab4, 0x520e, 0x6ad6,
	0x287e, 0x10b8, 0x18f2, 0x7107, 0x46ea, 0x1d85, 0x25cc, 0x2689, 0x35c1,
	0x3065, 0x6237, 0x3edd, 0x23d9, 0x6fb5, 0x37a1, 0x3211, 0x526a, 0x4b09,
	0x23f1, 0x58cc, 0x2e42, 0x341f, 0x5e16, 0x3d1a, 0x5e8c, 0x7a82, 0x4635,
	0x2bf8, 0x6577, 0x3603, 0x1daf, 0x539f, 0x2e91, 0x6bd8, 0x42d3, 0x7a93,
	0x26e3, 0x5a91, 0x6c67, 0x1b66, 0x3ac7, 0x18bf, 0x20d8, 0x7153, 0x558d,
	0x7262, 0x653d, 0x417d, 0x3ed3, 0x3117, 0x600d, 0x6d04, 0x719c, 0x3afd,
	0x6ba5, 0x17c5, 0x4935, 0x346c, 0x5479, 0x6ff6, 0x1fcc, 0x1054, 0x3f14,
	0x6266, 0x3acc, 0x3b77, 0x71d8, 0x478b, 0x20fa, 0x4e46, 0x7e77, 0x5554,
	0x3652, 0x719c, 0x072b, 0x61ad, 0x399f, 0x621d, 0x1bba, 0x41d0, 0x7fdc,
	0x3e6c, 0x6a2a, 0x5253, 0x094e, 0x0c10, 0x3f43, 0x73eb, 0x4c5f, 0x1f23,
	0x12c9, 0x0902, 0x5238, 0x50c0, 0x1b77, 0x3ffd, 0x0124, 0x302a, 0x26b9,
	0x3648, 0x30a6, 0x1abc, 0x3031, 0x4029, 0x6358, 0x6696, 0x74e8, 0x6142,
	0x4284, 0x0c00, 0x7e50, 0x41e3, 0x3782, 0x79a5, 0x60fe, 0x2d15, 0x3ed2,
	0x7f70, 0x2b27, 0x6366, 0x5100, 0x7c44, 0x3ee0, 0x4e76, 0x7d34, 0x3a60,
	0x140e, 0x613d, 0x1193, 0x268d, 0x1e2f, 0x3123, 0x6d61, 0x4e0b, 0x51ce,
	0x13bf, 0x58d4, 0x4f43, 0x05c6, 0x4d6a, 0x7eb5, 0x2921, 0x2c36, 0x1c89,
	0x63b9, 0x1555, 0x1f41, 0x2d9f,
};

const u32 TestRandom::expected_pcgrandom_results[256] = {
	0x48c593f8, 0x054f59f5, 0x0d062dc1, 0x23852a23, 0x7fbbc97b, 0x1f9f141e,
	0x364e6ed8, 0x995bba58, 0xc9307dc0, 0x73fb34c4, 0xcd8de88d, 0x52e8ce08,
	0x1c4a78e4, 0x25c0882e, 0x8a82e2e0, 0xe3bc3311, 0xb8068d42, 0x73186110,
	0x19988df4, 0x69bd970b, 0x7214728c, 0x0aee320c, 0x2a5a536c, 0xaf48d715,
	0x00bce504, 0xd2b8f548, 0x520df366, 0x96d8fff5, 0xa1bb510b, 0x63477049,
	0xb85990b7, 0x7e090689, 0x275fb468, 0x50206257, 0x8bab4f8a, 0x0d6823db,
	0x63faeaac, 0x2d92deeb, 0x2ba78024, 0x0d30f631, 0x338923a0, 0xd07248d8,
	0xa5db62d3, 0xddba8af6, 0x0ad454e9, 0x6f0fd13a, 0xbbfde2bf, 0x91188009,
	0x966b394d, 0xbb9d2012, 0x7e6926cb, 0x95183860, 0x5ff4c59b, 0x035f628a,
	0xb67085ef, 0x33867e23, 0x68d1b887, 0x2e3298d7, 0x84fd0650, 0x8bc91141,
	0x6fcb0452, 0x2836fee9, 0x2e83c0a3, 0xf1bafdc5, 0x9ff77777, 0xfdfbba87,
	0x527aebeb, 0x423e5248, 0xd1756490, 0xe41148fa, 0x3361f7b4, 0xa2824f23,
	0xf4e08072, 0xc50442be, 0x35adcc21, 0x36be153c, 0xc7709012, 0xf0eeb9f2,
	0x3d73114e, 0x1c1574ee, 0x92095b9c, 0x1503d01c, 0xd6ce0677, 0x026a8ec1,
	0x76d0084d, 0x86c23633, 0x36f75ce6, 0x08fa7bbe, 0x35f6ff2a, 0x31cc9525,
	0x2c1a35e6, 0x8effcd62, 0xc782fa07, 0x8a86e248, 0x8fdb7a9b, 0x77246626,
	0x5767723f, 0x3a78b699, 0xe548ce1c, 0x5820f37d, 0x148ed9b8, 0xf6796254,
	0x32232c20, 0x392bf3a2, 0xe9af6625, 0xd40b0d88, 0x636cfa23, 0x6a5de514,
	0xc4a69183, 0xc785c853, 0xab0de901, 0x16ae7e44, 0x376f13b5, 0x070f7f31,
	0x34cbc93b, 0xe6184345, 0x1b7f911f, 0x631fbe4b, 0x86d6e023, 0xc689b518,
	0x88ef4f7c, 0xddf06b45, 0xc97f18d4, 0x2aaee94b, 0x45694723, 0x6db111d2,
	0x91974fce, 0xe33e29e2, 0xc5e99494, 0x8017e02b, 0x3ebd8143, 0x471ffb80,
	0xc0d7ca1b, 0x4954c860, 0x48935d6a, 0xf2d27999, 0xb93d608d, 0x40696e90,
	0x60b18162, 0x1a156998, 0x09b8bbab, 0xc80a79b6, 0x8adbcfbc, 0xc375248c,
	0xa584e2ea, 0x5b46fe11, 0x58e84680, 0x8a8bc456, 0xd668b94f, 0x8b9035be,
	0x278509d4, 0x6663a140, 0x81a9817a, 0xd4f9d3cf, 0x6dc5f607, 0x6ae04450,
	0x694f22a4, 0x1d061788, 0x2e39ad8b, 0x748f4db2, 0xee569b52, 0xd157166d,
	0xdabc161e, 0xc8d50176, 0x7e3110e5, 0x9f7d033b, 0x128df67f, 0xb0078583,
	0xa3a75d26, 0xc1ad8011, 0x07dd89ec, 0xef04f456, 0x91bf866c, 0x6aac5306,
	0xdd5a1573, 0xf73ff97a, 0x4e1186ad, 0xb9680680, 0xc8894515, 0xdc95a08e,
	0xc894fd8e, 0xf84ade15, 0xd787f8c1, 0x40dcecca, 0x1b24743e, 0x1ce6ab23,
	0x72321653, 0xb80fbaf7, 0x1bcf099b, 0x1ff26805, 0x78f66c8e, 0xf93bf51a,
	0xfb0c06fe, 0xe50d48cf, 0x310947e0, 0x1b78804a, 0xe73e2c14, 0x8deb8381,
	0xe576122a, 0xe5a8df39, 0x42397c5e, 0xf5503f3c, 0xbe3dbf8d, 0x1b360e5c,
	0x9254caaf, 0x7a9f6744, 0x6d4144fa, 0xd77c65fe, 0x44ca7b12, 0xf58a4c00,
	0x159500d0, 0x92769857, 0x7134fdd4, 0xa3fea693, 0xbd044831, 0xeded39a1,
	0xe4570204, 0xaea37f2f, 0x9a302971, 0x620f8402, 0x1d2f3e5e, 0xf9c2f49c,
	0x738e813a, 0xb3c92251, 0x7ecba63b, 0xbe7eebc7, 0xf800267c, 0x3fdeb760,
	0xf12d5e7d, 0x5a18dce1, 0xb35a539c, 0xe565f057, 0x2babf38c, 0xae5800ad,
	0x421004dd, 0x6715acb6, 0xff529b64, 0xd520d207, 0x7cb193e7, 0xe9b18e4c,
	0xfd2a8a59, 0x47826ae3, 0x56ba43f8, 0x453b3d99, 0x8ae1675f, 0xf66f5c34,
	0x057a6ac1, 0x010769e4, 0xa8324158, 0x410379a5, 0x5dfc8c97, 0x72848afe,
	0x59f169e5, 0xe32acb78, 0x5dfaa9c4, 0x51bb956a,
};

const u8 TestRandom::expected_pcgrandom_bytes_result[24] = {
	0xf3, 0x79, 0x8f, 0x31, 0xac, 0xd9, 0x34, 0xf8, 0x3c, 0x6e, 0x82, 0x37,
	0x6b, 0x4b, 0x77, 0xe3, 0xbd, 0x0a, 0xee, 0x22, 0x79, 0x6e, 0x40, 0x00,
};

const u8 TestRandom::expected_pcgrandom_bytes_result2[24] = {
	0x47, 0x9e, 0x08, 0x3e, 0xd4, 0x21, 0x2d, 0xf6, 0xb4, 0xb1, 0x9d, 0x7a,
	0x60, 0x02, 0x5a, 0xb2, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
="hl opt">(); RemoteClient *client = getClient(peer_id, CS_Created); std::string addr_s; try { Address address = getPeerAddress(peer_id); addr_s = address.serializeString(); } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't * respond for some time, your server was overloaded or * things like that. */ infostream << "Server::ProcessData(): Canceling: peer " << peer_id << " not found" << std::endl; return; } // If net_proto_version is set, this client has already been handled if (client->getState() > CS_Created) { verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " << addr_s << " (peer_id=" << peer_id << ")" << std::endl; return; } verbosestream << "Server: Got TOSERVER_INIT from " << addr_s << " (peer_id=" << peer_id << ")" << std::endl; // Do not allow multiple players in simple singleplayer mode. // This isn't a perfect way to do it, but will suffice for now if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) { infostream << "Server: Not allowing another client (" << addr_s << ") to connect in simple singleplayer mode" << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER); return; } // First byte after command is maximum supported // serialization version u8 client_max; u16 supp_compr_modes; u16 min_net_proto_version = 0; u16 max_net_proto_version; std::string playerName; *pkt >> client_max >> supp_compr_modes >> min_net_proto_version >> max_net_proto_version >> playerName; u8 our_max = SER_FMT_VER_HIGHEST_READ; // Use the highest version supported by both u8 depl_serial_v = std::min(client_max, our_max); // If it's lower than the lowest supported, give up. if (depl_serial_v < SER_FMT_VER_LOWEST_READ) depl_serial_v = SER_FMT_VER_INVALID; if (depl_serial_v == SER_FMT_VER_INVALID) { actionstream << "Server: A mismatched client tried to connect from " << addr_s << " ser_fmt_max=" << (int)client_max << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION); return; } client->setPendingSerializationVersion(depl_serial_v); /* Read and check network protocol version */ u16 net_proto_version = 0; // Figure out a working version if it is possible at all if (max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) { // If maximum is larger than our maximum, go with our maximum if (max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) net_proto_version = SERVER_PROTOCOL_VERSION_MAX; // Else go with client's maximum else net_proto_version = max_net_proto_version; } verbosestream << "Server: " << addr_s << ": Protocol version: min: " << min_net_proto_version << ", max: " << max_net_proto_version << ", chosen: " << net_proto_version << std::endl; client->net_proto_version = net_proto_version; if ((g_settings->getBool("strict_protocol_version_checking") && net_proto_version != LATEST_PROTOCOL_VERSION) || net_proto_version < SERVER_PROTOCOL_VERSION_MIN || net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { actionstream << "Server: A mismatched client tried to connect from " << addr_s << " proto_max=" << (int)max_net_proto_version << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION); return; } /* Validate player name */ const char* playername = playerName.c_str(); size_t pns = playerName.size(); if (pns == 0 || pns > PLAYERNAME_SIZE) { actionstream << "Server: Player with " << ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty") << " name tried to connect from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME); return; } if (!string_allowed(playerName, PLAYERNAME_ALLOWED_CHARS)) { actionstream << "Server: Player with an invalid name tried to connect " "from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME); return; } m_clients.setPlayerName(peer_id, playername); //TODO (later) case insensitivity std::string legacyPlayerNameCasing = playerName; if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { actionstream << "Server: Player with the name \"singleplayer\" tried " "to connect from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME); return; } { std::string reason; if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { actionstream << "Server: Player with the name \"" << playerName << "\" tried to connect from " << addr_s << " but it was disallowed for the following reason: " << reason << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason); return; } } infostream << "Server: New connection: \"" << playerName << "\" from " << addr_s << " (peer_id=" << peer_id << ")" << std::endl; // Enforce user limit. // Don't enforce for users that have some admin right or mod permits it. if (m_clients.isUserLimitReached() && playername != g_settings->get("name") && !m_script->can_bypass_userlimit(playername, addr_s)) { actionstream << "Server: " << playername << " tried to join from " << addr_s << ", but there are already max_users=" << g_settings->getU16("max_users") << " players." << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS); return; } /* Compose auth methods for answer */ std::string encpwd; // encrypted Password field for the user bool has_auth = m_script->getAuth(playername, &encpwd, NULL); u32 auth_mechs = 0; client->chosen_mech = AUTH_MECHANISM_NONE; if (has_auth) { std::vector<std::string> pwd_components = str_split(encpwd, '#'); if (pwd_components.size() == 4) { if (pwd_components[1] == "1") { // 1 means srp auth_mechs |= AUTH_MECHANISM_SRP; client->enc_pwd = encpwd; } else { actionstream << "User " << playername << " tried to log in, " "but password field was invalid (unknown mechcode)." << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } } else if (base64_is_valid(encpwd)) { auth_mechs |= AUTH_MECHANISM_LEGACY_PASSWORD; client->enc_pwd = encpwd; } else { actionstream << "User " << playername << " tried to log in, but " "password field was invalid (invalid base64)." << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); return; } } else { std::string default_password = g_settings->get("default_password"); if (default_password.length() == 0) { auth_mechs |= AUTH_MECHANISM_FIRST_SRP; } else { // Take care of default passwords. client->enc_pwd = get_encoded_srp_verifier(playerName, default_password); auth_mechs |= AUTH_MECHANISM_SRP; // Allocate player in db, but only on successful login. client->create_player_on_auth_success = true; } } /* Answer with a TOCLIENT_HELLO */ verbosestream << "Sending TOCLIENT_HELLO with auth method field: " << auth_mechs << std::endl; NetworkPacket resp_pkt(TOCLIENT_HELLO, 1 + 4 + legacyPlayerNameCasing.size(), peer_id); u16 depl_compress_mode = NETPROTO_COMPRESSION_NONE; resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version << auth_mechs << legacyPlayerNameCasing; Send(&resp_pkt); client->allowed_auth_mechs = auth_mechs; client->setDeployedCompressionMode(depl_compress_mode); m_clients.event(peer_id, CSE_Hello); } void Server::handleCommand_Init2(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); verbosestream << "Server: Got TOSERVER_INIT2 from " << peer_id << std::endl; m_clients.event(peer_id, CSE_GotInit2); u16 protocol_version = m_clients.getProtocolVersion(peer_id); std::string lang; if (pkt->getSize() > 0) *pkt >> lang; /* Send some initialization data */ infostream << "Server: Sending content to " << getPlayerName(peer_id) << std::endl; // Send item definitions SendItemDef(peer_id, m_itemdef, protocol_version); // Send node definitions SendNodeDef(peer_id, m_nodedef, protocol_version); m_clients.event(peer_id, CSE_SetDefinitionsSent); // Send media announcement sendMediaAnnouncement(peer_id, lang); RemoteClient *client = getClient(peer_id, CS_InitDone); // Keep client language for server translations client->setLangCode(lang); // Send active objects { PlayerSAO *sao = getPlayerSAO(peer_id); if (client && sao) SendActiveObjectRemoveAdd(client, sao); } // Send detached inventories sendDetachedInventories(peer_id, false); // Send player movement settings SendMovement(peer_id); // Send time of day u16 time = m_env->getTimeOfDay(); float time_speed = g_settings->getFloat("time_speed"); SendTimeOfDay(peer_id, time, time_speed); SendCSMRestrictionFlags(peer_id); // Warnings about protocol version can be issued here if (client->net_proto_version < LATEST_PROTOCOL_VERSION) { SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, L"# Server: WARNING: YOUR CLIENT'S VERSION MAY NOT BE FULLY COMPATIBLE " L"WITH THIS SERVER!")); } } void Server::handleCommand_RequestMedia(NetworkPacket* pkt) { std::vector<std::string> tosend; u16 numfiles; *pkt >> numfiles; session_t peer_id = pkt->getPeerId(); infostream << "Sending " << numfiles << " files to " << getPlayerName(peer_id) << std::endl; verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; for (u16 i = 0; i < numfiles; i++) { std::string name; *pkt >> name; tosend.push_back(name); verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " << name << std::endl; } sendRequestedMedia(peer_id, tosend); } void Server::handleCommand_ClientReady(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); PlayerSAO* playersao = StageTwoClientInit(peer_id); if (playersao == NULL) { errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed " "peer_id=" << peer_id << std::endl; DisconnectPeer(peer_id); return; } if (pkt->getSize() < 8) { errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, " "disconnecting peer_id: " << peer_id << std::endl; DisconnectPeer(peer_id); return; } u8 major_ver, minor_ver, patch_ver, reserved; std::string full_ver; *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver; m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, full_ver); if (pkt->getRemainingBytes() >= 2) *pkt >> playersao->getPlayer()->formspec_version; const std::vector<std::string> &players = m_clients.getPlayerNames(); NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id); list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size(); for (const std::string &player: players) { list_pkt << player; } m_clients.send(peer_id, 0, &list_pkt, true); NetworkPacket notice_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, PEER_ID_INEXISTENT); // (u16) 1 + std::string represents a pseudo vector serialization representation notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName()); m_clients.sendToAll(&notice_pkt); m_clients.event(peer_id, CSE_SetClientReady); s64 last_login; m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login); m_script->on_joinplayer(playersao, last_login); // Send shutdown timer if shutdown has been scheduled if (m_shutdown_state.isTimerRunning()) { SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage()); } } void Server::handleCommand_GotBlocks(NetworkPacket* pkt) { if (pkt->getSize() < 1) return; /* [0] u16 command [2] u8 count [3] v3s16 pos_0 [3+6] v3s16 pos_1 ... */ u8 count; *pkt >> count; RemoteClient *client = getClient(pkt->getPeerId()); if ((s16)pkt->getSize() < 1 + (int)count * 6) { throw con::InvalidIncomingDataException ("GOTBLOCKS length is too short"); } for (u16 i = 0; i < count; i++) { v3s16 p; *pkt >> p; client->GotBlock(p); } } void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt) { if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4 + 4 + 1 + 1) return; v3s32 ps, ss; s32 f32pitch, f32yaw; u8 f32fov; *pkt >> ps; *pkt >> ss; *pkt >> f32pitch; *pkt >> f32yaw; f32 pitch = (f32)f32pitch / 100.0f; f32 yaw = (f32)f32yaw / 100.0f; u32 keyPressed = 0; // default behavior (in case an old client doesn't send these) f32 fov = 0; u8 wanted_range = 0; *pkt >> keyPressed; *pkt >> f32fov; fov = (f32)f32fov / 80.0f; *pkt >> wanted_range; v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); pitch = modulo360f(pitch); yaw = wrapDegrees_0_360(yaw); playersao->setBasePosition(position); player->setSpeed(speed); playersao->setLookPitch(pitch); playersao->setPlayerYaw(yaw); playersao->setFov(fov); playersao->setWantedRange(wanted_range); player->keyPressed = keyPressed; player->control.up = (keyPressed & (0x1 << 0)); player->control.down = (keyPressed & (0x1 << 1)); player->control.left = (keyPressed & (0x1 << 2)); player->control.right = (keyPressed & (0x1 << 3)); player->control.jump = (keyPressed & (0x1 << 4)); player->control.aux1 = (keyPressed & (0x1 << 5)); player->control.sneak = (keyPressed & (0x1 << 6)); player->control.dig = (keyPressed & (0x1 << 7)); player->control.place = (keyPressed & (0x1 << 8)); player->control.zoom = (keyPressed & (0x1 << 9)); if (playersao->checkMovementCheat()) { // Call callbacks m_script->on_cheat(playersao, "moved_too_fast"); SendMovePlayer(pkt->getPeerId()); } } void Server::handleCommand_PlayerPos(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << "Server::ProcessData(): Canceling: No player object for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } // If player is dead we don't care of this packet if (playersao->isDead()) { verbosestream << "TOSERVER_PLAYERPOS: " << player->getName() << " is dead. Ignoring packet"; return; } process_PlayerPos(player, playersao, pkt); } void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) { if (pkt->getSize() < 1) return; /* [0] u16 command [2] u8 count [3] v3s16 pos_0 [3+6] v3s16 pos_1 ... */ u8 count; *pkt >> count; RemoteClient *client = getClient(pkt->getPeerId()); if ((s16)pkt->getSize() < 1 + (int)count * 6) { throw con::InvalidIncomingDataException ("DELETEDBLOCKS length is too short"); } for (u16 i = 0; i < count; i++) { v3s16 p; *pkt >> p; client->SetBlockNotSent(p); } } void Server::handleCommand_InventoryAction(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << "Server::ProcessData(): Canceling: No player object for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } // Strip command and create a stream std::string datastring(pkt->getString(0), pkt->getSize()); verbosestream << "TOSERVER_INVENTORY_ACTION: data=" << datastring << std::endl; std::istringstream is(datastring, std::ios_base::binary); // Create an action std::unique_ptr<InventoryAction> a(InventoryAction::deSerialize(is)); if (!a) { infostream << "TOSERVER_INVENTORY_ACTION: " << "InventoryAction::deSerialize() returned NULL" << std::endl; return; } // If something goes wrong, this player is to blame RollbackScopeActor rollback_scope(m_rollback, std::string("player:")+player->getName()); /* Note: Always set inventory not sent, to repair cases where the client made a bad prediction. */ const bool player_has_interact = checkPriv(player->getName(), "interact"); auto check_inv_access = [player, player_has_interact] ( const InventoryLocation &loc) -> bool { if (loc.type == InventoryLocation::CURRENT_PLAYER) return false; // Only used internally on the client, never sent if (loc.type == InventoryLocation::PLAYER) { // Allow access to own inventory in all cases return loc.name == player->getName(); } if (!player_has_interact) { infostream << "Cannot modify foreign inventory: " << "No interact privilege" << std::endl; return false; } return true; }; /* Handle restrictions and special cases of the move action */ if (a->getType() == IAction::Move) { IMoveAction *ma = (IMoveAction*)a.get(); ma->from_inv.applyCurrentPlayer(player->getName()); ma->to_inv.applyCurrentPlayer(player->getName()); m_inventory_mgr->setInventoryModified(ma->from_inv); if (ma->from_inv != ma->to_inv) m_inventory_mgr->setInventoryModified(ma->to_inv); if (!check_inv_access(ma->from_inv) || !check_inv_access(ma->to_inv)) return; InventoryLocation *remote = ma->from_inv.type == InventoryLocation::PLAYER ? &ma->to_inv : &ma->from_inv; // Check for out-of-range interaction if (remote->type == InventoryLocation::NODEMETA) { v3f node_pos = intToFloat(remote->p, BS); v3f player_pos = player->getPlayerSAO()->getEyePosition(); f32 d = player_pos.getDistanceFrom(node_pos); if (!checkInteractDistance(player, d, "inventory")) return; } /* Disable moving items out of craftpreview */ if (ma->from_list == "craftpreview") { infostream << "Ignoring IMoveAction from " << (ma->from_inv.dump()) << ":" << ma->from_list << " to " << (ma->to_inv.dump()) << ":" << ma->to_list << " because src is " << ma->from_list << std::endl; return; } /* Disable moving items into craftresult and craftpreview */ if (ma->to_list == "craftpreview" || ma->to_list == "craftresult") { infostream << "Ignoring IMoveAction from " << (ma->from_inv.dump()) << ":" << ma->from_list << " to " << (ma->to_inv.dump()) << ":" << ma->to_list << " because dst is " << ma->to_list << std::endl; return; } } /* Handle restrictions and special cases of the drop action */ else if (a->getType() == IAction::Drop) { IDropAction *da = (IDropAction*)a.get(); da->from_inv.applyCurrentPlayer(player->getName()); m_inventory_mgr->setInventoryModified(da->from_inv); /* Disable dropping items out of craftpreview */ if (da->from_list == "craftpreview") { infostream << "Ignoring IDropAction from " << (da->from_inv.dump()) << ":" << da->from_list << " because src is " << da->from_list << std::endl; return; } // Disallow dropping items if not allowed to interact if (!player_has_interact || !check_inv_access(da->from_inv)) return; // Disallow dropping items if dead if (playersao->isDead()) { infostream << "Ignoring IDropAction from " << (da->from_inv.dump()) << ":" << da->from_list << " because player is dead." << std::endl; return; } } /* Handle restrictions and special cases of the craft action */ else if (a->getType() == IAction::Craft) { ICraftAction *ca = (ICraftAction*)a.get(); ca->craft_inv.applyCurrentPlayer(player->getName()); m_inventory_mgr->setInventoryModified(ca->craft_inv); // Disallow crafting if not allowed to interact if (!player_has_interact) { infostream << "Cannot craft: " << "No interact privilege" << std::endl; return; } if (!check_inv_access(ca->craft_inv)) return; } else { // Unknown action. Ignored. return; } // Do the action a->apply(m_inventory_mgr.get(), playersao, this); } void Server::handleCommand_ChatMessage(NetworkPacket* pkt) { /* u16 command u16 length wstring message */ u16 len; *pkt >> len; std::wstring message; for (u16 i = 0; i < len; i++) { u16 tmp_wchar; *pkt >> tmp_wchar; message += (wchar_t)tmp_wchar; } session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } // Get player name of this client std::string name = player->getName(); std::wstring wname = narrow_to_wide(name); std::wstring answer_to_sender = handleChat(name, wname, message, true, player); if (!answer_to_sender.empty()) { // Send the answer to sender SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_NORMAL, answer_to_sender, wname)); } } void Server::handleCommand_Damage(NetworkPacket* pkt) { u16 damage; *pkt >> damage; session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << "Server::ProcessData(): Canceling: No player object for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } if (!playersao->isImmortal()) { if (playersao->isDead()) { verbosestream << "Server::ProcessData(): Info: " "Ignoring damage as player " << player->getName() << " is already dead." << std::endl; return; } actionstream << player->getName() << " damaged by " << (int)damage << " hp at " << PP(playersao->getBasePosition() / BS) << std::endl; PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); playersao->setHP((s32)playersao->getHP() - (s32)damage, reason); SendPlayerHPOrDie(playersao, reason); } } void Server::handleCommand_PlayerItem(NetworkPacket* pkt) { if (pkt->getSize() < 2) return; session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << "Server::ProcessData(): Canceling: No player object for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } u16 item; *pkt >> item; if (item >= player->getHotbarItemcount()) { actionstream << "Player: " << player->getName() << " tried to access item=" << item << " out of hotbar_itemcount=" << player->getHotbarItemcount() << "; ignoring." << std::endl; return; } playersao->getPlayer()->setWieldIndex(item); } void Server::handleCommand_Respawn(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); if (player == NULL) { errorstream << "Server::ProcessData(): Canceling: No player for peer_id=" << peer_id << " disconnecting peer!" << std::endl; DisconnectPeer(peer_id); return; } PlayerSAO *playersao = player->getPlayerSAO(); assert(playersao); if (!playersao->isDead()) return; RespawnPlayer(peer_id); actionstream << player->getName() << " respawns at "