mod codec; mod handshake; pub use self::codec::ApCodec; pub use self::handshake::handshake; use std::io::{self, ErrorKind}; use futures_util::{SinkExt, StreamExt}; use num_traits::FromPrimitive; use protobuf::{self, Message, ProtobufError}; use thiserror::Error; use tokio::net::TcpStream; use tokio_util::codec::Framed; use url::Url; use crate::authentication::Credentials; use crate::packet::PacketType; use crate::protocol::keyexchange::{APLoginFailed, ErrorCode}; use crate::version; pub type Transport = Framed; fn login_error_message(code: &ErrorCode) -> &'static str { pub use ErrorCode::*; match code { ProtocolError => "Protocol error", TryAnotherAP => "Try another AP", BadConnectionId => "Bad connection id", TravelRestriction => "Travel restriction", PremiumAccountRequired => "Premium account required", BadCredentials => "Bad credentials", CouldNotValidateCredentials => "Could not validate credentials", AccountExists => "Account exists", ExtraVerificationRequired => "Extra verification required", InvalidAppKey => "Invalid app key", ApplicationBanned => "Application banned", } } #[derive(Debug, Error)] pub enum AuthenticationError { #[error("Login failed with reason: {}", login_error_message(.0))] LoginFailed(ErrorCode), #[error("Authentication failed: {0}")] IoError(#[from] io::Error), } impl From for AuthenticationError { fn from(e: ProtobufError) -> Self { io::Error::new(ErrorKind::InvalidData, e).into() } } impl From for AuthenticationError { fn from(login_failure: APLoginFailed) -> Self { Self::LoginFailed(login_failure.get_error_code()) } } pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result { let socket = crate::socket::connect(host, port, proxy).await?; handshake(socket).await } pub async fn authenticate( transport: &mut Transport, credentials: Credentials, device_id: &str, ) -> Result { use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}; let cpu_family = match std::env::consts::ARCH { "blackfin" => CpuFamily::CPU_BLACKFIN, "arm" | "arm64" => CpuFamily::CPU_ARM, "ia64" => CpuFamily::CPU_IA64, "mips" => CpuFamily::CPU_MIPS, "ppc" => CpuFamily::CPU_PPC, "ppc64" => CpuFamily::CPU_PPC_64, "sh" => CpuFamily::CPU_SH, "x86" => CpuFamily::CPU_X86, "x86_64" => CpuFamily::CPU_X86_64, _ => CpuFamily::CPU_UNKNOWN, }; let os = match std::env::consts::OS { "android" => Os::OS_ANDROID, "freebsd" | "netbsd" | "openbsd" => Os::OS_FREEBSD, "ios" => Os::OS_IPHONE, "linux" => Os::OS_LINUX, "macos" => Os::OS_OSX, "windows" => Os::OS_WINDOWS, _ => Os::OS_UNKNOWN, }; let mut packet = ClientResponseEncrypted::new(); packet .mut_login_credentials() .set_username(credentials.username); packet .mut_login_credentials() .set_typ(credentials.auth_type); packet .mut_login_credentials() .set_auth_data(credentials.auth_data); packet.mut_system_info().set_cpu_family(cpu_family); packet.mut_system_info().set_os(os); packet .mut_system_info() .set_system_information_string(format!( "librespot-{}-{}", version::SHA_SHORT, version::BUILD_ID )); packet .mut_system_info() .set_device_id(device_id.to_string()); packet.set_version_string(format!("librespot {}", version::SEMVER)); let cmd = PacketType::Login; let data = packet.write_to_bytes().unwrap(); transport.send((cmd as u8, data)).await?; let (cmd, data) = transport.next().await.expect("EOF")?; let packet_type = FromPrimitive::from_u8(cmd); match packet_type { Some(PacketType::APWelcome) => { let welcome_data = APWelcome::parse_from_bytes(data.as_ref())?; let reusable_credentials = Credentials { username: welcome_data.get_canonical_username().to_owned(), auth_type: welcome_data.get_reusable_auth_credentials_type(), auth_data: welcome_data.get_reusable_auth_credentials().to_owned(), }; Ok(reusable_credentials) } Some(PacketType::AuthFailure) => { let error_data = APLoginFailed::parse_from_bytes(data.as_ref())?; Err(error_data.into()) } _ => { let msg = format!("Received invalid packet: {}", cmd); Err(io::Error::new(ErrorKind::InvalidData, msg).into()) } } }