mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
commit
685c607c15
23 changed files with 298 additions and 249 deletions
|
@ -20,6 +20,10 @@ before_script:
|
||||||
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
||||||
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
||||||
- rustup target add armv7-unknown-linux-gnueabihf
|
- rustup target add armv7-unknown-linux-gnueabihf
|
||||||
|
- if [[ $TRAVIS_RUST_VERSION == *"nightly"* ]]; then
|
||||||
|
rustup component add rustfmt-preview;
|
||||||
|
cargo fmt --package=librespot-core -- --write-mode=diff;
|
||||||
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo build --no-default-features
|
- cargo build --no-default-features
|
||||||
|
|
|
@ -1,36 +1,35 @@
|
||||||
extern crate vergen;
|
|
||||||
extern crate protobuf_macros;
|
extern crate protobuf_macros;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
extern crate vergen;
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
vergen::vergen(vergen::OutputFns::all()).unwrap();
|
vergen::vergen(vergen::OutputFns::all()).unwrap();
|
||||||
|
|
||||||
let build_id: String = rand::thread_rng()
|
let build_id: String = rand::thread_rng().gen_ascii_chars().take(8).collect();
|
||||||
.gen_ascii_chars()
|
|
||||||
.take(8)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut version_file =
|
let mut version_file = OpenOptions::new()
|
||||||
OpenOptions::new()
|
|
||||||
.write(true)
|
.write(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(&out.join("version.rs"))
|
.open(&out.join("version.rs"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let build_id_fn = format!("
|
let build_id_fn = format!(
|
||||||
|
"
|
||||||
/// Generate a random build id.
|
/// Generate a random build id.
|
||||||
pub fn build_id() -> &'static str {{
|
pub fn build_id() -> &'static str {{
|
||||||
\"{}\"
|
\"{}\"
|
||||||
}}
|
}}
|
||||||
", build_id);
|
",
|
||||||
|
build_id
|
||||||
|
);
|
||||||
|
|
||||||
if let Err(e) = version_file.write_all(build_id_fn.as_bytes()) {
|
if let Err(e) = version_file.write_all(build_id_fn.as_bytes()) {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
const AP_FALLBACK : &'static str = "ap.spotify.com:80";
|
const AP_FALLBACK: &'static str = "ap.spotify.com:80";
|
||||||
const APRESOLVE_ENDPOINT : &'static str = "http://apresolve.spotify.com/";
|
const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com/";
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use futures::{Future, Stream};
|
use futures::{Future, Stream};
|
||||||
use hyper::{self, Uri, Client};
|
use hyper::{self, Client, Uri};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use std::str::FromStr;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
|
|
||||||
error_chain! { }
|
error_chain!{}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct APResolveData {
|
pub struct APResolveData {
|
||||||
ap_list: Vec<String>
|
ap_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apresolve(handle: &Handle) -> Box<Future<Item=String, Error=Error>> {
|
fn apresolve(handle: &Handle) -> Box<Future<Item = String, Error = Error>> {
|
||||||
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
||||||
|
|
||||||
let client = Client::new(handle);
|
let client = Client::new(handle);
|
||||||
|
@ -27,14 +27,10 @@ fn apresolve(handle: &Handle) -> Box<Future<Item=String, Error=Error>> {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let body = body.then(|result| result.chain_err(|| "HTTP error"));
|
let body = body.then(|result| result.chain_err(|| "HTTP error"));
|
||||||
let body = body.and_then(|body| {
|
let body = body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
|
||||||
String::from_utf8(body).chain_err(|| "invalid UTF8 in response")
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = body.and_then(|body| {
|
let data =
|
||||||
serde_json::from_str::<APResolveData>(&body)
|
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
||||||
.chain_err(|| "invalid JSON")
|
|
||||||
});
|
|
||||||
|
|
||||||
let ap = data.and_then(|data| {
|
let ap = data.and_then(|data| {
|
||||||
let ap = data.ap_list.first().ok_or("empty AP List")?;
|
let ap = data.ap_list.first().ok_or("empty AP List")?;
|
||||||
|
@ -44,9 +40,10 @@ fn apresolve(handle: &Handle) -> Box<Future<Item=String, Error=Error>> {
|
||||||
Box::new(ap)
|
Box::new(ap)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apresolve_or_fallback<E>(handle: &Handle)
|
pub(crate) fn apresolve_or_fallback<E>(handle: &Handle) -> Box<Future<Item = String, Error = E>>
|
||||||
-> Box<Future<Item=String, Error=E>>
|
where
|
||||||
where E: 'static {
|
E: 'static,
|
||||||
|
{
|
||||||
let ap = apresolve(handle).or_else(|e| {
|
let ap = apresolve(handle).or_else(|e| {
|
||||||
warn!("Failed to resolve Access Point: {}", e.description());
|
warn!("Failed to resolve Access Point: {}", e.description());
|
||||||
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::sync::oneshot;
|
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
|
use futures::sync::oneshot;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
use util::{FileId, SpotifyId};
|
||||||
use util::SeqGenerator;
|
use util::SeqGenerator;
|
||||||
use util::{SpotifyId, FileId};
|
|
||||||
|
|
||||||
#[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct AudioKey(pub [u8; 16]);
|
pub struct AudioKey(pub [u8; 16]);
|
||||||
|
|
||||||
#[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct AudioKeyError;
|
pub struct AudioKeyError;
|
||||||
|
|
||||||
component! {
|
component! {
|
||||||
|
@ -35,7 +35,11 @@ impl AudioKeyManager {
|
||||||
let _ = sender.send(Ok(AudioKey(key)));
|
let _ = sender.send(Ok(AudioKey(key)));
|
||||||
}
|
}
|
||||||
0xe => {
|
0xe => {
|
||||||
warn!("error audio key {:x} {:x}", data.as_ref()[0], data.as_ref()[1]);
|
warn!(
|
||||||
|
"error audio key {:x} {:x}",
|
||||||
|
data.as_ref()[0],
|
||||||
|
data.as_ref()[1]
|
||||||
|
);
|
||||||
let _ = sender.send(Err(AudioKeyError));
|
let _ = sender.send(Err(AudioKeyError));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -68,7 +72,7 @@ impl AudioKeyManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioKeyFuture<T>(oneshot::Receiver<Result<T, AudioKeyError>>);
|
pub struct AudioKeyFuture<T>(oneshot::Receiver<Result<T, AudioKeyError>>);
|
||||||
impl <T> Future for AudioKeyFuture<T> {
|
impl<T> Future for AudioKeyFuture<T> {
|
||||||
type Item = T;
|
type Item = T;
|
||||||
type Error = AudioKeyError;
|
type Error = AudioKeyError;
|
||||||
|
|
||||||
|
@ -81,4 +85,3 @@ impl <T> Future for AudioKeyFuture<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,23 +10,22 @@ use protobuf::ProtobufEnum;
|
||||||
use rpassword;
|
use rpassword;
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::io::{self, stderr, Read, Write};
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{self, stderr, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use protocol::authentication::AuthenticationType;
|
use protocol::authentication::AuthenticationType;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
||||||
#[serde(serialize_with="serialize_protobuf_enum")]
|
#[serde(serialize_with = "serialize_protobuf_enum")]
|
||||||
#[serde(deserialize_with="deserialize_protobuf_enum")]
|
#[serde(deserialize_with = "deserialize_protobuf_enum")]
|
||||||
pub auth_type: AuthenticationType,
|
pub auth_type: AuthenticationType,
|
||||||
|
|
||||||
#[serde(serialize_with="serialize_base64")]
|
#[serde(serialize_with = "serialize_base64")]
|
||||||
#[serde(deserialize_with="deserialize_base64")]
|
#[serde(deserialize_with = "deserialize_base64")]
|
||||||
pub auth_data: Vec<u8>,
|
pub auth_data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,13 +88,18 @@ impl Credentials {
|
||||||
let blob = {
|
let blob = {
|
||||||
// Anyone know what this block mode is ?
|
// Anyone know what this block mode is ?
|
||||||
let mut data = vec![0u8; encrypted_blob.len()];
|
let mut data = vec![0u8; encrypted_blob.len()];
|
||||||
let mut cipher = aes::ecb_decryptor(aes::KeySize::KeySize192,
|
let mut cipher = aes::ecb_decryptor(
|
||||||
&key,
|
aes::KeySize::KeySize192,
|
||||||
crypto::blockmodes::NoPadding);
|
&key,
|
||||||
cipher.decrypt(&mut crypto::buffer::RefReadBuffer::new(&encrypted_blob),
|
crypto::blockmodes::NoPadding,
|
||||||
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
);
|
||||||
true)
|
cipher
|
||||||
.unwrap();
|
.decrypt(
|
||||||
|
&mut crypto::buffer::RefReadBuffer::new(&encrypted_blob),
|
||||||
|
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let l = encrypted_blob.len();
|
let l = encrypted_blob.len();
|
||||||
for i in 0..l - 0x10 {
|
for i in 0..l - 0x10 {
|
||||||
|
@ -112,7 +116,7 @@ impl Credentials {
|
||||||
let auth_type = read_int(&mut cursor).unwrap();
|
let auth_type = read_int(&mut cursor).unwrap();
|
||||||
let auth_type = AuthenticationType::from_i32(auth_type as i32).unwrap();
|
let auth_type = AuthenticationType::from_i32(auth_type as i32).unwrap();
|
||||||
read_u8(&mut cursor).unwrap();
|
read_u8(&mut cursor).unwrap();
|
||||||
let auth_data = read_bytes(&mut cursor).unwrap();;
|
let auth_data = read_bytes(&mut cursor).unwrap();
|
||||||
|
|
||||||
Credentials {
|
Credentials {
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -144,42 +148,49 @@ impl Credentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
||||||
where T: ProtobufEnum, S: serde::Serializer {
|
where
|
||||||
|
T: ProtobufEnum,
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
serde::Serialize::serialize(&v.value(), ser)
|
serde::Serialize::serialize(&v.value(), ser)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_protobuf_enum<T, D>(de: D) -> Result<T, D::Error>
|
fn deserialize_protobuf_enum<T, D>(de: D) -> Result<T, D::Error>
|
||||||
where T: ProtobufEnum, D: serde::Deserializer {
|
where
|
||||||
|
T: ProtobufEnum,
|
||||||
let v : i32 = try!(serde::Deserialize::deserialize(de));
|
D: serde::Deserializer,
|
||||||
|
{
|
||||||
|
let v: i32 = try!(serde::Deserialize::deserialize(de));
|
||||||
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_base64<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
fn serialize_base64<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
||||||
where T: AsRef<[u8]>, S: serde::Serializer {
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
serde::Serialize::serialize(&base64::encode(v.as_ref()), ser)
|
serde::Serialize::serialize(&base64::encode(v.as_ref()), ser)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_base64<D>(de: D) -> Result<Vec<u8>, D::Error>
|
fn deserialize_base64<D>(de: D) -> Result<Vec<u8>, D::Error>
|
||||||
where D: serde::Deserializer {
|
where
|
||||||
|
D: serde::Deserializer,
|
||||||
let v : String = try!(serde::Deserialize::deserialize(de));
|
{
|
||||||
|
let v: String = try!(serde::Deserialize::deserialize(de));
|
||||||
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_credentials(username: Option<String>, password: Option<String>,
|
pub fn get_credentials(
|
||||||
cached_credentials: Option<Credentials>)
|
username: Option<String>,
|
||||||
-> Option<Credentials>
|
password: Option<String>,
|
||||||
{
|
cached_credentials: Option<Credentials>,
|
||||||
|
) -> Option<Credentials> {
|
||||||
match (username, password, cached_credentials) {
|
match (username, password, cached_credentials) {
|
||||||
|
(Some(username), Some(password), _) => Some(Credentials::with_password(username, password)),
|
||||||
|
|
||||||
(Some(username), Some(password), _)
|
(Some(ref username), _, Some(ref credentials)) if *username == credentials.username => {
|
||||||
=> Some(Credentials::with_password(username, password)),
|
Some(credentials.clone())
|
||||||
|
}
|
||||||
(Some(ref username), _, Some(ref credentials))
|
|
||||||
if *username == credentials.username => Some(credentials.clone()),
|
|
||||||
|
|
||||||
(Some(username), None, _) => {
|
(Some(username), None, _) => {
|
||||||
write!(stderr(), "Password for {}: ", username).unwrap();
|
write!(stderr(), "Password for {}: ", username).unwrap();
|
||||||
|
@ -188,8 +199,7 @@ pub fn get_credentials(username: Option<String>, password: Option<String>,
|
||||||
Some(Credentials::with_password(username.clone(), password))
|
Some(Credentials::with_password(username.clone(), password))
|
||||||
}
|
}
|
||||||
|
|
||||||
(None, _, Some(credentials))
|
(None, _, Some(credentials)) => Some(credentials),
|
||||||
=> Some(credentials),
|
|
||||||
|
|
||||||
(None, _, None) => None,
|
(None, _, None) => None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use util::FileId;
|
|
||||||
use authentication::Credentials;
|
use authentication::Credentials;
|
||||||
|
use util::FileId;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
|
@ -31,7 +31,7 @@ impl Cache {
|
||||||
|
|
||||||
Cache {
|
Cache {
|
||||||
root: location,
|
root: location,
|
||||||
use_audio_cache: use_audio_cache
|
use_audio_cache: use_audio_cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::sync::{BiLock, mpsc};
|
use futures::{Async, Poll, Stream};
|
||||||
use futures::{Poll, Async, Stream};
|
use futures::sync::{mpsc, BiLock};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use util::SeqGenerator;
|
use util::SeqGenerator;
|
||||||
|
@ -13,7 +13,7 @@ component! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct ChannelError;
|
pub struct ChannelError;
|
||||||
|
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
|
|
|
@ -36,15 +36,15 @@ macro_rules! component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
pub struct Lazy<T>(Mutex<bool>, UnsafeCell<Option<T>>);
|
pub struct Lazy<T>(Mutex<bool>, UnsafeCell<Option<T>>);
|
||||||
unsafe impl <T: Sync> Sync for Lazy<T> {}
|
unsafe impl<T: Sync> Sync for Lazy<T> {}
|
||||||
unsafe impl <T: Send> Send for Lazy<T> {}
|
unsafe impl<T: Send> Send for Lazy<T> {}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))]
|
#[cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))]
|
||||||
impl <T> Lazy<T> {
|
impl<T> Lazy<T> {
|
||||||
pub fn new() -> Lazy<T> {
|
pub fn new() -> Lazy<T> {
|
||||||
Lazy(Mutex::new(false), UnsafeCell::new(None))
|
Lazy(Mutex::new(false), UnsafeCell::new(None))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use uuid::Uuid;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SessionConfig {
|
pub struct SessionConfig {
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
pub device_id: String,
|
pub device_id: String,
|
||||||
|
@ -20,7 +20,6 @@ impl Default for SessionConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub enum Bitrate {
|
pub enum Bitrate {
|
||||||
Bitrate96,
|
Bitrate96,
|
||||||
|
@ -100,7 +99,7 @@ impl Default for DeviceType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayerConfig {
|
pub struct PlayerConfig {
|
||||||
pub bitrate: Bitrate,
|
pub bitrate: Bitrate,
|
||||||
pub onstart: Option<String>,
|
pub onstart: Option<String>,
|
||||||
|
@ -117,7 +116,7 @@ impl Default for PlayerConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ConnectConfig {
|
pub struct ConnectConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub device_type: DeviceType,
|
pub device_type: DeviceType,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use bytes::{Bytes, BytesMut, BufMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use shannon::Shannon;
|
use shannon::Shannon;
|
||||||
use std::io;
|
use std::io;
|
||||||
use tokio_io::codec::{Decoder, Encoder};
|
use tokio_io::codec::{Decoder, Encoder};
|
||||||
|
@ -88,7 +88,8 @@ impl Decoder for APCodec {
|
||||||
|
|
||||||
let mut payload = buf.split_to(size + MAC_SIZE);
|
let mut payload = buf.split_to(size + MAC_SIZE);
|
||||||
|
|
||||||
self.decode_cipher.decrypt(&mut payload.get_mut(..size).unwrap());
|
self.decode_cipher
|
||||||
|
.decrypt(&mut payload.get_mut(..size).unwrap());
|
||||||
let mac = payload.split_off(size);
|
let mac = payload.split_off(size);
|
||||||
self.decode_cipher.check_mac(mac.as_ref())?;
|
self.decode_cipher.check_mac(mac.as_ref())?;
|
||||||
|
|
||||||
|
@ -96,7 +97,6 @@ impl Decoder for APCodec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
use crypto::sha1::Sha1;
|
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||||
use crypto::hmac::Hmac;
|
use crypto::hmac::Hmac;
|
||||||
use crypto::mac::Mac;use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
use crypto::mac::Mac;
|
||||||
|
use crypto::sha1::Sha1;
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
use protobuf::{self, Message, MessageStatic};
|
use protobuf::{self, Message, MessageStatic};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_io::codec::Framed;
|
use tokio_io::codec::Framed;
|
||||||
use tokio_io::io::{write_all, WriteAll, read_exact, ReadExact, Window};
|
use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll};
|
||||||
use futures::{Poll, Async, Future};
|
|
||||||
|
|
||||||
|
use super::codec::APCodec;
|
||||||
use diffie_hellman::DHLocalKeys;
|
use diffie_hellman::DHLocalKeys;
|
||||||
use protocol;
|
use protocol;
|
||||||
use protocol::keyexchange::{ClientHello, APResponseMessage, ClientResponsePlaintext};
|
use protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
|
||||||
use util;
|
use util;
|
||||||
use super::codec::APCodec;
|
|
||||||
|
|
||||||
pub struct Handshake<T> {
|
pub struct Handshake<T> {
|
||||||
keys: DHLocalKeys,
|
keys: DHLocalKeys,
|
||||||
|
@ -37,7 +38,7 @@ pub fn handshake<T: AsyncRead + AsyncWrite>(connection: T) -> Handshake<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
impl<T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
||||||
type Item = Framed<T, APCodec>;
|
type Item = Framed<T, APCodec>;
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
|
|
||||||
|
@ -54,15 +55,15 @@ impl <T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
||||||
|
|
||||||
APResponse(ref mut read) => {
|
APResponse(ref mut read) => {
|
||||||
let (connection, message, accumulator) = try_ready!(read.poll());
|
let (connection, message, accumulator) = try_ready!(read.poll());
|
||||||
let remote_key = message.get_challenge()
|
let remote_key = message
|
||||||
|
.get_challenge()
|
||||||
.get_login_crypto_challenge()
|
.get_login_crypto_challenge()
|
||||||
.get_diffie_hellman()
|
.get_diffie_hellman()
|
||||||
.get_gs()
|
.get_gs()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let shared_secret = self.keys.shared_secret(&remote_key);
|
let shared_secret = self.keys.shared_secret(&remote_key);
|
||||||
let (challenge, send_key, recv_key) = compute_keys(&shared_secret,
|
let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator);
|
||||||
&accumulator);
|
|
||||||
let codec = APCodec::new(&send_key, &recv_key);
|
let codec = APCodec::new(&send_key, &recv_key);
|
||||||
|
|
||||||
let write = client_response(connection, challenge);
|
let write = client_response(connection, challenge);
|
||||||
|
@ -129,15 +130,17 @@ enum RecvPacket<T, M: MessageStatic> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv_packet<T: AsyncRead, M>(connection: T, acc: Vec<u8>) -> RecvPacket<T, M>
|
fn recv_packet<T: AsyncRead, M>(connection: T, acc: Vec<u8>) -> RecvPacket<T, M>
|
||||||
where T: Read,
|
where
|
||||||
M: MessageStatic
|
T: Read,
|
||||||
|
M: MessageStatic,
|
||||||
{
|
{
|
||||||
RecvPacket::Header(read_into_accumulator(connection, 4, acc), PhantomData)
|
RecvPacket::Header(read_into_accumulator(connection, 4, acc), PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <T: AsyncRead, M> Future for RecvPacket<T, M>
|
impl<T: AsyncRead, M> Future for RecvPacket<T, M>
|
||||||
where T: Read,
|
where
|
||||||
M: MessageStatic
|
T: Read,
|
||||||
|
M: MessageStatic,
|
||||||
{
|
{
|
||||||
type Item = (T, M, Vec<u8>);
|
type Item = (T, M, Vec<u8>);
|
||||||
type Error = io::Error;
|
type Error = io::Error;
|
||||||
|
@ -167,7 +170,11 @@ impl <T: AsyncRead, M> Future for RecvPacket<T, M>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_into_accumulator<T: AsyncRead>(connection: T, size: usize, mut acc: Vec<u8>) -> ReadExact<T, Window<Vec<u8>>> {
|
fn read_into_accumulator<T: AsyncRead>(
|
||||||
|
connection: T,
|
||||||
|
size: usize,
|
||||||
|
mut acc: Vec<u8>,
|
||||||
|
) -> ReadExact<T, Window<Vec<u8>>> {
|
||||||
let offset = acc.len();
|
let offset = acc.len();
|
||||||
acc.resize(offset + size, 0);
|
acc.resize(offset + size, 0);
|
||||||
|
|
||||||
|
@ -191,5 +198,9 @@ fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<
|
||||||
mac = Hmac::new(Sha1::new(), &data[..0x14]);
|
mac = Hmac::new(Sha1::new(), &data[..0x14]);
|
||||||
mac.input(packets);
|
mac.input(packets);
|
||||||
|
|
||||||
(mac.result().code().to_vec(), data[0x14..0x34].to_vec(), data[0x34..0x54].to_vec())
|
(
|
||||||
|
mac.result().code().to_vec(),
|
||||||
|
data[0x14..0x34].to_vec(),
|
||||||
|
data[0x34..0x54].to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,31 +5,34 @@ pub use self::codec::APCodec;
|
||||||
pub use self::handshake::handshake;
|
pub use self::handshake::handshake;
|
||||||
|
|
||||||
use futures::{Future, Sink, Stream};
|
use futures::{Future, Sink, Stream};
|
||||||
|
use protobuf::{self, Message};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
use tokio_io::codec::Framed;
|
use tokio_io::codec::Framed;
|
||||||
use protobuf::{self, Message};
|
|
||||||
|
|
||||||
use authentication::Credentials;
|
use authentication::Credentials;
|
||||||
use version;
|
use version;
|
||||||
|
|
||||||
pub type Transport = Framed<TcpStream, APCodec>;
|
pub type Transport = Framed<TcpStream, APCodec>;
|
||||||
|
|
||||||
pub fn connect<A: ToSocketAddrs>(addr: A, handle: &Handle) -> Box<Future<Item = Transport, Error = io::Error>> {
|
pub fn connect<A: ToSocketAddrs>(
|
||||||
|
addr: A,
|
||||||
|
handle: &Handle,
|
||||||
|
) -> Box<Future<Item = Transport, Error = io::Error>> {
|
||||||
let addr = addr.to_socket_addrs().unwrap().next().unwrap();
|
let addr = addr.to_socket_addrs().unwrap().next().unwrap();
|
||||||
let socket = TcpStream::connect(&addr, handle);
|
let socket = TcpStream::connect(&addr, handle);
|
||||||
let connection = socket.and_then(|socket| {
|
let connection = socket.and_then(|socket| handshake(socket));
|
||||||
handshake(socket)
|
|
||||||
});
|
|
||||||
|
|
||||||
Box::new(connection)
|
Box::new(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn authenticate(transport: Transport, credentials: Credentials, device_id: String)
|
pub fn authenticate(
|
||||||
-> Box<Future<Item = (Transport, Credentials), Error = io::Error>>
|
transport: Transport,
|
||||||
{
|
credentials: Credentials,
|
||||||
|
device_id: String,
|
||||||
|
) -> Box<Future<Item = (Transport, Credentials), Error = io::Error>> {
|
||||||
use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||||
|
|
||||||
let packet = protobuf_init!(ClientResponseEncrypted::new(), {
|
let packet = protobuf_init!(ClientResponseEncrypted::new(), {
|
||||||
|
@ -50,26 +53,26 @@ pub fn authenticate(transport: Transport, credentials: Credentials, device_id: S
|
||||||
let cmd = 0xab;
|
let cmd = 0xab;
|
||||||
let data = packet.write_to_bytes().unwrap();
|
let data = packet.write_to_bytes().unwrap();
|
||||||
|
|
||||||
Box::new(transport.send((cmd, data)).and_then(|transport| {
|
Box::new(
|
||||||
transport.into_future().map_err(|(err, _stream)| err)
|
transport
|
||||||
}).and_then(|(packet, transport)| {
|
.send((cmd, data))
|
||||||
match packet {
|
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
||||||
Some((0xac, data)) => {
|
.and_then(|(packet, transport)| match packet {
|
||||||
let welcome_data: APWelcome =
|
Some((0xac, data)) => {
|
||||||
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||||
|
|
||||||
let reusable_credentials = Credentials {
|
let reusable_credentials = Credentials {
|
||||||
username: welcome_data.get_canonical_username().to_owned(),
|
username: welcome_data.get_canonical_username().to_owned(),
|
||||||
auth_type: welcome_data.get_reusable_auth_credentials_type(),
|
auth_type: welcome_data.get_reusable_auth_credentials_type(),
|
||||||
auth_data: welcome_data.get_reusable_auth_credentials().to_owned(),
|
auth_data: welcome_data.get_reusable_auth_credentials().to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((transport, reusable_credentials))
|
Ok((transport, reusable_credentials))
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((0xad, _)) => panic!("Authentication failed"),
|
Some((0xad, _)) => panic!("Authentication failed"),
|
||||||
Some((cmd, _)) => panic!("Unexpected packet {:?}", cmd),
|
Some((cmd, _)) => panic!("Unexpected packet {:?}", cmd),
|
||||||
None => panic!("EOF"),
|
None => panic!("EOF"),
|
||||||
}
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,11 @@ impl DHLocalKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
||||||
let shared_key = util::powm(&BigUint::from_bytes_be(remote_key),
|
let shared_key = util::powm(
|
||||||
&self.private_key,
|
&BigUint::from_bytes_be(remote_key),
|
||||||
&DH_PRIME);
|
&self.private_key,
|
||||||
|
&DH_PRIME,
|
||||||
|
);
|
||||||
shared_key.to_bytes_be()
|
shared_key.to_bytes_be()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,19 @@ pub struct Token {
|
||||||
pub scope: Vec<String>,
|
pub scope: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_token(session: &Session, client_id: &str, scopes: &str) -> Box<Future<Item = Token, Error = MercuryError>> {
|
pub fn get_token(
|
||||||
let url = format!("hm://keymaster/token/authenticated?client_id={}&scope={}",
|
session: &Session,
|
||||||
client_id, scopes);
|
client_id: &str,
|
||||||
|
scopes: &str,
|
||||||
|
) -> Box<Future<Item = Token, Error = MercuryError>> {
|
||||||
|
let url = format!(
|
||||||
|
"hm://keymaster/token/authenticated?client_id={}&scope={}",
|
||||||
|
client_id, scopes
|
||||||
|
);
|
||||||
Box::new(session.mercury().get(url).map(move |response| {
|
Box::new(session.mercury().get(url).map(move |response| {
|
||||||
let data = response.payload.first().expect("Empty payload");
|
let data = response.payload.first().expect("Empty payload");
|
||||||
let data = String::from_utf8(data.clone()).unwrap();
|
let data = String::from_utf8(data.clone()).unwrap();
|
||||||
let token : Token = serde_json::from_str(&data).unwrap();
|
let token: Token = serde_json::from_str(&data).unwrap();
|
||||||
|
|
||||||
token
|
token
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
||||||
|
|
||||||
#[macro_use] extern crate error_chain;
|
#[macro_use]
|
||||||
#[macro_use] extern crate futures;
|
extern crate error_chain;
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use]
|
||||||
#[macro_use] extern crate log;
|
extern crate futures;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
@ -26,7 +31,8 @@ extern crate uuid;
|
||||||
|
|
||||||
extern crate librespot_protocol as protocol;
|
extern crate librespot_protocol as protocol;
|
||||||
|
|
||||||
#[macro_use] mod component;
|
#[macro_use]
|
||||||
|
mod component;
|
||||||
mod apresolve;
|
mod apresolve;
|
||||||
pub mod audio_key;
|
pub mod audio_key;
|
||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::sync::{oneshot, mpsc};
|
use futures::{Async, Future, Poll};
|
||||||
use futures::{Async, Poll, Future};
|
use futures::sync::{mpsc, oneshot};
|
||||||
use protobuf;
|
use protobuf;
|
||||||
use protocol;
|
use protocol;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -30,7 +30,7 @@ pub struct MercuryPending {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MercuryFuture<T>(oneshot::Receiver<Result<T, MercuryError>>);
|
pub struct MercuryFuture<T>(oneshot::Receiver<Result<T, MercuryError>>);
|
||||||
impl <T> Future for MercuryFuture<T> {
|
impl<T> Future for MercuryFuture<T> {
|
||||||
type Item = T;
|
type Item = T;
|
||||||
type Error = MercuryError;
|
type Error = MercuryError;
|
||||||
|
|
||||||
|
@ -51,9 +51,7 @@ impl MercuryManager {
|
||||||
seq
|
seq
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(&self, req: MercuryRequest)
|
pub fn request(&self, req: MercuryRequest) -> MercuryFuture<MercuryResponse> {
|
||||||
-> MercuryFuture<MercuryResponse>
|
|
||||||
{
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
let pending = MercuryPending {
|
let pending = MercuryPending {
|
||||||
|
@ -72,9 +70,7 @@ impl MercuryManager {
|
||||||
MercuryFuture(rx)
|
MercuryFuture(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<T: Into<String>>(&self, uri: T)
|
pub fn get<T: Into<String>>(&self, uri: T) -> MercuryFuture<MercuryResponse> {
|
||||||
-> MercuryFuture<MercuryResponse>
|
|
||||||
{
|
|
||||||
self.request(MercuryRequest {
|
self.request(MercuryRequest {
|
||||||
method: MercuryMethod::GET,
|
method: MercuryMethod::GET,
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
|
@ -83,9 +79,7 @@ impl MercuryManager {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send<T: Into<String>>(&self, uri: T, data: Vec<u8>)
|
pub fn send<T: Into<String>>(&self, uri: T, data: Vec<u8>) -> MercuryFuture<MercuryResponse> {
|
||||||
-> MercuryFuture<MercuryResponse>
|
|
||||||
{
|
|
||||||
self.request(MercuryRequest {
|
self.request(MercuryRequest {
|
||||||
method: MercuryMethod::SEND,
|
method: MercuryMethod::SEND,
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
|
@ -98,9 +92,10 @@ impl MercuryManager {
|
||||||
MercurySender::new(self.clone(), uri.into())
|
MercurySender::new(self.clone(), uri.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe<T: Into<String>>(&self, uri: T)
|
pub fn subscribe<T: Into<String>>(
|
||||||
-> Box<Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>>
|
&self,
|
||||||
{
|
uri: T,
|
||||||
|
) -> Box<Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>> {
|
||||||
let uri = uri.into();
|
let uri = uri.into();
|
||||||
let request = self.request(MercuryRequest {
|
let request = self.request(MercuryRequest {
|
||||||
method: MercuryMethod::SUB,
|
method: MercuryMethod::SUB,
|
||||||
|
@ -118,8 +113,8 @@ impl MercuryManager {
|
||||||
if response.payload.len() > 0 {
|
if response.payload.len() > 0 {
|
||||||
// Old subscription protocol, watch the provided list of URIs
|
// Old subscription protocol, watch the provided list of URIs
|
||||||
for sub in response.payload {
|
for sub in response.payload {
|
||||||
let mut sub : protocol::pubsub::Subscription
|
let mut sub: protocol::pubsub::Subscription =
|
||||||
= protobuf::parse_from_bytes(&sub).unwrap();
|
protobuf::parse_from_bytes(&sub).unwrap();
|
||||||
let sub_uri = sub.take_uri();
|
let sub_uri = sub.take_uri();
|
||||||
|
|
||||||
debug!("subscribed sub_uri={}", sub_uri);
|
debug!("subscribed sub_uri={}", sub_uri);
|
||||||
|
@ -147,13 +142,11 @@ impl MercuryManager {
|
||||||
|
|
||||||
let mut pending = match pending {
|
let mut pending = match pending {
|
||||||
Some(pending) => pending,
|
Some(pending) => pending,
|
||||||
None if cmd == 0xb5 => {
|
None if cmd == 0xb5 => MercuryPending {
|
||||||
MercuryPending {
|
parts: Vec::new(),
|
||||||
parts: Vec::new(),
|
partial: None,
|
||||||
partial: None,
|
callback: None,
|
||||||
callback: None,
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
warn!("Ignore seq {:?} cmd {:x}", seq, cmd);
|
warn!("Ignore seq {:?} cmd {:x}", seq, cmd);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use futures::{Async, AsyncSink, Future, Poll, Sink, StartSend};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use futures::{Async, Poll, Future, Sink, StartSend, AsyncSink};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -27,18 +27,17 @@ pub struct MercuryResponse {
|
||||||
pub payload: Vec<Vec<u8>>,
|
pub payload: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct MercuryError;
|
pub struct MercuryError;
|
||||||
|
|
||||||
impl ToString for MercuryMethod {
|
impl ToString for MercuryMethod {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match *self {
|
match *self {
|
||||||
MercuryMethod::GET => "GET",
|
MercuryMethod::GET => "GET",
|
||||||
MercuryMethod::SUB => "SUB",
|
MercuryMethod::SUB => "SUB",
|
||||||
MercuryMethod::UNSUB => "UNSUB",
|
MercuryMethod::UNSUB => "UNSUB",
|
||||||
MercuryMethod::SEND => "SEND",
|
MercuryMethod::SEND => "SEND",
|
||||||
}
|
}.to_owned()
|
||||||
.to_owned()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +57,9 @@ impl MercuryRequest {
|
||||||
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
||||||
packet.write_all(seq).unwrap();
|
packet.write_all(seq).unwrap();
|
||||||
packet.write_u8(1).unwrap(); // Flags: FINAL
|
packet.write_u8(1).unwrap(); // Flags: FINAL
|
||||||
packet.write_u16::<BigEndian>(1 + self.payload.len() as u16).unwrap(); // Part count
|
packet
|
||||||
|
.write_u16::<BigEndian>(1 + self.payload.len() as u16)
|
||||||
|
.unwrap(); // Part count
|
||||||
|
|
||||||
let mut header = protocol::mercury::Header::new();
|
let mut header = protocol::mercury::Header::new();
|
||||||
header.set_uri(self.uri.clone());
|
header.set_uri(self.uri.clone());
|
||||||
|
@ -68,7 +69,9 @@ impl MercuryRequest {
|
||||||
header.set_content_type(content_type.clone());
|
header.set_content_type(content_type.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
|
packet
|
||||||
|
.write_u16::<BigEndian>(header.compute_size() as u16)
|
||||||
|
.unwrap();
|
||||||
header.write_to_writer(&mut packet).unwrap();
|
header.write_to_writer(&mut packet).unwrap();
|
||||||
|
|
||||||
for p in &self.payload {
|
for p in &self.payload {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
use crypto::sha1::Sha1;
|
use crypto::sha1::Sha1;
|
||||||
|
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
use futures::{Future, Stream, IntoFuture, Poll, Async};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::{RwLock, Arc, Weak};
|
use std::sync::{Arc, RwLock, Weak};
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
|
||||||
use tokio_core::reactor::{Handle, Remote};
|
use tokio_core::reactor::{Handle, Remote};
|
||||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
|
||||||
|
|
||||||
use apresolve::apresolve_or_fallback;
|
use apresolve::apresolve_or_fallback;
|
||||||
use authentication::Credentials;
|
use authentication::Credentials;
|
||||||
use cache::Cache;
|
use cache::Cache;
|
||||||
use component::Lazy;
|
use component::Lazy;
|
||||||
use connection;
|
|
||||||
use config::SessionConfig;
|
use config::SessionConfig;
|
||||||
|
use connection;
|
||||||
|
|
||||||
use audio_key::AudioKeyManager;
|
use audio_key::AudioKeyManager;
|
||||||
use channel::ChannelManager;
|
use channel::ChannelManager;
|
||||||
|
@ -40,7 +40,7 @@ pub struct SessionInternal {
|
||||||
session_id: usize,
|
session_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
static SESSION_COUNTER : AtomicUsize = ATOMIC_USIZE_INIT;
|
static SESSION_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Session(pub Arc<SessionInternal>);
|
pub struct Session(pub Arc<SessionInternal>);
|
||||||
|
@ -52,13 +52,14 @@ pub fn device_id(name: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn connect(config: SessionConfig, credentials: Credentials,
|
pub fn connect(
|
||||||
cache: Option<Cache>, handle: Handle)
|
config: SessionConfig,
|
||||||
-> Box<Future<Item=Session, Error=io::Error>>
|
credentials: Credentials,
|
||||||
{
|
cache: Option<Cache>,
|
||||||
|
handle: Handle,
|
||||||
|
) -> Box<Future<Item = Session, Error = io::Error>> {
|
||||||
let access_point = apresolve_or_fallback::<io::Error>(&handle);
|
let access_point = apresolve_or_fallback::<io::Error>(&handle);
|
||||||
|
|
||||||
|
|
||||||
let handle_ = handle.clone();
|
let handle_ = handle.clone();
|
||||||
let connection = access_point.and_then(move |addr| {
|
let connection = access_point.and_then(move |addr| {
|
||||||
info!("Connecting to AP \"{}\"", addr);
|
info!("Connecting to AP \"{}\"", addr);
|
||||||
|
@ -66,9 +67,8 @@ impl Session {
|
||||||
});
|
});
|
||||||
|
|
||||||
let device_id = config.device_id.clone();
|
let device_id = config.device_id.clone();
|
||||||
let authentication = connection.and_then(move |connection| {
|
let authentication = connection
|
||||||
connection::authenticate(connection, credentials, device_id)
|
.and_then(move |connection| connection::authenticate(connection, credentials, device_id));
|
||||||
});
|
|
||||||
|
|
||||||
let result = authentication.map(move |(transport, reusable_credentials)| {
|
let result = authentication.map(move |(transport, reusable_credentials)| {
|
||||||
info!("Authenticated as \"{}\" !", reusable_credentials.username);
|
info!("Authenticated as \"{}\" !", reusable_credentials.username);
|
||||||
|
@ -77,7 +77,11 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (session, task) = Session::create(
|
let (session, task) = Session::create(
|
||||||
&handle, transport, config, cache, reusable_credentials.username.clone()
|
&handle,
|
||||||
|
transport,
|
||||||
|
config,
|
||||||
|
cache,
|
||||||
|
reusable_credentials.username.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
handle.spawn(task.map_err(|e| panic!(e)));
|
handle.spawn(task.map_err(|e| panic!(e)));
|
||||||
|
@ -88,10 +92,13 @@ impl Session {
|
||||||
Box::new(result)
|
Box::new(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(handle: &Handle, transport: connection::Transport,
|
fn create(
|
||||||
config: SessionConfig, cache: Option<Cache>, username: String)
|
handle: &Handle,
|
||||||
-> (Session, Box<Future<Item = (), Error = io::Error>>)
|
transport: connection::Transport,
|
||||||
{
|
config: SessionConfig,
|
||||||
|
cache: Option<Cache>,
|
||||||
|
username: String,
|
||||||
|
) -> (Session, Box<Future<Item = (), Error = io::Error>>) {
|
||||||
let (sink, stream) = transport.split();
|
let (sink, stream) = transport.split();
|
||||||
|
|
||||||
let (sender_tx, sender_rx) = mpsc::unbounded();
|
let (sender_tx, sender_rx) = mpsc::unbounded();
|
||||||
|
@ -121,11 +128,15 @@ impl Session {
|
||||||
|
|
||||||
let sender_task = sender_rx
|
let sender_task = sender_rx
|
||||||
.map_err(|e| -> io::Error { panic!(e) })
|
.map_err(|e| -> io::Error { panic!(e) })
|
||||||
.forward(sink).map(|_| ());
|
.forward(sink)
|
||||||
|
.map(|_| ());
|
||||||
let receiver_task = DispatchTask(stream, session.weak());
|
let receiver_task = DispatchTask(stream, session.weak());
|
||||||
|
|
||||||
let task = Box::new((receiver_task, sender_task).into_future()
|
let task = Box::new(
|
||||||
.map(|((), ())| ()));
|
(receiver_task, sender_task)
|
||||||
|
.into_future()
|
||||||
|
.map(|((), ())| ()),
|
||||||
|
);
|
||||||
|
|
||||||
(session, task)
|
(session, task)
|
||||||
}
|
}
|
||||||
|
@ -143,16 +154,21 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn<F, R>(&self, f: F)
|
pub fn spawn<F, R>(&self, f: F)
|
||||||
where F: FnOnce(&Handle) -> R + Send + 'static,
|
where
|
||||||
R: IntoFuture<Item=(), Error=()>,
|
F: FnOnce(&Handle) -> R + Send + 'static,
|
||||||
R::Future: 'static
|
R: IntoFuture<Item = (), Error = ()>,
|
||||||
|
R::Future: 'static,
|
||||||
{
|
{
|
||||||
self.0.handle.spawn(f)
|
self.0.handle.spawn(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_info(&self) {
|
fn debug_info(&self) {
|
||||||
debug!("Session[{}] strong={} weak={}",
|
debug!(
|
||||||
self.0.session_id, Arc::strong_count(&self.0), Arc::weak_count(&self.0));
|
"Session[{}] strong={} weak={}",
|
||||||
|
self.0.session_id,
|
||||||
|
Arc::strong_count(&self.0),
|
||||||
|
Arc::weak_count(&self.0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
|
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
|
||||||
|
@ -161,7 +177,7 @@ impl Session {
|
||||||
0x4 => {
|
0x4 => {
|
||||||
self.debug_info();
|
self.debug_info();
|
||||||
self.send_packet(0x49, data.as_ref().to_owned());
|
self.send_packet(0x49, data.as_ref().to_owned());
|
||||||
},
|
}
|
||||||
0x4a => (),
|
0x4a => (),
|
||||||
0x1b => {
|
0x1b => {
|
||||||
let country = String::from_utf8(data.as_ref().to_owned()).unwrap();
|
let country = String::from_utf8(data.as_ref().to_owned()).unwrap();
|
||||||
|
@ -229,10 +245,12 @@ impl Drop for SessionInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DispatchTask<S>(S, SessionWeak)
|
struct DispatchTask<S>(S, SessionWeak)
|
||||||
where S: Stream<Item = (u8, Bytes)>;
|
where
|
||||||
|
S: Stream<Item = (u8, Bytes)>;
|
||||||
|
|
||||||
impl <S> Future for DispatchTask<S>
|
impl<S> Future for DispatchTask<S>
|
||||||
where S: Stream<Item = (u8, Bytes)>
|
where
|
||||||
|
S: Stream<Item = (u8, Bytes)>,
|
||||||
{
|
{
|
||||||
type Item = ();
|
type Item = ();
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
|
@ -240,9 +258,7 @@ impl <S> Future for DispatchTask<S>
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
let session = match self.1.try_upgrade() {
|
let session = match self.1.try_upgrade() {
|
||||||
Some(session) => session,
|
Some(session) => session,
|
||||||
None => {
|
None => return Ok(Async::Ready(())),
|
||||||
return Ok(Async::Ready(()))
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -252,8 +268,9 @@ impl <S> Future for DispatchTask<S>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <S> Drop for DispatchTask<S>
|
impl<S> Drop for DispatchTask<S>
|
||||||
where S: Stream<Item = (u8, Bytes)>
|
where
|
||||||
|
S: Stream<Item = (u8, Bytes)>,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("drop Dispatch");
|
debug!("drop Dispatch");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std;
|
use std;
|
||||||
|
|
||||||
#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub struct u128 {
|
pub struct u128 {
|
||||||
high: u64,
|
high: u64,
|
||||||
|
@ -28,12 +28,7 @@ impl std::ops::Add<u128> for u128 {
|
||||||
type Output = u128;
|
type Output = u128;
|
||||||
fn add(self, rhs: u128) -> u128 {
|
fn add(self, rhs: u128) -> u128 {
|
||||||
let low = self.low + rhs.low;
|
let low = self.low + rhs.low;
|
||||||
let high = self.high + rhs.high +
|
let high = self.high + rhs.high + if low < self.low { 1 } else { 0 };
|
||||||
if low < self.low {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
u128::from_parts(high, low)
|
u128::from_parts(high, low)
|
||||||
}
|
}
|
||||||
|
@ -43,12 +38,7 @@ impl<'a> std::ops::Add<&'a u128> for u128 {
|
||||||
type Output = u128;
|
type Output = u128;
|
||||||
fn add(self, rhs: &'a u128) -> u128 {
|
fn add(self, rhs: &'a u128) -> u128 {
|
||||||
let low = self.low + rhs.low;
|
let low = self.low + rhs.low;
|
||||||
let high = self.high + rhs.high +
|
let high = self.high + rhs.high + if low < self.low { 1 } else { 0 };
|
||||||
if low < self.low {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
u128::from_parts(high, low)
|
u128::from_parts(high, low)
|
||||||
}
|
}
|
||||||
|
@ -60,20 +50,23 @@ impl std::convert::From<u8> for u128 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::ops::Mul<u128> for u128 {
|
impl std::ops::Mul<u128> for u128 {
|
||||||
type Output = u128;
|
type Output = u128;
|
||||||
|
|
||||||
fn mul(self, rhs: u128) -> u128 {
|
fn mul(self, rhs: u128) -> u128 {
|
||||||
let top: [u64; 4] = [self.high >> 32,
|
let top: [u64; 4] = [
|
||||||
self.high & 0xFFFFFFFF,
|
self.high >> 32,
|
||||||
self.low >> 32,
|
self.high & 0xFFFFFFFF,
|
||||||
self.low & 0xFFFFFFFF];
|
self.low >> 32,
|
||||||
|
self.low & 0xFFFFFFFF,
|
||||||
|
];
|
||||||
|
|
||||||
let bottom: [u64; 4] = [rhs.high >> 32,
|
let bottom: [u64; 4] = [
|
||||||
rhs.high & 0xFFFFFFFF,
|
rhs.high >> 32,
|
||||||
rhs.low >> 32,
|
rhs.high & 0xFFFFFFFF,
|
||||||
rhs.low & 0xFFFFFFFF];
|
rhs.low >> 32,
|
||||||
|
rhs.low & 0xFFFFFFFF,
|
||||||
|
];
|
||||||
|
|
||||||
let mut rows = [u128::zero(); 16];
|
let mut rows = [u128::zero(); 16];
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
use num_traits::{Zero, One};
|
|
||||||
use num_integer::Integer;
|
use num_integer::Integer;
|
||||||
use rand::{Rng, Rand};
|
use num_traits::{One, Zero};
|
||||||
|
use rand::{Rand, Rng};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Mul, Rem, Shr};
|
use std::ops::{Mul, Rem, Shr};
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ mod int128;
|
||||||
mod spotify_id;
|
mod spotify_id;
|
||||||
|
|
||||||
pub use util::int128::u128;
|
pub use util::int128::u128;
|
||||||
pub use util::spotify_id::{SpotifyId, FileId};
|
pub use util::spotify_id::{FileId, SpotifyId};
|
||||||
|
|
||||||
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
||||||
rng.gen_iter().take(size).collect()
|
rng.gen_iter().take(size).collect()
|
||||||
|
@ -57,8 +57,8 @@ impl<'s> Iterator for StrChunks<'s> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ReadSeek : ::std::io::Read + ::std::io::Seek { }
|
pub trait ReadSeek: ::std::io::Read + ::std::io::Seek {}
|
||||||
impl <T: ::std::io::Read + ::std::io::Seek> ReadSeek for T { }
|
impl<T: ::std::io::Read + ::std::io::Seek> ReadSeek for T {}
|
||||||
|
|
||||||
pub trait Seq {
|
pub trait Seq {
|
||||||
fn next(&self) -> Self;
|
fn next(&self) -> Self;
|
||||||
|
@ -77,7 +77,7 @@ impl_seq!(u8 u16 u32 u64 usize);
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||||
pub struct SeqGenerator<T: Seq>(T);
|
pub struct SeqGenerator<T: Seq>(T);
|
||||||
|
|
||||||
impl <T: Seq> SeqGenerator<T> {
|
impl<T: Seq> SeqGenerator<T> {
|
||||||
pub fn new(value: T) -> Self {
|
pub fn new(value: T) -> Self {
|
||||||
SeqGenerator(value)
|
SeqGenerator(value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use std;
|
use std;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use util::u128;
|
use util::u128;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
// Unneeded since 1.21
|
// Unneeded since 1.21
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
|
|
||||||
#[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct SpotifyId(u128);
|
pub struct SpotifyId(u128);
|
||||||
|
|
||||||
const BASE62_DIGITS: &'static [u8] =
|
const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
||||||
|
|
||||||
impl SpotifyId {
|
impl SpotifyId {
|
||||||
|
@ -79,7 +78,7 @@ impl SpotifyId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy,Clone,PartialEq,Eq,PartialOrd,Ord,Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct FileId(pub [u8; 20]);
|
pub struct FileId(pub [u8; 20]);
|
||||||
|
|
||||||
impl FileId {
|
impl FileId {
|
||||||
|
|
4
rustfmt.toml
Normal file
4
rustfmt.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
max_width = 105
|
||||||
|
reorder_imports = true
|
||||||
|
reorder_imports_in_group = true
|
||||||
|
reorder_modules = true
|
Loading…
Reference in a new issue