P2P なぜなにTorrent 2021 用 ネタ集め(2) Libtorrent の使い方 01

bittorrent の network に参加するプログラム を書く場合、多くの場合は何かしらのライブラリーを使うと思われます。

この記事は libtorrent 2.0 の使い方について解説します。

dev.to に投稿した [Hello libtorrent](https://dev.to/kyorohiro/series/10917
)

まで読んでいただけると、ドキュメント化されてない使い方やGoogleしても出てこない利用方法なども解説していますので、

libtorrent で こんな事もできるのかと
発見があると思います。

# Code

以下で公開しています。
https://github.com/kyorohiro/hello_libtorrent

\#DEVCommunity 向けに作成したものです
[Hello libtorrent (from dev.to)](https://dev.to/kyorohiro/series/10917
)

開発環境の構築は、[前回の記事](https://qiita.com/kyorohiro/items/721ba469fbfcfb2390a3)を参考にしてください


# 使い方


## Bencode 

Bittorrent の 　プロトコルでは bencode を利用してデータをformat します。
感覚としては、バイナリデータを使えるjson という感じです。

```c++:main_bencode.cpp

//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bencode.cpp -ltorrent-rasterbar -lpthread
//
#include<iostream>
#include<libtorrent/bdecode.hpp>
#include<libtorrent/bencode.hpp>
#include<vector>
#include<stdio.h>



// [Memo]
// using dictionary_type = std::map<std::string, entry, aux::strview_less>;
// using string_type = std::string;
// using list_type = std::vector<entry>;
// using integer_type = std::int64_t;
// using preformatted_type = std::vector<char>;

int main() {
    //
    // encode
    //
    {
        // string 
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry("hello"));
        std::cout << s << std::endl; // 5:hello
    }
    {
        // number
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(12));
        std::cout << s << std::endl; // i12e
    }
    {
        // list
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(std::vector<lt::entry>{
            lt::entry("hello"),lt::entry(12)
        }));
        std::cout << s << std::endl; // l5:helloi12ee
    }
    {
        // dict
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(
           lt::entry::dictionary_type {
                {"num", lt::entry(12)},
                {"str", lt::entry("hello")}
            }
        ));
        std::cout << s << std::endl; // d3:numi12e3:str5:helloe
    }
    {
        // binary
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(std::vector<char>{(char)0xFF,(char)0x01,(char)0x03}));
        std::cout << s << std::endl; // i-12e
    }
    {
        // other
        std::vector<char> output;
        lt::entry source_data;

        source_data["bstring"] = lt::entry::string_type("hello");
        source_data["bint"] = lt::entry::integer_type(12);
        source_data["elist"] = std::vector<lt::entry>{
            lt::entry(lt::entry::string_type("01")), lt::entry(lt::entry::integer_type(1))
        };
        source_data["tperformatted_type"] = lt::entry::preformatted_type{(char)0xFF,(char)0x01,(char)0x03};

        lt::bencode(std::back_inserter(output), source_data);
        std::cout << std::string(output.data(), output.size()) << std::endl;
    }

    return 0;
}
```

```c++:main_bdecode.cpp
//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bdecode.cpp -ltorrent-rasterbar -lpthread
//
#include<iostream>
#include<libtorrent/bencode.hpp>
#include<libtorrent/bdecode.hpp>

int main(int argc, char* argv[]) {
    {
        // deprecated? by TORRENT_DEPRECATED
        lt::entry result = lt::bdecode(std::string("i12e"));
        std::cout << result.integer() << std::endl; // 12
    }

    {
        // string 
        std::cout << "# string" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("5:hello", ec);
        std::cout << e.string_value() << std::endl; 
    }
    {
        // number
        std::cout << "# number" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("i12e", ec);
        std::cout << e.int_value() << std::endl; 
    }
    {
        // list
        std::cout << "# list" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("l5:helloi12ee", ec);

        std::cout << e.list_at(0).string_value() << std::endl; 
        std::cout << e.list_at(1).int_value() << std::endl; 
    }
    {
        // dict
        std::cout << "# dict" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("d3:numi12e3:str5:helloe", ec);

        std::cout << e.dict_find("num").int_value() << std::endl; 
        std::cout << e.dict_find("str").string_value() << std::endl;
    }
    return 0;
}
```



## Torrent file

Bittorrent では、ダウンロードしたい/アップロードしたいデータについての情報を、".torrent"にまとめます。


```c++:create_torrent.cpp

#include<iostream>
#include<vector>
#include<unistd.h>

#include<libtorrent/bencode.hpp>
#include<libtorrent/torrent_info.hpp>
#include<libtorrent/create_torrent.hpp>

std::string to_absolute_path(std::string args);
std::string extract_parent_path(std::string filepath);
void print_usage(std::string execute_file_name);

std::string tracker_address = "";
int main(int argc, char *argv[]) {
    std::string target_tracker_address = "";
    std::string target_file_path = "";

    //
    // extract tracker address and target file from argv
    // 
    std::vector<std::string> args;
    for(int i=1; i<argc;i++) {
        args.push_back(std::string(argv[i]));
    }

    if(args.size() <= 0) {
        print_usage(std::string(argv[0]));
        return -1;
    }

    for(auto v=args.begin(); v != args.end();v++){
        if(v->length() > 0 && v->rfind("-", 0) == 0){
            if((v+1) == args.end()){
                break;
            }
        }
        if(std::string("-a") == *v) {
            target_tracker_address = *(++v);
            continue; 
        }
        else {
            target_file_path = *v;
        }
    }

    //
    // setup file storage
    std::string source_absolute_path = to_absolute_path(target_file_path);
    lt::file_storage fs;
    lt::create_flags_t flags = {};
    lt::add_files(fs, source_absolute_path, flags);

    // 
    // create torrent creator
    lt::create_torrent torrent(fs, 16*1024);
    if(target_tracker_address.length() > 0) {
        torrent.add_tracker(target_tracker_address);
    }
    //
    // add hash info
    lt::set_piece_hashes(torrent, extract_parent_path(source_absolute_path));

    //
    // generate metadata
    lt::entry entry_info = torrent.generate();
    
    //
    // convert to bencoding
    std::vector<char> torrentfile_source;
    lt::bencode(std::back_inserter(torrentfile_source), entry_info); // metainfo -> binary
    std::cout.write(torrentfile_source.data(),torrentfile_source.size()); // binary -> stdout
    
    return 0;
}

void print_usage(std::string args0) {
    std::cerr << ""<< args0 << " <filename> [ -a <tracker address> ]" << std::endl;
}

std::string to_absolute_path(std::string path) {
    if(path.length() == 0) {
        throw std::runtime_error("failed to current_dir");
    }
    if(path[0] == '/') {
        return path;
    }
    char current_dir_path[2056];
    auto ret = getcwd(current_dir_path, sizeof(current_dir_path));
    if(ret == NULL) {
        std::cerr << "failed to current_dir ";
        throw std::runtime_error("failed to current_dir");
    }
    if(0==path.compare(0, 2, "./")) {
        path = path.substr(2);
    }
    return std::string(current_dir_path) + "/" + path;
} 

std::string extract_parent_path(std::string filepath)
{
  if (filepath.empty()) {
    return filepath;
  }
  int pos = filepath.find_last_of("/");
  
  return filepath.substr(0,pos
```

```c++:dump_torrent.cpp
//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bdecode_dump.cpp  -o dumper.out -ltorrent-rasterbar -lpthread
//

#include <iostream>
#include <fstream>
#include <vector>
#include <iterator>
#include <libtorrent/bdecode.hpp>
#include <regex>
#include <string>

void print_usage(std::string args0);
lt::bdecode_node decode_file(std::string filepath);
void print_bencode_node(lt::bdecode_node target);
std::string to_asis_hex_string(std::string target, int max);

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        print_usage(std::string(argv[0]));
        return -1;
    }

    // decode to entry from a file
    std::ifstream input(std::string(argv[1]), std::ios::binary);
    std::vector<char> source((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
    lt::error_code ec;
    lt::bdecode_node node = lt::bdecode(source, ec);

    // lt::bdecode_node node = decode_file(std::string(argv[1]));
    // decode_file() is wrong, do Segmentation fault (core dumped)
    print_bencode_node(node);
    return 0;
}

void print_usage(std::string args0)
{
    std::cerr << "" << args0 << " <filename>" << std::endl;
}

lt::bdecode_node decode_file(std::string filepath)
{
    std::ifstream input(filepath, std::ios::binary);
    std::vector<char> source((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
    lt::error_code ec;
    lt::bdecode_node node = lt::bdecode(source, ec);
    return node;
}

// display all
enum item_type
{
    ipair,
    inode,
    ilistend,
    idictend
};

class item
{
public:
    std::pair<lt::string_view, lt::bdecode_node> pair;
    lt::bdecode_node node;
    item_type type;
    int depth;
    item(lt::bdecode_node node, int depth)
    {
        this->node = node;
        this->type = item_type::inode;
        this->depth = depth;
    }
    item(std::pair<lt::string_view, lt::bdecode_node> pair, int depth)
    {
        this->pair = pair;
        this->type = item_type::ipair;
        this->depth = depth;
    }
    item(item_type type, int depth)
    {
        this->type = type;
        this->depth = depth;
    }
};

void print_bencode_node(lt::bdecode_node target)
{
    std::vector<item> stack;
    stack.push_back(item(target, 1));

    while (stack.size() > 0)
    {
        item im = stack.back();
        stack.pop_back();

        std::string spaces = "";
        for (int i = 0; i < im.depth; i++)
        {
            spaces += "  ";
        }
        if (im.type == item_type::inode)
        {
            lt::bdecode_node node = im.node;
            if (node.type() == lt::bdecode_node::dict_t)
            {
                std::cout << std::endl
                          << spaces + "{";
                stack.push_back(item(item_type::idictend, im.depth));
                for (int i = 0; i < node.dict_size(); i++)
                {
                    stack.push_back(item(node.dict_at(node.dict_size() - 1 - i), im.depth + 1));
                }
            }
            else if (node.type() == lt::bdecode_node::list_t)
            {
                std::cout << "["
                          << "  ";
                stack.push_back(item(item_type::ilistend, im.depth));
                for (int i = 0; i < node.list_size(); i++)
                {
                    stack.push_back(item(node.list_at(node.dict_size() - 1 - i), im.depth + 1));
                }
            }
            else if (node.type() == lt::bdecode_node::int_t)
            {
                std::cout << "'" << node.int_value() << "'";
            }
            else if (node.type() == lt::bdecode_node::string_t)
            {
                std::cout << "'" << to_asis_hex_string(node.string_value().to_string(), 100) << "'";
            }
            else if (node.type() == lt::bdecode_node::none_t)
            {
                std::cout << "<none>";
            }
            else
            {
                std::cout << "<else>" << node.type() << std::endl;
            }
        }
        else if (im.type == item_type::ilistend)
        {
            std::cout << "  ]";
        }
        else if (im.type == item_type::idictend)
        {
            std::cout << std::endl
                      << spaces << "}";
        }
        else
        {
            std::pair<lt::string_view, lt::bdecode_node> pair = im.pair;
            std::cout << std::endl
                      << spaces << "<" << to_asis_hex_string(im.pair.first.to_string(), 100) << "> : "; // << std::endl;
            stack.push_back(item(pair.second, im.depth + 1));
        }
    }
    std::cout << std::endl;
}

std::string to_asis_hex_string(std::string target, int max)
{

    bool isAscii = true;
    for (int i = 0; i < target.size(); i++)
    {
        if (!(0 <= target[i] && target[i] < 127))
        {
            isAscii = false;
            break;
        }
    }
    if (isAscii)
    {
        // ascii
        return target.substr(0, max);
    }
    else
    {
        if (max > target.size() * 2)
        {
            max = target.size() * 2 + 1;
        }
        std::vector<char> ret(max);
        const char x[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        for (int i = 0; i < target.size() && i * 2 < max; i++)
        {
            int v = (unsigned char)target[i];
            ret[i * 2] = x[(0xf0 & v) >> 4];
            ret[i * 2 + 1] = x[(0x0f & v)];
        }
        return std::string("(size:") + std::to_string(target.size()) + std::string(")") + std::string(ret.begin(), ret.end());
    }
}
```


## Torrentfile からデータを　ダウンロード


※ Bittorrent の ネットワークではデータのダウンロードをする際に、同時にデーターのアップロードします。


```c++:tiny_cliet.cpp

#include<iostream>
#include<libtorrent/session.hpp>
#include<libtorrent/alert_types.hpp>
#include<libtorrent/alert.hpp>
#include<libtorrent/magnet_uri.hpp>
#include<libtorrent/error_code.hpp>

void print_usage(std::string);

std::string my_listen_interfaces = "";// "0.0.0.0:6881,[::]:6881"  "{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777"
std::string target_torrentfile_path = "";
std::string target_magnet_link = "";//magnet:?xt=urn...

int main(int argc, char *argv[]) try {

    if(argc > 1) {
        // 
        std::string tmp = std::string(argv[1]);
        if(tmp.rfind("magnet:",0) == 0) {
            target_magnet_link = tmp;
            target_torrentfile_path = "";
        } else {
            target_magnet_link = "";
            target_torrentfile_path = tmp;
        }
    }

	lt::settings_pack session_params;
	session_params.set_int(lt::settings_pack::alert_mask, lt::alert_category::all);
    if(my_listen_interfaces.length() != 0) {
        session_params.set_str(lt::settings_pack::listen_interfaces, my_listen_interfaces);
    }
    lt::session session(session_params);

	lt::add_torrent_params torrent_params;
    torrent_params.save_path = ".data"; // save in this dir
    if(target_torrentfile_path.length() != 0) {
        torrent_params.ti = std::make_shared<lt::torrent_info>(target_torrentfile_path);
    } else {
        lt::error_code ec;
		lt::parse_magnet_uri(target_magnet_link,torrent_params, ec);
		if (ec){
            std::cerr << "wrong magnet link "  << target_magnet_link << std::endl;
            throw std::invalid_argument("wrong magnet link \""+ target_magnet_link+"\"");
        }
        torrent_params = lt::parse_magnet_uri(target_magnet_link);
    }
    session.add_torrent(torrent_params);

    while (true)
    {
        std::vector<lt::alert *> alerts;
        session.pop_alerts(&alerts);

        lt::state_update_alert *st;
        for (lt::alert *a : alerts)
        {
            std::cout << "[" << a->type() << "](" << a->what() << ") " << a->message() << std::endl;
            switch (a->type())
            {
            case lt::state_update_alert::alert_type:
                //lt::state_update_alert
                st = (lt::state_update_alert *)(a);
                {
                    lt::torrent_status const &s = st->status[0];
                    std::cout << '\r' //<< lt::state(s.state) << ' '
                              << (s.download_payload_rate / 1000) << " kB/s "
                              << (s.total_done / 1000) << " kB ("
                              << (s.progress_ppm / 10000) << "%) downloaded ("
                              << s.num_peers << " peers)\x1b[K";
                    std::cout.flush();
                }
                break;
            case lt::torrent_finished_alert::alert_type:
                std::cout << ">> finished : ==" << std::endl;
                goto END;
            case lt::torrent_error_alert::alert_type:
                std::cout << ">> error : ==" << std::endl;
                goto END;
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
END:
    return 0;
}
catch (std::exception &e)
{
    std::cerr << "Error: " << e.what() << std::endl;
}
```


## Magnetlink libtorrent extension plugin, log analysis 他


Magnetlink libtorrent extension plugin, log analysis などなど、
公式のExampleにない使い方の解説もしていますので、

より詳しく知りたい方は、以下もどうぞ
[Hello libtorrent (from dev.to)](https://dev.to/kyorohiro/series/10917
)


## コメント

最近だと、bittoreent の　network の覗いていて、最近は webtorrent を利用する方が多くなったように感じました。

https://github.com/webtorrent

WebTorrent では、TCP、UDPに加えて、WebRTC を利用してデーターのダウンロードとアップロードも行います。
また、Bittorrent では Trackerサーバーとして、TCPとUDPを利用しますが、加えて Websocket を利用した方法もサポートしています。


Nodeで書かれており、機能も分離されており
もしかすると、Bittorrent のネットワークに参加するプログラムを書く場合、WebTorrent を利用する方が、
楽かもしれません..


# PS 

フルスクラッチでTorrent を書いてみたい方は、
2014年に書いた [なぜなにTorrent 2014](https://nazenani-torrent.firefirestyle.net) で、実際にフルスクラッチでしながらm解説した記事もありますので

こちらもどうぞ..
※ ChromeApp と Dart 1系を利用していて 動きませんが..