Add aquatic_peer_id crate for peer client parsing

This commit is contained in:
Joakim Frostegård 2023-06-04 16:10:13 +02:00
parent 73a903ed44
commit 3ca21390df
4 changed files with 195 additions and 0 deletions

9
Cargo.lock generated
View file

@ -179,6 +179,15 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "aquatic_peer_id"
version = "0.8.0"
dependencies = [
"compact_str",
"regex",
"serde",
]
[[package]]
name = "aquatic_toml_config"
version = "0.8.0"

View file

@ -5,6 +5,7 @@ members = [
"aquatic_http",
"aquatic_http_load_test",
"aquatic_http_protocol",
"aquatic_peer_id",
"aquatic_toml_config",
"aquatic_toml_config_derive",
"aquatic_udp",
@ -29,6 +30,7 @@ rust-version = "1.64"
aquatic_common = { version = "0.8.0", path = "./aquatic_common" }
aquatic_http_protocol = { version = "0.8.0", path = "./aquatic_http_protocol" }
aquatic_http = { version = "0.8.0", path = "./aquatic_http" }
aquatic_peer_id = { version = "0.8.0", path = "./aquatic_peer_id" }
aquatic_toml_config = { version = "0.8.0", path = "./aquatic_toml_config" }
aquatic_toml_config_derive = { version = "0.8.0", path = "./aquatic_toml_config_derive" }
aquatic_udp_protocol = { version = "0.8.0", path = "./aquatic_udp_protocol" }

View file

@ -0,0 +1,18 @@
[package]
name = "aquatic_peer_id"
description = "BitTorrent peer ID handling"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
readme.workspace = true
rust-version.workspace = true
[lib]
name = "aquatic_peer_id"
[dependencies]
compact_str = "0.7"
regex = "1"
serde = { version = "1", features = ["derive"] }

166
aquatic_peer_id/src/lib.rs Normal file
View file

@ -0,0 +1,166 @@
use std::{fmt::Display, sync::OnceLock};
use compact_str::CompactString;
use regex::bytes::Regex;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct PeerId(pub [u8; 20]);
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PeerClient {
BitTorrent(CompactString),
Deluge(CompactString),
LibTorrentRakshasa(CompactString),
LibTorrentRasterbar(CompactString),
QBitTorrent(CompactString),
Transmission(CompactString),
UTorrent(CompactString),
UTorrentEmbedded(CompactString),
UTorrentMac(CompactString),
UTorrentWeb(CompactString),
Vuze(CompactString),
WebTorrent(CompactString),
WebTorrentDesktop(CompactString),
Mainline(CompactString),
OtherWithPrefixAndVersion {
prefix: CompactString,
version: CompactString,
},
OtherWithPrefix(CompactString),
Other,
}
impl PeerClient {
pub fn from_prefix_and_version(prefix: &[u8], version: &[u8]) -> Option<Self> {
let version = CompactString::from_utf8(version).ok()?;
match prefix {
b"AZ" => Some(Self::Vuze(version)),
b"BT" => Some(Self::BitTorrent(version)),
b"DE" => Some(Self::Deluge(version)),
b"lt" => Some(Self::LibTorrentRakshasa(version)),
b"LT" => Some(Self::LibTorrentRasterbar(version)),
b"qB" => Some(Self::QBitTorrent(version)),
b"TR" => Some(Self::Transmission(version)),
b"UE" => Some(Self::UTorrentEmbedded(version)),
b"UM" => Some(Self::UTorrentMac(version)),
b"UT" => Some(Self::UTorrent(version)),
b"UW" => Some(Self::UTorrentWeb(version)),
b"WD" => Some(Self::WebTorrentDesktop(version)),
b"WW" => Some(Self::WebTorrent(version)),
b"M" => Some(Self::Mainline(version)),
name => Some(Self::OtherWithPrefixAndVersion {
prefix: CompactString::from_utf8(name).ok()?,
version,
}),
}
}
pub fn from_peer_id(peer_id: PeerId) -> Self {
static AZ_RE: OnceLock<Regex> = OnceLock::new();
if let Some(caps) = AZ_RE
.get_or_init(|| {
Regex::new(r"^\-(?P<name>[a-zA-Z]{2})(?P<version>[0-9A-Z]{4})")
.expect("compile AZ_RE regex")
})
.captures(&peer_id.0)
{
if let Some(client) = Self::from_prefix_and_version(&caps["name"], &caps["version"]) {
return client;
}
}
static MAINLINE_RE: OnceLock<Regex> = OnceLock::new();
if let Some(caps) = MAINLINE_RE
.get_or_init(|| {
Regex::new(r"^(?P<name>[a-zA-Z])(?P<version>[0-9\-]{6})\-")
.expect("compile MAINLINE_RE regex")
})
.captures(&peer_id.0)
{
if let Some(client) = Self::from_prefix_and_version(&caps["name"], &caps["version"]) {
return client;
}
}
static PREFIX_RE: OnceLock<Regex> = OnceLock::new();
if let Some(caps) = PREFIX_RE
.get_or_init(|| {
Regex::new(r"^(?P<prefix>[a-zA-Z0-9\-]*)\-").expect("compile PREFIX_RE regex")
})
.captures(&peer_id.0)
{
if let Ok(prefix) = CompactString::from_utf8(&caps["prefix"]) {
return Self::OtherWithPrefix(prefix);
}
}
Self::Other
}
}
impl Display for PeerClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BitTorrent(v) => write!(f, "BitTorrent ({})", v.as_str()),
Self::Deluge(v) => write!(f, "Deluge ({})", v.as_str()),
Self::LibTorrentRakshasa(v) => write!(f, "libTorrent (rakshasa) ({})", v.as_str()),
Self::LibTorrentRasterbar(v) => write!(f, "libtorrent (rasterbar) ({})", v.as_str()),
Self::QBitTorrent(v) => write!(f, "QBitTorrent ({})", v.as_str()),
Self::Transmission(v) => write!(f, "Transmission ({})", v.as_str()),
Self::UTorrent(v) => write!(f, "uTorrent ({})", v.as_str()),
Self::UTorrentEmbedded(v) => write!(f, "uTorrent Embedded ({})", v.as_str()),
Self::UTorrentMac(v) => write!(f, "uTorrent Mac ({})", v.as_str()),
Self::UTorrentWeb(v) => write!(f, "uTorrent Web ({})", v.as_str()),
Self::Vuze(v) => write!(f, "Vuze ({})", v.as_str()),
Self::WebTorrent(v) => write!(f, "WebTorrent ({})", v.as_str()),
Self::WebTorrentDesktop(v) => write!(f, "WebTorrent Desktop ({})", v.as_str()),
Self::Mainline(v) => write!(f, "Mainline ({})", v.as_str()),
Self::OtherWithPrefixAndVersion { prefix, version } => {
write!(f, "Other ({}) ({})", prefix.as_str(), version.as_str())
}
Self::OtherWithPrefix(prefix) => write!(f, "Other ({})", prefix.as_str()),
Self::Other => f.write_str("Other"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_peer_id(bytes: &[u8]) -> PeerId {
let mut peer_id = PeerId([0; 20]);
let len = bytes.len();
(&mut peer_id.0[..len]).copy_from_slice(bytes);
peer_id
}
#[test]
fn test_client_from_peer_id() {
assert_eq!(
PeerClient::from_peer_id(create_peer_id(b"-lt1234-k/asdh3")),
PeerClient::LibTorrentRakshasa("1234".into())
);
assert_eq!(
PeerClient::from_peer_id(create_peer_id(b"M1-2-3--k/asdh3")),
PeerClient::Mainline("1-2-3-".into())
);
assert_eq!(
PeerClient::from_peer_id(create_peer_id(b"M1-23-4-k/asdh3")),
PeerClient::Mainline("1-23-4".into())
);
assert_eq!(
PeerClient::from_peer_id(create_peer_id(b"S3-k/asdh3")),
PeerClient::OtherWithPrefix("S3".into())
);
}
}