irontide_format/
peer_flags.rs1use irontide_session::{PeerInfo, PeerSource};
12
13#[must_use]
18pub fn peer_flags(p: &PeerInfo) -> Vec<(char, &'static str)> {
19 let mut flags = Vec::with_capacity(8);
20 if !p.peer_choking && p.num_pieces > 0 && p.am_interested {
21 flags.push(('D', "Downloading from peer"));
22 }
23 if p.am_interested && p.peer_choking {
24 flags.push(('d', "We want data but peer is choking us"));
25 }
26 if !p.am_choking && p.peer_interested {
27 flags.push(('U', "Uploading to peer"));
28 }
29 if p.peer_interested && p.am_choking {
30 flags.push(('u', "Peer wants data, we are choking them"));
31 }
32 if p.am_choking {
33 flags.push(('K', "We are choking the peer"));
34 }
35 if p.am_interested {
36 flags.push(('?', "We are interested in the peer"));
37 }
38 if p.snubbed {
39 flags.push(('S', "Peer is snubbed"));
40 }
41 if p.is_optimistic {
42 flags.push(('O', "Optimistic unchoke slot"));
43 }
44 if p.source == PeerSource::Incoming {
45 flags.push(('I', "Incoming connection"));
46 }
47 if p.source == PeerSource::Dht {
48 flags.push(('H', "Discovered via DHT"));
49 }
50 if p.source == PeerSource::Pex {
51 flags.push(('X', "Discovered via PeX (BEP 11)"));
52 }
53 if p.source == PeerSource::Lsd {
54 flags.push(('L', "Discovered via LSD (BEP 14)"));
55 }
56 if p.is_encrypted {
57 flags.push(('E', "Encrypted connection (MSE/PE)"));
58 }
59 if p.uses_utp {
60 flags.push(('P', "Using uTP (BEP 29)"));
61 }
62 if p.supports_fast {
63 flags.push(('F', "Supports fast extension (BEP 6)"));
64 }
65 flags
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
72
73 fn baseline_peer() -> PeerInfo {
74 PeerInfo {
75 addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 6881),
76 client: String::new(),
77 peer_choking: false,
78 peer_interested: false,
79 am_choking: false,
80 am_interested: false,
81 download_rate: 0,
82 upload_rate: 0,
83 num_pieces: 0,
84 source: PeerSource::Tracker,
85 supports_fast: false,
86 upload_only: false,
87 snubbed: false,
88 connected_duration_secs: 0,
89 num_pending_requests: 0,
90 num_incoming_requests: 0,
91 is_optimistic: false,
92 is_encrypted: false,
93 uses_utp: false,
94 uses_holepunch: false,
95 in_flight_requests: 0,
96 target_pipeline_depth: 0,
97 }
98 }
99
100 #[test]
101 fn glyph_d_downloading() {
102 let mut p = baseline_peer();
103 p.am_interested = true;
104 p.peer_choking = false;
105 p.num_pieces = 1;
106 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
107 assert!(glyphs.contains(&'D'));
108 assert!(glyphs.contains(&'?'));
109 }
110
111 #[test]
112 fn glyph_lowercase_d_choked_but_interested() {
113 let mut p = baseline_peer();
114 p.am_interested = true;
115 p.peer_choking = true;
116 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
117 assert!(glyphs.contains(&'d'));
118 assert!(!glyphs.contains(&'D'));
119 }
120
121 #[test]
122 fn glyph_u_uploading_and_lowercase_u_choking_them() {
123 let mut p = baseline_peer();
125 p.peer_interested = true;
126 p.am_choking = false;
127 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
128 assert!(glyphs.contains(&'U'));
129 assert!(!glyphs.contains(&'u'));
130
131 let mut p = baseline_peer();
133 p.peer_interested = true;
134 p.am_choking = true;
135 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
136 assert!(glyphs.contains(&'u'));
137 assert!(glyphs.contains(&'K'));
138 }
139
140 #[test]
141 fn glyph_source_letters() {
142 let cases = [
143 (PeerSource::Incoming, 'I'),
144 (PeerSource::Dht, 'H'),
145 (PeerSource::Pex, 'X'),
146 (PeerSource::Lsd, 'L'),
147 ];
148 for (src, expected) in cases {
149 let mut p = baseline_peer();
150 p.source = src;
151 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
152 assert!(
153 glyphs.contains(&expected),
154 "source {src:?} should produce glyph {expected}"
155 );
156 }
157 }
158
159 #[test]
160 fn glyph_capability_flags_independent() {
161 let mut p = baseline_peer();
162 p.is_optimistic = true;
163 p.is_encrypted = true;
164 p.uses_utp = true;
165 p.supports_fast = true;
166 p.snubbed = true;
167 let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
168 for g in ['O', 'E', 'P', 'F', 'S'] {
169 assert!(glyphs.contains(&g), "missing glyph {g}");
170 }
171 }
172
173 #[test]
174 fn empty_baseline_peer_has_no_active_glyphs() {
175 let p = baseline_peer();
176 let glyphs = peer_flags(&p);
177 assert!(glyphs.is_empty(), "got glyphs: {glyphs:?}");
180 }
181}