Skip to content

Commit e60b1c8

Browse files
authored
Merge pull request #688 from alanmcgovern/improve-bep47-compatibility
Improve compatibility (arguably incorrect) BEP47 torrents
2 parents 9910373 + 99e9a5b commit e60b1c8

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

src/MonoTorrent.Client/MonoTorrent/Torrent.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,26 @@ void ProcessInfo (BEncodedDictionary dictionary, ref PieceHashesV1? hashesV1, re
484484
if (v1File.Length != v2File.Length)
485485
throw new TorrentException ("Inconsistent hybrid torrent, file length mismatch.");
486486

487-
if (v1File.Padding != v2File.Padding)
488-
throw new TorrentException ("Inconsistent hybrid torrent, file padding mismatch.");
487+
if (v1File.Padding != v2File.Padding) {
488+
// BEP47 says padding is there so the *subsequent* file aligns with a piece start boundary.
489+
// By a literal reading, and in line with the rest of the bittorrent spec, the last file
490+
// can and should be considered the 'end' of the torrent (obviously :p) and so does not
491+
// have a subsequent file, and so does not need padding. Similar to how blocks are requested
492+
// in 16kB chunks, except for the final block which is just wahtever bytes are left over.
493+
//
494+
// Requested a clarification on the BEP. However both variants will need to be supported
495+
// regardless of what the spec says because both are in the wild.
496+
// Issue: https://github.com/bittorrent/bittorrent.org/issues/160
497+
//
498+
// If padding is mandatory for the last file, then remove the code which strips it out
499+
// inside 'LoadTorrentFilesV2'.
500+
if (v1File == v1Files.Last () && v2File == v2Files.Last ()) {
501+
var mutableFiles = v2Files.ToList ();
502+
mutableFiles[v2Files.Count - 1] = new TorrentFile (v2File.Path, v2File.Length, v2File.StartPieceIndex, v2File.EndPieceIndex, v2File.OffsetInTorrent, v2File.PiecesRoot, TorrentFileAttributes.None, v1File.Padding);
503+
v2Files = Array.AsReadOnly (mutableFiles.ToArray ());
504+
} else
505+
throw new TorrentException ("Inconsistent hybrid torrent, file padding mismatch.");
506+
}
489507
}
490508

491509
Files = v2Files;

src/Tests/Tests.MonoTorrent.Client/MonoTorrent/TorrentCreatorBep47Tests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,74 @@ public async Task HybridTorrentWithPadding ()
296296
Assert.IsTrue (sha256.AsSpan ().SequenceEqual (torrent.CreatePieceHashes ().GetHash (1).V2Hash.Span));
297297
}
298298

299+
[Test]
300+
public void HybridTorrent_FinalFileHasUnexpectedPadding ([Values(true, false)] bool hasFinalFilePadding)
301+
{
302+
// Test validating both variants of torrent can be loaded
303+
//
304+
// https://github.com/bittorrent/bittorrent.org/issues/160
305+
//
306+
var v1Files = new BEncodedList {
307+
new BEncodedDictionary {
308+
{ "length", (BEncodedNumber)9 },
309+
{ "path", new BEncodedList{ (BEncodedString)"file1.txt" } },
310+
},
311+
new BEncodedDictionary {
312+
{ "attr", (BEncodedString) "p"},
313+
{ "length", (BEncodedNumber)32759 },
314+
{ "path", new BEncodedList{ (BEncodedString)".pad32759" } },
315+
},
316+
317+
new BEncodedDictionary {
318+
{ "length", (BEncodedNumber) 14 },
319+
{ "path", new BEncodedList{ (BEncodedString)"file2.txt" } },
320+
}
321+
};
322+
323+
if (hasFinalFilePadding)
324+
v1Files.Add (new BEncodedDictionary {
325+
{ "attr", (BEncodedString) "p" },
326+
{ "length", (BEncodedNumber)32754 },
327+
{ "path", new BEncodedList{ (BEncodedString)".pad32754" } },
328+
});
329+
330+
var v2Files = new BEncodedDictionary {
331+
{ "file1.txt", new BEncodedDictionary {
332+
{"", new BEncodedDictionary {
333+
{ "length" , (BEncodedNumber) 9 },
334+
{ "pieces root", (BEncodedString) Enumerable.Repeat<byte>(0, 32).ToArray () }
335+
} }
336+
} },
337+
338+
{ "file2.txt", new BEncodedDictionary {
339+
{"", new BEncodedDictionary {
340+
{ "length", (BEncodedNumber) 14 },
341+
{ "pieces root", (BEncodedString) Enumerable.Repeat<byte>(1, 32).ToArray () }
342+
} }
343+
} },
344+
};
345+
346+
var infoDict = new BEncodedDictionary {
347+
{"files", v1Files },
348+
{"file tree", v2Files },
349+
{ "meta version", (BEncodedNumber) 2 },
350+
{ "name", (BEncodedString) "padding test"},
351+
{ "piece length", (BEncodedNumber) 32768},
352+
{ "pieces", (BEncodedString) new byte[40] }
353+
};
354+
355+
var dict = new BEncodedDictionary {
356+
{ "info", infoDict }
357+
};
358+
359+
var torrent = Torrent.Load (dict);
360+
Assert.AreEqual (2, torrent.Files.Count);
361+
Assert.AreEqual (9, torrent.Files[0].Length);
362+
Assert.AreEqual (32768 - 9, torrent.Files[0].Padding);
363+
Assert.AreEqual (14, torrent.Files[1].Length);
364+
Assert.AreEqual (hasFinalFilePadding ? 32768 - 14 : 0, torrent.Files[1].Padding);
365+
}
366+
299367
static BEncodedString SHA1SumZeros (long length)
300368
{
301369
using var hasher = IncrementalHash.CreateHash (HashAlgorithmName.SHA1);

0 commit comments

Comments
 (0)