/
IDAllocator.cpp
360 lines (294 loc) · 15.2 KB
/
IDAllocator.cpp
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#include "IDAllocator.h"
#include <limits>
#include "../util/AppInterface.h"
#include "../util/LoggerWithOptionsDB.h"
#include "../util/Logger.h"
#include "../util/Random.h"
#include "../util/Serialize.h"
#include "../util/Serialize.ipp"
namespace {
DeclareThreadSafeLogger(IDallocator);
}
IDAllocator::IDAllocator(const int server_id,
const std::vector<int>& client_ids,
const ID_t invalid_id,
const ID_t temp_id,
const ID_t highest_pre_allocated_id) :
m_invalid_id(invalid_id),
m_temp_id(temp_id),
m_stride(client_ids.size() + 1),
m_zero(std::max({m_invalid_id + 1, m_temp_id + 1, highest_pre_allocated_id + 1})),
m_server_id(server_id),
m_empire_id(server_id),
m_offset_to_empire_id(client_ids.size() + 1, server_id),
m_warn_threshold(std::numeric_limits<int>::max() - 1000 * m_stride),
m_exhausted_threshold(std::numeric_limits<int>::max() - 10 * m_stride),
m_random_generator()
{
TraceLogger(IDallocator) << "IDAllocator() server id = " << server_id << " invalid id = " << invalid_id
<< " temp_id = " << m_temp_id
<< " zero = " << m_zero
<< " warn threshold = " << m_warn_threshold << " num clients = " << client_ids.size();
// Assign the server and each client a unique initial offset modulo m_stride.
auto ii = m_zero;
// Assign the server to the first offset
m_offset_to_empire_id[(ii - m_zero) % m_stride] = m_server_id;
m_empire_id_to_next_assigned_object_id.emplace(m_server_id, ii);
++ii;
for (const auto empire_id : client_ids) {
if (empire_id == m_server_id)
continue;
AssigningEmpireForID(ii) = empire_id;
m_empire_id_to_next_assigned_object_id.emplace(empire_id, ii);
++ii;
}
}
int IDAllocator::NewID(const Universe& universe) {
// increment next id for this client until next id is not an already-used id
IncrementNextAssignedId(m_empire_id, universe.Objects().HighestObjectID());
IncrementNextAssignedId(m_empire_id, universe.HighestDestroyedObjectID());
// Find the next id for this client in the table.
auto it = m_empire_id_to_next_assigned_object_id.find(m_empire_id);
if (it == m_empire_id_to_next_assigned_object_id.end()) {
ErrorLogger() << "m_empire_id " << m_empire_id << " not in id manager table.";
return m_invalid_id;
}
// Copy the id and then update the id for the next time NewID() is called.
const auto retval = it->second;
auto apparent_assigning_empire = AssigningEmpireForID(retval);
if (apparent_assigning_empire != m_empire_id)
ErrorLogger() << "m_empire_id " << m_empire_id << " does not match apparent assigning id "
<< apparent_assigning_empire << " for id = " << retval << " m_zero = " << m_zero
<< " stride = " << m_stride;
// Increment the next id if not exhausted
if (it->second >= m_exhausted_threshold) {
it->second = m_invalid_id;
} else if (it->second != m_invalid_id) {
it->second += m_stride;
}
if (retval == m_invalid_id)
ErrorLogger() << "Object IDs are exhausted. No objects can be added to the Universe.";
if (retval >= m_warn_threshold)
WarnLogger() << "Object IDs are almost exhausted. Currently assigning id, " << retval;
TraceLogger(IDallocator) << "Allocating id = " << retval << " for empire = " << it->first;
return retval;
}
std::pair<bool, bool> IDAllocator::IsIDValidAndUnused(const ID_t checked_id,
const int checked_empire_id)
{
static constexpr std::pair<bool, bool> hard_fail = {false, false};
static constexpr std::pair<bool, bool> complete_success = {true, true};
// allow legacy loading and order processing
static constexpr std::pair<bool, bool> legacy_success = {true, false};
if (checked_id == m_invalid_id) {
ErrorLogger() << m_invalid_id << " is an invalid id.";
return hard_fail;
}
if (checked_id == m_temp_id) {
return complete_success;
}
if (checked_id >= m_exhausted_threshold) {
ErrorLogger() << " invalid id = " << checked_id << " is greater then the maximum id " << m_exhausted_threshold;
return hard_fail;
}
if (checked_id < m_zero) {
ErrorLogger() << " invalid id = " << checked_id
<< " is lower than the expected minimum new id this turn " << m_zero;
return hard_fail;
}
// On the server all ids are valid. On the client only the client id is valid.
bool is_valid_id = (m_empire_id == m_server_id) || (m_empire_id == checked_empire_id);
if (!is_valid_id)
return hard_fail;
// Make sure this empire exists.
const auto& check_it = m_empire_id_to_next_assigned_object_id.find(checked_empire_id);
if (check_it == m_empire_id_to_next_assigned_object_id.end()) {
ErrorLogger() << "empire_id " << checked_empire_id << " not in id manager table.";
return hard_fail;
}
// If ids are exhausted then fail.
if (check_it->second == m_invalid_id)
return hard_fail;
// Check that the checked_id has the correct modulus again allowing for legacy games with
// incorrect id.
bool is_correct_modulus = (AssigningEmpireForID(checked_id) == checked_empire_id);
if (!is_correct_modulus)
return legacy_success;
if (checked_empire_id != m_server_id)
TraceLogger(IDallocator) << "Allocated object id = " << checked_id
<< " is valid for empire = " << checked_empire_id;
return complete_success;
}
bool IDAllocator::UpdateIDAndCheckIfOwned(const ID_t checked_id) {
auto valid = IsIDValidAndUnused(checked_id, m_empire_id);
// Hard failure
if (!valid.first)
return false;
// If not on the server then ignore any legacy failures and return the check.
if (m_empire_id != m_server_id)
return valid.first;
// On the server
// Update the assigning empire's next assigned id.
auto assigning_empire = AssigningEmpireForID(checked_id);
IncrementNextAssignedId(assigning_empire, checked_id);
return true;;
}
IDAllocator::ID_t& IDAllocator::AssigningEmpireForID(ID_t id)
{ return m_offset_to_empire_id[(id - m_zero) % m_stride]; }
void IDAllocator::IncrementNextAssignedId(const int assigning_empire, const int checked_id) {
auto&& empire_and_next_id = m_empire_id_to_next_assigned_object_id.find(assigning_empire);
if (empire_and_next_id == m_empire_id_to_next_assigned_object_id.end())
return;
auto& next_id = empire_and_next_id->second;
auto init_next_id = next_id;
while (next_id <= checked_id && next_id != m_invalid_id) {
next_id += m_stride;
// Don't increment to the next_id if ids are exhausted.
if (next_id >= m_exhausted_threshold)
next_id = m_invalid_id;
}
if (init_next_id != next_id)
TraceLogger(IDallocator) << "next id for empire " << assigning_empire << " updated from "
<< init_next_id << " to " << next_id;
};
void IDAllocator::ObfuscateBeforeSerialization() {
/** Do three things to obfuscate the number of ids allocated by each client:
1. Randomize the association of modulus and client id.
2. Advance all clients to the same offset plus their new modulus.
3. Add a random amount to all clients to conceal the largest number of ids allocated by
a client. */
// Ignore on clients.
if (m_empire_id != m_server_id)
return;;
TraceLogger(IDallocator) << "Before obfuscation " << StateString();
// Randomize the moduli
std::shuffle(m_offset_to_empire_id.begin(), m_offset_to_empire_id.end(), m_random_generator);
// Move the zero offset to the highest next assigned id.
auto max_next_assigned = m_empire_id_to_next_assigned_object_id.begin()->second;
for (const auto& empire_and_id : m_empire_id_to_next_assigned_object_id) {
max_next_assigned = std::max(max_next_assigned, empire_and_id.second);
}
// The /2 factor makes the random offset decrease over time to near the typical amount of ids
// assigned per turn, instead of growing without bound.
auto max_random_offset = std::max(1, (max_next_assigned - m_zero) / 2);
m_zero = max_next_assigned;
// Check that this does not exhaust the ids
auto new_max_next_id = max_next_assigned + max_random_offset + m_stride;
if (new_max_next_id > m_warn_threshold)
WarnLogger() << "Object IDs are almost exhausted. Currently assigning id, " << new_max_next_id;
if (new_max_next_id > m_exhausted_threshold) {
ErrorLogger() << "Object IDs are exhausted. No objects can be added to the Universe.";
for (auto& empire_and_id : m_empire_id_to_next_assigned_object_id)
empire_and_id.second = m_invalid_id;
return;
}
// Advance each client to its own random offset.
for (const auto assigning_empire : m_offset_to_empire_id) {
auto empire_random_offset = RandInt(0, max_random_offset);
auto new_next_id = empire_random_offset + m_zero;
// Increment until it is at the correct offset
ID_t ii_dont_check_more_than_m_stride_ids = 0;
while (AssigningEmpireForID(new_next_id) != assigning_empire && ii_dont_check_more_than_m_stride_ids <= m_stride) {
++new_next_id;
++ii_dont_check_more_than_m_stride_ids;
}
// If m_stride consecutive ids have been checked, then
// assigning_empire is not in m_offset_to_empire_id, which is an error.
if (ii_dont_check_more_than_m_stride_ids > m_stride) {
ErrorLogger()
<< "While obfuscating id allocation empire " << assigning_empire
<< "is missing from the table m_offset_to_empire_id: "
<< "[(offset, empire id), " << [this]() {
std::stringstream ss;
std::size_t offset = 0;
for (auto& empire_id : m_offset_to_empire_id)
ss << " (" << offset++ << ", " << empire_id << "), ";
return ss.str();
}() << "]"
<< " Empire " << assigning_empire
<< " may not be able to create new designs or objects.";
}
m_empire_id_to_next_assigned_object_id[assigning_empire] = new_next_id;
}
TraceLogger(IDallocator) << "After obfuscation " << StateString();
}
std::string IDAllocator::StateString() const {
std::stringstream ss;
ss << "IDAllocator m_zero = " << m_zero << " (Empire, offset, next_id) = [" ;
ID_t offset = 0;
for (const auto empire_id : m_offset_to_empire_id) {
auto next_id_it = m_empire_id_to_next_assigned_object_id.find(empire_id);
if (next_id_it == m_empire_id_to_next_assigned_object_id.end()) {
ErrorLogger(IDallocator) << "missing empire_id = " << empire_id;
continue;
}
ss << "(" << empire_id << ", " << offset << ", " << next_id_it->second << ") ";
++offset;
}
ss << "]";
return ss.str();
}
template <typename Archive>
void IDAllocator::SerializeForEmpire(Archive& ar, const unsigned int version, int empire_id) {
DebugLogger(IDallocator) << (Archive::is_loading::value ? "Deserialize " : "Serialize ")
<< "IDAllocator() server id = "
<< m_server_id << " empire id = " << empire_id;
ar & BOOST_SERIALIZATION_NVP(m_invalid_id)
& BOOST_SERIALIZATION_NVP(m_temp_id)
& BOOST_SERIALIZATION_NVP(m_stride);
if (version > 0)
ar & BOOST_SERIALIZATION_NVP(m_zero);
ar & BOOST_SERIALIZATION_NVP(m_server_id)
& BOOST_SERIALIZATION_NVP(m_warn_threshold)
& BOOST_SERIALIZATION_NVP(m_exhausted_threshold);
if constexpr (Archive::is_loading::value) {
// Always load whatever is there.
ar & BOOST_SERIALIZATION_NVP(m_empire_id)
& BOOST_SERIALIZATION_NVP(m_empire_id_to_next_assigned_object_id)
& BOOST_SERIALIZATION_NVP(m_offset_to_empire_id);
DebugLogger(IDallocator) << "Deserialized [" << [this]() {
std::stringstream ss;
for (auto& empire_and_next_id : m_empire_id_to_next_assigned_object_id) {
ss << "empire = " << empire_and_next_id.first << " next id = " << empire_and_next_id.second << ", ";
}
return ss.str();
}() << "]";
} else {
if (m_empire_id != empire_id && m_empire_id != m_server_id)
ErrorLogger() << "An empire with id = " << m_empire_id << " which is not the server "
<< "is attempting to serialize the IDAllocator for a different empire " << empire_id;
// If the target empire is the server, provide the full map.
if (empire_id == m_server_id) {
ar & BOOST_SERIALIZATION_NVP(m_empire_id)
& BOOST_SERIALIZATION_NVP(m_empire_id_to_next_assigned_object_id)
& BOOST_SERIALIZATION_NVP(m_offset_to_empire_id);
} else {
ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_empire_id), empire_id);
// Filter the map for empires so they only have their own actual next id and no
// information about other clients.
std::unordered_map<int, ID_t> temp_empire_id_to_object_id{};
auto temp_offset_to_empire_id = std::vector<int>(m_offset_to_empire_id.size(), m_server_id);
auto&& it = m_empire_id_to_next_assigned_object_id.find(empire_id);
if (it == m_empire_id_to_next_assigned_object_id.end()) {
ErrorLogger() << "Attempt to serialize allocator for an empire_id "
<< empire_id << " not in id manager table.";
} else {
temp_empire_id_to_object_id.insert(*it);
temp_offset_to_empire_id[(it->second - m_zero) % m_stride] = empire_id;
}
ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_empire_id_to_next_assigned_object_id), temp_empire_id_to_object_id);
ar & boost::serialization::make_nvp(BOOST_PP_STRINGIZE(m_offset_to_empire_id), temp_offset_to_empire_id);
DebugLogger(IDallocator) << "Serialized [" << [&temp_empire_id_to_object_id]() {
std::stringstream ss;
for (auto& empire_and_next_id : temp_empire_id_to_object_id) {
ss << "empire = " << empire_and_next_id.first << " next id = " << empire_and_next_id.second << ", ";
}
return ss.str();
}() << "]";
}
}
}
template void IDAllocator::SerializeForEmpire<freeorion_bin_oarchive>(freeorion_bin_oarchive& ar, const unsigned int version, const int empire_id);
template void IDAllocator::SerializeForEmpire<freeorion_bin_iarchive>(freeorion_bin_iarchive& ar, const unsigned int version, const int empire_id);
template void IDAllocator::SerializeForEmpire<freeorion_xml_oarchive>(freeorion_xml_oarchive& ar, const unsigned int version, const int empire_id);
template void IDAllocator::SerializeForEmpire<freeorion_xml_iarchive>(freeorion_xml_iarchive& ar, const unsigned int version, const int empire_id);