Cuberite
A lightweight, fast and extensible game server for Minecraft
MojangAPI.cpp
Go to the documentation of this file.
1 
2 // MojangAPI.cpp
3 
4 // Implements the cMojangAPI class representing the various API points provided by Mojang's webservices, and a cache for their results
5 
6 #include "Globals.h"
7 #include "MojangAPI.h"
8 
9 #include "HTTP/UrlClient.h"
10 #include "IniFile.h"
11 #include "JsonUtils.h"
12 #include "json/json.h"
14 #include "mbedTLS++/SslConfig.h"
15 #include "OSSupport/IsThread.h"
16 #include "RankManager.h"
17 #include "Root.h"
18 #include "SQLiteCpp/Database.h"
19 #include "SQLiteCpp/Statement.h"
20 
21 
22 
23 
25 const Int64 MAX_AGE = 7 * 24 * 60 * 60; // 7 days ago
26 
28 const int MAX_PER_QUERY = 100;
29 
30 
31 
32 
33 
34 constexpr char DEFAULT_NAME_TO_UUID_SERVER[] = "api.mojang.com";
35 constexpr char DEFAULT_NAME_TO_UUID_ADDRESS[] = "/profiles/minecraft";
36 constexpr char DEFAULT_UUID_TO_PROFILE_SERVER[] = "sessionserver.mojang.com";
37 constexpr char DEFAULT_UUID_TO_PROFILE_ADDRESS[] = "/session/minecraft/profile/%UUID%?unsigned=false";
38 
39 
40 
41 
42 
44 {
46  static const AStringMap & UrlClientOptions()
47  {
48  static const AString CertString =
49  // DigiCert Global Root CA (sessionserver.mojang.com, api.mojang.com)
50  // Downloaded from https://www.digicert.com/kb/digicert-root-certificates.htm
51 
52  // DigiCert Global Root CA
53  "-----BEGIN CERTIFICATE-----\n"
54  "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n"
55  "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
56  "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
57  "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n"
58  "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
59  "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n"
60  "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n"
61  "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n"
62  "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n"
63  "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n"
64  "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n"
65  "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n"
66  "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n"
67  "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n"
68  "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n"
69  "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n"
70  "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n"
71  "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n"
72  "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n"
73  "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n"
74  "-----END CERTIFICATE-----\n"
75 
76  // AAA Certificate Services (authserver.ely.by GH#4832)
77  // Downloaded from https://www.tbs-certificates.co.uk/FAQ/en/Comodo_AAA_Certificate_Services.html
78  "-----BEGIN CERTIFICATE-----\n"
79  "MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb\n"
80  "MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow\n"
81  "GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj\n"
82  "YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL\n"
83  "MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\n"
84  "BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM\n"
85  "GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
86  "ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua\n"
87  "BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n"
88  "3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4\n"
89  "YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR\n"
90  "rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm\n"
91  "ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU\n"
92  "oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF\n"
93  "MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v\n"
94  "QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t\n"
95  "b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF\n"
96  "AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n"
97  "GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz\n"
98  "Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2\n"
99  "G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi\n"
100  "l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3\n"
101  "smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n"
102  "-----END CERTIFICATE-----\n"
103  ;
104  static const AStringMap UrlClientOptions = {{"TrustedRootCAs", CertString}};
105  return UrlClientOptions;
106  }
107 }
108 
109 
110 
111 
112 
114 // cMojangAPI::sProfile:
115 
117  const AString & a_PlayerName,
118  const cUUID & a_UUID,
119  const Json::Value & a_Properties,
120  Int64 a_DateTime
121 ) :
122  m_PlayerName(a_PlayerName),
123  m_UUID(a_UUID),
124  m_Textures(),
125  m_TexturesSignature(),
126  m_DateTime(a_DateTime)
127 {
128  /*
129  Example a_Profile contents:
130  "properties":
131  [
132  {
133  "name": "textures",
134  "value": "eyJ0aW1lc3RhbXAiOjE0MDcwNzAzMjEyNzEsInByb2ZpbGVJZCI6ImIxY2FmMjQyMDJhODQxYTc4MDU1YTA3OWM0NjBlZWU3IiwicHJvZmlsZU5hbWUiOiJ4b2Z0IiwiaXNQdWJsaWMiOnRydWUsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNzc5YmFiZjVhNTg3Zjk0OGFkNjc0N2VhOTEyNzU0MjliNjg4Mjk1YWUzYzA3YmQwZTJmNWJmNGQwNTIifX19",
135  "signature": "XCty+jGEF39hEPrPhYNnCX087kPaoCjYruzYI/DS4nkL5hbjnkSM5Rh15hnUyv/FHhC8OF5rif3D1tQjtMI19KSVaXoUFXpbJM8/+PB8GDgEbX8Fc3u9nYkzOcM/xfxdYsFAdFhLQMkvase/BZLSuPhdy9DdI+TCrO7xuSTZfYmmwVuWo3w5gCY+mSIAnqltnOzaOOTcly75xvO0WYpVk7nJdnR2tvSi0wfrQPDrIg/uzhX7p0SnDqijmBU4QaNez/TNKiFxy69dAzt0RSotlQzqkDbyVKhhv9a4eY8h3pXi4UMftKEj4FAKczxLImkukJXuOn5NN15/Q+le0rJVBC60/xjKIVzltEsMN6qjWD0lQjey7WEL+4pGhCVuWY5KzuZjFvgqszuJTFz7lo+bcHiceldJtea8/fa02eTRObZvdLxbWC9ZfFY0IhpOVKfcLdno/ddDMNMQMi5kMrJ8MZZ/PcW1w5n7MMGWPGCla1kOaC55AL0QYSMGRVEZqgU9wXI5M7sHGZKGM4mWxkbEJYBkpI/p3GyxWgV6v33ZWlsz65TqlNrR1gCLaoFCm7Sif8NqPBZUAONHYon0roXhin/DyEanS93WV6i6FC1Wisscjq2AcvnOlgTo/5nN/1QsMbjNumuMGo37sqjRqlXoPb8zEUbAhhztYuJjEfQ2Rd8="
136  }
137  ]
138  */
139 
140  // Parse the Textures and TexturesSignature from the Profile:
141  if (!a_Properties.isArray())
142  {
143  // Properties is not a valid array, bail out
144  return;
145  }
146  Json::UInt Size = a_Properties.size();
147  for (Json::UInt i = 0; i < Size; i++)
148  {
149  const Json::Value & Prop = a_Properties[i];
150  if (Prop.get("name", "").asString() != "textures")
151  {
152  continue;
153  }
154  m_Textures = Prop.get("value", "").asString();
155  m_TexturesSignature = Prop.get("signature", "").asString();
156  break;
157  } // for i - Properties[]
158 }
159 
160 
161 
162 
163 
165 // cMojangAPI::cUpdateThread:
166 
168  public cIsThread
169 {
170  using Super = cIsThread;
171 
172 public:
173 
174  cUpdateThread(cMojangAPI & a_MojangAPI):
175  Super("MojangAPI Updater"),
176  m_MojangAPI(a_MojangAPI)
177  {
178  }
179 
180  virtual ~cUpdateThread() override
181  {
182  // Notify the thread that it should stop:
183  m_ShouldTerminate = true;
184  m_evtNotify.Set();
185 
186  // Wait for the thread to actually finish work:
187  Stop();
188  }
189 
190 protected:
191 
194 
197 
198 
199  // cIsThread override:
200  virtual void Execute(void) override
201  {
202  do
203  {
205  } while (!m_ShouldTerminate && !m_evtNotify.Wait(60 * 60 * 1000)); // Repeat every 60 minutes until termination request
206  }
207 } ;
208 
209 
210 
211 
212 
214 // cMojangAPI:
215 
217  m_RankMgr(nullptr),
218  m_UpdateThread(new cUpdateThread(*this))
219 {
220 }
221 
222 
223 
224 
225 
227 {
229 }
230 
231 
232 
233 
234 
235 void cMojangAPI::Start(cSettingsRepositoryInterface & a_Settings, bool a_ShouldAuth)
236 {
237  auto NameToUUIDServer = a_Settings.GetValueSet("MojangAPI", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER);
238  auto NameToUUIDAddress = a_Settings.GetValueSet("MojangAPI", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS);
239  auto UUIDToProfileServer = a_Settings.GetValueSet("MojangAPI", "UUIDToProfileServer", DEFAULT_UUID_TO_PROFILE_SERVER);
240  auto UUIDToProfileAddress = a_Settings.GetValueSet("MojangAPI", "UUIDToProfileAddress", DEFAULT_UUID_TO_PROFILE_ADDRESS);
241  m_NameToUUIDUrl = "https://" + NameToUUIDServer + NameToUUIDAddress;
242  m_UUIDToProfileUrl = "https://" + UUIDToProfileServer + UUIDToProfileAddress;
244  if (a_ShouldAuth)
245  {
246  m_UpdateThread->Start();
247  }
248 }
249 
250 
251 
252 
253 
254 cUUID cMojangAPI::GetUUIDFromPlayerName(const AString & a_PlayerName, bool a_UseOnlyCached)
255 {
256  // Convert the playername to lowercase:
257  AString lcPlayerName = StrToLower(a_PlayerName);
258 
259  // Request the cache to query the name if not yet cached:
260  if (!a_UseOnlyCached)
261  {
262  AStringVector PlayerNames{ lcPlayerName };
263  CacheNamesToUUIDs(PlayerNames);
264  }
265 
266  // Retrieve from cache:
267  cCSLock Lock(m_CSNameToUUID);
268  cProfileMap::const_iterator itr = m_NameToUUID.find(lcPlayerName);
269  if (itr == m_NameToUUID.end())
270  {
271  // No UUID found
272  return {};
273  }
274  return itr->second.m_UUID;
275 }
276 
277 
278 
279 
280 
281 AString cMojangAPI::GetPlayerNameFromUUID(const cUUID & a_UUID, bool a_UseOnlyCached)
282 {
283  // Retrieve from caches:
284  {
286  auto itr = m_UUIDToProfile.find(a_UUID);
287  if (itr != m_UUIDToProfile.end())
288  {
289  return itr->second.m_PlayerName;
290  }
291  }
292  {
293  cCSLock Lock(m_CSUUIDToName);
294  auto itr = m_UUIDToName.find(a_UUID);
295  if (itr != m_UUIDToName.end())
296  {
297  return itr->second.m_PlayerName;
298  }
299  }
300 
301  // Name not yet cached, request cache and retry:
302  if (!a_UseOnlyCached)
303  {
304  CacheUUIDToProfile(a_UUID);
305  return GetPlayerNameFromUUID(a_UUID, true);
306  }
307 
308  // No value found, none queried. Return an error:
309  return {};
310 }
311 
312 
313 
314 
315 
316 std::vector<cUUID> cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames, bool a_UseOnlyCached)
317 {
318  // Convert all playernames to lowercase:
319  AStringVector PlayerNames;
320  for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr)
321  {
322  PlayerNames.push_back(StrToLower(*itr));
323  } // for itr - a_PlayerNames[]
324 
325  // Request the cache to populate any names not yet contained:
326  if (!a_UseOnlyCached)
327  {
328  CacheNamesToUUIDs(PlayerNames);
329  }
330 
331  // Retrieve from cache:
332  size_t idx = 0;
333  std::vector<cUUID> res;
334  res.resize(PlayerNames.size());
335  cCSLock Lock(m_CSNameToUUID);
336  for (AStringVector::const_iterator itr = PlayerNames.begin(), end = PlayerNames.end(); itr != end; ++itr, ++idx)
337  {
338  cProfileMap::const_iterator itrN = m_NameToUUID.find(*itr);
339  if (itrN != m_NameToUUID.end())
340  {
341  res[idx] = itrN->second.m_UUID;
342  }
343  } // for itr - PlayerNames[]
344  return res;
345 }
346 
347 
348 
349 
350 
351 void cMojangAPI::AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const cUUID & a_UUID)
352 {
353  Int64 Now = time(nullptr);
354  {
355  cCSLock Lock(m_CSNameToUUID);
356  m_NameToUUID[StrToLower(a_PlayerName)] = sProfile(a_PlayerName, a_UUID, "", "", Now);
357  }
358  {
359  cCSLock Lock(m_CSUUIDToName);
360  m_UUIDToName[a_UUID] = sProfile(a_PlayerName, a_UUID, "", "", Now);
361  }
362  NotifyNameUUID(a_PlayerName, a_UUID);
363 }
364 
365 
366 
367 
368 
369 void cMojangAPI::AddPlayerProfile(const AString & a_PlayerName, const cUUID & a_UUID, const Json::Value & a_Properties)
370 {
371  Int64 Now = time(nullptr);
372  {
373  cCSLock Lock(m_CSNameToUUID);
374  m_NameToUUID[StrToLower(a_PlayerName)] = sProfile(a_PlayerName, a_UUID, "", "", Now);
375  }
376  {
377  cCSLock Lock(m_CSUUIDToName);
378  m_UUIDToName[a_UUID] = sProfile(a_PlayerName, a_UUID, "", "", Now);
379  }
380  {
382  m_UUIDToProfile[a_UUID] = sProfile(a_PlayerName, a_UUID, a_Properties, Now);
383  }
384  NotifyNameUUID(a_PlayerName, a_UUID);
385 }
386 
387 
388 
389 
390 
392 {
393  try
394  {
395  // Open up the SQLite DB:
396  SQLite::Database db("MojangAPI.sqlite", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
397  db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)");
398  db.exec("CREATE TABLE IF NOT EXISTS UUIDToProfile (UUID, PlayerName, Textures, TexturesSignature, DateTime)");
399 
400  // Retrieve all entries:
401  {
402  SQLite::Statement stmt(db, "SELECT PlayerName, UUID, DateTime FROM PlayerNameToUUID");
403  while (stmt.executeStep())
404  {
405  AString PlayerName = stmt.getColumn(0);
406  AString StringUUID = stmt.getColumn(1);
407  Int64 DateTime = stmt.getColumn(2);
408 
409  cUUID UUID;
410  if (!UUID.FromString(StringUUID))
411  {
412  continue; // Invalid UUID
413  }
414 
415  m_NameToUUID[StrToLower(PlayerName)] = sProfile(PlayerName, UUID, "", "", DateTime);
416  m_UUIDToName[UUID] = sProfile(PlayerName, UUID, "", "", DateTime);
417  }
418  }
419  {
420  SQLite::Statement stmt(db, "SELECT PlayerName, UUID, Textures, TexturesSignature, DateTime FROM UUIDToProfile");
421  while (stmt.executeStep())
422  {
423  AString PlayerName = stmt.getColumn(0);
424  AString StringUUID = stmt.getColumn(1);
425  AString Textures = stmt.getColumn(2);
426  AString TexturesSignature = stmt.getColumn(2);
427  Int64 DateTime = stmt.getColumn(4);
428 
429  cUUID UUID;
430  if (!UUID.FromString(StringUUID))
431  {
432  continue; // Invalid UUID
433  }
434 
435  m_UUIDToProfile[UUID] = sProfile(PlayerName, UUID, Textures, TexturesSignature, DateTime);
436  }
437  }
438  }
439  catch (const SQLite::Exception & ex)
440  {
441  LOGINFO("Loading MojangAPI cache failed: %s", ex.what());
442  }
443 }
444 
445 
446 
447 
448 
450 {
451  try
452  {
453  // Open up the SQLite DB:
454  SQLite::Database db("MojangAPI.sqlite", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
455  db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)");
456  db.exec("CREATE TABLE IF NOT EXISTS UUIDToProfile (UUID, PlayerName, Textures, TexturesSignature, DateTime)");
457 
458  // Remove all entries:
459  db.exec("DELETE FROM PlayerNameToUUID");
460  db.exec("DELETE FROM UUIDToProfile");
461 
462  // Save all cache entries - m_PlayerNameToUUID:
463  Int64 LimitDateTime = time(nullptr) - MAX_AGE;
464  {
465  SQLite::Statement stmt(db, "INSERT INTO PlayerNameToUUID(PlayerName, UUID, DateTime) VALUES (?, ?, ?)");
466  cCSLock Lock(m_CSNameToUUID);
467  for (auto & NameToUUID : m_NameToUUID)
468  {
469  auto & Profile = NameToUUID.second;
470  if (Profile.m_DateTime < LimitDateTime)
471  {
472  // This item is too old, do not save
473  continue;
474  }
475  stmt.bind(1, Profile.m_PlayerName);
476  stmt.bind(2, Profile.m_UUID.ToShortString());
477  stmt.bind(3, Profile.m_DateTime);
478  stmt.exec();
479  stmt.reset();
480  }
481  }
482 
483  // Save all cache entries - m_UUIDToProfile:
484  {
485  SQLite::Statement stmt(db, "INSERT INTO UUIDToProfile(UUID, PlayerName, Textures, TexturesSignature, DateTime) VALUES (?, ?, ?, ?, ?)");
487  for (auto & UUIDToProfile : m_UUIDToProfile)
488  {
489  auto & Profile = UUIDToProfile.second;
490  if (Profile.m_DateTime < LimitDateTime)
491  {
492  // This item is too old, do not save
493  continue;
494  }
495  stmt.bind(1, Profile.m_UUID.ToShortString());
496  stmt.bind(2, Profile.m_PlayerName);
497  stmt.bind(3, Profile.m_Textures);
498  stmt.bind(4, Profile.m_TexturesSignature);
499  stmt.bind(5, Profile.m_DateTime);
500  stmt.exec();
501  stmt.reset();
502  }
503  }
504  }
505  catch (const SQLite::Exception & ex)
506  {
507  LOGINFO("Saving MojangAPI cache failed: %s", ex.what());
508  }
509 }
510 
511 
512 
513 
514 
515 void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames)
516 {
517  // Create a list of names to query, by removing those that are already cached:
518  AStringVector NamesToQuery;
519  NamesToQuery.reserve(a_PlayerNames.size());
520  {
521  cCSLock Lock(m_CSNameToUUID);
522  for (AStringVector::const_iterator itr = a_PlayerNames.begin(), end = a_PlayerNames.end(); itr != end; ++itr)
523  {
524  if (m_NameToUUID.find(*itr) == m_NameToUUID.end())
525  {
526  NamesToQuery.push_back(*itr);
527  }
528  } // for itr - a_PlayerNames[]
529  } // Lock(m_CSNameToUUID)
530 
531  QueryNamesToUUIDs(NamesToQuery);
532 }
533 
534 
535 
536 
537 
539 {
540  while (!a_NamesToQuery.empty())
541  {
542  // Create the request body - a JSON containing up to MAX_PER_QUERY playernames:
543  Json::Value root;
544  int Count = 0;
545  auto itr = a_NamesToQuery.begin();
546  auto end = a_NamesToQuery.end();
547  for (; (itr != end) && (Count < MAX_PER_QUERY); ++itr, ++Count)
548  {
549  Json::Value req(*itr);
550  root.append(req);
551  } // for itr - a_PlayerNames[]
552  a_NamesToQuery.erase(a_NamesToQuery.begin(), itr);
553  auto RequestBody = JsonUtils::WriteFastString(root);
554 
555  // Create and send the HTTP request
556  auto [IsSuccessful, Response] = cUrlClient::BlockingPost(m_NameToUUIDUrl, {}, std::move(RequestBody), MojangTrustedRootCAs::UrlClientOptions());
557  if (!IsSuccessful)
558  {
559  continue;
560  }
561  AString HexDump;
562 
563  // Parse the returned string into Json:
564  AString ParseError;
565  if (!JsonUtils::ParseString(Response, root, &ParseError) || !root.isArray())
566  {
567  LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON: \"%s\"", __METHOD_NAME__, ParseError);
568  LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16));
569  continue;
570  }
571 
572  // Store the returned results into cache:
573  Json::Value::UInt JsonCount = root.size();
574  Int64 Now = time(nullptr);
575  {
576  cCSLock Lock(m_CSNameToUUID);
577  for (Json::Value::UInt idx = 0; idx < JsonCount; ++idx)
578  {
579  Json::Value & Val = root[idx];
580  AString JsonName = Val.get("name", "").asString();
581  cUUID JsonUUID;
582  if (!JsonUUID.FromString(Val.get("id", "").asString()))
583  {
584  continue;
585  }
586  m_NameToUUID[StrToLower(JsonName)] = sProfile(JsonName, JsonUUID, "", "", Now);
587  NotifyNameUUID(JsonName, JsonUUID);
588  } // for idx - root[]
589  } // cCSLock (m_CSNameToUUID)
590 
591  // Also cache the UUIDToName:
592  {
593  cCSLock Lock(m_CSUUIDToName);
594  for (Json::Value::UInt idx = 0; idx < JsonCount; ++idx)
595  {
596  Json::Value & Val = root[idx];
597  AString JsonName = Val.get("name", "").asString();
598  cUUID JsonUUID;
599  if (!JsonUUID.FromString(Val.get("id", "").asString()))
600  {
601  continue;
602  }
603  m_UUIDToName[JsonUUID] = sProfile(JsonName, JsonUUID, "", "", Now);
604  } // for idx - root[]
605  }
606  } // while (!NamesToQuery.empty())
607 }
608 
609 
610 
611 
612 
614 {
615  // Check if already present:
616  {
618  if (m_UUIDToProfile.find(a_UUID) != m_UUIDToProfile.end())
619  {
620  return;
621  }
622  }
623 
624  QueryUUIDToProfile(a_UUID);
625 }
626 
627 
628 
629 
630 
632 {
633  // Create and send the HTTP request
634  auto Url = m_UUIDToProfileUrl;
635  ReplaceString(Url, "%UUID%", URLEncode(a_UUID.ToShortString()));
636  auto [IsSuccessful, Response] = cUrlClient::BlockingGet(Url, {}, {}, MojangTrustedRootCAs::UrlClientOptions());
637  if (!IsSuccessful)
638  {
639  return;
640  }
641 
642  // Parse the returned string into Json:
643  Json::Value root;
644  AString ParseError;
645  if (!JsonUtils::ParseString(Response, root, &ParseError) || !root.isObject())
646  {
647  LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON: \"%s\"", __FUNCTION__, ParseError);
648 #ifdef NDEBUG
649  AString HexDump;
650  LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
651 #endif
652  return;
653  }
654 
655  /* Example response:
656  {
657  "id": "b1caf24202a841a78055a079c460eee7",
658  "name": "xoft",
659  "properties":
660  [
661  {
662  "name": "textures",
663  "value": "eyJ0aW1lc3RhbXAiOjE0MDcwNzAzMjEyNzEsInByb2ZpbGVJZCI6ImIxY2FmMjQyMDJhODQxYTc4MDU1YTA3OWM0NjBlZWU3IiwicHJvZmlsZU5hbWUiOiJ4b2Z0IiwiaXNQdWJsaWMiOnRydWUsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNzc5YmFiZjVhNTg3Zjk0OGFkNjc0N2VhOTEyNzU0MjliNjg4Mjk1YWUzYzA3YmQwZTJmNWJmNGQwNTIifX19",
664  "signature": "XCty+jGEF39hEPrPhYNnCX087kPaoCjYruzYI/DS4nkL5hbjnkSM5Rh15hnUyv/FHhC8OF5rif3D1tQjtMI19KSVaXoUFXpbJM8/+PB8GDgEbX8Fc3u9nYkzOcM/xfxdYsFAdFhLQMkvase/BZLSuPhdy9DdI+TCrO7xuSTZfYmmwVuWo3w5gCY+mSIAnqltnOzaOOTcly75xvO0WYpVk7nJdnR2tvSi0wfrQPDrIg/uzhX7p0SnDqijmBU4QaNez/TNKiFxy69dAzt0RSotlQzqkDbyVKhhv9a4eY8h3pXi4UMftKEj4FAKczxLImkukJXuOn5NN15/Q+le0rJVBC60/xjKIVzltEsMN6qjWD0lQjey7WEL+4pGhCVuWY5KzuZjFvgqszuJTFz7lo+bcHiceldJtea8/fa02eTRObZvdLxbWC9ZfFY0IhpOVKfcLdno/ddDMNMQMi5kMrJ8MZZ/PcW1w5n7MMGWPGCla1kOaC55AL0QYSMGRVEZqgU9wXI5M7sHGZKGM4mWxkbEJYBkpI/p3GyxWgV6v33ZWlsz65TqlNrR1gCLaoFCm7Sif8NqPBZUAONHYon0roXhin/DyEanS93WV6i6FC1Wisscjq2AcvnOlgTo/5nN/1QsMbjNumuMGo37sqjRqlXoPb8zEUbAhhztYuJjEfQ2Rd8="
665  }
666  ]
667  }
668  */
669 
670  // Store the returned result into caches:
671  AString PlayerName = root.get("name", "").asString();
672  if (PlayerName.empty())
673  {
674  // No valid playername, bail out
675  return;
676  }
677  Json::Value Properties = root.get("properties", "");
678  Int64 Now = time(nullptr);
679  {
681  m_UUIDToProfile[a_UUID] = sProfile(PlayerName, a_UUID, Properties, Now);
682  }
683  {
684  cCSLock Lock(m_CSUUIDToName);
685  m_UUIDToName[a_UUID] = sProfile(PlayerName, a_UUID, Properties, Now);
686  }
687  {
688  cCSLock Lock(m_CSNameToUUID);
689  m_NameToUUID[StrToLower(PlayerName)] = sProfile(PlayerName, a_UUID, Properties, Now);
690  }
691  NotifyNameUUID(PlayerName, a_UUID);
692 }
693 
694 
695 
696 
697 
698 void cMojangAPI::NotifyNameUUID(const AString & a_PlayerName, const cUUID & a_UUID)
699 {
700  // Notify the rank manager:
701  cCSLock Lock(m_CSRankMgr);
702  if (m_RankMgr != nullptr)
703  {
704  m_RankMgr->NotifyNameUUID(a_PlayerName, a_UUID);
705  }
706 }
707 
708 
709 
710 
711 
713 {
714  Int64 LimitDateTime = time(nullptr) - MAX_AGE;
715 
716  // Re-query all playernames that are stale:
717  AStringVector PlayerNames;
718  {
719  cCSLock Lock(m_CSNameToUUID);
720  for (const auto & NameToUUID : m_NameToUUID)
721  {
722  if (NameToUUID.second.m_DateTime < LimitDateTime)
723  {
724  PlayerNames.push_back(NameToUUID.first);
725  }
726  } // for itr - m_NameToUUID[]
727  }
728  if (!PlayerNames.empty())
729  {
730  LOG("%s: Updating name-to-uuid cache for %u names", __METHOD_NAME__, static_cast<unsigned>(PlayerNames.size()));
731  QueryNamesToUUIDs(PlayerNames);
732  }
733 
734  // Re-query all profiles that are stale:
735  std::vector<cUUID> ProfileUUIDs;
736  {
738  for (const auto & UUIDToProfile : m_UUIDToProfile)
739  {
740  if (UUIDToProfile.second.m_DateTime < LimitDateTime)
741  {
742  ProfileUUIDs.push_back(UUIDToProfile.first);
743  }
744  } // for itr - m_UUIDToProfile[]
745  }
746  if (!ProfileUUIDs.empty())
747  {
748  LOG("%s: Updating uuid-to-profile cache for %u uuids", __METHOD_NAME__, static_cast<unsigned>(ProfileUUIDs.size()));
749  for (const auto & UUID : ProfileUUIDs)
750  {
751  QueryUUIDToProfile(UUID);
752  }
753  }
754 }
signed long long Int64
Definition: Globals.h:151
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
void LOG(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:55
#define LOGD
Definition: LoggerSimple.h:83
void LOGINFO(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:61
const int MAX_PER_QUERY
The maximum number of names to send in a single query.
Definition: MojangAPI.cpp:28
constexpr char DEFAULT_UUID_TO_PROFILE_SERVER[]
Definition: MojangAPI.cpp:36
constexpr char DEFAULT_NAME_TO_UUID_ADDRESS[]
Definition: MojangAPI.cpp:35
constexpr char DEFAULT_NAME_TO_UUID_SERVER[]
Definition: MojangAPI.cpp:34
const Int64 MAX_AGE
The maximum age for items to be kept in the cache.
Definition: MojangAPI.cpp:25
constexpr char DEFAULT_UUID_TO_PROFILE_ADDRESS[]
Definition: MojangAPI.cpp:37
AString URLEncode(const AString &a_Text)
URL-encodes the given string.
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
void ReplaceString(AString &iHayStack, const AString &iNeedle, const AString &iReplaceWith)
Replaces each occurence of iNeedle in iHayStack with iReplaceWith.
AString & CreateHexDump(AString &a_Out, const void *a_Data, size_t a_Size, size_t a_BytesPerLine)
format binary data this way: 00001234: 31 32 33 34 35 36 37 38 39 30 61 62 63 64 65 66 1234567890abcd...
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
std::map< AString, AString > AStringMap
A string dictionary, used for key-value pairs.
Definition: StringUtils.h:16
AString WriteFastString(const Json::Value &a_Root)
Definition: JsonUtils.cpp:12
bool ParseString(const AString &a_JsonStr, Json::Value &a_Root, AString *a_ErrorMsg)
Definition: JsonUtils.cpp:34
static const AStringMap & UrlClientOptions()
Returns the Options that should be used for cUrlClient queries to the Mojang APIs.
Definition: MojangAPI.cpp:46
static std::pair< bool, AString > BlockingPost(const AString &a_URL, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options={})
Alias for BlockingRequest("POST", ...)
Definition: UrlClient.cpp:793
static std::pair< bool, AString > BlockingGet(const AString &a_URL, AStringMap a_Headers={}, const AString &a_Body={}, const AStringMap &a_Options={})
Alias for BlockingRequest("GET", ...)
Definition: UrlClient.cpp:779
RAII for cCriticalSection - locks the CS on creation, unlocks on destruction.
Definition: Event.h:18
void Wait(void)
Waits until the event has been set.
Definition: Event.cpp:23
void Set(void)
Sets the event - releases one thread that has been waiting in Wait().
Definition: Event.cpp:52
std::atomic< bool > m_ShouldTerminate
The overriden Execute() method should check this value periodically and terminate if this is true.
Definition: IsThread.h:45
cIsThread(AString &&a_ThreadName)
Definition: IsThread.cpp:16
void Stop(void)
Signals the thread to terminate and waits until it's finished.
Definition: IsThread.cpp:48
virtual void Execute(void) override
This function, overloaded by the descendants, is called in the new thread.
Definition: MojangAPI.cpp:200
cMojangAPI & m_MojangAPI
The cMojangAPI instance to update.
Definition: MojangAPI.cpp:193
cEvent m_evtNotify
The event used for notifying that the thread should terminate, as well as timing.
Definition: MojangAPI.cpp:196
virtual ~cUpdateThread() override
Definition: MojangAPI.cpp:180
cUpdateThread(cMojangAPI &a_MojangAPI)
Definition: MojangAPI.cpp:174
void AddPlayerNameToUUIDMapping(const AString &a_PlayerName, const cUUID &a_UUID)
Called by the Authenticator to add a PlayerName -> UUID mapping that it has received from authenticat...
Definition: MojangAPI.cpp:351
cCriticalSection m_CSNameToUUID
Protects m_NameToUUID against simultaneous multi-threaded access.
Definition: MojangAPI.h:146
AString GetPlayerNameFromUUID(const cUUID &a_UUID, bool a_UseOnlyCached=false)
Converts a UUID into a playername.
Definition: MojangAPI.cpp:281
void NotifyNameUUID(const AString &a_PlayerName, const cUUID &a_PlayerUUID)
Called for each name-uuid pairing that is discovered.
Definition: MojangAPI.cpp:698
void CacheNamesToUUIDs(const AStringVector &a_PlayerNames)
Makes sure all specified names are in the m_PlayerNameToUUID cache.
Definition: MojangAPI.cpp:515
void LoadCachesFromDisk(void)
Loads the caches from a disk storage.
Definition: MojangAPI.cpp:391
void Update(void)
Updates the stale values in the DB from the Mojang servers.
Definition: MojangAPI.cpp:712
cUUIDProfileMap m_UUIDToProfile
Cache for the UUID-to-profile lookups.
Definition: MojangAPI.h:156
cCriticalSection m_CSUUIDToName
Protects m_UUIDToName against simultaneous multi-threaded access.
Definition: MojangAPI.h:152
void AddPlayerProfile(const AString &a_PlayerName, const cUUID &a_UUID, const Json::Value &a_Properties)
Called by the Authenticator to add a profile that it has received from authenticating a user.
Definition: MojangAPI.cpp:369
std::vector< cUUID > GetUUIDsFromPlayerNames(const AStringVector &a_PlayerName, bool a_UseOnlyCached=false)
Converts the player names into UUIDs.
Definition: MojangAPI.cpp:316
cCriticalSection m_CSUUIDToProfile
Protects m_UUIDToProfile against simultaneous multi-threaded access.
Definition: MojangAPI.h:159
AString m_NameToUUIDUrl
The full URL to check when converting player names to UUIDs.
Definition: MojangAPI.h:135
cProfileMap m_NameToUUID
Cache for the Name-to-UUID lookups.
Definition: MojangAPI.h:143
void QueryNamesToUUIDs(AStringVector &a_PlayerNames)
Queries all the specified names and stores them into the m_PlayerNameToUUID cache.
Definition: MojangAPI.cpp:538
void Start(cSettingsRepositoryInterface &a_Settings, bool a_ShouldAuth)
Initializes the API; reads the settings from the specified ini file.
Definition: MojangAPI.cpp:235
cRankManager * m_RankMgr
The rank manager that is notified of the name-uuid pairings.
Definition: MojangAPI.h:162
cCriticalSection m_CSRankMgr
Protects m_RankMgr agains simultaneous multi-threaded access.
Definition: MojangAPI.h:165
void SaveCachesToDisk(void)
Saves the caches to a disk storage.
Definition: MojangAPI.cpp:449
AString m_UUIDToProfileUrl
The full URL to use for converting UUID to profile.
Definition: MojangAPI.h:140
void CacheUUIDToProfile(const cUUID &a_UUID)
Makes sure the specified UUID is in the m_UUIDToProfile cache.
Definition: MojangAPI.cpp:613
void QueryUUIDToProfile(const cUUID &a_UUID)
Queries the specified UUID's profile and stores it in the m_UUIDToProfile cache.
Definition: MojangAPI.cpp:631
cUUID GetUUIDFromPlayerName(const AString &a_PlayerName, bool a_UseOnlyCached=false)
Converts a player name into a UUID.
Definition: MojangAPI.cpp:254
cMojangAPI(void)
Definition: MojangAPI.cpp:216
cUUIDProfileMap m_UUIDToName
Cache for the Name-to-UUID lookups.
Definition: MojangAPI.h:149
std::shared_ptr< cUpdateThread > m_UpdateThread
The thread that periodically updates the stale data in the DB from the Mojang servers.
Definition: MojangAPI.h:168
Holds data for a single player profile.
Definition: MojangAPI.h:88
sProfile(void)
Default constructor for the container's sake.
Definition: MojangAPI.h:96
AString m_TexturesSignature
Definition: MojangAPI.h:92
void NotifyNameUUID(const AString &a_PlayerName, const cUUID &a_UUID)
Called by cMojangAPI whenever the playername-uuid pairing is discovered.
virtual AString GetValueSet(const AString &keyname, const AString &valuename, const AString &defValue="")=0
Gets the value; if not found, write the default to the repository.
Definition: UUID.h:11
AString ToShortString() const
Converts the UUID to a short form string (i.e without dashes).
Definition: UUID.cpp:133
bool FromString(const AString &a_StringUUID)
Tries to interpret the string as a short or long form UUID and assign from it.
Definition: UUID.cpp:102