mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-10 01:13:10 +00:00
adds info hash validation
This commit is contained in:
parent
bcf622ecd8
commit
8a8bab3672
18
codex/bittorrent/bencoding.nim
Normal file
18
codex/bittorrent/bencoding.nim
Normal file
@ -0,0 +1,18 @@
|
||||
import std/strformat
|
||||
|
||||
import pkg/stew/byteutils
|
||||
|
||||
func bencode*(value: uint64): seq[byte] =
|
||||
fmt"i{value}e".toBytes
|
||||
|
||||
func bencode*(value: int64): seq[byte] =
|
||||
fmt"i{value}e".toBytes
|
||||
|
||||
func bencode*(value: openArray[byte]): seq[byte] =
|
||||
fmt"{value.len}:".toBytes & @value
|
||||
|
||||
func bencode*(value: string): seq[byte] =
|
||||
bencode(value.toBytes)
|
||||
|
||||
proc bencode*[T: not byte](value: openArray[T]): seq[byte] =
|
||||
fmt"l{value.mapIt(bencode(it).toString).join}e".toBytes
|
||||
@ -2,6 +2,10 @@ import pkg/libp2p
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
|
||||
import ../../errors
|
||||
import ../../codextypes
|
||||
import ../bencoding
|
||||
|
||||
type
|
||||
BitTorrentPiece* = MultiHash
|
||||
BitTorrentInfo* = ref object
|
||||
@ -21,6 +25,19 @@ proc newBitTorrentManifest*(
|
||||
): BitTorrentManifest =
|
||||
BitTorrentManifest(info: info, codexManifestCid: codexManifestCid)
|
||||
|
||||
func bencode*(info: BitTorrentInfo): seq[byte] =
|
||||
# flatten pieces
|
||||
var pieces: seq[byte]
|
||||
for mh in info.pieces:
|
||||
pieces.add(mh.data.buffer.toOpenArray(mh.dpos, mh.dpos + mh.size - 1))
|
||||
result = @['d'.byte]
|
||||
result.add(bencode("length") & bencode(info.length))
|
||||
if name =? info.name:
|
||||
result.add(bencode("name") & bencode(name))
|
||||
result.add(bencode("piece length") & bencode(info.pieceLength))
|
||||
result.add(bencode("pieces") & bencode(pieces))
|
||||
result.add('e'.byte)
|
||||
|
||||
func validate*(self: BitTorrentManifest, cid: Cid): ?!bool =
|
||||
# First stage of validation:
|
||||
# (1) bencode the info dictionary from the torrent manifest
|
||||
@ -32,4 +49,9 @@ func validate*(self: BitTorrentManifest, cid: Cid): ?!bool =
|
||||
# points to genuine content. This validation will be done while fetching blocks
|
||||
# where we will be able to detect that the aggregated pieces do not match
|
||||
# the hashes in the info dictionary from the torrent manifest.
|
||||
return success true
|
||||
let infoBencoded = bencode(self.info)
|
||||
without infoHash =? MultiHash.digest($Sha1HashCodec, infoBencoded).mapFailure, err:
|
||||
return failure(err.msg)
|
||||
without cidInfoHash =? cid.mhash.mapFailure, err:
|
||||
return failure(err.msg)
|
||||
return success(infoHash == cidInfoHash)
|
||||
|
||||
@ -182,7 +182,11 @@ proc fetchTorrentManifest*(
|
||||
|
||||
trace "Decoded torrent manifest", cid
|
||||
|
||||
if err =? torrentManifest.validate(cid).errorOption:
|
||||
without isValid =? torrentManifest.validate(cid), err:
|
||||
trace "Error validating torrent manifest", cid, err = err.msg
|
||||
return failure(err.msg)
|
||||
|
||||
if not isValid:
|
||||
trace "Torrent manifest does not match torrent info hash", cid
|
||||
return failure "Torrent manifest does not match torrent info hash {$cid}"
|
||||
|
||||
|
||||
116
tests/codex/bittorrent/testbencoding.nim
Normal file
116
tests/codex/bittorrent/testbencoding.nim
Normal file
@ -0,0 +1,116 @@
|
||||
import std/unittest
|
||||
import std/strformat
|
||||
import std/sequtils
|
||||
|
||||
import pkg/nimcrypto
|
||||
import pkg/stew/byteutils
|
||||
import pkg/questionable
|
||||
|
||||
import ../../examples
|
||||
import ../../../codex/bittorrent/bencoding
|
||||
|
||||
type ExampleObject* = ref object
|
||||
length*: uint64
|
||||
pieceLength*: uint32
|
||||
pieces*: seq[seq[byte]]
|
||||
name*: ?string
|
||||
|
||||
func bencode(obj: ExampleObject): seq[byte] =
|
||||
# flatten pieces
|
||||
var pieces: seq[byte]
|
||||
for piece in obj.pieces:
|
||||
pieces.add(piece)
|
||||
result = @['d'.byte]
|
||||
result.add(bencode("length") & bencode(obj.length))
|
||||
if name =? obj.name:
|
||||
result.add(bencode("name") & bencode(name))
|
||||
result.add(bencode("piece length") & bencode(obj.pieceLength))
|
||||
result.add(bencode("pieces") & bencode(pieces))
|
||||
result.add('e'.byte)
|
||||
|
||||
proc toString(bytes: seq[byte]): string =
|
||||
result = newStringOfCap(len(bytes))
|
||||
for b in bytes:
|
||||
add(result, b.char)
|
||||
|
||||
proc checkEncoding(actual: seq[byte], expected: string) =
|
||||
check actual.toString == expected
|
||||
|
||||
suite "b-encoding":
|
||||
test "int":
|
||||
checkEncoding(bencode(1'i8), "i1e")
|
||||
checkEncoding(bencode(-1'i8), "i-1e")
|
||||
checkEncoding(bencode(int8.low), fmt"i{int8.low}e")
|
||||
checkEncoding(bencode(int8.high), fmt"i{int8.high}e")
|
||||
checkEncoding(bencode(uint8.low), fmt"i{uint8.low}e")
|
||||
checkEncoding(bencode(uint8.high), fmt"i{uint8.high}e")
|
||||
checkEncoding(bencode(int16.low), fmt"i{int16.low}e")
|
||||
checkEncoding(bencode(int16.high), fmt"i{int16.high}e")
|
||||
checkEncoding(bencode(uint16.low), fmt"i{uint16.low}e")
|
||||
checkEncoding(bencode(uint16.high), fmt"i{uint16.high}e")
|
||||
checkEncoding(bencode(int32.low), fmt"i{int32.low}e")
|
||||
checkEncoding(bencode(int32.high), fmt"i{int32.high}e")
|
||||
checkEncoding(bencode(uint32.low), fmt"i{uint32.low}e")
|
||||
checkEncoding(bencode(uint32.high), fmt"i{uint32.high}e")
|
||||
checkEncoding(bencode(uint.high), fmt"i{uint.high}e")
|
||||
checkEncoding(bencode(int64.low), fmt"i{int64.low}e")
|
||||
checkEncoding(bencode(int64.high), fmt"i{int64.high}e")
|
||||
checkEncoding(bencode(uint64.low), fmt"i{uint64.low}e")
|
||||
checkEncoding(bencode(uint64.high), fmt"i{uint64.high}e")
|
||||
checkEncoding(bencode(int.low), fmt"i{int.low}e")
|
||||
checkEncoding(bencode(int.high), fmt"i{int.high}e")
|
||||
|
||||
test "empty buffer":
|
||||
let input: array[0, byte] = []
|
||||
check bencode(input) == "0:".toBytes
|
||||
|
||||
test "buffer":
|
||||
let input = [1.byte, 2, 3]
|
||||
check bencode(input) == fmt"{input.len}:".toBytes() & @input
|
||||
|
||||
test "longer buffer":
|
||||
let input = toSeq(1.byte .. 127.byte)
|
||||
check bencode(input) == fmt"{input.len}:".toBytes() & @input
|
||||
|
||||
test "string":
|
||||
let input = "abc"
|
||||
check bencode(input) == "3:abc".toBytes
|
||||
|
||||
test "longer string":
|
||||
let input = exampleString(127)
|
||||
check bencode(input) == fmt"{input.len}:{input}".toBytes
|
||||
|
||||
test "empty string":
|
||||
let input = ""
|
||||
check bencode(input) == "0:".toBytes
|
||||
|
||||
test "empty list":
|
||||
let input: seq[string] = @[]
|
||||
check bencode(input) == "le".toBytes
|
||||
|
||||
test "list (of strings)":
|
||||
let input = ["abc", "def"]
|
||||
check bencode(input) == "l3:abc3:defe".toBytes
|
||||
|
||||
test "list (of seq[byte])":
|
||||
let seq1 = toSeq(1.byte .. 127.byte)
|
||||
let seq2 = toSeq(128.byte .. 150.byte)
|
||||
let input = [seq1, seq2]
|
||||
check bencode(input) ==
|
||||
fmt"l{seq1.len}:".toBytes & seq1 & fmt"{seq2.len}:".toBytes & seq2 & @['e'.byte]
|
||||
|
||||
test "list (of integers)":
|
||||
let input = [1, -2, 3, 0x7f, -0x80, 0xff]
|
||||
check bencode(input) == "li1ei-2ei3ei127ei-128ei255ee".toBytes
|
||||
|
||||
test "custom type":
|
||||
let piece = "1cc46da027e7ff6f1970a2e58880dbc6a08992a0".hexToSeqByte
|
||||
let obj = ExampleObject(
|
||||
length: 40960, pieceLength: 65536, pieces: @[piece], name: "data40k.bin".some
|
||||
)
|
||||
let encoded = bencode(obj)
|
||||
check encoded ==
|
||||
"d6:lengthi40960e4:name11:data40k.bin12:piece lengthi65536e6:pieces20:".toBytes &
|
||||
piece & @['e'.byte]
|
||||
let expectedInfoHash = "1902d602db8c350f4f6d809ed01eff32f030da95"
|
||||
check $sha1.digest(encoded) == expectedInfoHash.toUpperAscii
|
||||
51
tests/codex/bittorrent/testmanifest.nim
Normal file
51
tests/codex/bittorrent/testmanifest.nim
Normal file
@ -0,0 +1,51 @@
|
||||
import std/unittest
|
||||
|
||||
import pkg/libp2p/[cid, multicodec, multihash]
|
||||
import pkg/stew/byteutils
|
||||
import pkg/questionable
|
||||
|
||||
import ../../examples
|
||||
import ../../../codex/bittorrent/manifest
|
||||
|
||||
suite "BitTorrent manifest":
|
||||
# In the tests below, we use an example info dictionary
|
||||
# from a valid torrent file (v1 so far).
|
||||
# {
|
||||
# "info": {
|
||||
# "length": 40960,
|
||||
# "name": "data40k.bin",
|
||||
# "piece length": 65536,
|
||||
# "pieces": [
|
||||
# "1cc46da027e7ff6f1970a2e58880dbc6a08992a0"
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
let examplePieceHash = "1cc46da027e7ff6f1970a2e58880dbc6a08992a0".hexToSeqByte
|
||||
let examplePieceMultihash = MultiHash.init($Sha1HashCodec, examplePieceHash).tryGet
|
||||
let exampleInfo = BitTorrentInfo(
|
||||
length: 40960,
|
||||
pieceLength: 65536,
|
||||
pieces: @[examplePieceMultihash],
|
||||
name: "data40k.bin".some,
|
||||
)
|
||||
let dummyCodexManifestCid = Cid.init(
|
||||
CIDv1, ManifestCodec, MultiHash.digest($Sha256HashCodec, seq[byte].example()).tryGet
|
||||
).tryGet
|
||||
|
||||
test "b-encoding info dictionary":
|
||||
let infoEncoded = bencode(exampleInfo)
|
||||
check infoEncoded ==
|
||||
"d6:lengthi40960e4:name11:data40k.bin12:piece lengthi65536e6:pieces20:".toBytes &
|
||||
examplePieceHash & @['e'.byte]
|
||||
let expectedInfoHash = "1902d602db8c350f4f6d809ed01eff32f030da95"
|
||||
check $sha1.digest(infoEncoded) == expectedInfoHash.toUpperAscii
|
||||
|
||||
test "validating against info hash Cid":
|
||||
let infoHash = "1902d602db8c350f4f6d809ed01eff32f030da95".hexToSeqByte
|
||||
let infoMultiHash = MultiHash.init($Sha1HashCodec, infoHash).tryGet
|
||||
let infoHashCid = Cid.init(CIDv1, InfoHashV1Codec, infoMultiHash).tryGet
|
||||
let bitTorrentManifest = newBitTorrentManifest(
|
||||
info = exampleInfo, codexManifestCid = dummyCodexManifestCid
|
||||
)
|
||||
|
||||
check bitTorrentManifest.validate(cid = infoHashCid).tryGet == true
|
||||
4
tests/codex/testbittorrent.nim
Normal file
4
tests/codex/testbittorrent.nim
Normal file
@ -0,0 +1,4 @@
|
||||
import ./bittorrent/testbencoding
|
||||
import ./bittorrent/testmanifest
|
||||
|
||||
{.warning[UnusedImport]: off.}
|
||||
@ -1,3 +1,4 @@
|
||||
import ./codex/testbittorrent
|
||||
import ./codex/teststores
|
||||
import ./codex/testblockexchange
|
||||
import ./codex/testasyncheapqueue
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user