Skip to content

Commit 24e9b08

Browse files
committed
WIP
1 parent d354619 commit 24e9b08

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

swarm/index.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
var arrayRemove = require('unordered-array-remove')
2+
var debug = require('debug')('bittorrent-tracker:swarm')
3+
var LRU = require('lru')
4+
var randomIterate = require('random-iterate')
5+
6+
// Regard this as the default implementation of an interface that you
7+
// need to support when overriding Server.createSwarm() and Server.getSwarm()
8+
class Swarm {
9+
constructor (infoHash, server) {
10+
var self = this
11+
self.infoHash = infoHash
12+
self.complete = 0
13+
self.incomplete = 0
14+
15+
self.peers = new LRU({
16+
max: server.peersCacheLength || 1000,
17+
maxAge: server.peersCacheTtl || 20 * 60 * 1000 // 20 minutes
18+
})
19+
20+
// maps offer_id to peer_id so that trickling candidates
21+
// can be sent to the correct peer
22+
self.offers = new LRU({
23+
max: 2000,
24+
maxAge: 60 * 1000 * 5 // 5 min
25+
})
26+
27+
// When a peer is evicted from the LRU store, send a synthetic 'stopped' event
28+
// so the stats get updated correctly.
29+
self.peers.on('evict', function (data) {
30+
var peer = data.value
31+
var params = {
32+
type: peer.type,
33+
event: 'stopped',
34+
numwant: 0,
35+
peer_id: peer.peerId
36+
}
37+
self._onAnnounceStopped(params, peer, peer.peerId)
38+
peer.socket = null
39+
})
40+
}
41+
42+
announce(params, cb) {
43+
const onAnnounce = resolve => {
44+
this._announce(params, cb)
45+
}
46+
47+
return new Promise(onAnnounce)
48+
}
49+
50+
_announce (params, cb) {
51+
var self = this
52+
var id = params.type === 'ws' ? params.peer_id : params.addr
53+
// Mark the source peer as recently used in cache
54+
var peer = self.peers.get(id)
55+
56+
if (params.event === 'started') {
57+
self._onAnnounceStarted(params, peer, id)
58+
} else if (params.event === 'stopped') {
59+
self._onAnnounceStopped(params, peer, id)
60+
} else if (params.event === 'completed') {
61+
self._onAnnounceCompleted(params, peer, id)
62+
} else if (params.event === 'update') {
63+
self._onAnnounceUpdate(params, peer, id)
64+
} else if (params.event === 'trickle') {
65+
cb(null, { peers: self._getPeersByOfferId(params) })
66+
return
67+
} else {
68+
cb(new Error('invalid event'))
69+
return
70+
}
71+
72+
cb(null, {
73+
complete: self.complete,
74+
incomplete: self.incomplete,
75+
peers: self._getPeers(params.numwant, params.peer_id, !!params.socket)
76+
})
77+
}
78+
79+
scrape (params, cb) {
80+
cb(null, {
81+
complete: this.complete,
82+
incomplete: this.incomplete
83+
})
84+
}
85+
86+
_onAnnounceStarted (params, peer, id) {
87+
if (peer) {
88+
debug('unexpected `started` event from peer that is already in swarm')
89+
return this._onAnnounceUpdate(params, peer, id) // treat as an update
90+
}
91+
92+
if (params.left === 0) this.complete += 1
93+
else this.incomplete += 1
94+
this.peers.set(id, {
95+
type: params.type,
96+
complete: params.left === 0,
97+
peerId: params.peer_id, // as hex
98+
ip: params.ip,
99+
port: params.port,
100+
socket: params.socket // only websocket
101+
})
102+
}
103+
104+
_onAnnounceStopped (params, peer, id) {
105+
if (!peer) {
106+
debug('unexpected `stopped` event from peer that is not in swarm')
107+
return // do nothing
108+
}
109+
110+
if (peer.complete) this.complete -= 1
111+
else this.incomplete -= 1
112+
113+
// If it's a websocket, remove this swarm's infohash from the list of active
114+
// swarms that this peer is participating in.
115+
if (peer.socket && !peer.socket.destroyed) {
116+
var index = peer.socket.infoHashes.indexOf(this.infoHash)
117+
arrayRemove(peer.socket.infoHashes, index)
118+
}
119+
120+
this.peers.remove(id)
121+
}
122+
123+
_onAnnounceCompleted (params, peer, id) {
124+
if (!peer) {
125+
debug('unexpected `completed` event from peer that is not in swarm')
126+
return this._onAnnounceStarted(params, peer, id) // treat as a start
127+
}
128+
if (peer.complete) {
129+
debug('unexpected `completed` event from peer that is already completed')
130+
return this._onAnnounceUpdate(params, peer, id) // treat as an update
131+
}
132+
133+
this.complete += 1
134+
this.incomplete -= 1
135+
peer.complete = true
136+
this.peers.set(id, peer)
137+
}
138+
139+
_onAnnounceUpdate (params, peer, id) {
140+
if (!peer) {
141+
debug('unexpected `update` event from peer that is not in swarm')
142+
return this._onAnnounceStarted(params, peer, id) // treat as a start
143+
}
144+
145+
if (!peer.complete && params.left === 0) {
146+
this.complete += 1
147+
this.incomplete -= 1
148+
peer.complete = true
149+
}
150+
this.peers.set(id, peer)
151+
}
152+
153+
_getPeersByOfferId (params, ownPeerId) {
154+
const peers = []
155+
const { offer_id } = params.offers[0]
156+
const peerId = this.offers.get(offer_id)
157+
const peer = this.peers.peek(peerId)
158+
if (peer) {
159+
peers.push(peer)
160+
}
161+
return peers
162+
}
163+
164+
_getPeers(numwant, ownPeerId, isWebRTC) {
165+
var peers = []
166+
var ite = randomIterate(this.peers.keys)
167+
var peerId
168+
while ((peerId = ite()) && peers.length < numwant) {
169+
// Don't mark the peer as most recently used on announce
170+
var peer = this.peers.peek(peerId)
171+
if (!peer) continue
172+
if (isWebRTC && peer.peerId === ownPeerId) continue // don't send peer to itself
173+
if ((isWebRTC && peer.type !== 'ws') || (!isWebRTC && peer.type === 'ws')) continue // send proper peer type
174+
peers.push(peer)
175+
}
176+
return peers
177+
}
178+
}
179+
180+
module.exports = Swarm;

0 commit comments

Comments
 (0)