use irontide_session::{PeerInfo, PeerSource};
#[must_use]
pub fn peer_flags(p: &PeerInfo) -> Vec<(char, &'static str)> {
let mut flags = Vec::with_capacity(8);
if !p.peer_choking && p.num_pieces > 0 && p.am_interested {
flags.push(('D', "Downloading from peer"));
}
if p.am_interested && p.peer_choking {
flags.push(('d', "We want data but peer is choking us"));
}
if !p.am_choking && p.peer_interested {
flags.push(('U', "Uploading to peer"));
}
if p.peer_interested && p.am_choking {
flags.push(('u', "Peer wants data, we are choking them"));
}
if p.am_choking {
flags.push(('K', "We are choking the peer"));
}
if p.am_interested {
flags.push(('?', "We are interested in the peer"));
}
if p.snubbed {
flags.push(('S', "Peer is snubbed"));
}
if p.is_optimistic {
flags.push(('O', "Optimistic unchoke slot"));
}
if p.source == PeerSource::Incoming {
flags.push(('I', "Incoming connection"));
}
if p.source == PeerSource::Dht {
flags.push(('H', "Discovered via DHT"));
}
if p.source == PeerSource::Pex {
flags.push(('X', "Discovered via PeX (BEP 11)"));
}
if p.source == PeerSource::Lsd {
flags.push(('L', "Discovered via LSD (BEP 14)"));
}
if p.is_encrypted {
flags.push(('E', "Encrypted connection (MSE/PE)"));
}
if p.uses_utp {
flags.push(('P', "Using uTP (BEP 29)"));
}
if p.supports_fast {
flags.push(('F', "Supports fast extension (BEP 6)"));
}
flags
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
fn baseline_peer() -> PeerInfo {
PeerInfo {
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 6881),
client: String::new(),
peer_choking: false,
peer_interested: false,
am_choking: false,
am_interested: false,
download_rate: 0,
upload_rate: 0,
num_pieces: 0,
source: PeerSource::Tracker,
supports_fast: false,
upload_only: false,
snubbed: false,
connected_duration_secs: 0,
num_pending_requests: 0,
num_incoming_requests: 0,
is_optimistic: false,
is_encrypted: false,
uses_utp: false,
uses_holepunch: false,
in_flight_requests: 0,
target_pipeline_depth: 0,
}
}
#[test]
fn glyph_d_downloading() {
let mut p = baseline_peer();
p.am_interested = true;
p.peer_choking = false;
p.num_pieces = 1;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
assert!(glyphs.contains(&'D'));
assert!(glyphs.contains(&'?'));
}
#[test]
fn glyph_lowercase_d_choked_but_interested() {
let mut p = baseline_peer();
p.am_interested = true;
p.peer_choking = true;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
assert!(glyphs.contains(&'d'));
assert!(!glyphs.contains(&'D'));
}
#[test]
fn glyph_u_uploading_and_lowercase_u_choking_them() {
let mut p = baseline_peer();
p.peer_interested = true;
p.am_choking = false;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
assert!(glyphs.contains(&'U'));
assert!(!glyphs.contains(&'u'));
let mut p = baseline_peer();
p.peer_interested = true;
p.am_choking = true;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
assert!(glyphs.contains(&'u'));
assert!(glyphs.contains(&'K'));
}
#[test]
fn glyph_source_letters() {
let cases = [
(PeerSource::Incoming, 'I'),
(PeerSource::Dht, 'H'),
(PeerSource::Pex, 'X'),
(PeerSource::Lsd, 'L'),
];
for (src, expected) in cases {
let mut p = baseline_peer();
p.source = src;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
assert!(
glyphs.contains(&expected),
"source {src:?} should produce glyph {expected}"
);
}
}
#[test]
fn glyph_capability_flags_independent() {
let mut p = baseline_peer();
p.is_optimistic = true;
p.is_encrypted = true;
p.uses_utp = true;
p.supports_fast = true;
p.snubbed = true;
let glyphs: Vec<char> = peer_flags(&p).into_iter().map(|(c, _)| c).collect();
for g in ['O', 'E', 'P', 'F', 'S'] {
assert!(glyphs.contains(&g), "missing glyph {g}");
}
}
#[test]
fn empty_baseline_peer_has_no_active_glyphs() {
let p = baseline_peer();
let glyphs = peer_flags(&p);
assert!(glyphs.is_empty(), "got glyphs: {glyphs:?}");
}
}