Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import once from 'once'
import parallel from 'run-parallel'
import Peer from 'simple-peer'
import queueMicrotask from 'queue-microtask'
import { hex2arr, hex2bin, text2arr, arr2hex, arr2text } from 'uint8-util'

import common from './lib/common.js'
import HTTPTracker from './lib/client/http-tracker.js' // empty object in browser
Expand All @@ -18,8 +19,8 @@ const debug = Debug('bittorrent-tracker:client')
* Find torrent peers, to help a torrent client participate in a torrent swarm.
*
* @param {Object} opts options object
* @param {string|Buffer} opts.infoHash torrent info hash
* @param {string|Buffer} opts.peerId peer id
* @param {string|Uint8Array} opts.infoHash torrent info hash
* @param {string|Uint8Array} opts.peerId peer id
* @param {string|Array.<string>} opts.announce announce
* @param {number} opts.port torrent client listening port
* @param {function} opts.getAnnounceOpts callback to provide data to tracker
Expand All @@ -39,15 +40,15 @@ class Client extends EventEmitter {

this.peerId = typeof opts.peerId === 'string'
? opts.peerId
: opts.peerId.toString('hex')
this._peerIdBuffer = Buffer.from(this.peerId, 'hex')
this._peerIdBinary = this._peerIdBuffer.toString('binary')
: arr2hex(opts.peerId)
this._peerIdBuffer = hex2arr(this.peerId)
this._peerIdBinary = hex2bin(this.peerId)

this.infoHash = typeof opts.infoHash === 'string'
? opts.infoHash.toLowerCase()
: opts.infoHash.toString('hex')
this._infoHashBuffer = Buffer.from(this.infoHash, 'hex')
this._infoHashBinary = this._infoHashBuffer.toString('binary')
: arr2hex(opts.infoHash)
this._infoHashBuffer = hex2arr(this.infoHash)
this._infoHashBinary = hex2bin(this.infoHash)

debug('new client %s', this.infoHash)

Expand All @@ -69,7 +70,7 @@ class Client extends EventEmitter {

// Remove trailing slash from trackers to catch duplicates
announce = announce.map(announceUrl => {
announceUrl = announceUrl.toString()
announceUrl = arr2text(announceUrl)
if (announceUrl[announceUrl.length - 1] === '/') {
announceUrl = announceUrl.substring(0, announceUrl.length - 1)
}
Expand Down Expand Up @@ -260,7 +261,7 @@ Client.scrape = (opts, cb) => {

const clientOpts = Object.assign({}, opts, {
infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash,
peerId: Buffer.from('01234567890123456789'), // dummy value
peerId: text2arr('01234567890123456789'), // dummy value
port: 6881 // dummy value
})

Expand All @@ -284,9 +285,6 @@ Client.scrape = (opts, cb) => {
}
})

opts.infoHash = Array.isArray(opts.infoHash)
? opts.infoHash.map(infoHash => Buffer.from(infoHash, 'hex'))
: Buffer.from(opts.infoHash, 'hex')
client.scrape({ infoHash: opts.infoHash })
return client
}
Expand Down
13 changes: 7 additions & 6 deletions lib/client/http-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import clone from 'clone'
import Debug from 'debug'
import get from 'simple-get'
import Socks from 'socks'
import { bin2hex, hex2bin, arr2text } from 'uint8-util'

import common from '../common.js'
import Tracker from './tracker.js'
Expand Down Expand Up @@ -65,8 +66,8 @@ class HTTPTracker extends Tracker {
}

const infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? opts.infoHash.map(infoHash => infoHash.toString('binary'))
: (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary
? opts.infoHash.map(infoHash => hex2bin(infoHash))
: (opts.infoHash && hex2bin(opts.infoHash)) || this.client._infoHashBinary
const params = {
info_hash: infoHashes
}
Expand Down Expand Up @@ -159,13 +160,13 @@ class HTTPTracker extends Tracker {
} catch (err) {
return cb(new Error(`Error decoding tracker response: ${err.message}`))
}
const failure = data['failure reason'] && Buffer.from(data['failure reason']).toString()
const failure = data['failure reason'] && arr2text(data['failure reason'])
if (failure) {
debug(`failure from ${requestUrl} (${failure})`)
return cb(new Error(failure))
}

const warning = data['warning message'] && Buffer.from(data['warning message']).toString()
const warning = data['warning message'] && arr2text(data['warning message'])
if (warning) {
debug(`warning from ${requestUrl} (${warning})`)
self.client.emit('warning', new Error(warning))
Expand All @@ -189,7 +190,7 @@ class HTTPTracker extends Tracker {

const response = Object.assign({}, data, {
announce: this.announceUrl,
infoHash: common.binaryToHex(data.info_hash)
infoHash: bin2hex(data.info_hash || String(data.info_hash))
})
this.client.emit('update', response)

Expand Down Expand Up @@ -248,7 +249,7 @@ class HTTPTracker extends Tracker {
// (separate from announce interval)
const response = Object.assign(data[infoHash], {
announce: this.announceUrl,
infoHash: common.binaryToHex(infoHash)
infoHash: bin2hex(infoHash)
})
this.client.emit('scrape', response)
})
Expand Down
34 changes: 17 additions & 17 deletions lib/client/udp-tracker.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import arrayRemove from 'unordered-array-remove'
import BN from 'bn.js'
import clone from 'clone'
import Debug from 'debug'
import dgram from 'dgram'
import randombytes from 'randombytes'
import Socks from 'socks'
import { concat, hex2arr, randomBytes } from 'uint8-util'

import common from '../common.js'
import Tracker from './tracker.js'
Expand Down Expand Up @@ -131,7 +130,7 @@ class UDPTracker extends Tracker {
}, common.REQUEST_TIMEOUT)
if (timeout.unref) timeout.unref()

send(Buffer.concat([
send(concat([
common.CONNECTION_ID,
common.toUInt32(common.ACTIONS.CONNECT),
transactionId
Expand Down Expand Up @@ -175,7 +174,8 @@ class UDPTracker extends Tracker {

function onSocketMessage (msg) {
if (proxySocket) msg = msg.slice(10)
if (msg.length < 8 || msg.readUInt32BE(4) !== transactionId.readUInt32BE(0)) {
const view = new DataView(transactionId.buffer)
if (msg.length < 8 || msg.readUInt32BE(4) !== view.getUint32(0)) {
return onError(new Error('tracker sent invalid transaction id'))
}

Expand Down Expand Up @@ -270,14 +270,14 @@ class UDPTracker extends Tracker {
function announce (connectionId, opts) {
transactionId = genTransactionId()

send(Buffer.concat([
send(concat([
connectionId,
common.toUInt32(common.ACTIONS.ANNOUNCE),
transactionId,
self.client._infoHashBuffer,
self.client._peerIdBuffer,
toUInt64(opts.downloaded),
opts.left != null ? toUInt64(opts.left) : Buffer.from('FFFFFFFFFFFFFFFF', 'hex'),
opts.left != null ? toUInt64(opts.left) : hex2arr('ffffffffffffffff'),
toUInt64(opts.uploaded),
common.toUInt32(common.EVENTS[opts.event] || 0),
common.toUInt32(0), // ip address (optional)
Expand All @@ -291,10 +291,10 @@ class UDPTracker extends Tracker {
transactionId = genTransactionId()

const infoHash = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? Buffer.concat(opts.infoHash)
? concat(opts.infoHash)
: (opts.infoHash || self.client._infoHashBuffer)

send(Buffer.concat([
send(concat([
connectionId,
common.toUInt32(common.ACTIONS.SCRAPE),
transactionId,
Expand All @@ -307,26 +307,26 @@ class UDPTracker extends Tracker {
UDPTracker.prototype.DEFAULT_ANNOUNCE_INTERVAL = 30 * 60 * 1000 // 30 minutes

function genTransactionId () {
return randombytes(4)
return randomBytes(4)
}

function toUInt16 (n) {
const buf = Buffer.allocUnsafe(2)
buf.writeUInt16BE(n, 0)
const buf = new Uint8Array(2)
const view = new DataView(buf.buffer)
view.setUint16(0, n)
return buf
}

const MAX_UINT = 4294967295

function toUInt64 (n) {
if (n > MAX_UINT || typeof n === 'string') {
const bytes = new BN(n).toArray()
while (bytes.length < 8) {
bytes.unshift(0)
}
return Buffer.from(bytes)
const buf = new Uint8Array(8)
const view = new DataView(buf.buffer)
view.setBigUint64(0, n)
return buf
}
return Buffer.concat([common.toUInt32(0), common.toUInt32(n)])
return concat([new Uint8Array(4), common.toUInt32(n)])
}

function noop () {}
Expand Down
28 changes: 14 additions & 14 deletions lib/client/websocket-tracker.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import clone from 'clone'
import Debug from 'debug'
import Peer from 'simple-peer'
import randombytes from 'randombytes'
import Socket from '@thaunknown/simple-websocket'
import Socks from 'socks'
import { arr2text, arr2hex, hex2bin, bin2hex, randomBytes } from 'uint8-util'

import common from '../common.js'
import { DESTROY_TIMEOUT } from '../common.js'
import Tracker from './tracker.js'

const debug = Debug('bittorrent-tracker:websocket-tracker')
Expand Down Expand Up @@ -80,8 +80,8 @@ class WebSocketTracker extends Tracker {
}

const infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0)
? opts.infoHash.map(infoHash => infoHash.toString('binary'))
: (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary
? opts.infoHash.map(infoHash => hex2bin(infoHash))
: (opts.infoHash && hex2bin(opts.infoHash)) || this.client._infoHashBinary
const params = {
action: 'scrape',
info_hash: infoHashes
Expand Down Expand Up @@ -138,7 +138,7 @@ class WebSocketTracker extends Tracker {

// Otherwise, wait a short time for potential responses to come in from the
// server, then force close the socket.
timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT)
timeout = setTimeout(destroyCleanup, DESTROY_TIMEOUT)

// But, if a response comes from the server before the timeout fires, do cleanup
// right away.
Expand Down Expand Up @@ -214,7 +214,7 @@ class WebSocketTracker extends Tracker {
this.expectingResponse = false

try {
data = JSON.parse(Buffer.from(data))
data = JSON.parse(arr2text(data))
} catch (err) {
this.client.emit('warning', new Error('Invalid tracker response'))
return
Expand All @@ -233,7 +233,7 @@ class WebSocketTracker extends Tracker {
if (data.info_hash !== this.client._infoHashBinary) {
debug(
'ignoring websocket data from %s for %s (looking for %s: reused socket)',
this.announceUrl, common.binaryToHex(data.info_hash), this.client.infoHash
this.announceUrl, bin2hex(data.info_hash), this.client.infoHash
)
return
}
Expand Down Expand Up @@ -266,7 +266,7 @@ class WebSocketTracker extends Tracker {
if (data.complete != null) {
const response = Object.assign({}, data, {
announce: this.announceUrl,
infoHash: common.binaryToHex(data.info_hash)
infoHash: bin2hex(data.info_hash)
})
this.client.emit('update', response)
}
Expand All @@ -275,7 +275,7 @@ class WebSocketTracker extends Tracker {
if (data.offer && data.peer_id) {
debug('creating peer (from remote offer)')
peer = this._createPeer()
peer.id = common.binaryToHex(data.peer_id)
peer.id = bin2hex(data.peer_id)
peer.once('signal', answer => {
const params = {
action: 'announce',
Expand All @@ -293,10 +293,10 @@ class WebSocketTracker extends Tracker {
}

if (data.answer && data.peer_id) {
const offerId = common.binaryToHex(data.offer_id)
const offerId = bin2hex(data.offer_id)
peer = this.peers[offerId]
if (peer) {
peer.id = common.binaryToHex(data.peer_id)
peer.id = bin2hex(data.peer_id)
this.client.emit('peer', peer)
peer.signal(data.answer)

Expand All @@ -323,7 +323,7 @@ class WebSocketTracker extends Tracker {
// (separate from announce interval)
const response = Object.assign(data[infoHash], {
announce: this.announceUrl,
infoHash: common.binaryToHex(infoHash)
infoHash: bin2hex(infoHash)
})
this.client.emit('scrape', response)
})
Expand Down Expand Up @@ -376,13 +376,13 @@ class WebSocketTracker extends Tracker {
checkDone()

function generateOffer () {
const offerId = randombytes(20).toString('hex')
const offerId = arr2hex(randomBytes(20))
debug('creating peer (from _generateOffers)')
const peer = self.peers[offerId] = self._createPeer({ initiator: true })
peer.once('signal', offer => {
offers.push({
offer,
offer_id: common.hexToBinary(offerId)
offer_id: hex2bin(offerId)
})
checkDone()
})
Expand Down
8 changes: 5 additions & 3 deletions lib/common-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
*/

import querystring from 'querystring'
import { concat } from 'uint8-util'

export const IPV4_RE = /^[\d.]+$/
export const IPV6_RE = /^[\da-fA-F:]+$/
export const REMOVE_IPV4_MAPPED_IPV6_RE = /^::ffff:/

export const CONNECTION_ID = Buffer.concat([toUInt32(0x417), toUInt32(0x27101980)])
export const CONNECTION_ID = concat([toUInt32(0x417), toUInt32(0x27101980)])
export const ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 }
export const EVENTS = { update: 0, completed: 1, started: 2, stopped: 3, paused: 4 }
export const EVENT_IDS = {
Expand Down Expand Up @@ -40,8 +41,9 @@ export const REQUEST_TIMEOUT = 15000
export const DESTROY_TIMEOUT = 1000

export function toUInt32 (n) {
const buf = Buffer.allocUnsafe(4)
buf.writeUInt32BE(n, 0)
const buf = new Uint8Array(4)
const view = new DataView(buf.buffer)
view.setUint32(0, n)
return buf
}

Expand Down
17 changes: 1 addition & 16 deletions lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,11 @@
* Functions/constants needed by both the client and server.
*/
import * as common from './common-node.js'
export * from './common-node.js'

export const DEFAULT_ANNOUNCE_PEERS = 50
export const MAX_ANNOUNCE_PEERS = 82

export const binaryToHex = str => {
if (typeof str !== 'string') {
str = String(str)
}
return Buffer.from(str, 'binary').toString('hex')
}

export const hexToBinary = str => {
if (typeof str !== 'string') {
str = String(str)
}
return Buffer.from(str, 'hex').toString('binary')
}

// HACK: Fix for WHATWG URL object not parsing non-standard URL schemes like
// 'udp:'. Just replace it with 'http:' since we only need a few properties.
//
Expand Down Expand Up @@ -49,8 +36,6 @@ export const parseUrl = str => {
export default {
DEFAULT_ANNOUNCE_PEERS,
MAX_ANNOUNCE_PEERS,
binaryToHex,
hexToBinary,
parseUrl,
...common
}
Loading