librqbit_core/
peer_id.rs

1use crate::hash_id::Id20;
2use rand::{self, RngCore};
3
4/// Return the version of the invoking crate as a tuple
5#[macro_export]
6macro_rules! crate_version {
7    () => {
8        (
9            env!("CARGO_PKG_VERSION_MAJOR").parse::<u8>().unwrap_or(0),
10            env!("CARGO_PKG_VERSION_MINOR").parse::<u8>().unwrap_or(0),
11            env!("CARGO_PKG_VERSION_PATCH").parse::<u8>().unwrap_or(0),
12            env!("CARGO_PKG_VERSION_PRE").parse::<u8>().unwrap_or(0),
13        )
14    };
15}
16
17#[derive(Debug)]
18pub enum AzureusStyleKind {
19    Deluge,
20    LibTorrent,
21    Transmission,
22    QBittorrent,
23    UTorrent,
24    RQBit,
25    Other([u8; 2]),
26}
27
28#[derive(Debug)]
29pub struct AzureusStyle {
30    pub kind: AzureusStyleKind,
31    pub version: [u8; 4],
32}
33
34impl AzureusStyleKind {
35    pub const fn from_bytes(b1: u8, b2: u8) -> Self {
36        match &[b1, b2] {
37            b"DE" => AzureusStyleKind::Deluge,
38            b"lt" | b"LT" => AzureusStyleKind::LibTorrent,
39            b"TR" => AzureusStyleKind::Transmission,
40            b"qB" => AzureusStyleKind::QBittorrent,
41            b"UT" => AzureusStyleKind::UTorrent,
42            b"rQ" => AzureusStyleKind::RQBit,
43            other => AzureusStyleKind::Other(*other),
44        }
45    }
46}
47
48fn try_decode_azureus_style(p: &Id20) -> Option<AzureusStyle> {
49    let p = p.0;
50    if !(p[0] == b'-' && p[7] == b'-') {
51        return None;
52    }
53    let mut version = [b'0'; 4];
54    for (i, c) in p[3..7].iter().copied().enumerate() {
55        version[i] = version_digit_from_id(c)?;
56    }
57    let kind = AzureusStyleKind::from_bytes(p[1], p[2]);
58    Some(AzureusStyle { kind, version })
59}
60
61#[derive(Debug)]
62pub enum PeerId {
63    AzureusStyle(AzureusStyle),
64}
65
66pub fn try_decode_peer_id(p: Id20) -> Option<PeerId> {
67    Some(PeerId::AzureusStyle(try_decode_azureus_style(&p)?))
68}
69
70/// Returns `None` for bytes greater than 64
71fn version_digit_to_id(d: u8) -> Option<u8> {
72    let version_map = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-";
73    version_map.get(d as usize).copied()
74}
75
76/// Returns `None` for bytes that aren't alphanumeric, `.` or `-`.
77fn version_digit_from_id(d: u8) -> Option<u8> {
78    match d {
79        b'0'..=b'9' => Some(d - b'0'),
80        b'A'..=b'Z' => Some(d - b'0' - 10),
81        b'a'..=b'z' => Some(d - b'0' - 10 - 26),
82        b'.' => Some(62),
83        b'-' => Some(63),
84        _ => None,
85    }
86}
87
88/// Generate a client fingerprint in the Azereus format, where `b"-xx1234-"` corresponds to version `1.2.3.4`` of the torrent client abbreviated by `xx`
89pub fn generate_azereus_style(client: [u8; 2], version: (u8, u8, u8, u8)) -> Id20 {
90    let mut fingerprint = [b'-'; 8];
91
92    fingerprint[1..3].copy_from_slice(&client);
93    fingerprint[3] = version_digit_to_id(version.0).unwrap();
94    fingerprint[4] = version_digit_to_id(version.1).unwrap();
95    fingerprint[5] = version_digit_to_id(version.2).unwrap();
96    fingerprint[6] = version_digit_to_id(version.3).unwrap();
97    generate_peer_id(&fingerprint)
98}
99
100/// Panics if the `fingerprint` slice isn't eight bytes long
101pub fn generate_peer_id(fingerprint: &[u8]) -> Id20 {
102    let mut peer_id = [0u8; 20];
103
104    peer_id[..8].copy_from_slice(fingerprint);
105    rand::rng().fill_bytes(&mut peer_id[8..]);
106
107    Id20::new(peer_id)
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::peer_id::generate_azereus_style;
113
114    #[test]
115    fn test_azereus_peer_id_generation() {
116        for (client, version, correct_fingerprint) in [
117            (*b"xx", (1, 2, 3, 4), *b"-xx1234-"),
118            (*b"00", (10, 0, 0, 0), *b"-00A000-"),
119            (*b"\xFF\xFF", (36, 37, 62, 63), *b"-\xFF\xFFab.--"),
120        ] {
121            let id1 = generate_azereus_style(client, version);
122            let id2 = generate_azereus_style(client, version);
123            assert_ne!(id1, id2);
124            assert_eq!(id1.0[..8], id2.0[..8]);
125            assert_eq!(id1.0[..8], correct_fingerprint);
126        }
127    }
128}