Files
developer-roadmap/guides/torrent-client.html

391 lines
160 KiB
HTML
Raw Permalink Normal View History

2022-02-15 00:11:44 +00:00
<!DOCTYPE html><html><head><meta charSet="UTF-8"/><title>Building a BitTorrent Client</title><meta name="description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta name="author" content="Kamran Ahmed"/><meta name="keywords" content="roadmap,developer roadmaps,developer roadmap,frontend developer,frontend developer roadmap,frontend,frontend roadmap,backend,backend developer,backend developer roadmap,devops,devops roadmap,fullstack developer roadmap,guide to becoming a developer,sre roadmap,sre,operations roadmap,qa roadmap,android roadmap,android developer roadmap,react roadmap,react developer roadmap,dba roadmap,postgresql dba roadmap"/><meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0"/><meta http-equiv="Content-Language" content="en"/><meta property="og:title" content="Building a BitTorrent Client"/><meta property="og:description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta property="og:image" content="https://roadmap.sh/brand-square.png"/><meta property="og:url" content="https://roadmap.sh"/><meta property="og:type" content="website"/><meta property="article:publisher" content="https://facebook.com/kamranahmedse"/><meta property="og:site_name" content="roadmap.sh"/><meta property="article:author" content="Kamran Ahmed"/><meta name="twitter:card" content="summary"/><meta name="twitter:site" content="@kamranahmedse"/><meta name="twitter:title" content="Building a BitTorrent Client"/><meta name="twitter:description" content="Learn everything you need to know about BitTorrent by writing a client in Go"/><meta name="twitter:image" content="https://roadmap.sh/brand-square.png"/><meta name="twitter:image:alt" content="roadmap.sh"/><meta name="mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><link rel="apple-touch-icon" sizes="180x180" href="/manifest/apple-touch-icon.png"/><meta name="msapplication-TileColor" content="#101010"/><meta name="theme-color" content="#848a9a"/><link rel="manifest" href="/manifest/manifest.json"/><link rel="icon" type="image/png" sizes="32x32" href="/manifest/icon32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/manifest/icon16.png"/><link rel="shortcut icon" href="/manifest/favicon.ico" type="image/x-icon"/><link rel="icon" href="/manifest/favicon.ico" type="image/x-icon"/><script async="" src="https://www.googletagmanager.com/gtag/js?id=UA-139582634-1"></script><script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-139582634-1');
2022-06-28 11:05:04 +00:00
</script><meta name="next-head-count" content="34"/><link rel="preload" href="/_next/static/css/9630938f8579f20e.css" as="style"/><link rel="stylesheet" href="/_next/static/css/9630938f8579f20e.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-5cd94c89d3acac5f.js"></script><script src="/_next/static/chunks/webpack-378e68e29c265886.js" defer=""></script><script src="/_next/static/chunks/framework-dc33c0b5493501f0.js" defer=""></script><script src="/_next/static/chunks/main-056d531d8af152ad.js" defer=""></script><script src="/_next/static/chunks/pages/_app-981db2c906b1d6be.js" defer=""></script><script src="/_next/static/chunks/680-f2ef73bf59f135ea.js" defer=""></script><script src="/_next/static/chunks/22-14757297dd54265a.js" defer=""></script><script src="/_next/static/chunks/515-79757bf997a2c9cf.js" defer=""></script><script src="/_next/static/chunks/13-b259223214a65539.js" defer=""></script><script src="/_next/static/chunks/pages/guides/%5Bguide%5D-522b73f8763bfcbb.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_buildManifest.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_ssgManifest.js" defer=""></script><script src="/_next/static/ooSnr_WJpF8nzY9n_DSAx/_middlewareManifest.js" defer=""></script><style data-styled="" data-styled-version="5.3.3">.kiIUjN{line-height:27px;font-size:16px;color:black;margin-bottom:18px;}/*!sc*/
2022-02-15 00:11:44 +00:00
data-styled.g1[id="sc-bdvvtL"]{content:"kiIUjN,"}/*!sc*/
.dKUOdJ{position:relative;font-size:32px;line-height:40px;font-weight:700;margin:20px 0 10px !important;}/*!sc*/
.dKUOdJ:hover .sc-gsDKAQ{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}/*!sc*/
data-styled.g3[id="sc-dkPtRN"]{content:"dKUOdJ,"}/*!sc*/
.kqfgwy{font-size:30px;}/*!sc*/
data-styled.g4[id="sc-hKwDye"]{content:"kqfgwy,"}/*!sc*/
.cabxUq{margin:22px 0 8px;font-size:28px;}/*!sc*/
data-styled.g5[id="sc-eCImPb"]{content:"cabxUq,"}/*!sc*/
.gRXaYU{margin:18px 0 8px;font-size:24px;}/*!sc*/
data-styled.g6[id="sc-jRQBWg"]{content:"gRXaYU,"}/*!sc*/
.jiGIaj{margin:25px -25px 25px -25px !important;padding:20px 25px !important;border-radius:10px;line-height:1.5 !important;}/*!sc*/
.jiGIaj code{background:transparent;}/*!sc*/
data-styled.g9[id="sc-furwcr"]{content:"jiGIaj,"}/*!sc*/
.bIxQHp{max-width:100%;margin:25px auto;display:block;}/*!sc*/
data-styled.g13[id="sc-iqseJM"]{content:"bIxQHp,"}/*!sc*/
.gJMBjK{font-weight:600;-webkit-text-decoration:underline;text-decoration:underline;}/*!sc*/
data-styled.g14[id="sc-crHmcD"]{content:"gJMBjK,"}/*!sc*/
.gfrORe{margin-left:40px;margin-bottom:18px;}/*!sc*/
.gfrORe ul{margin-top:18px;}/*!sc*/
data-styled.g15[id="sc-egiyK"]{content:"gfrORe,"}/*!sc*/
.xTMXv{margin-bottom:7px;}/*!sc*/
data-styled.g16[id="sc-bqiRlB"]{content:"xTMXv,"}/*!sc*/
</style></head><body><div id="__next" data-reactroot=""><style data-emotion="css-global brzcv3">:host,:root{--chakra-ring-inset:var(--chakra-empty,/*!*/ /*!*/);--chakra-ring-offset-width:0px;--chakra-ring-offset-color:#fff;--chakra-ring-color:rgba(66, 153, 225, 0.6);--chakra-ring-offset-shadow:0 0 #0000;--chakra-ring-shadow:0 0 #0000;--chakra-space-x-reverse:0;--chakra-space-y-reverse:0;--chakra-colors-transparent:transparent;--chakra-colors-current:currentColor;--chakra-colors-black:#000000;--chakra-colors-white:#FFFFFF;--chakra-colors-whiteAlpha-50:rgba(255, 255, 255, 0.04);--chakra-colors-whiteAlpha-100:rgba(255, 255, 255, 0.06);--chakra-colors-whiteAlpha-200:rgba(255, 255, 255, 0.08);--chakra-colors-whiteAlpha-300:rgba(255, 255, 255, 0.16);--chakra-colors-whiteAlpha-400:rgba(255, 255, 255, 0.24);--chakra-colors-whiteAlpha-500:rgba(255, 255, 255, 0.36);--chakra-colors-whiteAlpha-600:rgba(255, 255, 255, 0.48);--chakra-colors-whiteAlpha-700:rgba(255, 255, 255, 0.64);--chakra-colors-whiteAlpha-800:rgba(255, 255, 255, 0.80);--chakra-colors-whiteAlpha-900:rgba(255, 255, 255, 0.92);--chakra-colors-blackAlpha-50:rgba(0, 0, 0, 0.04);--chakra-colors-blackAlpha-100:rgba(0, 0, 0, 0.06);--chakra-colors-blackAlpha-200:rgba(0, 0, 0, 0.08);--chakra-colors-blackAlpha-300:rgba(0, 0, 0, 0.16);--chakra-colors-blackAlpha-400:rgba(0, 0, 0, 0.24);--chakra-colors-blackAlpha-500:rgba(0, 0, 0, 0.36);--chakra-colors-blackAlpha-600:rgba(0, 0, 0, 0.48);--chakra-colors-blackAlpha-700:rgba(0, 0, 0, 0.64);--chakra-colors-blackAlpha-800:rgba(0, 0, 0, 0.80);--chakra-colors-blackAlpha-900:rgba(0, 0, 0, 0.92);--chakra-colors-gray-50:#F7FAFC;--chakra-colors-gray-100:#EDF2F7;--chakra-colors-gray-200:#E2E8F0;--chakra-colors-gray-300:#CBD5E0;--chakra-colors-gray-400:#A0AEC0;--chakra-colors-gray-500:#718096;--chakra-colors-gray-600:#4A5568;--chakra-colors-gray-700:#2D3748;--chakra-colors-gray-800:#1A202C;--chakra-colors-gray-900:#171923;--chakra-colors-red-50:#FFF5F5;--chakra-colors-red-100:#FED7D7;--chakra-colors-red-200:#FEB2B2;--chakra-colors-red-300:#FC8181;--chakra-colors-red-400:#F56565;--chakra-colors-red-500:#E53E3E;--chakra-colors-red-600:#C53030;--chakra-colors-red-700:#9B2C2C;--chakra-colors-red-800:#822727;--chakra-colors-red-900:#63171B;--chakra-colors-orange-50:#FFFAF0;--chakra-colors-orange-100:#FEEBC8;--chakra-colors-orange-200:#FBD38D;--chakra-colors-orange-300:#F6AD55;--chakra-colors-orange-400:#ED8936;--chakra-colors-orange-500:#DD6B20;--chakra-colors-orange-600:#C05621;--chakra-colors-orange-700:#9C4221;--chakra-colors-orange-800:#7B341E;--chakra-colors-orange-900:#652B19;--chakra-colors-yellow-50:#FFFFF0;--chakra-colors-yellow-100:#FEFCBF;--chakra-colors-yellow-200:#FAF089;--chakra-colors-yellow-300:#F6E05E;--chakra-colors-yellow-400:#ECC94B;--chakra-colors-yellow-500:#D69E2E;--chakra-colors-yellow-600:#B7791F;--chakra-colors-yellow-700:#975A16;--chakra-colors-yellow-800:#744210;--chakra-colors-yellow-900:#5F370E;--chakra-colors-green-50:#F0FFF4;--chakra-colors-green-100:#C6F6D5;--chakra-colors-green-200:#9AE6B4;--chakra-colors-green-300:#68D391;--chakra-colors-green-400:#48BB78;--chakra-colors-green-500:#38A169;--chakra-colors-green-600:#2F855A;--chakra-colors-green-700:#276749;--chakra-colors-green-800:#22543D;--chakra-colors-green-900:#1C4532;--chakra-colors-teal-50:#E6FFFA;--chakra-colors-teal-100:#B2F5EA;--chakra-colors-teal-200:#81E6D9;--chakra-colors-teal-300:#4FD1C5;--chakra-colors-teal-400:#38B2AC;--chakra-colors-teal-500:#319795;--chakra-colors-teal-600:#2C7A7B;--chakra-colors-teal-700:#285E61;--chakra-colors-teal-800:#234E52;--chakra-colors-teal-900:#1D4044;--chakra-colors-blue-50:#ebf8ff;--chakra-colors-blue-100:#bee3f8;--chakra-colors-blue-200:#90cdf4;--chakra-colors-blue-300:#63b3ed;--chakra-colors-blue-400:#4299e1;--chakra-colors-blue-500:#3182ce;--chakra-colors-blue-600:#2b6cb0;--chakra-colors-blue-700:#2c5282;--chakra-colors-blue-800:#2a4365;--chakra-colors-blue-900:#1A365D;--chakra-colors-cyan-50:#EDFDFD;--chakra-colors-cyan-100:#C4F1F9;--chakra-colors-cyan-200:#9DECF9;--chakra-colors-cyan-300:#76E4F7;--chakra-colors-
</code></pre><p class="sc-bdvvtL kiIUjN">That mess is encoded in a format called <strong>Bencode</strong> (pronounced <em>bee-encode</em>), and we&#x27;ll need to decode it.</p><p class="sc-bdvvtL kiIUjN">Bencode can encode roughly the same types of structures as JSON—strings, integers, lists, and dictionaries. Bencoded data is not as human-readable/writable as JSON, but it can efficiently handle binary data and it&#x27;s really simple to parse from a stream. Strings come with a length prefix, and look like <code>4:spam</code>. Integers go between <em>start</em> and <em>end</em> markers, so <code>7</code> would encode to <code>i7e</code>. Lists and dictionaries work in a similar way: <code>l4:spami7ee</code> represents <code>[&#x27;spam&#x27;, 7]</code>, while <code>d4:spami7ee</code> means <code>{spam: 7}</code>.</p><p class="sc-bdvvtL kiIUjN">In a prettier format, our .torrent file looks like this:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="chakra-code language-markdown css-4m8w8z">d
8:announce
41:http://bttracker.debian.org:6969/announce
7:comment
35:&quot;Debian CD from cdimage.debian.org&quot;
13:creation date
i1573903810e
4:info
d
6:length
i351272960e
4:name
31:debian-10.2.0-amd64-netinst.iso
12:piece length
i262144e
6:pieces
26800:<3A><1F><0F><><EFBFBD>PS<50>^<5E><> (binary blob of the hashes of each piece)
e
e
</code></pre><p class="sc-bdvvtL kiIUjN">In this file, we can spot the URL of the tracker, the creation date (as a Unix timestamp), the name and size of the file, and a big binary blob containing the SHA-1 hashes of each <strong>piece</strong>, which are equally-sized parts of the file we want to download. The exact size of a piece varies between torrents, but they are usually somewhere between 256KB and 1MB. This means that a large file might be made up of <em>thousands</em> of pieces. We&#x27;ll download these pieces from our peers, check them against the hashes from our torrent file, assemble them together, and boom, we&#x27;ve got a file!</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/pieces.png" alt="&quot;illustration of a file being cut with scissors into multiple pieces, starting with piece 0" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">This mechanism allows us to verify the integrity of each piece as we go. It makes BitTorrent resistant to accidental corruption or intentional <strong>torrent poisoning</strong>. Unless an attacker is capable of breaking SHA-1 with a preimage attack, we will get exactly the content we asked for.</p><p class="sc-bdvvtL kiIUjN">It would be really fun to write a bencode parser, but parsing isn&#x27;t our focus today. But I found Fredrik Lundh&#x27;s <a href="https://effbot.org/zone/bencode.htm" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">50 line parser</a> to be especially illuminating. For this project, I used <a href="https://github.com/jackpal/bencode-go" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">github.com/jackpal/bencode-go</a>:</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token string">&quot;github.com/jackpal/bencode-go&quot;</span>
<span class="token punctuation">)</span>
<span class="token keyword">type</span> bencodeInfo <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Pieces <span class="token builtin">string</span> <span class="token string">`bencode:&quot;pieces&quot;`</span>
PieceLength <span class="token builtin">int</span> <span class="token string">`bencode:&quot;piece length&quot;`</span>
Length <span class="token builtin">int</span> <span class="token string">`bencode:&quot;length&quot;`</span>
Name <span class="token builtin">string</span> <span class="token string">`bencode:&quot;name&quot;`</span>
<span class="token punctuation">}</span>
<span class="token keyword">type</span> bencodeTorrent <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Announce <span class="token builtin">string</span> <span class="token string">`bencode:&quot;announce&quot;`</span>
Info bencodeInfo <span class="token string">`bencode:&quot;info&quot;`</span>
<span class="token punctuation">}</span>
<span class="token comment">// Open parses a torrent file</span>
<span class="token keyword">func</span> <span class="token function">Open</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>bencodeTorrent<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
bto <span class="token operator">:=</span> bencodeTorrent<span class="token punctuation">{</span><span class="token punctuation">}</span>
err <span class="token operator">:=</span> bencode<span class="token punctuation">.</span><span class="token function">Unmarshal</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> <span class="token operator">&amp;</span>bto<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token operator">&amp;</span>bto<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><p class="sc-bdvvtL kiIUjN">Because I like to keep my structures relatively flat, and I like to keep my application structs separate from my serialization structs, I exported a different, flatter struct named <code>TorrentFile</code> and wrote a few helper functions to convert between the two.</p><p class="sc-bdvvtL kiIUjN">Notably, I split <code>pieces</code> (previously a string) into a slice of hashes (each <code>[20]byte</code>) so that I can easily access individual hashes later. I also computed the SHA-1 hash of the entire bencoded <code>info</code> dict (the one which contained the name, size, and piece hashes). We know this as the <strong>infohash</strong> and it uniquely identifies files when we talk to trackers and peers. More on this later.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/info-hash.png" alt="a name tag saying &#x27;Hello my name is 86d4c80024a469be4c50bc5a102cf71780310074&#x27;" class="sc-iqseJM bIxQHp"/></p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> TorrentFile <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Announce <span class="token builtin">string</span>
InfoHash <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
PieceHashes <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
PieceLength <span class="token builtin">int</span>
Length <span class="token builtin">int</span>
Name <span class="token builtin">string</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>bto <span class="token operator">*</span>bencodeTorrent<span class="token punctuation">)</span> <span class="token function">toTorrentFile</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>TorrentFile<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Retrieving peers from the tracker</h3><p class="sc-bdvvtL kiIUjN">Now that we have information about the file and its tracker, let&#x27;s talk to the tracker to <strong>announce</strong> our presence as a peer and to retrieve a list of other peers. We just need to make a GET request to the <code>announce</code> URL supplied in the .torrent file, with a few query parameters:</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>TorrentFile<span class="token punctuation">)</span> <span class="token function">buildTrackerURL</span><span class="token punctuation">(</span>peerID <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> port <span class="token builtin">uint16</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
base<span class="token punctuation">,</span> err <span class="token operator">:=</span> url<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>Announce<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
params <span class="token operator">:=</span> url<span class="token punctuation">.</span>Values<span class="token punctuation">{</span>
<span class="token string">&quot;info_hash&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token function">string</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>InfoHash<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;peer_id&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token function">string</span><span class="token punctuation">(</span>peerID<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;port&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span><span class="token function">int</span><span class="token punctuation">(</span>Port<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;uploaded&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">&quot;0&quot;</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;downloaded&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">&quot;0&quot;</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;compact&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">&quot;1&quot;</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token string">&quot;left&quot;</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span>strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
base<span class="token punctuation">.</span>RawQuery <span class="token operator">=</span> params<span class="token punctuation">.</span><span class="token function">Encode</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> base<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><p class="sc-bdvvtL kiIUjN">The important ones:</p><ul class="sc-egiyK gfrORe"><li class="sc-bqiRlB xTMXv"><strong>info_hash</strong>: Identifies the <em>file</em> we&#x27;re trying to download. It&#x27;s the infohash we calculated earlier from the bencoded <code>info</code> dict. The tracker will use this to figure out which peers to show us.</li><li class="sc-bqiRlB xTMXv"><strong>peer_id</strong>: A 20 byte name to identify <em>ourselves</em> to trackers and peers. We&#x27;ll just generate 20 random bytes for this. Real BitTorrent clients have IDs like <code>-TR2940-k8hj0wgej6ch</code> which identify the client software and version—in this case, TR2940 stands for Transmission client 2.94.</li></ul><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/info-hash-peer-id.png" alt="a file with a name tag saying &#x27;info_hash&#x27; and a person with a name tag &#x27;peer_id&#x27;" class="sc-iqseJM bIxQHp"/></p><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Parsing the tracker response</h3><p class="sc-bdvvtL kiIUjN">We get back a bencoded response:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="chakra-code language-markdown css-4m8w8z">d
8:interval
i900e
5:peers
252:(another long binary blob)
e
</code></pre><p class="sc-bdvvtL kiIUjN"><code>Interval</code> tells us how often we&#x27;re supposed to connect to the tracker again to refresh our list of peers. A value of 900 means we should reconnect every 15 minutes (900 seconds).</p><p class="sc-bdvvtL kiIUjN"><code>Peers</code> is another long binary blob containing the IP addresses of each peer. It&#x27;s made out of <strong>groups of six bytes</strong>. The first four bytes in each group represent the peer&#x27;s IP address—each byte represents a number in the IP. The last two bytes represent the port, as a big-endian <code>uint16</code>. <strong>Big-endian</strong>, or <strong>network order</strong>, means that we can interpret a group of bytes as an integer by just squishing them together left to right. For example, the bytes <code>0x1A</code>, <code>0xE1</code> make <code>0x1AE1</code>, or 6881 in decimal.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/address.png" alt="diagram showing how 192, 0, 2, 123, 0x1A, 0xE1 can be interpreted as 192.0.1.123:6881" class="sc-iqseJM bIxQHp"/></p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Peer encodes connection information for a peer</span>
<span class="token keyword">type</span> Peer <span class="token keyword">struct</span> <span class="token punctuation">{</span>
IP net<span class="token punctuation">.</span>IP
Port <span class="token builtin">uint16</span>
<span class="token punctuation">}</span>
<span class="token comment">// Unmarshal parses peer IP addresses and ports from a buffer</span>
<span class="token keyword">func</span> <span class="token function">Unmarshal</span><span class="token punctuation">(</span>peersBin <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>Peer<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> peerSize <span class="token operator">=</span> <span class="token number">6</span> <span class="token comment">// 4 for IP, 2 for port</span>
numPeers <span class="token operator">:=</span> <span class="token function">len</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">)</span> <span class="token operator">/</span> peerSize
<span class="token keyword">if</span> <span class="token function">len</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">)</span><span class="token operator">%</span>peerSize <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span>
err <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Errorf</span><span class="token punctuation">(</span><span class="token string">&quot;Received malformed peers&quot;</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
peers <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span>Peer<span class="token punctuation">,</span> numPeers<span class="token punctuation">)</span>
<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> numPeers<span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
offset <span class="token operator">:=</span> i <span class="token operator">*</span> peerSize
peers<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>IP <span class="token operator">=</span> net<span class="token punctuation">.</span><span class="token function">IP</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">[</span>offset <span class="token punctuation">:</span> offset<span class="token operator">+</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
peers<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>Port <span class="token operator">=</span> binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">Uint16</span><span class="token punctuation">(</span>peersBin<span class="token punctuation">[</span>offset<span class="token operator">+</span><span class="token number">4</span> <span class="token punctuation">:</span> offset<span class="token operator">+</span><span class="token number">6</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> peers<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Downloading from peers</h2><p class="sc-bdvvtL kiIUjN">Now that we have a list of peers, it&#x27;s time to connect with them and start downloading pieces! We can break down the process into a few steps. For each peer, we want to:</p><ol><li class="sc-bqiRlB xTMXv">Start a TCP connection with the peer. This is like starting a phone call.</li><li class="sc-bqiRlB xTMXv">Complete a two-way BitTorrent <strong>handshake</strong>. <em>&quot;Hello?&quot; &quot;Hello.&quot;</em></li><li class="sc-bqiRlB xTMXv">Exchange <strong>messages</strong> to download <strong>pieces</strong>. <em>&quot;I&#x27;d like piece #231 please.&quot;</em></li></ol><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">Start a TCP connection</h2><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z">conn<span class="token punctuation">,</span> err <span class="token operator">:=</span> net<span class="token punctuation">.</span><span class="token function">DialTimeout</span><span class="token punctuation">(</span><span class="token string">&quot;tcp&quot;</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span><span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token operator">*</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
</code></pre><p class="sc-bdvvtL kiIUjN">I set a timeout so that I don&#x27;t waste too much time on peers that aren&#x27;t going to let me connect. For the most part, it&#x27;s a pretty standard TCP connection.</p><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Complete the handshake</h3><p class="sc-bdvvtL kiIUjN">We&#x27;ve just set up a connection with a peer, but we want do a handshake to validate our assumptions that the peer</p><ul class="sc-egiyK gfrORe"><li class="sc-bqiRlB xTMXv">can communicate using the BitTorrent protocol</li><li class="sc-bqiRlB xTMXv">is able to understand and respond to our messages</li><li class="sc-bqiRlB xTMXv">has the file that we want, or at least knows what we&#x27;re talking about</li></ul><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/handshake.png" alt="Two computers communicating. One asks &#x27;do you speak BitTorrent and have this file?&#x27; and the other replies &#x27;I speak BitTorrent and have that file&#x27;" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">My father told me that the secret to a good handshake is a firm grip and eye contact. The secret to a good BitTorrent handshake is that it&#x27;s made up of five parts:</p><ol><li class="sc-bqiRlB xTMXv">The length of the protocol identifier, which is always 19 (0x13 in hex)</li><li class="sc-bqiRlB xTMXv">The protocol identifier, called the <strong>pstr</strong> which is always <code>BitTorrent protocol</code></li><li class="sc-bqiRlB xTMXv">Eight <strong>reserved bytes</strong>, all set to 0. We&#x27;d flip some of them to 1 to indicate that we support certain <a href="http://www.bittorrent.org/beps/bep_0010.html" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">extensions</a>. But we don&#x27;t, so we&#x27;ll keep them at 0.</li><li class="sc-bqiRlB xTMXv">The <strong>infohash</strong> that we calculated earlier to identify which file we want</li><li class="sc-bqiRlB xTMXv">The <strong>Peer ID</strong> that we made up to identify ourselves</li></ol><p class="sc-bdvvtL kiIUjN">Put together, a handshake string might look like this:</p><pre class="sc-furwcr jiGIaj language-markdown"><code class="chakra-code language-markdown css-4m8w8z">\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00\x86\xd4\xc8\x00\x24\xa4\x69\xbe\x4c\x50\xbc\x5a\x10\x2c\xf7\x17\x80\x31\x00\x74-TR2940-k8hj0wgej6ch
</code></pre><p class="sc-bdvvtL kiIUjN">After we send a handshake to our peer, we should receive a handshake back in the same format. The infohash we get back should match the one we sent so that we know that we&#x27;re talking about the same file. If everything goes as planned, we&#x27;re good to go. If not, we can sever the connection because there&#x27;s something wrong. <em>&quot;Hello?&quot; &quot;这是谁? 你想要什么?&quot; &quot;Okay, wow, wrong number.&quot;</em></p><p class="sc-bdvvtL kiIUjN">In our code, let&#x27;s make a struct to represent a handshake, and write a few methods for serializing and reading them:</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// A Handshake is a special message that a peer uses to identify itself</span>
<span class="token keyword">type</span> Handshake <span class="token keyword">struct</span> <span class="token punctuation">{</span>
Pstr <span class="token builtin">string</span>
InfoHash <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
PeerID <span class="token punctuation">[</span><span class="token number">20</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
<span class="token punctuation">}</span>
<span class="token comment">// Serialize serializes the handshake to a buffer</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>Handshake<span class="token punctuation">)</span> <span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span> <span class="token punctuation">{</span>
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">49</span><span class="token punctuation">)</span>
buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">byte</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span><span class="token punctuation">)</span>
curr <span class="token operator">:=</span> <span class="token number">1</span>
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>Pstr<span class="token punctuation">)</span>
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 8 reserved bytes</span>
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>InfoHash<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
curr <span class="token operator">+=</span> <span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>curr<span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>PeerID<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> buf
<span class="token punctuation">}</span>
<span class="token comment">// Read parses a handshake from a stream</span>
<span class="token keyword">func</span> <span class="token function">Read</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>Handshake<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// Do Serialize(), but backwards</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Send and receive messages</h3><p class="sc-bdvvtL kiIUjN">Once we&#x27;ve completed the initial handshake, we can send and receive <strong>messages</strong>. Well, not quite—if the other peer isn&#x27;t ready to accept messages, we can&#x27;t send any until they tell us they&#x27;re ready. In this state, we&#x27;re considered <strong>choked</strong> by the other peer. They&#x27;ll send us an <strong>unchoke</strong> message to let us know that we can begin asking them for data. By default, we assume that we&#x27;re choked until proven otherwise.</p><p class="sc-bdvvtL kiIUjN">Once we&#x27;ve been unchoked, we can then begin sending <strong>requests</strong> for pieces, and they can send us messages back containing pieces.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/choke.png" alt="&quot;A cartoon in which person 1 says &#x27;hello I would like piece number—&#x27; and person 2 grabs him by the neck and says &#x27;00 00 00 01 00 (choke)&#x27;" class="sc-iqseJM bIxQHp"/></p><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Interpreting messages</h4><p class="sc-bdvvtL kiIUjN">A message has a length, an <strong>ID</strong> and a <strong>payload</strong>. On the wire, it looks like:</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/message.png" alt="A message with 4 byte for the length, 1 byte for ID, and an optional payload" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">A message starts with a length indicator which tells us how many bytes long the message will be. It&#x27;s a 32-bit integer, meaning it&#x27;s made out of four bytes smooshed together in big-endian order. The next byte, the <strong>ID</strong>, tells us which type of message we&#x27;re receiving—for example, a <code>2</code> byte means &quot;interested.&quot; Finally, the optional <strong>payload</strong> fills out the remaining length of the message.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> messageID <span class="token builtin">uint8</span>
<span class="token keyword">const</span> <span class="token punctuation">(</span>
MsgChoke messageID <span class="token operator">=</span> <span class="token number">0</span>
MsgUnchoke messageID <span class="token operator">=</span> <span class="token number">1</span>
MsgInterested messageID <span class="token operator">=</span> <span class="token number">2</span>
MsgNotInterested messageID <span class="token operator">=</span> <span class="token number">3</span>
MsgHave messageID <span class="token operator">=</span> <span class="token number">4</span>
MsgBitfield messageID <span class="token operator">=</span> <span class="token number">5</span>
MsgRequest messageID <span class="token operator">=</span> <span class="token number">6</span>
MsgPiece messageID <span class="token operator">=</span> <span class="token number">7</span>
MsgCancel messageID <span class="token operator">=</span> <span class="token number">8</span>
<span class="token punctuation">)</span>
<span class="token comment">// Message stores ID and payload of a message</span>
<span class="token keyword">type</span> Message <span class="token keyword">struct</span> <span class="token punctuation">{</span>
ID messageID
Payload <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
<span class="token punctuation">}</span>
<span class="token comment">// Serialize serializes a message into a buffer of the form</span>
<span class="token comment">// &lt;length prefix&gt;&lt;message ID&gt;&lt;payload&gt;</span>
<span class="token comment">// Interprets `nil` as a keep-alive message</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>Message<span class="token punctuation">)</span> <span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> m <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
length <span class="token operator">:=</span> <span class="token function">uint32</span><span class="token punctuation">(</span><span class="token function">len</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// +1 for id</span>
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token operator">+</span>length<span class="token punctuation">)</span>
binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">PutUint32</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span>
buf<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">byte</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span>ID<span class="token punctuation">)</span>
<span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span> m<span class="token punctuation">.</span>Payload<span class="token punctuation">)</span>
<span class="token keyword">return</span> buf
<span class="token punctuation">}</span>
</code></pre><p class="sc-bdvvtL kiIUjN">To read a message from a stream, we just follow the format of a message. We read four bytes and interpret them as a <code>uint32</code> to get the <strong>length</strong> of the message. Then, we read that number of bytes to get the <strong>ID</strong> (the first byte) and the <strong>payload</strong> (the remaining bytes).</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Read parses a message from a stream. Returns `nil` on keep-alive message</span>
<span class="token keyword">func</span> <span class="token function">Read</span><span class="token punctuation">(</span>r io<span class="token punctuation">.</span>Reader<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token operator">*</span>Message<span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
lengthBuf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span>
<span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">:=</span> io<span class="token punctuation">.</span><span class="token function">ReadFull</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> lengthBuf<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
length <span class="token operator">:=</span> binary<span class="token punctuation">.</span>BigEndian<span class="token punctuation">.</span><span class="token function">Uint32</span><span class="token punctuation">(</span>lengthBuf<span class="token punctuation">)</span>
<span class="token comment">// keep-alive message</span>
<span class="token keyword">if</span> length <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
messageBuf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> length<span class="token punctuation">)</span>
<span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> io<span class="token punctuation">.</span><span class="token function">ReadFull</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> messageBuf<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
m <span class="token operator">:=</span> Message<span class="token punctuation">{</span>
ID<span class="token punctuation">:</span> <span class="token function">messageID</span><span class="token punctuation">(</span>messageBuf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
Payload<span class="token punctuation">:</span> messageBuf<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token operator">&amp;</span>m<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Bitfields</h4><p class="sc-bdvvtL kiIUjN">One of the most interesting types of message is the <strong>bitfield</strong>, which is a data structure that peers use to efficiently encode which pieces they are able to send us. A bitfield looks like a byte array, and to check which pieces they have, we just need to look at the positions of the <em>bits</em> set to 1. You can think of it like the digital equivalent of a coffee shop loyalty card. We start with a blank card of all <code>0</code>, and flip bits to <code>1</code> to mark their positions as &quot;stamped.&quot;</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/bitfield.png" alt="a coffee shop loyalty card with eight slots, with stamps on the first four slots and a stamp on the second to last slot, represented as 11110010" class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">By working with <em>bits</em> instead of <em>bytes</em>, this data structure is super compact. We can stuff information about eight pieces in the space of a single byte—the size of a <code>bool</code>. The tradeoff is that accessing values becomes a little more tricky. The smallest unit of memory that computers can address are bytes, so to get to our bits, we have to do some bitwise manipulation:</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// A Bitfield represents the pieces that a peer has</span>
<span class="token keyword">type</span> Bitfield <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
<span class="token comment">// HasPiece tells if a bitfield has a particular index set</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>bf Bitfield<span class="token punctuation">)</span> <span class="token function">HasPiece</span><span class="token punctuation">(</span>index <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span>
byteIndex <span class="token operator">:=</span> index <span class="token operator">/</span> <span class="token number">8</span>
offset <span class="token operator">:=</span> index <span class="token operator">%</span> <span class="token number">8</span>
<span class="token keyword">return</span> bf<span class="token punctuation">[</span>byteIndex<span class="token punctuation">]</span><span class="token operator">&gt;&gt;</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token operator">-</span>offset<span class="token punctuation">)</span><span class="token operator">&amp;</span><span class="token number">1</span> <span class="token operator">!=</span> <span class="token number">0</span>
<span class="token punctuation">}</span>
<span class="token comment">// SetPiece sets a bit in the bitfield</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>bf Bitfield<span class="token punctuation">)</span> <span class="token function">SetPiece</span><span class="token punctuation">(</span>index <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
byteIndex <span class="token operator">:=</span> index <span class="token operator">/</span> <span class="token number">8</span>
offset <span class="token operator">:=</span> index <span class="token operator">%</span> <span class="token number">8</span>
bf<span class="token punctuation">[</span>byteIndex<span class="token punctuation">]</span> <span class="token operator">|=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token punctuation">(</span><span class="token number">7</span> <span class="token operator">-</span> offset<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre><h3 class="sc-dkPtRN sc-eCImPb dKUOdJ cabxUq">Putting it all together</h3><p class="sc-bdvvtL kiIUjN">We now have all the tools we need to download a torrent: we have a list of peers obtained from the tracker, and we can communicate with them by dialing a TCP connection, initiating a handshake, and sending and receiving messages. Our last big problems are handling the <strong>concurrency</strong> involved in talking to multiple peers at once, and managing the <strong>state</strong> of our peers as we interact with them. These are both classically Hard problems.</p><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing concurrency: channels as queues</h4><p class="sc-bdvvtL kiIUjN">In Go, we <a href="https://blog.golang.org/share-memory-by-communicating" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">share memory by communicating</a>, and we can think of a Go channel as a cheap thread-safe queue.</p><p class="sc-bdvvtL kiIUjN">We&#x27;ll set up two channels to synchronize our concurrent workers: one for dishing out work (pieces to download) between peers, and another for collecting downloaded pieces. As downloaded pieces come in through the results channel, we can copy them into a buffer to start assembling our complete file.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// Init queues for workers to retrieve work and send results</span>
workQueue <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token operator">*</span>pieceWork<span class="token punctuation">,</span> <span class="token function">len</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>PieceHashes<span class="token punctuation">)</span><span class="token punctuation">)</span>
results <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token operator">*</span>pieceResult<span class="token punctuation">)</span>
<span class="token keyword">for</span> index<span class="token punctuation">,</span> hash <span class="token operator">:=</span> <span class="token keyword">range</span> t<span class="token punctuation">.</span>PieceHashes <span class="token punctuation">{</span>
length <span class="token operator">:=</span> t<span class="token punctuation">.</span><span class="token function">calculatePieceSize</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span>
workQueue <span class="token operator">&lt;-</span> <span class="token operator">&amp;</span>pieceWork<span class="token punctuation">{</span>index<span class="token punctuation">,</span> hash<span class="token punctuation">,</span> length<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// Start workers</span>
<span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> peer <span class="token operator">:=</span> <span class="token keyword">range</span> t<span class="token punctuation">.</span>Peers <span class="token punctuation">{</span>
<span class="token keyword">go</span> t<span class="token punctuation">.</span><span class="token function">startDownloadWorker</span><span class="token punctuation">(</span>peer<span class="token punctuation">,</span> workQueue<span class="token punctuation">,</span> results<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// Collect results into a buffer until full</span>
buf <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>Length<span class="token punctuation">)</span>
donePieces <span class="token operator">:=</span> <span class="token number">0</span>
<span class="token keyword">for</span> donePieces <span class="token operator">&lt;</span> <span class="token function">len</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>PieceHashes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
res <span class="token operator">:=</span> <span class="token operator">&lt;-</span>results
begin<span class="token punctuation">,</span> end <span class="token operator">:=</span> t<span class="token punctuation">.</span><span class="token function">calculateBoundsForPiece</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
<span class="token function">copy</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span>begin<span class="token punctuation">:</span>end<span class="token punctuation">]</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>buf<span class="token punctuation">)</span>
donePieces<span class="token operator">++</span>
<span class="token punctuation">}</span>
<span class="token function">close</span><span class="token punctuation">(</span>workQueue<span class="token punctuation">)</span>
</code></pre><p class="sc-bdvvtL kiIUjN">We&#x27;ll spawn a worker goroutine for each peer we&#x27;ve received from the tracker. It&#x27;ll connect and handshake with the peer, and then start retrieving work from the <code>workQueue</code>, attempting to download it, and sending downloaded pieces back through the <code>results</code> channel.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/download.png" alt="a flow chart of the download strategy" class="sc-iqseJM bIxQHp"/></p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">func</span> <span class="token punctuation">(</span>t <span class="token operator">*</span>Torrent<span class="token punctuation">)</span> <span class="token function">startDownloadWorker</span><span class="token punctuation">(</span>peer peers<span class="token punctuation">.</span>Peer<span class="token punctuation">,</span> workQueue <span class="token keyword">chan</span> <span class="token operator">*</span>pieceWork<span class="token punctuation">,</span> results <span class="token keyword">chan</span> <span class="token operator">*</span>pieceResult<span class="token punctuation">)</span> <span class="token punctuation">{</span>
c<span class="token punctuation">,</span> err <span class="token operator">:=</span> client<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span>peer<span class="token punctuation">,</span> t<span class="token punctuation">.</span>PeerID<span class="token punctuation">,</span> t<span class="token punctuation">.</span>InfoHash<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;Could not handshake with %s. Disconnecting\n&quot;</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span>IP<span class="token punctuation">)</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
<span class="token keyword">defer</span> c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;Completed handshake with %s\n&quot;</span><span class="token punctuation">,</span> peer<span class="token punctuation">.</span>IP<span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">SendUnchoke</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
c<span class="token punctuation">.</span><span class="token function">SendInterested</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">for</span> pw <span class="token operator">:=</span> <span class="token keyword">range</span> workQueue <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>Bitfield<span class="token punctuation">.</span><span class="token function">HasPiece</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span> <span class="token punctuation">{</span>
workQueue <span class="token operator">&lt;-</span> pw <span class="token comment">// Put piece back on the queue</span>
<span class="token keyword">continue</span>
<span class="token punctuation">}</span>
<span class="token comment">// Download the piece</span>
buf<span class="token punctuation">,</span> err <span class="token operator">:=</span> <span class="token function">attemptDownloadPiece</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> pw<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;Exiting&quot;</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span>
workQueue <span class="token operator">&lt;-</span> pw <span class="token comment">// Put piece back on the queue</span>
<span class="token keyword">return</span>
<span class="token punctuation">}</span>
err <span class="token operator">=</span> <span class="token function">checkIntegrity</span><span class="token punctuation">(</span>pw<span class="token punctuation">,</span> buf<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;Piece #%d failed integrity check\n&quot;</span><span class="token punctuation">,</span> pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
workQueue <span class="token operator">&lt;-</span> pw <span class="token comment">// Put piece back on the queue</span>
<span class="token keyword">continue</span>
<span class="token punctuation">}</span>
c<span class="token punctuation">.</span><span class="token function">SendHave</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">)</span>
results <span class="token operator">&lt;-</span> <span class="token operator">&amp;</span>pieceResult<span class="token punctuation">{</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span> buf<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Managing state</h4><p class="sc-bdvvtL kiIUjN">We&#x27;ll keep track of each peer in a struct, and modify that struct as we read messages. It&#x27;ll include data like how much we&#x27;ve downloaded from the peer, how much we&#x27;ve requested from them, and whether we&#x27;re choked. If we wanted to scale this further, we could formalize this as a finite state machine. But a struct and a switch are good enough for now.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">type</span> pieceProgress <span class="token keyword">struct</span> <span class="token punctuation">{</span>
index <span class="token builtin">int</span>
client <span class="token operator">*</span>client<span class="token punctuation">.</span>Client
buf <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span>
downloaded <span class="token builtin">int</span>
requested <span class="token builtin">int</span>
backlog <span class="token builtin">int</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token punctuation">(</span>state <span class="token operator">*</span>pieceProgress<span class="token punctuation">)</span> <span class="token function">readMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">error</span> <span class="token punctuation">{</span>
msg<span class="token punctuation">,</span> err <span class="token operator">:=</span> state<span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">Read</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// this call blocks</span>
<span class="token keyword">switch</span> msg<span class="token punctuation">.</span>ID <span class="token punctuation">{</span>
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgUnchoke<span class="token punctuation">:</span>
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgChoke<span class="token punctuation">:</span>
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgHave<span class="token punctuation">:</span>
index<span class="token punctuation">,</span> err <span class="token operator">:=</span> message<span class="token punctuation">.</span><span class="token function">ParseHave</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span>
state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Bitfield<span class="token punctuation">.</span><span class="token function">SetPiece</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span>
<span class="token keyword">case</span> message<span class="token punctuation">.</span>MsgPiece<span class="token punctuation">:</span>
n<span class="token punctuation">,</span> err <span class="token operator">:=</span> message<span class="token punctuation">.</span><span class="token function">ParsePiece</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>index<span class="token punctuation">,</span> state<span class="token punctuation">.</span>buf<span class="token punctuation">,</span> msg<span class="token punctuation">)</span>
state<span class="token punctuation">.</span>downloaded <span class="token operator">+=</span> n
state<span class="token punctuation">.</span>backlog<span class="token operator">--</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Time to make requests!</h4><p class="sc-bdvvtL kiIUjN">Files, pieces, and piece hashes aren&#x27;t the full story—we can go further by breaking down pieces into <strong>blocks</strong>. A block is a part of a piece, and we can fully define a block by the <strong>index</strong> of the piece it&#x27;s part of, its byte <strong>offset</strong> within the piece, and its <strong>length</strong>. When we make requests for data from peers, we are actually requesting <em>blocks</em>. A block is usually 16KB large, meaning that a single 256 KB piece might actually require 16 requests.</p><p class="sc-bdvvtL kiIUjN">A peer is supposed to sever the connection if they receive a request for a block larger than 16KB. However, based on my experience, they&#x27;re often perfectly happy to satisfy requests up to 128KB. I only got moderate gains in overall speed with larger block sizes, so it&#x27;s probably better to stick with the spec.</p><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">Pipelining</h4><p class="sc-bdvvtL kiIUjN">Network round-trips are expensive, and requesting each block one by one will absolutely tank the performance of our download. Therefore, it&#x27;s important to <strong>pipeline</strong> our requests such that we keep up a constant pressure of some number of unfulfilled requests. This can increase the throughput of our connection by an order of magnitude.</p><p class="sc-bdvvtL kiIUjN"><img src="/guides/torrent-client/pipelining.png" alt="Two email threads simulating peer connections. The thread on the left shows a request followed by a reply, repeated three times. The thread on the left sends three requests, and receives three replies in quick succession." class="sc-iqseJM bIxQHp"/></p><p class="sc-bdvvtL kiIUjN">Classically, BitTorrent clients kept a queue of five pipelined requests, and that&#x27;s the value I&#x27;ll be using. I found that increasing it can up to double the speed of a download. Newer clients use an <a href="https://luminarys.com/posts/writing-a-bittorrent-client.html" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">adaptive</a> queue size to better accommodate modern network speeds and conditions. This is definitely a parameter worth tweaking, and it&#x27;s pretty low hanging fruit for future performance optimization.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token comment">// MaxBlockSize is the largest number of bytes a request can ask for</span>
<span class="token keyword">const</span> MaxBlockSize <span class="token operator">=</span> <span class="token number">16384</span>
<span class="token comment">// MaxBacklog is the number of unfulfilled requests a client can have in its pipeline</span>
<span class="token keyword">const</span> MaxBacklog <span class="token operator">=</span> <span class="token number">5</span>
<span class="token keyword">func</span> <span class="token function">attemptDownloadPiece</span><span class="token punctuation">(</span>c <span class="token operator">*</span>client<span class="token punctuation">.</span>Client<span class="token punctuation">,</span> pw <span class="token operator">*</span>pieceWork<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
state <span class="token operator">:=</span> pieceProgress<span class="token punctuation">{</span>
index<span class="token punctuation">:</span> pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span>
client<span class="token punctuation">:</span> c<span class="token punctuation">,</span>
buf<span class="token punctuation">:</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span><span class="token punctuation">,</span> pw<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token comment">// Setting a deadline helps get unresponsive peers unstuck.</span>
<span class="token comment">// 30 seconds is more than enough time to download a 262 KB piece</span>
c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">SetDeadline</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">30</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">defer</span> c<span class="token punctuation">.</span>Conn<span class="token punctuation">.</span><span class="token function">SetDeadline</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Time<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// Disable the deadline</span>
<span class="token keyword">for</span> state<span class="token punctuation">.</span>downloaded <span class="token operator">&lt;</span> pw<span class="token punctuation">.</span>length <span class="token punctuation">{</span>
<span class="token comment">// If unchoked, send requests until we have enough unfulfilled requests</span>
<span class="token keyword">if</span> <span class="token operator">!</span>state<span class="token punctuation">.</span>client<span class="token punctuation">.</span>Choked <span class="token punctuation">{</span>
<span class="token keyword">for</span> state<span class="token punctuation">.</span>backlog <span class="token operator">&lt;</span> MaxBacklog <span class="token operator">&amp;&amp;</span> state<span class="token punctuation">.</span>requested <span class="token operator">&lt;</span> pw<span class="token punctuation">.</span>length <span class="token punctuation">{</span>
blockSize <span class="token operator">:=</span> MaxBlockSize
<span class="token comment">// Last block might be shorter than the typical block</span>
<span class="token keyword">if</span> pw<span class="token punctuation">.</span>length<span class="token operator">-</span>state<span class="token punctuation">.</span>requested <span class="token operator">&lt;</span> blockSize <span class="token punctuation">{</span>
blockSize <span class="token operator">=</span> pw<span class="token punctuation">.</span>length <span class="token operator">-</span> state<span class="token punctuation">.</span>requested
<span class="token punctuation">}</span>
err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">SendRequest</span><span class="token punctuation">(</span>pw<span class="token punctuation">.</span>index<span class="token punctuation">,</span> state<span class="token punctuation">.</span>requested<span class="token punctuation">,</span> blockSize<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
state<span class="token punctuation">.</span>backlog<span class="token operator">++</span>
state<span class="token punctuation">.</span>requested <span class="token operator">+=</span> blockSize
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
err <span class="token operator">:=</span> state<span class="token punctuation">.</span><span class="token function">readMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> err
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> state<span class="token punctuation">.</span>buf<span class="token punctuation">,</span> <span class="token boolean">nil</span>
<span class="token punctuation">}</span>
</code></pre><h4 class="sc-dkPtRN sc-jRQBWg dKUOdJ gRXaYU">main.go</h4><p class="sc-bdvvtL kiIUjN">This is a short one. We&#x27;re almost there.</p><pre class="sc-furwcr jiGIaj language-go"><code class="chakra-code language-go css-4m8w8z"><span class="token keyword">package</span> main
<span class="token keyword">import</span> <span class="token punctuation">(</span>
<span class="token string">&quot;log&quot;</span>
<span class="token string">&quot;os&quot;</span>
<span class="token string">&quot;github.com/veggiedefender/torrent-client/torrentfile&quot;</span>
<span class="token punctuation">)</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
inPath <span class="token operator">:=</span> os<span class="token punctuation">.</span>Args<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
outPath <span class="token operator">:=</span> os<span class="token punctuation">.</span>Args<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>
tf<span class="token punctuation">,</span> err <span class="token operator">:=</span> torrentfile<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>inPath<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
err <span class="token operator">=</span> tf<span class="token punctuation">.</span><span class="token function">DownloadToFile</span><span class="token punctuation">(</span>outPath<span class="token punctuation">)</span>
<span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
log<span class="token punctuation">.</span><span class="token function">Fatal</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
2022-06-28 11:05:04 +00:00
</code></pre><script id="asciicast-xqRSB0Jec8RN91Zt89rbb9PcL" src="https://asciinema.org/a/xqRSB0Jec8RN91Zt89rbb9PcL.js" async=""></script><h2 class="sc-dkPtRN sc-hKwDye dKUOdJ kqfgwy">This isn&#x27;t the full story</h2><p class="sc-bdvvtL kiIUjN">For brevity, I included only a few of the important snippets of code. Notably, I left out all the glue code, parsing, unit tests, and the boring parts that build character. View my <a href="https://github.com/veggiedefender/torrent-client" target="_blank" rel="nofollow" class="sc-crHmcD gJMBjK">full implementation</a> if you&#x27;re interested.</p><span></span></div></div><style data-emotion="css zeuzl6">.css-zeuzl6{background:var(--chakra-colors-white);border-top-width:1px;padding-top:45px;padding-bottom:60px;text-align:center;}@media screen and (min-width: 30em){.css-zeuzl6{padding-top:45px;padding-bottom:60px;}}@media screen and (min-width: 48em){.css-zeuzl6{padding-top:70px;padding-bottom:90px;}}</style><div class="css-zeuzl6"><div class="chakra-container css-nm5t63"><style data-emotion="css 482acf">.css-482acf{font-family:var(--chakra-fonts-heading);font-weight:var(--chakra-fontWeights-bold);font-size:25px;line-height:1.33;margin-bottom:10px;}@media screen and (min-width: 30em){.css-482acf{font-size:25px;margin-bottom:10px;}}@media screen and (min-width: 48em){.css-482acf{font-size:35px;line-height:1.2;margin-bottom:20px;}}</style><h2 class="chakra-heading css-482acf">Open Source</h2><style data-emotion="css tmji1h">.css-tmji1h{line-height:26px;font-size:15px;margin-bottom:20px;}@media screen and (min-width: 30em){.css-tmji1h{font-size:15px;}}@media screen and (min-width: 48em){.css-tmji1h{font-size:16px;}}</style><p class="chakra-text css-tmji1h">The project is OpenSource, <style data-emotion="css 1om4i6h">.css-1om4i6h{transition-property:var(--chakra-transition-property-common);transition-duration:var(--chakra-transition-duration-fast);transition-timing-function:var(--chakra-transition-easing-ease-out);cursor:pointer;-webkit-text-decoration:none;text-decoration:none;outline:2px solid transparent;outline-offset:2px;color:inherit;border-bottom-width:1px;font-weight:600;}.css-1om4i6h:hover,.css-1om4i6h[data-hover]{-webkit-text-decoration:none;text-decoration:none;}.css-1om4i6h:focus,.css-1om4i6h[data-focus]{box-shadow:var(--chakra-shadows-outline);}</style><a target="_blank" class="chakra-link css-1om4i6h" href="https://github.com/search?o=desc&amp;q=stars%3A%3E100000&amp;s=stars&amp;type=Repositories">7th most starred project on GitHub</a> and is visited by hundreds of thousands of developers every month.</p><iframe src="https://ghbtns.com/github-btn.html?user=kamranahmedse&amp;repo=developer-roadmap&amp;type=star&amp;count=true&amp;size=large" frameBorder="0" scrolling="0" width="170" height="30" style="margin:auto;margin-bottom:30px" title="GitHub"></iframe><style data-emotion="css mz2q9v">.css-mz2q9v{line-height:25px;font-size:15px;margin-bottom:15px;}@media screen and (min-width: 30em){.css-mz2q9v{line-height:25px;font-size:15px;}}@media screen and (min-width: 48em){.css-mz2q9v{line-height:26px;font-size:16px;}}</style><p class="chakra-text css-mz2q9v">A considerable amount of my time is spent doing unpaid community work on things that I hope will help humanity in some way. Your sponsorship helps me continue to produce more open-source and free educational material consumed by hundreds of thousands of developers every month.</p><div class="css-0"><iframe src="https://ghbtns.com/github-btn.html?user=kamranahmedse&amp;type=sponsor&amp;size=large" frameBorder="0" scrolling="0" width="260" height="30" title="GitHub" style="margin:auto"></iframe></div></div></div><style data-emotion="css llwly4">.css-llwly4{border-top-width:1px;padding-top:40px;padding-bottom:40px;text-align:left;background:var(--chakra-colors-brand-footer);}@media screen and (min-width: 30em){.css-llwly4{padding-top:40px;padding-bottom:45px;}}@media screen and (min-width: 48em){.css-llwly4{padding-top:70px;padding-bottom:80px;}}</style><div class="css-llwly4"><div class="chakra-container css-nm5t63"><sty