settings_pack load_pack_from_dict(bdecode_node const& settings) { settings_pack pack; for (int i = 0; i < settings.dict_size(); ++i) { string_view key; bdecode_node val; std::tie(key, val) = settings.dict_at(i); switch (val.type()) { case bdecode_node::dict_t: case bdecode_node::list_t: continue; case bdecode_node::int_t: { bool found = false; for (int k = 0; k < sizeof(int_settings) / sizeof(int_settings[0]); ++k) { if (key != int_settings[k].name) continue; pack.set_int(settings_pack::int_type_base + k, val.int_value()); found = true; break; } if (found) continue; for (int k = 0; k < sizeof(bool_settings) / sizeof(bool_settings[0]); ++k) { if (key != bool_settings[k].name) continue; pack.set_bool(settings_pack::bool_type_base + k, val.int_value() != 0); break; } } break; case bdecode_node::string_t: for (int k = 0; k < sizeof(str_settings) / sizeof(str_settings[0]); ++k) { if (key != str_settings[k].name) continue; pack.set_str(settings_pack::string_type_base + k, val.string_value().to_string()); break; } break; case bdecode_node::none_t: break; } } return pack; }
// convert a bdecode_node into an old skool entry void entry::operator=(bdecode_node const& e) { switch (e.type()) { case bdecode_node::string_t: this->string() = e.string_value(); break; case bdecode_node::int_t: this->integer() = e.int_value(); break; case bdecode_node::dict_t: { dictionary_type& d = this->dict(); for (int i = 0; i < e.dict_size(); ++i) { std::pair<std::string, bdecode_node> elem = e.dict_at(i); d[elem.first] = elem.second; } break; } case bdecode_node::list_t: { list_type& l = this->list(); for (int i = 0; i < e.list_size(); ++i) { l.push_back(entry()); l.back() = e.list_at(i); } break; } case bdecode_node::none_t: destruct(); break; } }
bool item::assign(bdecode_node const& v, span<char const> salt , sequence_number const seq, public_key const& pk, signature const& sig) { TORRENT_ASSERT(v.data_section().size() <= 1000); if (!verify_mutable_item(v.data_section(), salt, seq, pk, sig)) return false; m_pk = pk; m_sig = sig; if (salt.size() > 0) m_salt.assign(salt.data(), salt.size()); else m_salt.clear(); m_seq = seq; m_mutable = true; m_value = v; return true; }
void render_variant(std::string& out, bdecode_node const& e) { switch (e.type()) { case bdecode_node::dict_t: print_dict(out); for (int i = 0; i < e.dict_size(); ++i) { std::pair<lt::string_view, bdecode_node> item = e.dict_at(i); const bool duplicate = g_seed == 1; const bool skipped = g_seed == 2; g_seed -= 2; if (duplicate) { print_string(out, item.first.to_string()); render_variant(out, item.second); } if (!skipped) { print_string(out, item.first.to_string()); render_variant(out, item.second); } render_arbitrary_item(out); } print_terminate(out); break; case bdecode_node::list_t: print_list(out); for (int i = 0; i < e.list_size(); ++i) { const bool duplicate = g_seed == 1; const bool skipped = g_seed == 2; g_seed -= 2; if (duplicate) render_variant(out, e.list_at(i)); render_arbitrary_item(out); if (!skipped) render_variant(out, e.list_at(i)); } print_terminate(out); break; case bdecode_node::int_t: print_int(out, e.int_value()); break; case bdecode_node::string_t: print_string(out, e.string_value().to_string()); break; default: abort(); } }
// root_dir is the name of the torrent, unless this is a single file // torrent, in which case it's empty. bool extract_files(bdecode_node const& list, file_storage& target , std::string const& root_dir, ptrdiff_t info_ptr_diff, error_code& ec) { if (list.type() != bdecode_node::list_t) { ec = errors::torrent_file_parse_failed; return false; } target.reserve(list.list_size()); // this is the counter used to name pad files int pad_file_cnt = 0; for (int i = 0, end(list.list_size()); i < end; ++i) { if (!extract_single_file(list.list_at(i), target, root_dir , info_ptr_diff, false, pad_file_cnt, ec)) return false; } return true; }
void get_item::got_data(bdecode_node const& v, public_key const& pk, sequence_number const seq, signature const& sig) { // we received data! // if no data_callback, we needn't care about the data we get. // only put_immutable_item no data_callback if (!m_data_callback) return; // for get_immutable_item if (m_immutable) { // If m_data isn't empty, we should have post alert. if (!m_data.empty()) return; sha1_hash incoming_target = item_target_id(v.data_section()); if (incoming_target != target()) return; m_data.assign(v); // There can only be one true immutable item with a given id // Now that we've got it and the user doesn't want to do a put // there's no point in continuing to query other nodes m_data_callback(m_data, true); done(); return; } // immutable data should have been handled before this line, only mutable // data can reach here, which means pk, sig and seq must be valid. std::string const salt_copy(m_data.salt()); sha1_hash const incoming_target = item_target_id(salt_copy, pk); if (incoming_target != target()) return; // this is mutable data. If it passes the signature // check, remember it. Just keep the version with // the highest sequence number. if (m_data.empty() || m_data.seq() < seq) { if (!m_data.assign(v, salt_copy, seq, pk, sig)) return; // for get_item, we should call callback when we get data, // even if the date is not authoritative, we can update later. // so caller can get response ASAP without waiting transaction // time-out (15 seconds). // for put_item, the callback function will do nothing // if the data is non-authoritative. m_data_callback(m_data, false); } }
// TODO: 2 returning a bool here is redundant. Instead this function should // return the peer_entry bool extract_peer_info(bdecode_node const& info, peer_entry& ret, error_code& ec) { // extract peer id (if any) if (info.type() != bdecode_node::dict_t) { ec.assign(errors::invalid_peer_dict, get_libtorrent_category()); return false; } bdecode_node i = info.dict_find_string("peer id"); if (i && i.string_length() == 20) { std::copy(i.string_ptr(), i.string_ptr()+20, ret.pid.begin()); } else { // if there's no peer_id, just initialize it to a bunch of zeroes std::fill_n(ret.pid.begin(), 20, 0); } // extract ip i = info.dict_find_string("ip"); if (i == 0) { ec.assign(errors::invalid_tracker_response, get_libtorrent_category()); return false; } ret.hostname = i.string_value(); // extract port i = info.dict_find_int("port"); if (i == 0) { ec.assign(errors::invalid_tracker_response, get_libtorrent_category()); return false; } ret.port = boost::uint16_t(i.int_value()); return true; }
void on_read(lt::error_code const& ec, std::size_t bytes_transferred) { if (ec) return; using libtorrent::entry; using libtorrent::bdecode; int pos; error_code err; // since the simulation is single threaded, we can get away with just // allocating a single of these static bdecode_node msg; int ret = bdecode(m_buffer, m_buffer + bytes_transferred, msg, err, &pos, 10, 500); if (ret != 0) return; if (msg.type() != bdecode_node::dict_t) return; libtorrent::dht::msg m(msg, m_ep); dht().incoming(m); sock().async_receive_from(asio::mutable_buffers_1(m_buffer, sizeof(m_buffer)) , m_ep, boost::bind(&dht_node::on_read, this, _1, _2)); }
bool item::assign(bdecode_node const& v , std::pair<char const*, int> salt , boost::uint64_t seq, char const* pk, char const* sig) { TORRENT_ASSERT(v.data_section().second <= 1000); if (pk && sig) { if (!verify_mutable_item(v.data_section(), salt, seq, pk, sig)) return false; memcpy(m_pk.c_array(), pk, item_pk_len); memcpy(m_sig.c_array(), sig, item_sig_len); if (salt.second > 0) m_salt.assign(salt.first, salt.second); else m_salt.clear(); m_seq = seq; m_mutable = true; } else m_mutable = false; m_value = v; return true; }
bool verify_message(bdecode_node const& message, key_desc_t const desc[] , bdecode_node ret[], int size, char* error, int error_size) { // get a non-root bdecode_node that still // points to the root. message should not be copied bdecode_node msg = message.non_owning(); // clear the return buffer for (int i = 0; i < size; ++i) ret[i].clear(); // when parsing child nodes, this is the stack // of bdecode_nodes to return to bdecode_node stack[5]; int stack_ptr = -1; if (msg.type() != bdecode_node::dict_t) { snprintf(error, error_size, "not a dictionary"); return false; } ++stack_ptr; stack[stack_ptr] = msg; for (int i = 0; i < size; ++i) { key_desc_t const& k = desc[i]; // fprintf(stderr, "looking for %s in %s\n", k.name, print_entry(*msg).c_str()); ret[i] = msg.dict_find(k.name); // none_t means any type if (ret[i] && ret[i].type() != k.type && k.type != bdecode_node::none_t) ret[i].clear(); if (ret[i] == 0 && (k.flags & key_desc_t::optional) == 0) { // the key was not found, and it's not an optional key snprintf(error, error_size, "missing '%s' key", k.name); return false; } if (k.size > 0 && ret[i] && k.type == bdecode_node::string_t) { bool invalid = false; if (k.flags & key_desc_t::size_divisible) invalid = (ret[i].string_length() % k.size) != 0; else invalid = ret[i].string_length() != k.size; if (invalid) { // the string was not of the required size ret[i].clear(); if ((k.flags & key_desc_t::optional) == 0) { snprintf(error, error_size, "invalid value for '%s'", k.name); return false; } } } if (k.flags & key_desc_t::parse_children) { TORRENT_ASSERT(k.type == bdecode_node::dict_t); if (ret[i]) { ++stack_ptr; TORRENT_ASSERT(stack_ptr < int(sizeof(stack) / sizeof(stack[0]))); msg = ret[i]; stack[stack_ptr] = msg; } else { // skip all children while (i < size && (desc[i].flags & key_desc_t::last_child) == 0) ++i; // if this assert is hit, desc is incorrect TORRENT_ASSERT(i < size); } } else if (k.flags & key_desc_t::last_child) { TORRENT_ASSERT(stack_ptr > 0); // this can happen if the specification passed // in is unbalanced. i.e. contain more last_child // nodes than parse_children if (stack_ptr == 0) return false; --stack_ptr; msg = stack[stack_ptr]; } } return true; }
int bdecode(char const* start, char const* end, bdecode_node& ret , error_code& ec, int* error_pos, int depth_limit, int token_limit) { ec.clear(); ret.clear(); if (end - start > bdecode_token::max_offset) { if (error_pos) *error_pos = 0; ec = make_error_code(bdecode_errors::limit_exceeded); return -1; } // this is the stack of bdecode_token indices, into m_tokens. // sp is the stack pointer, as index into the array, stack int sp = 0; stack_frame* stack = TORRENT_ALLOCA(stack_frame, depth_limit); char const* const orig_start = start; if (start == end) return 0; while (start <= end) { if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); if (sp >= depth_limit) TORRENT_FAIL_BDECODE(bdecode_errors::depth_exceeded); --token_limit; if (token_limit < 0) TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); // look for a new token const char t = *start; const int current_frame = sp; // if we're currently parsing a dictionary, assert that // every other node is a string. if (current_frame > 0 && ret.m_tokens[stack[current_frame-1].token].type == bdecode_token::dict) { if (stack[current_frame-1].state == 0) { // the current parent is a dict and we are parsing a key. // only allow a digit (for a string) or 'e' to terminate if (!numeric(t) && t != 'e') TORRENT_FAIL_BDECODE(bdecode_errors::expected_digit); } } switch (t) { case 'd': stack[sp++] = ret.m_tokens.size(); // we push it into the stack so that we know where to fill // in the next_node field once we pop this node off the stack. // i.e. get to the node following the dictionary in the buffer ret.m_tokens.push_back(bdecode_token(start - orig_start , bdecode_token::dict)); ++start; break; case 'l': stack[sp++] = ret.m_tokens.size(); // we push it into the stack so that we know where to fill // in the next_node field once we pop this node off the stack. // i.e. get to the node following the list in the buffer ret.m_tokens.push_back(bdecode_token(start - orig_start , bdecode_token::list)); ++start; break; case 'i': { char const* int_start = start; bdecode_errors::error_code_enum e = bdecode_errors::no_error; // +1 here to point to the first digit, rather than 'i' start = check_integer(start + 1, end, e); if (e) { // in order to gracefully terminate the tree, // make sure the end of the previous token is set correctly if (error_pos) *error_pos = start - orig_start; error_pos = NULL; start = int_start; TORRENT_FAIL_BDECODE(e); } ret.m_tokens.push_back(bdecode_token(int_start - orig_start , 1, bdecode_token::integer, 1)); TORRENT_ASSERT(*start == 'e'); // skip 'e' ++start; break; } case 'e': { // this is the end of a list or dict if (sp == 0) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); if (sp > 0 && ret.m_tokens[stack[sp-1].token].type == bdecode_token::dict && stack[sp-1].state == 1) { // this means we're parsing a dictionary and about to parse a // value associated with a key. Instad, we got a termination TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); } // insert the end-of-sequence token ret.m_tokens.push_back(bdecode_token(start - orig_start, 1 , bdecode_token::end)); // and back-patch the start of this sequence with the offset // to the next token we'll insert int top = stack[sp-1].token; // subtract the token's own index, since this is a relative // offset if (ret.m_tokens.size() - top > bdecode_token::max_next_item) TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); ret.m_tokens[top].next_item = ret.m_tokens.size() - top; // and pop it from the stack. --sp; ++start; break; } default: { // this is the case for strings. The start character is any // numeric digit if (!numeric(t)) TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); boost::int64_t len = t - '0'; char const* str_start = start; ++start; bdecode_errors::error_code_enum e = bdecode_errors::no_error; start = parse_int(start, end, ':', len, e); if (e) TORRENT_FAIL_BDECODE(e); // remaining buffer size excluding ':' const ptrdiff_t buff_size = end - start - 1; if (len > buff_size) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); if (len < 0) TORRENT_FAIL_BDECODE(bdecode_errors::overflow); // skip ':' ++start; if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); // the bdecode_token only has 8 bits to keep the header size // in. If it overflows, fail! if (start - str_start - 2 > detail::bdecode_token::max_header) TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); ret.m_tokens.push_back(bdecode_token(str_start - orig_start , 1, bdecode_token::string, start - str_start)); start += len; break; } } if (current_frame > 0 && ret.m_tokens[stack[current_frame-1].token].type == bdecode_token::dict) { // the next item we parse is the opposite stack[current_frame-1].state = ~stack[current_frame-1].state; } // this terminates the top level node, we're done! if (sp == 0) break; } done: // if parse failed, sp will be greater than 1 // unwind the stack by inserting terminator to make whatever we have // so far valid while (sp > 0) { TORRENT_ASSERT(ec); --sp; // we may need to insert a dummy token to properly terminate the tree, // in case we just parsed a key to a dict and failed in the value if (ret.m_tokens[stack[sp].token].type == bdecode_token::dict && stack[sp].state == 1) { // insert an empty dictionary as the value ret.m_tokens.push_back(bdecode_token(start - orig_start , 2, bdecode_token::dict)); ret.m_tokens.push_back(bdecode_token(start - orig_start , bdecode_token::end)); } int top = stack[sp].token; TORRENT_ASSERT(ret.m_tokens.size() - top <= bdecode_token::max_next_item); ret.m_tokens[top].next_item = ret.m_tokens.size() - top; ret.m_tokens.push_back(bdecode_token(start - orig_start, 1, bdecode_token::end)); } ret.m_tokens.push_back(bdecode_token(start - orig_start, 0 , bdecode_token::end)); ret.m_token_idx = 0; ret.m_buffer = orig_start; ret.m_buffer_size = start - orig_start; ret.m_root_tokens = &ret.m_tokens[0]; return ec ? -1 : 0; }
std::string print_entry(bdecode_node const& e , bool single_line, int indent) { char indent_str[200]; using std::memset; memset(indent_str, ' ', 200); indent_str[0] = ','; indent_str[1] = '\n'; indent_str[199] = 0; if (indent < 197 && indent >= 0) indent_str[indent+2] = 0; std::string ret; switch (e.type()) { case bdecode_node::none_t: return "none"; case bdecode_node::int_t: { char str[100]; snprintf(str, sizeof(str), "%" PRId64, e.int_value()); return str; } case bdecode_node::string_t: { print_string(ret, e.string_ptr(), e.string_length(), single_line); return ret; } case bdecode_node::list_t: { ret += '['; bool one_liner = line_longer_than(e, 200) != -1 || single_line; if (!one_liner) ret += indent_str + 1; for (int i = 0; i < e.list_size(); ++i) { if (i == 0 && one_liner) ret += " "; ret += print_entry(e.list_at(i), single_line, indent + 2); if (i < e.list_size() - 1) ret += (one_liner?", ":indent_str); else ret += (one_liner?" ":indent_str+1); } ret += "]"; return ret; } case bdecode_node::dict_t: { ret += "{"; bool one_liner = line_longer_than(e, 200) != -1 || single_line; if (!one_liner) ret += indent_str+1; for (int i = 0; i < e.dict_size(); ++i) { if (i == 0 && one_liner) ret += " "; std::pair<std::string, bdecode_node> ent = e.dict_at(i); print_string(ret, ent.first.c_str(), ent.first.size(), true); ret += ": "; ret += print_entry(ent.second, single_line, indent + 2); if (i < e.dict_size() - 1) ret += (one_liner?", ":indent_str); else ret += (one_liner?" ":indent_str+1); } ret += "}"; return ret; } } return ret; }
// 'top_level' is extracting the file for a single-file torrent. The // distinction is that the filename is found in "name" rather than // "path" // root_dir is the name of the torrent, unless this is a single file // torrent, in which case it's empty. bool extract_single_file(bdecode_node const& dict, file_storage& files , std::string const& root_dir, ptrdiff_t info_ptr_diff, bool top_level , int& pad_file_cnt, error_code& ec) { if (dict.type() != bdecode_node::dict_t) return false; boost::uint32_t file_flags = get_file_attributes(dict); // symlinks have an implied "size" of zero. i.e. they use up 0 bytes of // the torrent payload space boost::int64_t const file_size = (file_flags & file_storage::flag_symlink) ? 0 : dict.dict_find_int_value("length", -1); if (file_size < 0 ) { ec = errors::torrent_invalid_length; return false; } boost::int64_t const mtime = dict.dict_find_int_value("mtime", 0); std::string path = root_dir; std::string path_element; char const* filename = NULL; int filename_len = 0; if (top_level) { // prefer the name.utf-8 because if it exists, it is more likely to be // correctly encoded bdecode_node p = dict.dict_find_string("name.utf-8"); if (!p) p = dict.dict_find_string("name"); if (!p || p.string_length() == 0) { ec = errors::torrent_missing_name; return false; } filename = p.string_ptr() + info_ptr_diff; filename_len = p.string_length(); while (filename_len > 0 && filename[0] == TORRENT_SEPARATOR) { filename += 1; filename_len -= 1; } sanitize_append_path_element(path, p.string_ptr(), p.string_length()); } else { bdecode_node p = dict.dict_find_list("path.utf-8"); if (!p) p = dict.dict_find_list("path"); if (p && p.list_size() > 0) { std::size_t const orig_path_len = path.size(); int const preallocate = path.size() + path_length(p, ec); if (ec) return false; path.reserve(preallocate); for (int i = 0, end(p.list_size()); i < end; ++i) { bdecode_node e = p.list_at(i); if (i == end - 1) { filename = e.string_ptr() + info_ptr_diff; filename_len = e.string_length(); } while (filename_len > 0 && filename[0] == TORRENT_SEPARATOR) { filename += 1; filename_len -= 1; } sanitize_append_path_element(path, e.string_ptr(), e.string_length()); } // if all path elements were sanitized away, we need to use another // name instead if (path.size() == orig_path_len) { path += TORRENT_SEPARATOR; path += "_"; } } else if (file_flags & file_storage::flag_pad_file) { // pad files don't need a path element, we'll just store them // under the .pad directory char cnt[10]; snprintf(cnt, sizeof(cnt), "%d", pad_file_cnt); path = combine_path(".pad", cnt); ++pad_file_cnt; } else { ec = errors::torrent_missing_name; return false; } } // bitcomet pad file if (path.find("_____padding_file_") != std::string::npos) file_flags = file_storage::flag_pad_file; bdecode_node fh = dict.dict_find_string("sha1"); char const* filehash = NULL; if (fh && fh.string_length() == 20) filehash = fh.string_ptr() + info_ptr_diff; std::string symlink_path; if (file_flags & file_storage::flag_symlink) { if (bdecode_node s_p = dict.dict_find_list("symlink path")) { int const preallocate = path_length(s_p, ec); if (ec) return false; symlink_path.reserve(preallocate); for (int i = 0, end(s_p.list_size()); i < end; ++i) { bdecode_node const& n = s_p.list_at(i); sanitize_append_path_element(symlink_path, n.string_ptr() , n.string_length()); } } } else { file_flags &= ~file_storage::flag_symlink; } if (filename_len > path.length() || path.compare(path.size() - filename_len, filename_len, filename , filename_len) != 0) { // if the filename was sanitized and differ, clear it to just use path filename = NULL; filename_len = 0; } files.add_file_borrow(filename, filename_len, path, file_size, file_flags, filehash , mtime, symlink_path); return true; }
void get_item_observer::reply(msg const& m) { public_key pk; signature sig; sequence_number seq{0}; bdecode_node const r = m.message.dict_find_dict("r"); if (!r) { #ifndef TORRENT_DISABLE_LOGGING get_observer()->log(dht_logger::traversal, "[%p] missing response dict" , static_cast<void*>(algorithm())); #endif timeout(); return; } bdecode_node const k = r.dict_find_string("k"); if (k && k.string_length() == public_key::len) std::memcpy(pk.bytes.data(), k.string_ptr(), public_key::len); bdecode_node const s = r.dict_find_string("sig"); if (s && s.string_length() == signature::len) std::memcpy(sig.bytes.data(), s.string_ptr(), signature::len); bdecode_node const q = r.dict_find_int("seq"); if (q) { seq = sequence_number(q.int_value()); } else if (k && s) { timeout(); return; } bdecode_node v = r.dict_find("v"); if (v) { static_cast<get_item*>(algorithm())->got_data(v, pk, seq, sig); } find_data_observer::reply(m); }