clientmedia.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "clientmedia.h"
  17. #include "httpfetch.h"
  18. #include "client.h"
  19. #include "filecache.h"
  20. #include "filesys.h"
  21. #include "log.h"
  22. #include "porting.h"
  23. #include "settings.h"
  24. #include "util/hex.h"
  25. #include "util/serialize.h"
  26. #include "util/sha1.h"
  27. #include "util/string.h"
  28. static std::string getMediaCacheDir()
  29. {
  30. return porting::path_cache + DIR_DELIM + "media";
  31. }
  32. bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata)
  33. {
  34. FileCache media_cache(getMediaCacheDir());
  35. std::string sha1_hex = hex_encode(raw_hash);
  36. if (!media_cache.exists(sha1_hex))
  37. return media_cache.update(sha1_hex, filedata);
  38. return true;
  39. }
  40. /*
  41. ClientMediaDownloader
  42. */
  43. ClientMediaDownloader::ClientMediaDownloader():
  44. m_media_cache(getMediaCacheDir()),
  45. m_httpfetch_caller(HTTPFETCH_DISCARD)
  46. {
  47. }
  48. ClientMediaDownloader::~ClientMediaDownloader()
  49. {
  50. if (m_httpfetch_caller != HTTPFETCH_DISCARD)
  51. httpfetch_caller_free(m_httpfetch_caller);
  52. for (auto &file_it : m_files)
  53. delete file_it.second;
  54. for (auto &remote : m_remotes)
  55. delete remote;
  56. }
  57. void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
  58. {
  59. assert(!m_initial_step_done); // pre-condition
  60. // if name was already announced, ignore the new announcement
  61. if (m_files.count(name) != 0) {
  62. errorstream << "Client: ignoring duplicate media announcement "
  63. << "sent by server: \"" << name << "\""
  64. << std::endl;
  65. return;
  66. }
  67. // if name is empty or contains illegal characters, ignore the file
  68. if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
  69. errorstream << "Client: ignoring illegal file name "
  70. << "sent by server: \"" << name << "\""
  71. << std::endl;
  72. return;
  73. }
  74. // length of sha1 must be exactly 20 (160 bits), else ignore the file
  75. if (sha1.size() != 20) {
  76. errorstream << "Client: ignoring illegal SHA1 sent by server: "
  77. << hex_encode(sha1) << " \"" << name << "\""
  78. << std::endl;
  79. return;
  80. }
  81. FileStatus *filestatus = new FileStatus();
  82. filestatus->received = false;
  83. filestatus->sha1 = sha1;
  84. filestatus->current_remote = -1;
  85. m_files.insert(std::make_pair(name, filestatus));
  86. }
  87. void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
  88. {
  89. assert(!m_initial_step_done); // pre-condition
  90. #ifdef USE_CURL
  91. if (g_settings->getBool("enable_remote_media_server")) {
  92. infostream << "Client: Adding remote server \""
  93. << baseurl << "\" for media download" << std::endl;
  94. RemoteServerStatus *remote = new RemoteServerStatus();
  95. remote->baseurl = baseurl;
  96. remote->active_count = 0;
  97. m_remotes.push_back(remote);
  98. }
  99. #else
  100. infostream << "Client: Ignoring remote server \""
  101. << baseurl << "\" because cURL support is not compiled in"
  102. << std::endl;
  103. #endif
  104. }
  105. void ClientMediaDownloader::step(Client *client)
  106. {
  107. if (!m_initial_step_done) {
  108. initialStep(client);
  109. m_initial_step_done = true;
  110. }
  111. // Remote media: check for completion of fetches
  112. if (m_httpfetch_active) {
  113. bool fetched_something = false;
  114. HTTPFetchResult fetch_result;
  115. while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
  116. m_httpfetch_active--;
  117. fetched_something = true;
  118. // Is this a hashset (index.mth) or a media file?
  119. if (fetch_result.request_id < m_remotes.size())
  120. remoteHashSetReceived(fetch_result);
  121. else
  122. remoteMediaReceived(fetch_result, client);
  123. }
  124. if (fetched_something)
  125. startRemoteMediaTransfers();
  126. // Did all remote transfers end and no new ones can be started?
  127. // If so, request still missing files from the minetest server
  128. // (Or report that we have all files.)
  129. if (m_httpfetch_active == 0) {
  130. if (m_uncached_received_count < m_uncached_count) {
  131. infostream << "Client: Failed to remote-fetch "
  132. << (m_uncached_count-m_uncached_received_count)
  133. << " files. Requesting them"
  134. << " the usual way." << std::endl;
  135. }
  136. startConventionalTransfers(client);
  137. }
  138. }
  139. }
  140. void ClientMediaDownloader::initialStep(Client *client)
  141. {
  142. // Check media cache
  143. m_uncached_count = m_files.size();
  144. for (auto &file_it : m_files) {
  145. std::string name = file_it.first;
  146. FileStatus *filestatus = file_it.second;
  147. const std::string &sha1 = filestatus->sha1;
  148. std::ostringstream tmp_os(std::ios_base::binary);
  149. bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
  150. // If found in cache, try to load it from there
  151. if (found_in_cache) {
  152. bool success = checkAndLoad(name, sha1,
  153. tmp_os.str(), true, client);
  154. if (success) {
  155. filestatus->received = true;
  156. m_uncached_count--;
  157. }
  158. }
  159. }
  160. assert(m_uncached_received_count == 0);
  161. // Create the media cache dir if we are likely to write to it
  162. if (m_uncached_count != 0) {
  163. bool did = fs::CreateAllDirs(getMediaCacheDir());
  164. if (!did) {
  165. errorstream << "Client: "
  166. << "Could not create media cache directory: "
  167. << getMediaCacheDir()
  168. << std::endl;
  169. }
  170. }
  171. // If we found all files in the cache, report this fact to the server.
  172. // If the server reported no remote servers, immediately start
  173. // conventional transfers. Note: if cURL support is not compiled in,
  174. // m_remotes is always empty, so "!USE_CURL" is redundant but may
  175. // reduce the size of the compiled code
  176. if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
  177. startConventionalTransfers(client);
  178. }
  179. else {
  180. // Otherwise start off by requesting each server's sha1 set
  181. // This is the first time we use httpfetch, so alloc a caller ID
  182. m_httpfetch_caller = httpfetch_caller_alloc();
  183. m_httpfetch_timeout = g_settings->getS32("curl_timeout");
  184. // Set the active fetch limit to curl_parallel_limit or 84,
  185. // whichever is greater. This gives us some leeway so that
  186. // inefficiencies in communicating with the httpfetch thread
  187. // don't slow down fetches too much. (We still want some limit
  188. // so that when the first remote server returns its hash set,
  189. // not all files are requested from that server immediately.)
  190. // One such inefficiency is that ClientMediaDownloader::step()
  191. // is only called a couple times per second, while httpfetch
  192. // might return responses much faster than that.
  193. // Note that httpfetch strictly enforces curl_parallel_limit
  194. // but at no inter-thread communication cost. This however
  195. // doesn't help with the aforementioned inefficiencies.
  196. // The signifance of 84 is that it is 2*6*9 in base 13.
  197. m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
  198. m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
  199. // Write a list of hashes that we need. This will be POSTed
  200. // to the server using Content-Type: application/octet-stream
  201. std::string required_hash_set = serializeRequiredHashSet();
  202. // minor fixme: this loop ignores m_httpfetch_active_limit
  203. // another minor fixme, unlikely to matter in normal usage:
  204. // these index.mth fetches do (however) count against
  205. // m_httpfetch_active_limit when starting actual media file
  206. // requests, so if there are lots of remote servers that are
  207. // not responding, those will stall new media file transfers.
  208. for (u32 i = 0; i < m_remotes.size(); ++i) {
  209. assert(m_httpfetch_next_id == i);
  210. RemoteServerStatus *remote = m_remotes[i];
  211. actionstream << "Client: Contacting remote server \""
  212. << remote->baseurl << "\"" << std::endl;
  213. HTTPFetchRequest fetch_request;
  214. fetch_request.url =
  215. remote->baseurl + MTHASHSET_FILE_NAME;
  216. fetch_request.caller = m_httpfetch_caller;
  217. fetch_request.request_id = m_httpfetch_next_id; // == i
  218. fetch_request.timeout = m_httpfetch_timeout;
  219. fetch_request.connect_timeout = m_httpfetch_timeout;
  220. fetch_request.post_data = required_hash_set;
  221. fetch_request.extra_headers.emplace_back(
  222. "Content-Type: application/octet-stream");
  223. // Encapsulate possible IPv6 plain address in []
  224. std::string addr = client->getAddressName();
  225. if (addr.find(':', 0) != std::string::npos)
  226. addr = '[' + addr + ']';
  227. fetch_request.extra_headers.emplace_back(
  228. std::string("Referer: minetest://") +
  229. addr + ":" +
  230. std::to_string(client->getServerAddress().getPort()));
  231. httpfetch_async(fetch_request);
  232. m_httpfetch_active++;
  233. m_httpfetch_next_id++;
  234. m_outstanding_hash_sets++;
  235. }
  236. }
  237. }
  238. void ClientMediaDownloader::remoteHashSetReceived(
  239. const HTTPFetchResult &fetch_result)
  240. {
  241. u32 remote_id = fetch_result.request_id;
  242. assert(remote_id < m_remotes.size());
  243. RemoteServerStatus *remote = m_remotes[remote_id];
  244. m_outstanding_hash_sets--;
  245. if (fetch_result.succeeded) {
  246. try {
  247. // Server sent a list of file hashes that are
  248. // available on it, try to parse the list
  249. std::set<std::string> sha1_set;
  250. deSerializeHashSet(fetch_result.data, sha1_set);
  251. // Parsing succeeded: For every file that is
  252. // available on this server, add this server
  253. // to the available_remotes array
  254. for(std::map<std::string, FileStatus*>::iterator
  255. it = m_files.upper_bound(m_name_bound);
  256. it != m_files.end(); ++it) {
  257. FileStatus *f = it->second;
  258. if (!f->received && sha1_set.count(f->sha1))
  259. f->available_remotes.push_back(remote_id);
  260. }
  261. }
  262. catch (SerializationError &e) {
  263. infostream << "Client: Remote server \""
  264. << remote->baseurl << "\" sent invalid hash set: "
  265. << e.what() << std::endl;
  266. }
  267. }
  268. }
  269. void ClientMediaDownloader::remoteMediaReceived(
  270. const HTTPFetchResult &fetch_result,
  271. Client *client)
  272. {
  273. // Some remote server sent us a file.
  274. // -> decrement number of active fetches
  275. // -> mark file as received if fetch succeeded
  276. // -> try to load media
  277. std::string name;
  278. {
  279. std::unordered_map<unsigned long, std::string>::iterator it =
  280. m_remote_file_transfers.find(fetch_result.request_id);
  281. assert(it != m_remote_file_transfers.end());
  282. name = it->second;
  283. m_remote_file_transfers.erase(it);
  284. }
  285. sanity_check(m_files.count(name) != 0);
  286. FileStatus *filestatus = m_files[name];
  287. sanity_check(!filestatus->received);
  288. sanity_check(filestatus->current_remote >= 0);
  289. RemoteServerStatus *remote = m_remotes[filestatus->current_remote];
  290. filestatus->current_remote = -1;
  291. remote->active_count--;
  292. // If fetch succeeded, try to load media file
  293. if (fetch_result.succeeded) {
  294. bool success = checkAndLoad(name, filestatus->sha1,
  295. fetch_result.data, false, client);
  296. if (success) {
  297. filestatus->received = true;
  298. assert(m_uncached_received_count < m_uncached_count);
  299. m_uncached_received_count++;
  300. }
  301. }
  302. }
  303. s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
  304. {
  305. // Pre-conditions
  306. assert(filestatus != NULL);
  307. assert(!filestatus->received);
  308. assert(filestatus->current_remote < 0);
  309. if (filestatus->available_remotes.empty())
  310. return -1;
  311. // Of all servers that claim to provide the file (and haven't
  312. // been unsuccessfully tried before), find the one with the
  313. // smallest number of currently active transfers
  314. s32 best = 0;
  315. s32 best_remote_id = filestatus->available_remotes[best];
  316. s32 best_active_count = m_remotes[best_remote_id]->active_count;
  317. for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
  318. s32 remote_id = filestatus->available_remotes[i];
  319. s32 active_count = m_remotes[remote_id]->active_count;
  320. if (active_count < best_active_count) {
  321. best = i;
  322. best_remote_id = remote_id;
  323. best_active_count = active_count;
  324. }
  325. }
  326. filestatus->available_remotes.erase(
  327. filestatus->available_remotes.begin() + best);
  328. return best_remote_id;
  329. }
  330. void ClientMediaDownloader::startRemoteMediaTransfers()
  331. {
  332. bool changing_name_bound = true;
  333. for (std::map<std::string, FileStatus*>::iterator
  334. files_iter = m_files.upper_bound(m_name_bound);
  335. files_iter != m_files.end(); ++files_iter) {
  336. // Abort if active fetch limit is exceeded
  337. if (m_httpfetch_active >= m_httpfetch_active_limit)
  338. break;
  339. const std::string &name = files_iter->first;
  340. FileStatus *filestatus = files_iter->second;
  341. if (!filestatus->received && filestatus->current_remote < 0) {
  342. // File has not been received yet and is not currently
  343. // being transferred. Choose a server for it.
  344. s32 remote_id = selectRemoteServer(filestatus);
  345. if (remote_id >= 0) {
  346. // Found a server, so start fetching
  347. RemoteServerStatus *remote =
  348. m_remotes[remote_id];
  349. std::string url = remote->baseurl +
  350. hex_encode(filestatus->sha1);
  351. verbosestream << "Client: "
  352. << "Requesting remote media file "
  353. << "\"" << name << "\" "
  354. << "\"" << url << "\"" << std::endl;
  355. HTTPFetchRequest fetch_request;
  356. fetch_request.url = url;
  357. fetch_request.caller = m_httpfetch_caller;
  358. fetch_request.request_id = m_httpfetch_next_id;
  359. fetch_request.timeout = 0; // no data timeout!
  360. fetch_request.connect_timeout =
  361. m_httpfetch_timeout;
  362. httpfetch_async(fetch_request);
  363. m_remote_file_transfers.insert(std::make_pair(
  364. m_httpfetch_next_id,
  365. name));
  366. filestatus->current_remote = remote_id;
  367. remote->active_count++;
  368. m_httpfetch_active++;
  369. m_httpfetch_next_id++;
  370. }
  371. }
  372. if (filestatus->received ||
  373. (filestatus->current_remote < 0 &&
  374. !m_outstanding_hash_sets)) {
  375. // If we arrive here, we conclusively know that we
  376. // won't fetch this file from a remote server in the
  377. // future. So update the name bound if possible.
  378. if (changing_name_bound)
  379. m_name_bound = name;
  380. }
  381. else
  382. changing_name_bound = false;
  383. }
  384. }
  385. void ClientMediaDownloader::startConventionalTransfers(Client *client)
  386. {
  387. assert(m_httpfetch_active == 0); // pre-condition
  388. if (m_uncached_received_count != m_uncached_count) {
  389. // Some media files have not been received yet, use the
  390. // conventional slow method (minetest protocol) to get them
  391. std::vector<std::string> file_requests;
  392. for (auto &file : m_files) {
  393. if (!file.second->received)
  394. file_requests.push_back(file.first);
  395. }
  396. assert((s32) file_requests.size() ==
  397. m_uncached_count - m_uncached_received_count);
  398. client->request_media(file_requests);
  399. }
  400. }
  401. void ClientMediaDownloader::conventionalTransferDone(
  402. const std::string &name,
  403. const std::string &data,
  404. Client *client)
  405. {
  406. // Check that file was announced
  407. std::map<std::string, FileStatus*>::iterator
  408. file_iter = m_files.find(name);
  409. if (file_iter == m_files.end()) {
  410. errorstream << "Client: server sent media file that was"
  411. << "not announced, ignoring it: \"" << name << "\""
  412. << std::endl;
  413. return;
  414. }
  415. FileStatus *filestatus = file_iter->second;
  416. assert(filestatus != NULL);
  417. // Check that file hasn't already been received
  418. if (filestatus->received) {
  419. errorstream << "Client: server sent media file that we already"
  420. << "received, ignoring it: \"" << name << "\""
  421. << std::endl;
  422. return;
  423. }
  424. // Mark file as received, regardless of whether loading it works and
  425. // whether the checksum matches (because at this point there is no
  426. // other server that could send a replacement)
  427. filestatus->received = true;
  428. assert(m_uncached_received_count < m_uncached_count);
  429. m_uncached_received_count++;
  430. // Check that received file matches announced checksum
  431. // If so, load it
  432. checkAndLoad(name, filestatus->sha1, data, false, client);
  433. }
  434. bool ClientMediaDownloader::checkAndLoad(
  435. const std::string &name, const std::string &sha1,
  436. const std::string &data, bool is_from_cache, Client *client)
  437. {
  438. const char *cached_or_received = is_from_cache ? "cached" : "received";
  439. const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
  440. std::string sha1_hex = hex_encode(sha1);
  441. // Compute actual checksum of data
  442. std::string data_sha1;
  443. {
  444. SHA1 data_sha1_calculator;
  445. data_sha1_calculator.addBytes(data.c_str(), data.size());
  446. unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
  447. data_sha1.assign((char*) data_tmpdigest, 20);
  448. free(data_tmpdigest);
  449. }
  450. // Check that received file matches announced checksum
  451. if (data_sha1 != sha1) {
  452. std::string data_sha1_hex = hex_encode(data_sha1);
  453. infostream << "Client: "
  454. << cached_or_received_uc << " media file "
  455. << sha1_hex << " \"" << name << "\" "
  456. << "mismatches actual checksum " << data_sha1_hex
  457. << std::endl;
  458. return false;
  459. }
  460. // Checksum is ok, try loading the file
  461. bool success = client->loadMedia(data, name);
  462. if (!success) {
  463. infostream << "Client: "
  464. << "Failed to load " << cached_or_received << " media: "
  465. << sha1_hex << " \"" << name << "\""
  466. << std::endl;
  467. return false;
  468. }
  469. verbosestream << "Client: "
  470. << "Loaded " << cached_or_received << " media: "
  471. << sha1_hex << " \"" << name << "\""
  472. << std::endl;
  473. // Update cache (unless we just loaded the file from the cache)
  474. if (!is_from_cache)
  475. m_media_cache.update(sha1_hex, data);
  476. return true;
  477. }
  478. /*
  479. Minetest Hashset File Format
  480. All values are stored in big-endian byte order.
  481. [u32] signature: 'MTHS'
  482. [u16] version: 1
  483. For each hash in set:
  484. [u8*20] SHA1 hash
  485. Version changes:
  486. 1 - Initial version
  487. */
  488. std::string ClientMediaDownloader::serializeRequiredHashSet()
  489. {
  490. std::ostringstream os(std::ios::binary);
  491. writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
  492. writeU16(os, 1); // version
  493. // Write list of hashes of files that have not been
  494. // received (found in cache) yet
  495. for (std::map<std::string, FileStatus*>::iterator
  496. it = m_files.begin();
  497. it != m_files.end(); ++it) {
  498. if (!it->second->received) {
  499. FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
  500. os << it->second->sha1;
  501. }
  502. }
  503. return os.str();
  504. }
  505. void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
  506. std::set<std::string> &result)
  507. {
  508. if (data.size() < 6 || data.size() % 20 != 6) {
  509. throw SerializationError(
  510. "ClientMediaDownloader::deSerializeHashSet: "
  511. "invalid hash set file size");
  512. }
  513. const u8 *data_cstr = (const u8*) data.c_str();
  514. u32 signature = readU32(&data_cstr[0]);
  515. if (signature != MTHASHSET_FILE_SIGNATURE) {
  516. throw SerializationError(
  517. "ClientMediaDownloader::deSerializeHashSet: "
  518. "invalid hash set file signature");
  519. }
  520. u16 version = readU16(&data_cstr[4]);
  521. if (version != 1) {
  522. throw SerializationError(
  523. "ClientMediaDownloader::deSerializeHashSet: "
  524. "unsupported hash set file version");
  525. }
  526. for (u32 pos = 6; pos < data.size(); pos += 20) {
  527. result.insert(data.substr(pos, 20));
  528. }
  529. }