/ lib / server / parse-udp.js
parse-udp.js
 1  import ipLib from 'ip'
 2  import common from '../common.js'
 3  import { equal } from 'uint8-util'
 4  
 5  export default function (msg, rinfo) {
 6    if (msg.length < 16) throw new Error('received packet is too short')
 7  
 8    const params = {
 9      connectionId: msg.slice(0, 8), // 64-bit
10      action: msg.readUInt32BE(8),
11      transactionId: msg.readUInt32BE(12),
12      type: 'udp'
13    }
14  
15    if (!equal(common.CONNECTION_ID, params.connectionId)) {
16      throw new Error('received packet with invalid connection id')
17    }
18  
19    if (params.action === common.ACTIONS.CONNECT) {
20      // No further params
21    } else if (params.action === common.ACTIONS.ANNOUNCE) {
22      params.info_hash = msg.slice(16, 36).toString('hex') // 20 bytes
23      params.peer_id = msg.slice(36, 56).toString('hex') // 20 bytes
24      params.downloaded = fromUInt64(msg.slice(56, 64)) // TODO: track this?
25      params.left = fromUInt64(msg.slice(64, 72))
26      params.uploaded = fromUInt64(msg.slice(72, 80)) // TODO: track this?
27  
28      params.event = common.EVENT_IDS[msg.readUInt32BE(80)]
29      if (!params.event) throw new Error('invalid event') // early return
30  
31      const ip = msg.readUInt32BE(84) // optional
32      params.ip = ip
33        ? ipLib.toString(ip)
34        : rinfo.address
35  
36      params.key = msg.readUInt32BE(88) // Optional: unique random key from client
37  
38      // never send more than MAX_ANNOUNCE_PEERS or else the UDP packet will get bigger than
39      // 512 bytes which is not safe
40      params.numwant = Math.min(
41        msg.readUInt32BE(92) || common.DEFAULT_ANNOUNCE_PEERS, // optional
42        common.MAX_ANNOUNCE_PEERS
43      )
44  
45      params.port = msg.readUInt16BE(96) || rinfo.port // optional
46      params.addr = `${params.ip}:${params.port}` // TODO: ipv6 brackets
47      params.compact = 1 // udp is always compact
48    } else if (params.action === common.ACTIONS.SCRAPE) { // scrape message
49      if ((msg.length - 16) % 20 !== 0) throw new Error('invalid scrape message')
50      params.info_hash = []
51      for (let i = 0, len = (msg.length - 16) / 20; i < len; i += 1) {
52        const infoHash = msg.slice(16 + (i * 20), 36 + (i * 20)).toString('hex') // 20 bytes
53        params.info_hash.push(infoHash)
54      }
55    } else {
56      throw new Error(`Invalid action in UDP packet: ${params.action}`)
57    }
58  
59    return params
60  }
61  
62  const TWO_PWR_32 = (1 << 16) * 2
63  
64  /**
65   * Return the closest floating-point representation to the buffer value. Precision will be
66   * lost for big numbers.
67   */
68  function fromUInt64 (buf) {
69    const high = buf.readUInt32BE(0) | 0 // force
70    const low = buf.readUInt32BE(4) | 0
71    const lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low
72  
73    return (high * TWO_PWR_32) + lowUnsigned
74  }