Skip to content

Commit 60dec8f

Browse files
committed
makes torrent API ready for torrents v2, closes torrent streaming loop
1 parent f209507 commit 60dec8f

File tree

5 files changed

+144
-18
lines changed

5 files changed

+144
-18
lines changed

codex/bittorrent/manifest/manifest.nim

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import pkg/libp2p
2+
import pkg/stew/byteutils
23
import pkg/questionable
34
import pkg/questionable/results
45

6+
import ../../merkletree/codex/codex
7+
58
import ../../errors
69
import ../../codextypes
710
import ../bencoding
@@ -14,8 +17,6 @@ type
1417
pieces*: seq[BitTorrentPiece]
1518
name*: ?string
1619

17-
BitTorrentInfoHash* = MultiHash
18-
1920
BitTorrentManifest* = ref object
2021
info*: BitTorrentInfo
2122
codexManifestCid*: Cid
@@ -55,3 +56,22 @@ func validate*(self: BitTorrentManifest, cid: Cid): ?!bool =
5556
without cidInfoHash =? cid.mhash.mapFailure, err:
5657
return failure(err.msg)
5758
return success(infoHash == cidInfoHash)
59+
60+
func buildMultiHash*(_: type BitTorrentInfo, input: string): ?!MultiHash =
61+
without bytes =? input.hexToSeqByte.catch, err:
62+
return failure err.msg
63+
without hash =? MultiHash.init(bytes):
64+
without mhashMetaSha1 =? Sha1HashCodec.mhash, err:
65+
return failure err.msg
66+
if bytes.len == mhashMetaSha1.size:
67+
without hash =? MultiHash.init($Sha1HashCodec, bytes).mapFailure, err:
68+
return failure err.msg
69+
return success hash
70+
without mhashMetaSha256 =? Sha256HashCodec.mhash, err:
71+
return failure err.msg
72+
if bytes.len == mhashMetaSha256.size:
73+
without hash =? MultiHash.init($Sha256HashCodec, bytes).mapFailure, err:
74+
return failure err.msg
75+
return success hash
76+
return failure "given bytes is not a correct multihash"
77+
return success hash

codex/node.nim

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func discovery*(self: CodexNodeRef): Discovery =
9999
return self.discovery
100100

101101
proc storeBitTorrentManifest*(
102-
self: CodexNodeRef, manifest: BitTorrentManifest, infoHash: BitTorrentInfoHash
102+
self: CodexNodeRef, manifest: BitTorrentManifest, infoHash: MultiHash
103103
): Future[?!bt.Block] {.async.} =
104104
let encodedManifest = manifest.encode()
105105

@@ -460,13 +460,9 @@ proc streamTorrent(
460460
trace "Creating store stream for torrent manifest"
461461
stream.success
462462

463-
proc retrieveInfoHash*(
464-
self: CodexNodeRef, infoHashString: string
463+
proc retrieveTorrent*(
464+
self: CodexNodeRef, infoHash: MultiHash
465465
): Future[?!LPStream] {.async.} =
466-
without infoHash =? MultiHash.init("sha1", infoHashString.hexToSeqByte).mapFailure,
467-
err:
468-
return failure(err)
469-
470466
without infoHashCid =? Cid.init(CIDv1, InfoHashV1Codec, infoHash).mapFailure, error:
471467
trace "Unable to create CID for BitTorrent info hash"
472468
return failure(error)
@@ -622,11 +618,11 @@ proc store*(
622618

623619
return manifestBlk.cid.success
624620

625-
proc storeBitTorrent*(
621+
proc storeTorrent*(
626622
self: CodexNodeRef,
627623
stream: LPStream,
628624
info: BitTorrentInfo,
629-
infoHash: BitTorrentInfoHash,
625+
infoHash: MultiHash,
630626
mimetype: ?string = string.none,
631627
): Future[?!Cid] {.async.} =
632628
info "Storing BitTorrent data"

codex/rest/api.nim

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,80 @@ proc retrieveCid(
129129
if not stream.isNil:
130130
await stream.close()
131131

132+
proc retrieveInfoHash(
133+
node: CodexNodeRef, infoHash: MultiHash, resp: HttpResponseRef
134+
): Future[RestApiResponse] {.async.} =
135+
## Download torrent from the node in a streaming
136+
## manner
137+
##
138+
var stream: LPStream
139+
140+
var bytes = 0
141+
try:
142+
without stream =? (await node.retrieveTorrent(infoHash)), error:
143+
if error of BlockNotFoundError:
144+
resp.status = Http404
145+
return await resp.sendBody("")
146+
else:
147+
resp.status = Http500
148+
return await resp.sendBody(error.msg)
149+
150+
# It is ok to fetch again the manifest because it will hit the cache
151+
without infoHashCid =? Cid.init(CIDv1, InfoHashV1Codec, infoHash).mapFailure, err:
152+
error "Unable to create CID for BitTorrent info hash", err = err.msg
153+
resp.status = Http404
154+
return await resp.sendBody(err.msg)
155+
156+
without torrentManifest =? (await node.fetchTorrentManifest(infoHashCid)), err:
157+
error "Unable to fetch Torrent Manifest", err = err.msg
158+
resp.status = Http404
159+
return await resp.sendBody(err.msg)
160+
161+
without codexManifest =? (
162+
await node.fetchManifest(torrentManifest.codexManifestCid)
163+
), err:
164+
error "Unable to fetch Codex Manifest for torrent info hash", err = err.msg
165+
resp.status = Http404
166+
return await resp.sendBody(err.msg)
167+
168+
if codexManifest.mimetype.isSome:
169+
resp.setHeader("Content-Type", codexManifest.mimetype.get())
170+
else:
171+
resp.addHeader("Content-Type", "application/octet-stream")
172+
173+
if codexManifest.filename.isSome:
174+
resp.setHeader(
175+
"Content-Disposition",
176+
"attachment; filename=\"" & codexManifest.filename.get() & "\"",
177+
)
178+
else:
179+
resp.setHeader("Content-Disposition", "attachment")
180+
181+
await resp.prepareChunked()
182+
183+
while not stream.atEof:
184+
var
185+
buff = newSeqUninitialized[byte](int(NBytes 1024 * 16))
186+
len = await stream.readOnce(addr buff[0], buff.len)
187+
188+
buff.setLen(len)
189+
if buff.len <= 0:
190+
break
191+
192+
bytes += buff.len
193+
194+
await resp.sendChunk(addr buff[0], buff.len)
195+
await resp.finish()
196+
codex_api_downloads.inc()
197+
except CatchableError as exc:
198+
warn "Error streaming blocks", exc = exc.msg
199+
resp.status = Http500
200+
return await resp.sendBody("")
201+
finally:
202+
info "Sent bytes for torrent", infoHash = $infoHash, bytes
203+
if not stream.isNil:
204+
await stream.close()
205+
132206
proc buildCorsHeaders(
133207
httpMethod: string, allowedOrigin: Option[string]
134208
): seq[(string, string)] =
@@ -339,13 +413,20 @@ proc initDataApi(node: CodexNodeRef, repoStore: RepoStore, router: var RestRoute
339413
without infoHash =? infoHash.mapFailure, error:
340414
return RestApiResponse.error(Http400, error.msg, headers = headers)
341415

416+
if infoHash.mcodec != Sha1HashCodec:
417+
return RestApiResponse.error(
418+
Http400, "Only torrents version 1 are currently supported!", headers = headers
419+
)
420+
342421
if corsOrigin =? allowedOrigin:
343422
resp.setCorsHeaders("GET", corsOrigin)
344423
resp.setHeader("Access-Control-Headers", "X-Requested-With")
345424

346425
trace "torrent requested: ", multihash = $infoHash
347426

348-
return RestApiResponse.response(Http200)
427+
await node.retrieveInfoHash(infoHash, resp = resp)
428+
429+
# return RestApiResponse.response(Http200)
349430

350431
router.api(MethodGet, "/api/codex/v1/data/{cid}/network/manifest") do(
351432
cid: Cid, resp: HttpResponseRef

codex/rest/coders.nim

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import ../purchasing
2222
import ../utils/stintutils
2323

2424
from ../codextypes import Sha1HashCodec
25+
import ../bittorrent/manifest
2526

2627
proc encodeString*(cid: type Cid): Result[string, cstring] =
2728
ok($cid)
@@ -85,11 +86,14 @@ proc decodeString*(
8586
err e.msg.cstring
8687

8788
proc decodeString*(_: type MultiHash, value: string): Result[MultiHash, cstring] =
88-
try:
89-
let bytes = value.hexToSeqByte
90-
MultiHash.init($Sha1HashCodec, bytes)
91-
except ValueError as e:
92-
err e.msg.cstring
89+
without mhash =? BitTorrentInfo.buildMultiHash(value), e:
90+
return err e.msg.cstring
91+
ok mhash
92+
# try:
93+
# let bytes = value.hexToSeqByte
94+
# MultiHash.init($Sha1HashCodec, bytes)
95+
# except ValueError as e:
96+
# err e.msg.cstring
9397

9498
proc decodeString*[T: PurchaseId | RequestId | Nonce | SlotId | AvailabilityId](
9599
_: type T, value: string

tests/codex/bittorrent/testmanifest.nim

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import std/unittest
2+
import std/strformat
23

34
import pkg/libp2p/[cid, multicodec, multihash]
45
import pkg/stew/byteutils
56
import pkg/questionable
67

78
import ../../examples
8-
import ../../../codex/bittorrent/manifest
9+
10+
import pkg/codex/bittorrent/manifest
911

1012
suite "BitTorrent manifest":
1113
# In the tests below, we use an example info dictionary
@@ -49,3 +51,26 @@ suite "BitTorrent manifest":
4951
)
5052

5153
check bitTorrentManifest.validate(cid = infoHashCid).tryGet == true
54+
55+
for testData in [
56+
(
57+
"1902d602db8c350f4f6d809ed01eff32f030da95",
58+
"11141902D602DB8C350F4F6D809ED01EFF32F030DA95",
59+
),
60+
(
61+
"499B3A24C2C653C9600D0C22B33EC504ECCA1999AAF56E559505F342A2062497",
62+
"1220499B3A24C2C653C9600D0C22B33EC504ECCA1999AAF56E559505F342A2062497",
63+
),
64+
(
65+
"1220499B3A24C2C653C9600D0C22B33EC504ECCA1999AAF56E559505F342A2062497",
66+
"1220499B3A24C2C653C9600D0C22B33EC504ECCA1999AAF56E559505F342A2062497",
67+
),
68+
(
69+
"11141902D602DB8C350F4F6D809ED01EFF32F030DA95",
70+
"11141902D602DB8C350F4F6D809ED01EFF32F030DA95",
71+
),
72+
]:
73+
let (input, expectedOutput) = testData
74+
test fmt"Build MultiHash from '{input}'":
75+
let hash = BitTorrentInfo.buildMultiHash(input).tryGet
76+
check hash.hex == expectedOutput

0 commit comments

Comments
 (0)