Every release since 14 has been a major, because the actor mailboxes, the share index's struct and the CLI's command enum were all exhaustive public API: adding a peer message, a scanned field or a subcommand read as a breaking change to the library. The actors, dispatcher, peer plumbing and download store are how the client is built, not what a host programs against, so they move inside the crate; PeerMessage, SharedFile and the CLI command enums are non-exhaustive; ConnectionType is re-exported at the root for the one thing hosts used it for. A one-time break so that the next additions are not.
206 lines
5.8 KiB
Rust
206 lines
5.8 KiB
Rust
mod download_peer;
|
|
pub mod listen;
|
|
pub mod upload_peer;
|
|
|
|
// Export actor types
|
|
pub use crate::actor::peer_actor::PeerMessage;
|
|
|
|
pub use download_peer::part_path_of;
|
|
pub use download_peer::{DownloadError, DownloadPeer};
|
|
|
|
use crate::message::Message;
|
|
use core::fmt;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum ConnectionType {
|
|
P,
|
|
F,
|
|
D,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ParseConnectionTypeError;
|
|
|
|
impl fmt::Display for ParseConnectionTypeError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "invalid connection type")
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ParseConnectionTypeError {}
|
|
|
|
impl FromStr for ConnectionType {
|
|
type Err = ParseConnectionTypeError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"P" => Ok(Self::P),
|
|
"F" => Ok(Self::F),
|
|
"D" => Ok(Self::D),
|
|
_ => Err(ParseConnectionTypeError),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ConnectionType {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let s = match self {
|
|
Self::P => "P",
|
|
Self::F => "F",
|
|
Self::D => "D",
|
|
};
|
|
write!(f, "{s}")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Peer {
|
|
pub username: String,
|
|
pub connection_type: ConnectionType,
|
|
pub host: String,
|
|
pub port: u32,
|
|
pub token: Option<u32>,
|
|
pub privileged: Option<u8>,
|
|
pub unknown: Option<u8>,
|
|
pub obfuscated_port: Option<u16>,
|
|
}
|
|
impl Peer {
|
|
#[allow(clippy::too_many_arguments)]
|
|
#[must_use]
|
|
pub const fn new(
|
|
username: String,
|
|
connection_type: ConnectionType,
|
|
host: String,
|
|
port: u32,
|
|
token: Option<u32>,
|
|
privileged: u8,
|
|
unknown: u8,
|
|
obfuscated_port: u16,
|
|
) -> Self {
|
|
Self {
|
|
username,
|
|
connection_type,
|
|
host,
|
|
port,
|
|
token,
|
|
privileged: Some(privileged),
|
|
unknown: Some(unknown),
|
|
obfuscated_port: Some(obfuscated_port),
|
|
}
|
|
}
|
|
pub fn new_from_message(message: &mut Message) -> Option<Self> {
|
|
let username = message.read_string();
|
|
// The connection type is an untrusted string; an unknown value must not
|
|
// panic the actor that parses it.
|
|
let connection_type = message.read_string().parse().ok()?;
|
|
|
|
let ip = [
|
|
message.read_int8(),
|
|
message.read_int8(),
|
|
message.read_int8(),
|
|
message.read_int8(),
|
|
];
|
|
let host = format!("{}.{}.{}.{}", ip[3], ip[2], ip[1], ip[0]);
|
|
|
|
let (port, token, privileged, unknown, obfuscated_port) = (
|
|
message.read_int32(),
|
|
message.read_int32(),
|
|
message.read_int8(),
|
|
message.read_int8(),
|
|
message.read_int8(),
|
|
);
|
|
|
|
Some(Self {
|
|
username,
|
|
connection_type,
|
|
host,
|
|
port,
|
|
token: Some(token),
|
|
privileged: Some(privileged),
|
|
unknown: Some(unknown),
|
|
obfuscated_port: Some(u16::from(obfuscated_port)),
|
|
})
|
|
}
|
|
}
|
|
#[test]
|
|
fn new_accepts_a_full_range_obfuscated_port() {
|
|
// A real obfuscated port is a u16; it must be stored without truncation.
|
|
// (The GetPeerAddressResponse path previously narrowed it to u8 and
|
|
// panicked for any value > 255, crashing the client's ops thread.)
|
|
let peer = Peer::new(
|
|
"u".to_string(),
|
|
ConnectionType::P,
|
|
"1.2.3.4".to_string(),
|
|
1,
|
|
None,
|
|
0,
|
|
0,
|
|
51770,
|
|
);
|
|
assert_eq!(peer.obfuscated_port, Some(51770));
|
|
}
|
|
|
|
#[test]
|
|
fn new_from_message_returns_none_on_invalid_connection_type() {
|
|
// username "ab", connection_type "X" (not P/F/D) from an untrusted server.
|
|
let mut data: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0];
|
|
data.extend([2, 0, 0, 0, 97, 98]); // username = "ab"
|
|
data.extend([1, 0, 0, 0, 88]); // connection_type = "X"
|
|
data.extend([1, 2, 3, 4]); // ip
|
|
data.extend([0, 0, 0, 0]); // port
|
|
data.extend([0, 0, 0, 0]); // token
|
|
data.extend([0, 0, 0]); // privileged, unknown, obfuscated_port
|
|
let mut message = Message::new_with_data(data);
|
|
message.set_pointer(8);
|
|
|
|
assert!(Peer::new_from_message(&mut message).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_from_message() {
|
|
let data: Vec<u8> = [
|
|
36, 0, 0, 0, 18, 0, 0, 0, 2, 0, 0, 0, 100, 112, 1, 0, 0, 0, 80, 27,
|
|
231, 37, 45, 186, 8, 0, 0, 178, 78, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
]
|
|
.to_vec();
|
|
let mut message = Message::new_with_data(data);
|
|
message.set_pointer(8);
|
|
|
|
let peer = Peer::new_from_message(&mut message).unwrap();
|
|
|
|
assert_eq!(peer.username, "dp");
|
|
assert!(matches!(peer.connection_type, ConnectionType::P));
|
|
assert_eq!(peer.host, "45.37.231.27");
|
|
assert_eq!(peer.port, 2234);
|
|
assert_eq!(peer.token, Some(1658546));
|
|
assert_eq!(peer.privileged, Some(0));
|
|
assert_eq!(peer.unknown, Some(0));
|
|
assert_eq!(peer.obfuscated_port, Some(0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_new_from_message2() {
|
|
let data: Vec<u8> = [
|
|
42, 0, 0, 0, 18, 0, 0, 0, 8, 0, 0, 0, 103, 114, 97, 110, 100, 112, 97,
|
|
103, 1, 0, 0, 0, 80, 137, 128, 193, 68, 187, 8, 0, 0, 58, 16, 0, 0, 0,
|
|
1, 0, 0, 0, 188, 8, 0, 0,
|
|
]
|
|
.to_vec();
|
|
let mut message = Message::new_with_data(data);
|
|
message.set_pointer(8);
|
|
|
|
println!("code: {}", message.get_message_code());
|
|
|
|
let peer = Peer::new_from_message(&mut message).unwrap();
|
|
|
|
assert_eq!(peer.username, "grandpag");
|
|
assert!(matches!(peer.connection_type, ConnectionType::P));
|
|
assert_eq!(peer.host, "68.193.128.137");
|
|
assert_eq!(peer.port, 2235);
|
|
assert_eq!(peer.token, Some(4154));
|
|
assert_eq!(peer.privileged, Some(0));
|
|
assert_eq!(peer.unknown, Some(1));
|
|
assert_eq!(peer.obfuscated_port, Some(0));
|
|
}
|