Skip to main content

mtorrent_core/pwp/
states.rs

1use core::fmt;
2use serde::{Serialize, Serializer};
3use std::collections::{HashMap, HashSet};
4use std::net::SocketAddr;
5use tokio::time::Instant;
6
7/// Indicates how the peer was discovered.
8#[derive(Default, Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize)]
9pub enum PeerOrigin {
10    Tracker,
11    Listener,
12    Pex,
13    Dht,
14    #[default]
15    Other,
16}
17
18#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize)]
19pub enum TransportProto {
20    Tcp,
21    Utp,
22}
23
24/// The current state of the download of data from a remote peer.
25#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct DownloadState {
28    pub am_interested: bool,
29    pub peer_choking: bool,
30    pub bytes_received: usize,
31    pub last_bitrate_bps: usize,
32}
33
34impl Default for DownloadState {
35    fn default() -> Self {
36        Self {
37            am_interested: false,
38            peer_choking: true,
39            bytes_received: 0,
40            last_bitrate_bps: 0,
41        }
42    }
43}
44
45impl fmt::Display for DownloadState {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(
48            f,
49            "am_interested={:<5} peer_choking={:<5} rx_bps={:<8} bytes_recv={:<12}",
50            self.am_interested, self.peer_choking, self.last_bitrate_bps, self.bytes_received
51        )?;
52        Ok(())
53    }
54}
55
56/// The current state of the update of data to a remote peer.
57#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
58#[serde(rename_all = "camelCase")]
59pub struct UploadState {
60    pub am_choking: bool,
61    pub peer_interested: bool,
62    pub bytes_sent: usize,
63    pub last_bitrate_bps: usize,
64}
65
66impl Default for UploadState {
67    fn default() -> Self {
68        Self {
69            am_choking: true,
70            peer_interested: false,
71            bytes_sent: 0,
72            last_bitrate_bps: 0,
73        }
74    }
75}
76
77impl fmt::Display for UploadState {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(
80            f,
81            "peer_interested={:<5} am_choking={:<5} tx_bps={:<8} bytes_sent={:<12}",
82            self.peer_interested, self.am_choking, self.last_bitrate_bps, self.bytes_sent
83        )?;
84        Ok(())
85    }
86}
87
88/// The current state of a connected peer.
89#[derive(Clone, PartialEq, Eq, Debug)]
90pub struct PeerState {
91    pub download: DownloadState,
92    pub upload: UploadState,
93    pub extensions: Option<Box<super::ExtendedHandshake>>,
94    pub origin: PeerOrigin,
95    pub transport: Option<TransportProto>,
96    pub encryption: bool,
97    pub last_download_time: Instant,
98    pub last_upload_time: Instant,
99}
100
101impl Default for PeerState {
102    fn default() -> Self {
103        Self {
104            download: Default::default(),
105            upload: Default::default(),
106            extensions: None,
107            encryption: false,
108            origin: Default::default(),
109            transport: None,
110            last_download_time: Instant::now(),
111            last_upload_time: Instant::now(),
112        }
113    }
114}
115
116impl Serialize for PeerState {
117    /// [`PeerState`] is serialized in the following format:
118    /// ```json
119    /// {
120    ///   "download": {
121    ///     "amInterested": false,
122    ///     "peerChoking": true,
123    ///     "bytesReceived": 0,
124    ///     "lastBitrateBps": 0
125    ///   },
126    ///   "upload": {
127    ///     "peerInterested": false,
128    ///     "amChoking": true,
129    ///     "bytesSent": 0,
130    ///     "lastBitrateBps": 0
131    ///   },
132    ///   "client": "n/a",
133    ///   "reqq": null,
134    ///   "origin": "Tracker",
135    ///   "proto": null,
136    ///   "encrypted": false
137    /// }
138    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
139    where
140        S: Serializer,
141    {
142        #[derive(Serialize)]
143        struct Data<'a> {
144            download: &'a DownloadState,
145            upload: &'a UploadState,
146            client: &'a str,
147            reqq: Option<usize>,
148            origin: PeerOrigin,
149            proto: Option<TransportProto>,
150            encrypted: bool,
151        }
152        let data = Data {
153            download: &self.download,
154            upload: &self.upload,
155            client: self
156                .extensions
157                .as_ref()
158                .and_then(|ext| ext.client_type.as_deref())
159                .unwrap_or("n/a"),
160            reqq: self.extensions.as_ref().and_then(|ext| ext.request_limit),
161            origin: self.origin,
162            proto: self.transport,
163            encrypted: self.encryption,
164        };
165        data.serialize(serializer)
166    }
167}
168
169/// Collections of states of the connected peers.
170#[derive(Default, Debug)]
171pub struct PeerStates {
172    peers: HashMap<SocketAddr, PeerState>,
173    seeders: HashSet<SocketAddr>,
174    leeches: HashSet<SocketAddr>,
175    previously_uploaded_bytes: usize,
176}
177
178impl PeerStates {
179    /// Update the download state of the peer at `remote_ip`.
180    pub fn update_download(&mut self, remote_ip: &SocketAddr, new_state: &DownloadState) {
181        let state = self.peers.entry(*remote_ip).or_default();
182        if new_state.bytes_received > state.download.bytes_received {
183            state.last_download_time = Instant::now();
184        }
185        state.download = new_state.clone();
186        if state.download.am_interested && !state.download.peer_choking {
187            self.seeders.insert(*remote_ip);
188        } else {
189            self.seeders.remove(remote_ip);
190        }
191    }
192
193    /// Update the upload state of the peer at `remote_ip`.
194    pub fn update_upload(&mut self, remote_ip: &SocketAddr, new_state: &UploadState) {
195        let state = self.peers.entry(*remote_ip).or_default();
196        if new_state.bytes_sent > state.upload.bytes_sent {
197            state.last_upload_time = Instant::now();
198        }
199        state.upload = new_state.clone();
200        if state.upload.peer_interested && !state.upload.am_choking {
201            self.leeches.insert(*remote_ip);
202        } else {
203            self.leeches.remove(remote_ip);
204        }
205    }
206
207    /// Update the extended handshake received from the peer at `remote_ip`.
208    pub fn set_extended_handshake(
209        &mut self,
210        remote_ip: &SocketAddr,
211        extended_handshake: Box<super::ExtendedHandshake>,
212    ) {
213        let state = self.peers.entry(*remote_ip).or_default();
214        if state.extensions.is_none() {
215            state.extensions = Some(extended_handshake);
216        }
217    }
218
219    /// Update how the peer at `remote_ip` was discovered.
220    pub fn set_info(
221        &mut self,
222        remote_ip: &SocketAddr,
223        origin: PeerOrigin,
224        transport: TransportProto,
225        encryption: bool,
226    ) {
227        let state = self.peers.entry(*remote_ip).or_default();
228        state.origin = origin;
229        state.transport = Some(transport);
230        state.encryption = encryption;
231    }
232
233    /// Erase peer.
234    pub fn remove_peer(&mut self, remote_ip: &SocketAddr) {
235        if let Some(state) = self.peers.get(remote_ip) {
236            self.previously_uploaded_bytes += state.upload.bytes_sent;
237            self.peers.remove(remote_ip);
238            self.seeders.remove(remote_ip);
239            self.leeches.remove(remote_ip);
240        }
241    }
242
243    /// Get peer state for a given remote socket addr.
244    pub fn get(&self, peer_ip: &SocketAddr) -> Option<&PeerState> {
245        self.peers.get(peer_ip)
246    }
247
248    /// Total number of seeding peers.
249    pub fn seeders_count(&self) -> usize {
250        self.seeders.len()
251    }
252
253    /// Total number of leeching peers.
254    pub fn leeches_count(&self) -> usize {
255        self.leeches.len()
256    }
257
258    /// Addresses of all leeching peers.
259    pub fn leeches(&self) -> &HashSet<SocketAddr> {
260        &self.leeches
261    }
262
263    /// States of all connected peers.
264    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&SocketAddr, &PeerState)> {
265        self.peers.iter()
266    }
267
268    /// Total number of bytes uploaded to all peers during the entire lifetime (including peers that
269    /// have been erased).
270    pub fn uploaded_bytes(&self) -> usize {
271        self.previously_uploaded_bytes
272            + self.peers.values().map(|state| state.upload.bytes_sent).sum::<usize>()
273    }
274}