Skip to content

Commit

Permalink
Merge pull request #688 from alanmcgovern/improve-bep47-compatibility
Browse files Browse the repository at this point in the history
Improve compatibility (arguably incorrect) BEP47 torrents
  • Loading branch information
alanmcgovern authored Aug 28, 2024
2 parents 9910373 + 99e9a5b commit e60b1c8
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
22 changes: 20 additions & 2 deletions src/MonoTorrent.Client/MonoTorrent/Torrent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,26 @@ void ProcessInfo (BEncodedDictionary dictionary, ref PieceHashesV1? hashesV1, re
if (v1File.Length != v2File.Length)
throw new TorrentException ("Inconsistent hybrid torrent, file length mismatch.");

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

Files = v2Files;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,74 @@ public async Task HybridTorrentWithPadding ()
Assert.IsTrue (sha256.AsSpan ().SequenceEqual (torrent.CreatePieceHashes ().GetHash (1).V2Hash.Span));
}

[Test]
public void HybridTorrent_FinalFileHasUnexpectedPadding ([Values(true, false)] bool hasFinalFilePadding)
{
// Test validating both variants of torrent can be loaded
//
// https://github.com/bittorrent/bittorrent.org/issues/160
//
var v1Files = new BEncodedList {
new BEncodedDictionary {
{ "length", (BEncodedNumber)9 },
{ "path", new BEncodedList{ (BEncodedString)"file1.txt" } },
},
new BEncodedDictionary {
{ "attr", (BEncodedString) "p"},
{ "length", (BEncodedNumber)32759 },
{ "path", new BEncodedList{ (BEncodedString)".pad32759" } },
},

new BEncodedDictionary {
{ "length", (BEncodedNumber) 14 },
{ "path", new BEncodedList{ (BEncodedString)"file2.txt" } },
}
};

if (hasFinalFilePadding)
v1Files.Add (new BEncodedDictionary {
{ "attr", (BEncodedString) "p" },
{ "length", (BEncodedNumber)32754 },
{ "path", new BEncodedList{ (BEncodedString)".pad32754" } },
});

var v2Files = new BEncodedDictionary {
{ "file1.txt", new BEncodedDictionary {
{"", new BEncodedDictionary {
{ "length" , (BEncodedNumber) 9 },
{ "pieces root", (BEncodedString) Enumerable.Repeat<byte>(0, 32).ToArray () }
} }
} },

{ "file2.txt", new BEncodedDictionary {
{"", new BEncodedDictionary {
{ "length", (BEncodedNumber) 14 },
{ "pieces root", (BEncodedString) Enumerable.Repeat<byte>(1, 32).ToArray () }
} }
} },
};

var infoDict = new BEncodedDictionary {
{"files", v1Files },
{"file tree", v2Files },
{ "meta version", (BEncodedNumber) 2 },
{ "name", (BEncodedString) "padding test"},
{ "piece length", (BEncodedNumber) 32768},
{ "pieces", (BEncodedString) new byte[40] }
};

var dict = new BEncodedDictionary {
{ "info", infoDict }
};

var torrent = Torrent.Load (dict);
Assert.AreEqual (2, torrent.Files.Count);
Assert.AreEqual (9, torrent.Files[0].Length);
Assert.AreEqual (32768 - 9, torrent.Files[0].Padding);
Assert.AreEqual (14, torrent.Files[1].Length);
Assert.AreEqual (hasFinalFilePadding ? 32768 - 14 : 0, torrent.Files[1].Padding);
}

static BEncodedString SHA1SumZeros (long length)
{
using var hasher = IncrementalHash.CreateHash (HashAlgorithmName.SHA1);
Expand Down

0 comments on commit e60b1c8

Please sign in to comment.