Torrent文件的解析与转换
Torrent简介
BitTorrent协议的种子文件(英语:Torrent file)可以保存一组文件的元数据。这种格式的文件被BitTorrent协议所定义。扩展名一般为“.torrent”。
.torrent种子文件本质上是文本文件,包含Tracker信息和文件信息两部分。Tracker信息主要是BT下载中需要用到的Tracker服务器的地址和针对Tracker服务器的设置,文件信息是根据对目标文件的计算生成的,计算结果根据BitTorrent协议内的Bencode规则进行编码。它的主要原理是需要把提供下载的文件虚拟分成大小相等的块,块大小必须为2k的整数次方(由于是虚拟分块,硬盘上并不产生各个块文件),并把每个块的索引信息和Hash验证码写入种子文件中;所以,种子文件就是被下载文件的“索引”。
Torrent结构
Torrent文件内容都已Bencoding编码类型进行存储,整体上是一个字典结构,见下:
Torrent总体结构
键值 | 数据类型 | 可选项 | 键值含义 |
---|---|---|---|
announce | string | required | Tracker的Url |
info | dictionary | required | 该条映射到一个字典,该字典的键将取决于共享的一个或多个文件 |
announce-list | array[] | optional | 备用Tracker的Url,以列表形式存在 |
comment | string | optional | 备注 |
created by | string | optional | 创建人或创建程序的信息 |
Torrent单文件Info结构
键值 | 数据类型 | 可选项 | 键值含义 |
---|---|---|---|
name | string | required | 建议保存到的文件名称 |
piceces | byte[] | required | 每个文件块的SHA-1的集成Hash。 |
piece length | long | required | 每个文件块的字节数 |
Torrent多文件Info结构
键值 | 数据类型 | 可选项 | 键值含义 |
---|---|---|---|
name | string | required | 建议保存到的目录名称 |
piceces | byte[] | required | 每个文件块的SHA-1的集成Hash。 |
piece length | long | required | 每个文件块的字节数 |
files | array[] | required | 文件列表,列表存储的内容是字典结构 |
files字典结构:
键值 | 数据类型 | 可选项 | 键值含义 |
---|---|---|---|
path | array[] | required | 一个对应子目录名的字符串列表,最后一项是实际的文件名称 |
length | long | required | 文件的大小(以字节为单位) |
Torrent实际结构预览
以JSON
序列化整个字典后,单文件和多文件的结构大致如下,注意:JSON内容省略了pieces摘要大部分内容,仅展示了开头部分,另外由于本人序列化工具设置所致,所有的整型都会序列化成字符串类型。
单文件结构
{
"creation date": "1581674765",
"comment": "dynamic metainfo from client",
"announce-list": [
[
"udp://tracker.leechers-paradise.org:6969/announce"
],
[
"udp://tracker.internetwarriors.net:1337/announce"
],
[
"udp://tracker.opentrackr.org:1337/announce"
],
[
"udp://tracker.coppersurfer.tk:6969/announce"
],
[
"udp://tracker.pirateparty.gr:6969/announce"
]
],
"created by": "go.torrent",
"announce": "udp://tracker.leechers-paradise.org:6969/announce",
"info": {
"pieces": "レJᅯ\ufff4ᅯ*f\nᄍ\ufff0... ...",
"length": "54358058387",
"name": "Frozen.II.2019.BDREMUX.2160p.HDR.seleZen.mkv",
"piece length": "16777216"
}
}
多文件结构
{
"creation date": "1604347014",
"comment": "Torrent downloaded from https://YTS.MX",
"announce-list": [
[
"udp://tracker.coppersurfer.tk:6969/announce"
],
[
"udp://9.rarbg.com:2710/announce"
],
[
"udp://p4p.arenabg.com:1337"
],
[
"udp://tracker.internetwarriors.net:1337"
],
[
"udp://tracker.opentrackr.org:1337/announce"
]
],
"created by": "YTS.AG",
"announce": "udp://tracker.coppersurfer.tk:6969/announce",
"info": {
"pieces": "ᆲimᅬヒ\u000b*゚ᆲト... ...",
"name": "Love And Monsters (2020) [2160p] [4K] [WEB] [5.1] [YTS.MX]",
"files": [
{
"path": [
"Love.And.Monsters.2020.2160p.4K.WEB.x265.10bit.mkv"
],
"length": "5215702961"
},
{
"path": [
"www.YTS.MX.jpg"
],
"length": "53226"
}
],
"piece length": "524288"
}
}
Torrent文件编码
根据上文所说,Torrent文件均以Bencoding编码进行存储,故我们需要大致了解一下Bencoding编码。
Bencoding以四种基本类型数据构成:
- string : 字符串
- intergers : 整数类型
- lists:列表类型
- dictionary:字典类型
字符串类型
字符串类型由以下结构表示:字符串长度:字符串原文
,例如:42:udp://tracker.pirateparty.gr:6969/announce
。
整形类型
整型类型由以下结构表示:i<整形数据>e
,例如i1234e
,则表明的整形数据为1234。
列表类型
列表类型由以下结构表示:l<列表数据>e
,即列表以字母l
开头,以字母e
结束,中间的均为列表中的数据,中间的值可以为任意的四种类型之一。
字典类型
字典类型由以下结构表示:d<字典数据>e
,即字典由字母d
开头,以字母e
结束,中间的均为字典中的数据,中间的值可以为任意的四种类型之一。
实际组合解析
根据上述描述来看看实际的内容解析,我们以下方的数据为例:
d8:announce49:udp://tracker.leechers-paradise.org:6969/announce13:announce-listll49:udp://tracker.leechers-paradise.org:6969/announceel48:udp://tracker.internetwarriors.net:1337/announceeee
大家可以先尝试根据上面的内容对这一串内容进行解析,我将这一串数据拆分开来方便大家理解和查看,可以明显看出其由一个拥有两个键值的字典,其中一个键为announce
,另一个键为announce-list
,两者的值一个为udp://tracker.leechers-paradise.org:6969/announce
,一个为列表,列表内还嵌套了一层列表。
d
8:announce
49:udp://tracker.leechers-paradise.org:6969/announce
13:announce-list
l
l
49:udp://tracker.leechers-paradise.org:6969/announce
e
l
48:udp://tracker.internetwarriors.net:1337/announce
e
e
e
Torrent文件解析
根据上文对Torrent文件编码的了解,那么我们使用代码对Torrent文件就很简单了。我们只需要读取种子字节流,判断具体是哪种类型并进行相应转换即可。
即:读取文件字节,判断字节属于哪一种类型:0-9 : 字符串类型、i:整形数据、l:列表数据、d:字典数据
再根据每个数据具体类型获取该数据的内容,再读取下一个文件字节获取下一个数据类型即可,根据这个分析,伪代码如下:
获取字符串值
// 当读取到字节对应的内容为0-9时进入该方法
String readString(byte[] info,int offset) {
// 读取‘:’以前的数据,即字符串长度
int length = readLength(info,offset);
// 根据字符串长度,获取实际字符串内容
string data = readData(info,length,offset);
// 返回读取到的字符串内容,整个读取过程中读过的偏移量要累加到offset
return data;
}
获取整数类型
这里有一个注意项,考虑到数据边界问题,例如java
等语言,推荐使用Long
类型,以防数据越界。
// 当读取到的字节对应的内容为i时,进入该方法
Long readInt(byte[] info,int offset) {
// 读取第一个'e'之前的数据,包括'e'
string data = readInt(info,offset)
return Long.valueOf(data);
}
获取列表类型
因为列表类型中可以夹杂所有四种类型中任意要给即需要用到上面两个方法。
// 当读取到的字节对应的内容为l时,进入该方法
List readList(byte[] info,int offset){
List list = new List();
// 读取到第一个'e'为止
while(info[offset] != 'e'){
swtich(info[offset]){
// 如果是列表,读取列表并向列表添加
case 'l':
list.add(readList(info,offset));
break;
// 如果是字典,读取字典并向列表添加
case 'd':
list.add(readDictionary(info,offset));
break;
// 如果是整形数据,读取数据并向列表添加
case 'i':
list.add(readInt(info,offset));
break;
// 如果是字符串,读取字符串数据并向列表添加
case '0-9':
list.add(readString(info,offset));
}
}
// offset向前移一位,把列表的结束符'e'移动为已读
offset++;
return list;
}
读取字典类型
读取字典类型与列表十分相似,唯一不同的就是需要区分键值,字典的键只可能为字符串,故依次来判断。
// 当读取到的字节对应的内容为d时,进入该方法
Dictionary readDictionary(byte[] info,int offset){
Dictionary dic = new Dictionary();
// key为null时,字符串为键,否则为值
String key = null;
// 读取到第一个'e'为止
while(info[offset] != 'e'){
swtich(info[offset]){
// 如果是列表,读取列表并向字典添加,添加列表时肯定存在键,直接添加并将键置空
case 'l':
dic.put(key,readList(info,offset));
key = null;
break;
// 如果是字典,读取字典并向字典添加,添加字典时肯定存在键,直接添加并将键置空
case 'd':
dic.put(key,readDictionary(info,offset));
key = null;
break;
// 如果是整形数据,读取数据并向字典添加,添加整形数据时肯定存在键,直接添加并将键置空
case 'i':
dic.put(key,readInt(info,offset));
key = null;
break;
// 如果是字符串
case '0-9':
string data = readString(info,offset);
// key为null时,字符串为键,否则为值
if(key == null){
key = data;
}else{
dic.put(key,data);
key = null;
}
}
}
// offset向前移一位,把列表的结束符'e'移动为已读
offset++;
return dic;
}
Torrent文件与Magnet
磁力链接与Torrent文件是可以相互转换的,此文只讨论根据Torrent文件如何转换为Magnet磁力链接。
Magnet概述
磁力链接由一组参数组成,参数间的顺序没有讲究,其格式与在HTTP链接末尾的查询字符串相同。最常见的参数是"xt",是"exact topic"的缩写,通常是一个特定文件的内容散列函数值形成的URN,例如:
magnet:?xt=urn:bith:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C
注意,虽然这个链接指向一个特定文件,但是客户端应用程序仍然必须进行搜索来确定哪里,如果有,能够获取那个文件(即通过DHT进行搜索,这样就实现了Magnet到Torrent的转换,本文不讨论)。
部分字段名见下方表格:
字段名 | 含义 |
---|---|
magnet | 协议名 |
xt | exact topic的缩写,包含文件哈希值的统一资源名称。BTIH(BitTorrent Info Hash)表示哈希方法名,这里还可以使用ED2K,AICH,SHA1和MD5等。这个值是文件的标识符,是不可缺少的。 |
dn | display name的缩写,表示向用户显示的文件名。这一项是选填的。 |
tr | tracker的缩写,表示tracker服务器的地址。这一项也是选填的。 |
bith | BitTorrent info hash,种子散列函数 |
Torrent转换为Magnet
- dn : 向用户显示的文件名
即为Torrent文件中,Info字典下的name键所对应的值
- tr : tracker服务器地址
即为Torrent文件中,announce以及announce-list两个键所对应的值
- bitch : 种子散列值
即为Torrent文件中,info对应的字典的SHA1哈希值(Hex)
根据上述可知:
magnet = 'magnet:?xt=urn:btih:'+Hex(Sha1(info))+'&dn='+encode(name)+'&tr='+encode(announce)
结合上一部分的实现,我们可以在读取info时记录startindex和endindex,即:
Dictionary readDictionary(byte[] info,int offset){
//...
case 'd':
bool record = key == 'info';
if(record){
startindex = offset;
}
readDictoinary(info,offset);
if(record){
endindex = offset
}
}
string getBith(byte[] info,int start,int end){
// 获取info中从start到end的字节数组,并对其进行摘要计算
byte[] infoByte = new byte[infoEnd - infoStart + 1];
System.arraycopy(torrentBytes, infoStart, infoByte, 0, infoEnd - infoStart + 1);
return Hex.toHex(Sha1.toSha1(infoByte));
}
具体实现
本人通过Java实现了以上部分逻辑(Torrent文件解析以及Magnet链接生成),若有需要参考的读者可以到以下网址获取相关内容:
工具类目录:https://github.com/Rekent/common-utils/tree/master/src/main/java/com/rekent/tools/utils/torrent
依赖jar包:https://github.com/Rekent/common-utils/releases/tag/v0.0.3
调用方式:
public void testResolve() throws Exception {
String path = "C:\\Users\\Refkent\\Downloads\\Test.torrent";
TorrentFile torrentFile = TorrentFileUtils.resolve(path);
System.out.println(torrentFile.print());
System.out.println(torrentFile.getHash());
System.out.println(torrentFile.getMagnetUri());
}
Reference
Torrent文件的解析与转换的更多相关文章
- C# 解析torrent文件
基础知识: torrent文件信息存储格式: bencoding是一种以简洁格式指定和组织数据的方法.支持下列类型:字节串.整数.列表和字典. 1 字符串存储格式: <字符串的长度>:& ...
- MyBatis源码分析(1)-MapConfig文件的解析
1.简述 MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下: String re ...
- SpringMVC文件上传 Excle文件 Poi解析 验证 去重 并批量导入 MYSQL数据库
SpringMVC文件上传 Excle文件 Poi解析并批量导入 MYSQL数据库 /** * 业务需求说明: * 1 批量导入成员 并且 自主创建账号 * 2 校验数据格式 且 重复导入提示 已被 ...
- 【转】C语言文件操作解析(三)
原文网址:http://www.cnblogs.com/dolphin0520/archive/2011/10/07/2200454.html C语言文件操作解析(三) 在前面已经讨论了文件打开操作, ...
- xml文件以及解析
1.创建一个xml文件 <?xml version="1.0" encoding="UTF-8"?> <!-- xml:是一个可扩展的标记语言 ...
- 透过现象看webpack处理css文件中图片路径转换的具体过程
webpack是目前使用比较流行的一个前端模块打包器,前端的任何资源都被当成一个模块来处理,如图片.css文件等等.在基于webpack构建的前端项目中,一般都会配置有关css文件处理的规则,这其中也 ...
- 自制C#版3DS文件的解析器并用SharpGL显示3DS模型
自制C#版3DS文件的解析器并用SharpGL显示3DS模型 我已经重写了3ds解析器,详情在此(http://www.cnblogs.com/bitzhuwei/p/CSharpGL-2-parse ...
- apt系统中sources.list文件的解析
/etc/apt/sources.list 一般源信息都存在这个文件中.但众多软件源都放在一个文件中实在有点乱,于是新版ubuntu也有了分类的方法: 文件夹 /etc/apt/sources.li ...
- JVM-class文件完全解析-字段表集合
字段表集合 这个class文件的解析,分析得有点太久了.前面介绍类魔数,次版本号,主板本号,常量池入口,常量池,访问标志,类索引,父类索引和接口索引集合.下面就应该到字段表集合了. 紧接着接口索引 ...
随机推荐
- SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期
写在前面 通过前几篇文章的学习,我们从大体上了解了shiro关于认证和授权方面的应用.在接下来的文章当中,我将通过一个demo,带领大家搭建一个SpringBoot整合Shiro的一个项目开发脚手架, ...
- 一些IT service的相关知识
1. cmd是什么,怎么在电脑上打开cmd命令框. 在windows环境下,命令行程序为cmd.exe,是一个32位的命令行程序,微软Windows系统基于Windows上的命令解释程序,类似于微软的 ...
- id+is+深浅co'p'y
day06 一.id.is 关键字:id #唯一的,如果id相同,说明2个变量指向同一个地址,就是变量一==变量二 注意:id相同值一定相同,值相同但是id不一定相同(不同代码块的值相同,他们就像太阳 ...
- 宜宾1178.9873(薇)xiaojie:宜宾哪里有xiaomei
宜宾哪里有小姐服务大保健[微信:1178.9873倩儿小妹[宜宾叫小姐服务√o服务微信:1178.9873倩儿小妹[宜宾叫小姐服务][十微信:1178.9873倩儿小妹][宜宾叫小姐包夜服务][十微信 ...
- 最近集训的图论(思路+实现)题目汇总(内容包含tarjan、分层图、拓扑、差分、奇怪的最短路):
(集训模拟赛2)抢掠计划(tarjan强) 题目:给你n个点,m条边的图,每个点有点权,有一些点是"酒吧"点,终点只能在"酒吧",起点给定,路可以重复经过,但点 ...
- spring boot:用spring security加强spring boot admin的安全(spring boot admin 2.3.0 / spring boot 2.3.3)
一,spring boot admin的安全环节: 1,修改context-path,默认时首页就是admin, 我们修改这个地址可以更安全 2,配置ip地址白名单,有ip限制才安全, 我们使用了sp ...
- python 爬虫可视化函数,可以先看看要爬取的数据是否存在
import requests url = "http://www.spbeen.com" headers = { "User-Agent":"tes ...
- 利用Docker搭建开发环境
一. 前言 随着平台的不断壮大,项目的研发对于开发人员而言,对于外部各类环境的依赖逐渐增加,特别是针对基础服务的依赖.这些现象导致开 发人员常常是为了简单从而直接使用公有的基础组件进行协同开发,在出现 ...
- 【应用服务 App Service】发布到Azure上的应用显示时间不是本地时间的问题,修改应用服务的默认时区
问题情形 应用程序发布到App Service后,时间显示不是北京时间,默认情况为UTC时间,比中国时间晚 8 个小时. 详细日志 无 问题原因 Azure 上所有的服务时间都采用了 UTC 时间. ...
- ubuntu18 ssh服务器拒绝连了密码
问题 xshell 远程连接ubuntu时 解决方法 参考:链接 网上的kenghuo太多!!! 1.如果没有安装ssh服务,请先安装 sudo apt-get install openssh-ser ...