tdyne_peer_id_registry/lib.rs
1#![warn(missing_docs)]
2
3//! # BitTorrent peer ID registry/parser/(soon) encoder
4//!
5//! By convention, BitTorrent clients identify themselves and their versions in
6//! peer IDs they send to trackers and other clients.
7//! Unfortunately, there is no single client/version encoding, so over time different clients
8//! adopted different conventions, which made parsing peer IDs difficult.
9//! This crate provides a comprehensive peer ID parser and a registry of all known
10//! BitTorrent clients.
11//!
12//! Peer ID encoding is one of immediate goals, see "Roadmap"
13//! below for details.
14//!
15//! This crate uses [`tdyne_peer_id`] to encode peer IDs.
16//!
17//! Example:
18//!
19//! ```
20//! use tdyne_peer_id::PeerId;
21//! use tdyne_peer_id_registry::parse;
22//!
23//! let peer_id = PeerId::from(b"-TR404Z-*\x00\x01d7xkqq04n");
24//!
25//! let parsed = parse(peer_id).expect("recognised peer ID");
26//! assert_eq!(parsed.client, "Transmission");
27//!
28//! let version = parsed.version
29//! .expect("valid version encoding")
30//! .expect("Transmission does encode a version in its peer ID");
31//! assert_eq!(version, "4.0.4 (Dev)");
32//! ```
33//!
34//! ## Current status
35//!
36//! * used in production on [TORRENTDYNE](https://torrentdyne.com)
37//! * test parity with Webtorrent's [`bittorrent-peerid`](https://github.com/webtorrent/bittorrent-peerid),
38//! the most popular JS implementation of BitTorrent peer ID parsing
39//! * improves on [`bittorrent-peerid`](https://github.com/webtorrent/bittorrent-peerid) by
40//! recognising more clients and versions
41//! * regularly fuzzed to verify absence of panics
42//!
43//! ## Roadmap
44//!
45//! ### Encoding peer IDs
46//!
47//! A peer ID formatting API that only accepts known clients (in release mode) and
48//! takes choices out of peer ID formatting would help the ecosystem to stay more consistent.
49//!
50//! ### Test parity with Transmission
51//!
52//! Transmission has
53//! [an extensive peer ID parser](https://github.com/transmission/transmission/blob/0c52b710ad241c2b68cb9c7a9eb68a8532b290d0/libtransmission/clients.cc).
54//! Right now the Venn diagram of clients that `tdyne_peer_id_registry` and Transmission
55//! can handle is two intersecting circles. It needs to get closer to two concentric rings.
56//!
57//! ### Structured allocation-free API
58//!
59//! `tdyne_peer_id_registry` is designed to parse into an allocation-free tree
60//! of structs and enums. In the current version the tree is not exposed as it is not stable yet
61//! and changing it would be a breaking change. Instead, the API immediately turns the tree
62//! into strings, the lowest common denominator. However, exposing the tree directly
63//! would help projects that need to act on the information from the peer ID,
64//! as they would be able to work directly with the structures instead of re-parsing strings.
65
66use crate::client::Client;
67use crate::errors::{ClientParsingError, VersionParsingError};
68use tdyne_peer_id::PeerId;
69
70mod client;
71mod client_styles;
72///
73pub mod errors;
74mod known_clients;
75mod version;
76mod version_utils;
77
78/// Human-readable representation of the client and the version (if it exists) encoded
79/// in the parsed peer ID.
80#[derive(Debug, Clone)]
81pub struct Parsed {
82 /// Name of the client. Can include suffixes such as `(Dev)` or `(Beta)`.
83 pub client: String,
84 /// Version, if any. The outer `Result` encodes parsing errors, while the internal
85 /// `Option` can be `None` if the recognised client doesn't encode a version.
86 /// Can also be set to `[unknown version]` if the version exists, but is not parsed yet.
87 /// If you see this value, please consider reporting the corresponding peer ID
88 /// in a github issue.
89 pub version: Result<Option<String>, VersionParsingError>,
90}
91
92/// The main entry point for the library. Returns [`Parsed`] with human-readable string
93/// representation of the parsed client and version. See [`Parsed`] for more details.
94///
95/// Example:
96///
97/// ```
98/// use tdyne_peer_id::PeerId;
99/// use tdyne_peer_id_registry::{parse, Parsed};
100///
101/// let parsed = parse(PeerId::from(b"-TR4040-xxxxxxxxxxxx"));
102/// assert_eq!(
103/// format!("{parsed:?}").as_str(),
104/// r#"Ok(Parsed { client: "Transmission", version: Ok(Some("4.0.4")) })"#
105/// );
106/// ```
107pub fn parse(peer_id: PeerId) -> Result<Parsed, ClientParsingError> {
108 let client = Client::try_from(peer_id)?;
109
110 Ok(Parsed {
111 client: client.to_canonical().to_string(),
112 version: parse_version(client, peer_id),
113 })
114}
115
116fn parse_version(client: Client, peer_id: PeerId) -> Result<Option<String>, VersionParsingError> {
117 Ok(client.parse_version(peer_id)?.map(|x| x.to_string()))
118}