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