feat: log in with soulseek-rs's own client major version (176) (#13)
The Login message hardcoded SoulseekQt's major version 157, so the server could not tell soulseek-rs apart from the official client. The protocol document now reserves a unique major version per project, so claim 176 and send it via a new ClientVersion on ClientSettings. Clients and bots built on the library keep major 176 and pick their own minor version (default 1); the reference CLI/TUI logs in with minor 100. Refs #12
This commit is contained in:
@@ -24,7 +24,7 @@ use crate::message::{Handlers, MessageType};
|
||||
use crate::message::{Message, MessageReader};
|
||||
use crate::peer::ConnectionType;
|
||||
use crate::peer::Peer;
|
||||
use crate::types::{RoomEvent, RoomInfo};
|
||||
use crate::types::{ClientVersion, RoomEvent, RoomInfo};
|
||||
use crate::utils::lock::RwLockExt;
|
||||
|
||||
use std::io::{self, Error, Write};
|
||||
@@ -159,6 +159,7 @@ pub enum ServerMessage {
|
||||
Login {
|
||||
username: String,
|
||||
password: String,
|
||||
version: ClientVersion,
|
||||
response: std::sync::mpsc::Sender<Result<bool, SoulseekRs>>,
|
||||
},
|
||||
FileSearch {
|
||||
@@ -533,9 +534,10 @@ impl ServerActor {
|
||||
ServerMessage::Login {
|
||||
username,
|
||||
password,
|
||||
version,
|
||||
response,
|
||||
} => {
|
||||
self.handle_login(username, password, response);
|
||||
self.handle_login(username, password, version, response);
|
||||
}
|
||||
ServerMessage::FileSearch { token, query } => {
|
||||
self.file_search(token, &query);
|
||||
@@ -640,10 +642,11 @@ impl ServerActor {
|
||||
&mut self,
|
||||
username: String,
|
||||
password: String,
|
||||
version: ClientVersion,
|
||||
response: std::sync::mpsc::Sender<Result<bool, SoulseekRs>>,
|
||||
) {
|
||||
self.queue_message(MessageFactory::build_login_message(
|
||||
&username, &password,
|
||||
&username, &password, version,
|
||||
));
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
@@ -108,6 +108,7 @@ impl Client {
|
||||
let _ = handle.send(ServerMessage::Login {
|
||||
username: self.username.clone(),
|
||||
password: self.password.clone(),
|
||||
version: self.version,
|
||||
response: tx,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::actor::server_actor::{
|
||||
};
|
||||
use crate::download_store::{DownloadStore, collect_failed_tokens};
|
||||
use crate::types::{
|
||||
DownloadMetadata, DownloadStatus, RoomEvent, RoomInfo, UserInfo,
|
||||
UserPresence, UserStats, UserStatus,
|
||||
ClientVersion, DownloadMetadata, DownloadStatus, RoomEvent, RoomInfo,
|
||||
UserInfo, UserPresence, UserStats, UserStatus,
|
||||
};
|
||||
use crate::utils::logger;
|
||||
use crate::{
|
||||
@@ -139,6 +139,10 @@ pub struct ClientSettings {
|
||||
/// Directories whose files are shared with (uploaded to) other peers.
|
||||
/// Empty means nothing is shared.
|
||||
pub shared_directories: Vec<String>,
|
||||
/// The version reported to the server on login. Defaults to the
|
||||
/// soulseek-rs major version with minor version 1; clients and bots built
|
||||
/// on this library should pick their own minor version.
|
||||
pub version: ClientVersion,
|
||||
}
|
||||
|
||||
impl ClientSettings {
|
||||
@@ -166,6 +170,7 @@ impl Default for ClientSettings {
|
||||
enable_listen: true,
|
||||
listen_port: DEFAULT_LISTEN_PORT,
|
||||
shared_directories: Vec::new(),
|
||||
version: ClientVersion::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,6 +767,7 @@ pub struct Client {
|
||||
address: PeerAddress,
|
||||
username: String,
|
||||
password: String,
|
||||
version: ClientVersion,
|
||||
shared_directories: Vec<String>,
|
||||
server_handle: Option<ActorHandle<ServerMessage>>,
|
||||
context: Arc<RwLock<ClientContext>>,
|
||||
@@ -784,6 +790,7 @@ impl Client {
|
||||
address: settings.server_address,
|
||||
username: settings.username,
|
||||
password: settings.password,
|
||||
version: settings.version,
|
||||
shared_directories: settings.shared_directories,
|
||||
context: Arc::new(RwLock::new(ClientContext::new())),
|
||||
server_handle: None,
|
||||
|
||||
@@ -15,9 +15,9 @@ pub mod utils;
|
||||
pub mod prelude {
|
||||
pub use crate::actor::server_actor::PeerAddress;
|
||||
pub use crate::types::{
|
||||
DownloadStatus, File, RoomEvent, RoomInfo, Search, SearchResult,
|
||||
Transfer, UploadInfo, UploadStatus, UserInfo, UserPresence, UserStats,
|
||||
UserStatus,
|
||||
ClientVersion, DownloadStatus, File, RoomEvent, RoomInfo, Search,
|
||||
SearchResult, Transfer, UploadInfo, UploadStatus, UserInfo,
|
||||
UserPresence, UserStats, UserStatus,
|
||||
};
|
||||
pub use crate::{debug, error, info, trace, warn};
|
||||
}
|
||||
@@ -28,6 +28,7 @@ pub use client::{Client, ClientSettings};
|
||||
pub use error::{Result, SoulseekRs};
|
||||
pub use message::peer::SharedDirectory;
|
||||
pub use types::{
|
||||
DownloadStatus, File, RoomEvent, RoomInfo, Search, SearchResult, Transfer,
|
||||
UploadInfo, UploadStatus, UserInfo, UserPresence, UserStats, UserStatus,
|
||||
ClientVersion, DownloadStatus, File, RoomEvent, RoomInfo, Search,
|
||||
SearchResult, Transfer, UploadInfo, UploadStatus, UserInfo, UserPresence,
|
||||
UserStats, UserStatus,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use crate::{
|
||||
message::Message, peer::ConnectionType, types::Transfer, utils::md5::md5,
|
||||
message::Message,
|
||||
peer::ConnectionType,
|
||||
types::{ClientVersion, Transfer},
|
||||
utils::md5::md5,
|
||||
};
|
||||
|
||||
pub struct MessageFactory;
|
||||
@@ -13,23 +16,11 @@ impl MessageFactory {
|
||||
message
|
||||
}
|
||||
#[must_use]
|
||||
pub fn build_login_message(username: &str, password: &str) -> Message {
|
||||
// Message::new_with_data(
|
||||
// [
|
||||
// 1, 0, 0, 0, 20, 0, 0, 0, 105, 110, 115, 97, 110, 101, 95, 105, 110, 95, 116, 104, 101,
|
||||
// 95, 98, 114, 97, 105, 110, 50, 8, 0, 0, 0, 49, 51, 51, 55, 53, 49, 51, 55, 160, 0, 0,
|
||||
// 0, 32, 0, 0, 0, 50, 101, 100, 102, 53, 49, 100, 48, 51, 55, 57, 52, 51, 55, 56, 102,
|
||||
// 56, 98, 98, 54, 51, 49, 48, 100, 52, 54, 48, 99, 50, 50, 98, 49, 17, 0, 0,
|
||||
// 0,
|
||||
// //0, // 84, 0, 0, 0, 1, 0, 0, 0, 20, 0, 0, 0, 105, 110, 115, 97, 110, 101, 95, 105, 110, 95,
|
||||
// // 116, 104, 101, 95, 98, 114, 97, 105, 110, 50, 8, 0, 0, 0, 49, 51, 51, 55, 53, 49, 51,
|
||||
// // 55, 160, 0, 0, 0, 32, 0, 0, 0, 50, 101, 100, 102, 53, 49, 100, 48, 51, 55, 57, 52, 51,
|
||||
// // 55, 56, 102, 56, 98, 98, 54, 51, 49, 48, 100, 52, 54, 48, 99, 50, 50, 98, 49, 17, 0, 0,
|
||||
// // 0,
|
||||
// ]
|
||||
// .to_vec(),
|
||||
// )
|
||||
// .clone()fac
|
||||
pub fn build_login_message(
|
||||
username: &str,
|
||||
password: &str,
|
||||
version: ClientVersion,
|
||||
) -> Message {
|
||||
let hash = md5([username, password].join("").as_str());
|
||||
|
||||
let mut message = Message::new();
|
||||
@@ -38,9 +29,9 @@ impl MessageFactory {
|
||||
.write_int32(1)
|
||||
.write_string(username)
|
||||
.write_string(password)
|
||||
.write_int32(157) // version
|
||||
.write_int32(version.major)
|
||||
.write_string(&hash)
|
||||
.write_int32(100)
|
||||
.write_int32(version.minor)
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -268,15 +259,18 @@ fn test_build_watch_user() {
|
||||
|
||||
#[test]
|
||||
fn test_build_login_message() {
|
||||
let message =
|
||||
MessageFactory::build_login_message("insane_in_the_brain2", "13375137");
|
||||
let message = MessageFactory::build_login_message(
|
||||
"insane_in_the_brain2",
|
||||
"13375137",
|
||||
ClientVersion::default(),
|
||||
);
|
||||
|
||||
let expect: Vec<u8> = [
|
||||
1, 0, 0, 0, 20, 0, 0, 0, 105, 110, 115, 97, 110, 101, 95, 105, 110, 95,
|
||||
116, 104, 101, 95, 98, 114, 97, 105, 110, 50, 8, 0, 0, 0, 49, 51, 51,
|
||||
55, 53, 49, 51, 55, 157, 0, 0, 0, 32, 0, 0, 0, 50, 101, 100, 102, 53,
|
||||
55, 53, 49, 51, 55, 176, 0, 0, 0, 32, 0, 0, 0, 50, 101, 100, 102, 53,
|
||||
49, 100, 48, 51, 55, 57, 52, 51, 55, 56, 102, 56, 98, 98, 54, 51, 49,
|
||||
48, 100, 52, 54, 48, 99, 50, 50, 98, 49, 100, 0, 0, 0,
|
||||
48, 100, 52, 54, 48, 99, 50, 50, 98, 49, 1, 0, 0, 0,
|
||||
]
|
||||
.to_vec();
|
||||
|
||||
|
||||
@@ -10,6 +10,39 @@ pub struct File {
|
||||
pub size: u64,
|
||||
pub attribs: HashMap<u32, u32>,
|
||||
}
|
||||
/// The client version sent to the server on login.
|
||||
///
|
||||
/// Major versions identify client projects on the Soulseek network and are
|
||||
/// reserved per project for the project's lifetime:
|
||||
/// <https://nicotine-plus.org/doc/SLSKPROTOCOL.html#major-versions>.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ClientVersion {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
}
|
||||
|
||||
impl ClientVersion {
|
||||
/// The major version reserved for the soulseek-rs project. Clients and
|
||||
/// bots built on this library keep this major and pick their own non-zero
|
||||
/// minor version.
|
||||
pub const MAJOR: u32 = 176;
|
||||
|
||||
/// The version the soulseek-rs reference client (CLI/TUI) logs in with.
|
||||
pub const REFERENCE_CLIENT: Self = Self {
|
||||
major: Self::MAJOR,
|
||||
minor: 100,
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for ClientVersion {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
major: Self::MAJOR,
|
||||
minor: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UploadFailed {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ use std::time::{Duration, Instant};
|
||||
use soulseek_rs::message::Message;
|
||||
use soulseek_rs::message::server::MessageFactory;
|
||||
use soulseek_rs::peer::ConnectionType;
|
||||
use soulseek_rs::{Client, ClientSettings, DownloadStatus, PeerAddress};
|
||||
use soulseek_rs::{
|
||||
Client, ClientSettings, ClientVersion, DownloadStatus, PeerAddress,
|
||||
};
|
||||
|
||||
/// A Soulseek server to test against: either a child soulfind process we
|
||||
/// spawned, or an external server referenced by `SOULSEEK_TEST_SERVER`.
|
||||
@@ -97,6 +99,7 @@ impl TestServer {
|
||||
enable_listen: false,
|
||||
listen_port: 0,
|
||||
shared_directories: Vec::new(),
|
||||
version: ClientVersion::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,7 +604,12 @@ fn login_raw(
|
||||
let mut srv = connect_retry(server_addr, Duration::from_secs(5))?;
|
||||
srv.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
srv.write_all(
|
||||
&MessageFactory::build_login_message(username, password).get_buffer(),
|
||||
&MessageFactory::build_login_message(
|
||||
username,
|
||||
password,
|
||||
ClientVersion::default(),
|
||||
)
|
||||
.get_buffer(),
|
||||
)?;
|
||||
srv.flush()?;
|
||||
loop {
|
||||
@@ -816,8 +824,12 @@ fn run_mock_direct_peer(cfg: &MockDirectUpload) -> std::io::Result<()> {
|
||||
let mut srv = connect_retry(&cfg.server_addr, Duration::from_secs(5))?;
|
||||
srv.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
srv.write_all(
|
||||
&MessageFactory::build_login_message(&cfg.username, &cfg.password)
|
||||
.get_buffer(),
|
||||
&MessageFactory::build_login_message(
|
||||
&cfg.username,
|
||||
&cfg.password,
|
||||
ClientVersion::default(),
|
||||
)
|
||||
.get_buffer(),
|
||||
)?;
|
||||
srv.flush()?;
|
||||
loop {
|
||||
|
||||
@@ -11,7 +11,7 @@ use clap::Parser;
|
||||
use cli::{Cli, Commands, ConfigCommand, SharesCommand, parse_server_address};
|
||||
use commands::Ctx;
|
||||
use output::{CliError, CliResult, Exit, Out};
|
||||
use soulseek_rs::{ClientSettings, PeerAddress};
|
||||
use soulseek_rs::{ClientSettings, ClientVersion, PeerAddress};
|
||||
use std::io::{BufRead, IsTerminal};
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
@@ -146,6 +146,7 @@ fn context(
|
||||
enable_listen: !resolved.disable_listener,
|
||||
listen_port: resolved.listener_port,
|
||||
shared_directories: shared_directories(resolved, out),
|
||||
version: ClientVersion::REFERENCE_CLIENT,
|
||||
},
|
||||
download_dir: resolved.download_dir.clone(),
|
||||
max_concurrent_downloads: resolved.max_concurrent_downloads,
|
||||
@@ -281,6 +282,7 @@ fn run_default_tui(
|
||||
enable_listen,
|
||||
listen_port,
|
||||
shared_directories: shared_directories.clone(),
|
||||
version: ClientVersion::REFERENCE_CLIENT,
|
||||
};
|
||||
|
||||
// Clear screen and enable mouse capture before initializing TUI
|
||||
|
||||
@@ -442,6 +442,7 @@ impl TestServer {
|
||||
enable_listen: true,
|
||||
listen_port: port,
|
||||
shared_directories: shares,
|
||||
version: soulseek_rs::ClientVersion::default(),
|
||||
});
|
||||
client.connect().expect("peer connect");
|
||||
assert!(client.login().expect("peer login"), "peer should log in");
|
||||
|
||||
Reference in New Issue
Block a user