1use crate::hash_id::Id20;
2use rand::{self, RngCore};
3
4#[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
70fn version_digit_to_id(d: u8) -> Option<u8> {
72 let version_map = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-";
73 version_map.get(d as usize).copied()
74}
75
76fn 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
88pub 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
100pub 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}