mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Initial commit.
This commit is contained in:
commit
a993b60ffa
17 changed files with 872 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
target
|
||||
Cargo.lock
|
||||
.cargo
|
107
Cargo.lock
generated
Normal file
107
Cargo.lock
generated
Normal file
|
@ -0,0 +1,107 @@
|
|||
[root]
|
||||
name = "librespot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mod_path 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf 0.0.9 (git+https://github.com/plietar/rust-protobuf.git)",
|
||||
"protobuf_macros 0.1.0 (git+https://github.com/plietar/rust-protobuf-macros.git)",
|
||||
"rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mod_path"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/plietar/rust-protobuf.git#f26efc36c09602109a01885449e16d15a8494cb8"
|
||||
|
||||
[[package]]
|
||||
name = "protobuf_macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/plietar/rust-protobuf-macros.git#3631dbaac78e955b36fdb71bb79c9b3cdc4bd4d9"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-crypto"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-gmp"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/plietar/rust-gmp.git#150cb69f787d413e95860dc7268a6399163f468f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "librespot"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||
build = "build.rs"
|
||||
links = "gmp"
|
||||
|
||||
#[[bin]]
|
||||
#name = "librespot"
|
||||
#path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
mod_path = "*"
|
||||
byteorder = "*"
|
||||
num = "*"
|
||||
rand = "*"
|
||||
lazy_static = "0.1.*"
|
||||
rust-crypto = "*"
|
||||
|
||||
[dependencies.protobuf]
|
||||
git = "https://github.com/plietar/rust-protobuf.git"
|
||||
|
||||
[dependencies.protobuf_macros]
|
||||
git = "https://github.com/plietar/rust-protobuf-macros.git"
|
||||
|
||||
[dependencies.rust-gmp]
|
||||
git = "https://github.com/plietar/rust-gmp.git"
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Paul Lietar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
43
build.rs
Normal file
43
build.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use std::env;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProtobufError {
|
||||
IoError(::std::io::Error),
|
||||
Other
|
||||
}
|
||||
|
||||
impl std::convert::From<::std::io::Error> for ProtobufError {
|
||||
fn from(e: ::std::io::Error) -> ProtobufError {
|
||||
ProtobufError::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn compile(prefix : &Path, files : &[&Path]) -> Result<(),ProtobufError>{
|
||||
let mut c = Command::new("protoc");
|
||||
c.arg("--rust_out").arg(env::var("OUT_DIR").unwrap())
|
||||
.arg("--proto_path").arg(prefix.to_str().unwrap());
|
||||
for f in files.iter() {
|
||||
c.arg(f.to_str().unwrap());
|
||||
}
|
||||
|
||||
//c.stdout(Stdio::inherit());
|
||||
c.stderr(Stdio::inherit());
|
||||
|
||||
let mut p = try!(c.spawn());
|
||||
let r = try!(p.wait());
|
||||
return match r.success() {
|
||||
true => Ok(()),
|
||||
false => Err(ProtobufError::Other),
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let prefix = Path::new("protocol");
|
||||
compile(&prefix, &[
|
||||
&prefix.join("keyexchange.proto"),
|
||||
&prefix.join("authentication.proto")
|
||||
]).unwrap();
|
||||
}
|
||||
|
51
protocol/authentication.proto
Normal file
51
protocol/authentication.proto
Normal file
|
@ -0,0 +1,51 @@
|
|||
message AuthRequest {
|
||||
enum LoginMethod {
|
||||
PASSWORD = 0x0;
|
||||
TOKEN = 0x3;
|
||||
}
|
||||
|
||||
message Credentials {
|
||||
optional string username = 0x0a;
|
||||
required LoginMethod method = 0x14;
|
||||
required bytes password = 0x1e;
|
||||
}
|
||||
required Credentials credentials = 0x0a;
|
||||
|
||||
message Data1 {
|
||||
required uint32 data0 = 0x0a;
|
||||
required uint32 data1 = 0x3c;
|
||||
required string partner = 0x5a; // "Partner %s %s;%s" % ("lenbrook_bluesound", brand, model)
|
||||
required string deviceid = 0x64; // sha1(os_device_id).hexdigest()
|
||||
}
|
||||
required Data1 data1 = 0x32;
|
||||
|
||||
required string version = 0x46;
|
||||
|
||||
message Data3 {
|
||||
required uint32 data0 = 0x01;
|
||||
required bytes appkey1 = 0x02;
|
||||
required bytes appkey2 = 0x03;
|
||||
required string data3 = 0x04;
|
||||
required bytes data4 = 0x05;
|
||||
}
|
||||
required Data3 data3 = 0x50;
|
||||
}
|
||||
|
||||
message AuthSuccess {
|
||||
required string username = 0x0a;
|
||||
required uint32 data1 = 0x14;
|
||||
required uint32 data2 = 0x19;
|
||||
required uint32 data3 = 0x1e;
|
||||
required bytes data4 = 0x28;
|
||||
required bytes data5 = 0x32;
|
||||
}
|
||||
|
||||
message AuthFailure {
|
||||
required uint32 code = 0x0a;
|
||||
required Data1 data1 = 0x32;
|
||||
|
||||
message Data1 {
|
||||
required string data0 = 0x01;
|
||||
}
|
||||
}
|
||||
|
61
protocol/keyexchange.proto
Normal file
61
protocol/keyexchange.proto
Normal file
|
@ -0,0 +1,61 @@
|
|||
message Request {
|
||||
message Data0 {
|
||||
required uint32 data0 = 0x0a; // 05
|
||||
required uint32 data1 = 0x1e; // 02
|
||||
required uint64 data2 = 0x28; // 10800000000
|
||||
}
|
||||
|
||||
message Data2 {
|
||||
message Data0 {
|
||||
required bytes data0 = 0x0a; // 60
|
||||
required uint32 data1 = 0x14; // 01
|
||||
}
|
||||
required Data0 data0 = 0x0a; // 65
|
||||
}
|
||||
|
||||
required Data0 data0 = 0x0a; // 0d
|
||||
required uint32 data1 = 0x1e; // 00
|
||||
required Data2 data2 = 0x32; // 67
|
||||
required bytes random = 0x3c; // 10
|
||||
required bytes data4 = 0x46; // 01
|
||||
required bytes data5 = 0x50; // 02
|
||||
|
||||
}
|
||||
|
||||
message Response {
|
||||
message Data {
|
||||
message Data0 {
|
||||
message Data0 {
|
||||
required bytes data0 = 0x0a; // 60
|
||||
required uint32 data1 = 0x14;
|
||||
required bytes data2 = 0x1e; // 100
|
||||
}
|
||||
required Data0 data0 = 0x0a;
|
||||
}
|
||||
message Data3 {
|
||||
required bytes data0 = 0x0a;
|
||||
}
|
||||
|
||||
required Data0 data0 = 0x0a;
|
||||
required bytes data1 = 0x14;
|
||||
required bytes data2 = 0x1e;
|
||||
required Data3 data3 = 0x28;
|
||||
required bytes data4 = 0x32;
|
||||
required bytes data5 = 0x3c;
|
||||
}
|
||||
|
||||
required Data data = 0x0a;
|
||||
}
|
||||
|
||||
message ChallengePacket {
|
||||
message Data0 {
|
||||
message Data0 {
|
||||
required bytes data0 = 0x0a;
|
||||
}
|
||||
required Data0 data0 = 0x0a;
|
||||
}
|
||||
required Data0 data0 = 0x0a;
|
||||
required bytes data1 = 0x14;
|
||||
required bytes data2 = 0x1e;
|
||||
}
|
||||
|
49
protocol/mercury.proto
Normal file
49
protocol/mercury.proto
Normal file
|
@ -0,0 +1,49 @@
|
|||
message MercuryRequest {
|
||||
required string url = 0x01;
|
||||
optional string mime = 0x02;
|
||||
required string method = 0x03;
|
||||
}
|
||||
|
||||
message MercuryReply {
|
||||
required string url = 0x01;
|
||||
required string mime = 0x02;
|
||||
required sint32 code = 0x04;
|
||||
repeated Header header = 0x06;
|
||||
|
||||
message Header {
|
||||
required string key = 0x01;
|
||||
required bytes value = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
message MercuryGetRequest {
|
||||
required string url = 0x01;
|
||||
optional string method = 0x03;
|
||||
}
|
||||
|
||||
message MercuryMultiGetRequest {
|
||||
repeated MercuryGetRequest request = 0x01;
|
||||
}
|
||||
|
||||
message MercuryGetReply {
|
||||
enum CachePolicy {
|
||||
CACHE_NO = 1;
|
||||
CACHE_PRIVATE = 2;
|
||||
CACHE_PUBLIC = 3;
|
||||
}
|
||||
optional sint32 code = 0x01;
|
||||
optional CachePolicy cache_policy = 0x03;
|
||||
optional uint32 ttl = 0x04;
|
||||
optional bytes etag = 0x05;
|
||||
optional string mime = 0x06;
|
||||
optional bytes body = 0x07;
|
||||
}
|
||||
|
||||
message MercuryMultiGetReply {
|
||||
repeated MercuryGetReply reply = 0x1;
|
||||
}
|
||||
|
||||
message MercurySubscribed {
|
||||
required string url = 0x1;
|
||||
}
|
||||
|
154
protocol/metadata.proto
Normal file
154
protocol/metadata.proto
Normal file
|
@ -0,0 +1,154 @@
|
|||
message TopTracks {
|
||||
optional string country = 1;
|
||||
repeated Track track = 2;
|
||||
}
|
||||
message ActivityPeriod {
|
||||
optional sint32 start_year = 1;
|
||||
optional sint32 end_year = 2;
|
||||
optional sint32 decade = 3;
|
||||
}
|
||||
message Artist {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 popularity = 3;
|
||||
repeated TopTracks top_track = 4;
|
||||
repeated AlbumGroup album_group = 5;
|
||||
repeated AlbumGroup single_group = 6;
|
||||
repeated AlbumGroup compilation_group = 7;
|
||||
repeated AlbumGroup appears_on_group = 8;
|
||||
repeated string genre = 9;
|
||||
repeated ExternalId external_id = 10;
|
||||
repeated Image portrait = 11;
|
||||
repeated Biography biography = 12;
|
||||
repeated ActivityPeriod activity_period = 13;
|
||||
repeated Restriction restriction = 14;
|
||||
repeated Artist related = 15;
|
||||
optional bool is_portrait_album_cover = 16;
|
||||
optional ImageGroup portrait_group = 17;
|
||||
}
|
||||
message AlbumGroup {
|
||||
repeated Album album = 1;
|
||||
}
|
||||
message Date {
|
||||
optional sint32 year = 1;
|
||||
optional sint32 month = 2;
|
||||
optional sint32 day = 3;
|
||||
}
|
||||
message Album {
|
||||
enum Type {
|
||||
ALBUM = 1;
|
||||
SINGLE = 2;
|
||||
COMPILATION = 3;
|
||||
}
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
repeated Artist artist = 3;
|
||||
optional Type type = 4;
|
||||
optional string label = 5;
|
||||
optional Date date = 6;
|
||||
optional sint32 popularity = 7;
|
||||
repeated string genre = 8;
|
||||
repeated Image cover = 9;
|
||||
repeated ExternalId external_id = 10;
|
||||
repeated Disc disc = 11;
|
||||
repeated string review = 12;
|
||||
repeated Copyright copyright = 13;
|
||||
repeated Restriction restriction = 14;
|
||||
repeated Album related = 15;
|
||||
repeated SalePeriod sale_period = 16;
|
||||
optional ImageGroup cover_group = 17;
|
||||
}
|
||||
|
||||
message Track {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional Album album = 3;
|
||||
repeated Artist artist = 4;
|
||||
optional sint32 number = 5;
|
||||
optional sint32 disc_number = 6;
|
||||
optional sint32 duration = 7;
|
||||
optional sint32 popularity = 8;
|
||||
optional bool explicit = 9;
|
||||
repeated ExternalId external_id = 10;
|
||||
repeated Restriction restriction = 11;
|
||||
repeated AudioFile file = 12;
|
||||
repeated Track alternative = 13;
|
||||
repeated SalePeriod sale_period = 14;
|
||||
repeated AudioFile preview = 15;
|
||||
}
|
||||
message Image {
|
||||
enum Size {
|
||||
DEFAULT = 0;
|
||||
SMALL = 1;
|
||||
LARGE = 2;
|
||||
XLARGE = 3;
|
||||
}
|
||||
optional bytes file_id = 1;
|
||||
optional Size size = 2;
|
||||
optional sint32 width = 3;
|
||||
optional sint32 height = 4;
|
||||
}
|
||||
message ImageGroup {
|
||||
repeated Image image = 1;
|
||||
}
|
||||
message Biography {
|
||||
optional string text = 1;
|
||||
repeated Image portrait = 2;
|
||||
repeated ImageGroup portrait_group = 3;
|
||||
}
|
||||
message Disc {
|
||||
optional sint32 number = 1;
|
||||
optional string name = 2;
|
||||
repeated Track track = 3;
|
||||
}
|
||||
message Copyright {
|
||||
enum Type {
|
||||
P = 0;
|
||||
C = 1;
|
||||
}
|
||||
optional Type type = 1;
|
||||
optional string text = 2;
|
||||
}
|
||||
message Restriction {
|
||||
enum Catalogue {
|
||||
FREE = 0;
|
||||
PREMIUM = 1;
|
||||
SHUFFLE = 3;
|
||||
COMMERCIAL = 4;
|
||||
}
|
||||
enum Type {
|
||||
STREAMING = 0;
|
||||
}
|
||||
repeated Catalogue catalogue = 1;
|
||||
optional string countries_allowed = 2;
|
||||
optional string countries_forbidden = 3;
|
||||
optional Type type = 4;
|
||||
repeated string usage = 5;
|
||||
}
|
||||
|
||||
message SalePeriod {
|
||||
repeated Restriction restriction = 1;
|
||||
optional Date start = 2;
|
||||
optional Date end = 3;
|
||||
}
|
||||
|
||||
message ExternalId {
|
||||
optional string type = 1;
|
||||
optional string id = 2;
|
||||
}
|
||||
|
||||
message AudioFile {
|
||||
enum Format {
|
||||
OGG_VORBIS_96 = 0;
|
||||
OGG_VORBIS_160 = 1;
|
||||
OGG_VORBIS_320 = 2;
|
||||
MP3_256 = 3;
|
||||
MP3_320 = 4;
|
||||
MP3_160 = 5;
|
||||
MP3_96 = 6;
|
||||
OTHER1 = 7; // TODO
|
||||
OTHER2 = 8; // TODO
|
||||
}
|
||||
optional bytes gid = 1;
|
||||
optional Format format = 2;
|
||||
}
|
92
protocol/spirc.proto
Normal file
92
protocol/spirc.proto
Normal file
|
@ -0,0 +1,92 @@
|
|||
enum MessageType {
|
||||
kMessageTypeHello = 1;
|
||||
kMessageTypeGoodbye = 2;
|
||||
kMessageTypeNotify = 10;
|
||||
kMessageTypeLoad = 20;
|
||||
kMessageTypePlay = 21;
|
||||
kMessageTypePause = 22;
|
||||
// kMessageTypePlayPause = 23;
|
||||
kMessageTypeSeek = 24;
|
||||
kMessageTypePrev = 25;
|
||||
kMessageTypeNext = 26;
|
||||
kMessageTypeVolume = 27;
|
||||
kMessageTypeShuffle = 28;
|
||||
kMessageTypeRepeat = 29;
|
||||
kMessageTypeQueue = 30;
|
||||
kMessageTypeVolumeDown = 31;
|
||||
kMessageTypeVolumeUp = 32;
|
||||
kMessageTypeAddToQueue = 33;
|
||||
}
|
||||
enum PlayStatus {
|
||||
kPlayStatusStop = 0;
|
||||
kPlayStatusPlay = 1;
|
||||
kPlayStatusPause = 2;
|
||||
kPlayStatusLoading = 3;
|
||||
kPlayStatusError = 4;
|
||||
}
|
||||
message Goodbye {
|
||||
required string reason = 1;
|
||||
}
|
||||
message State {
|
||||
optional string contextURI = 0x2;
|
||||
optional uint32 index = 0x3;
|
||||
optional uint32 position = 0x4;
|
||||
optional PlayStatus status = 0x5;
|
||||
|
||||
optional uint64 timestamp = 0x7;
|
||||
optional string context_name = 0x8;
|
||||
optional uint32 duration = 0x9;
|
||||
optional uint32 data9 = 0xa;
|
||||
repeated uint64 data10 = 0xb;
|
||||
optional bool shuffle = 0xd;
|
||||
optional bool repeat = 0xe;
|
||||
|
||||
optional string data12 = 0x14;
|
||||
optional uint32 data13 = 0x15;
|
||||
optional uint32 data14 = 0x18;
|
||||
optional uint32 data15 = 0x19;
|
||||
optional uint32 data16 = 0x1a;
|
||||
repeated QueuedTrack queued = 0x1b;
|
||||
message QueuedTrack {
|
||||
optional bytes gid = 0x1;
|
||||
optional string local_uri = 0x2;
|
||||
optional uint32 data1 = 0x3;
|
||||
}
|
||||
}
|
||||
|
||||
message Frame {
|
||||
required uint32 version = 1;
|
||||
required string source = 2;
|
||||
required string version_string = 3;
|
||||
required uint32 msgid = 4;
|
||||
required uint32 type = 5;
|
||||
|
||||
required DeviceInfo device = 0x7;
|
||||
|
||||
//required Goodbye goodbye = 0xb;
|
||||
optional State state = 0xc;
|
||||
|
||||
optional uint32 position = 0xd;
|
||||
optional uint32 volume = 0xe;
|
||||
|
||||
optional uint64 timestamp = 0x11;
|
||||
optional string destination = 0x12;
|
||||
|
||||
message DeviceInfo {
|
||||
optional string version = 0x1;
|
||||
required bool active = 0xa;
|
||||
required bool foreground = 0xb;
|
||||
required uint32 volume = 0xc;
|
||||
required string name = 0xd;
|
||||
optional uint32 data15 = 0xe;
|
||||
required uint64 activeTime = 0xf;
|
||||
repeated Data17 data17 = 0x11;
|
||||
|
||||
message Data17 {
|
||||
required uint32 data0 = 0x1;
|
||||
optional uint32 data1 = 0x2;
|
||||
repeated string data2 = 0x3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
0
protocol/spotify.proto
Normal file
0
protocol/spotify.proto
Normal file
43
src/connection.rs
Normal file
43
src/connection.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use util;
|
||||
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
|
||||
use std::io::{Write,Read};
|
||||
use std::net::TcpStream;
|
||||
|
||||
pub struct Connection {
|
||||
stream: TcpStream,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn connect() -> Connection {
|
||||
Connection {
|
||||
stream: TcpStream::connect("lon3-accesspoint-a26.ap.spotify.com:4070").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_packet(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
self.send_packet_prefix(&[], data)
|
||||
}
|
||||
|
||||
pub fn send_packet_prefix(&mut self, prefix: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
let size = prefix.len() + 4 + data.len();
|
||||
let mut buf = Vec::with_capacity(size);
|
||||
|
||||
buf.write(prefix).unwrap();
|
||||
buf.write_u32::<BigEndian>(size as u32).unwrap();
|
||||
buf.write(data).unwrap();
|
||||
self.stream.write(&buf).unwrap();
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn recv_packet(&mut self) -> Vec<u8> {
|
||||
let size : usize = self.stream.read_u32::<BigEndian>().unwrap() as usize;
|
||||
let mut buffer = util::alloc_buffer(size - 4);
|
||||
|
||||
self.stream.read(&mut buffer).unwrap();
|
||||
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
91
src/cryptoutil.rs
Normal file
91
src/cryptoutil.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use rand;
|
||||
use gmp::Mpz;
|
||||
use std::num::FromPrimitive;
|
||||
use crypto;
|
||||
use crypto::mac::Mac;
|
||||
use std::io::Write;
|
||||
|
||||
use util;
|
||||
|
||||
lazy_static! {
|
||||
static ref DH_GENERATOR: Mpz = Mpz::from_u64(0x2).unwrap();
|
||||
static ref DH_PRIME: Mpz = Mpz::from_bytes_be(&[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
|
||||
0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
|
||||
0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e,
|
||||
0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
|
||||
0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
|
||||
0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a,
|
||||
0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14,
|
||||
0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
|
||||
0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4,
|
||||
0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]);
|
||||
}
|
||||
|
||||
pub struct Crypto {
|
||||
private_key: Mpz,
|
||||
public_key: Mpz,
|
||||
shared: Option<SharedKeys>,
|
||||
}
|
||||
|
||||
pub struct SharedKeys {
|
||||
pub challenge: Vec<u8>,
|
||||
pub send_key: Vec<u8>,
|
||||
pub recv_key: Vec<u8>
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
pub fn new() -> Crypto {
|
||||
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
||||
Self::new_with_key(&key_data)
|
||||
}
|
||||
|
||||
pub fn new_with_key(key_data: &[u8]) -> Crypto {
|
||||
let private_key = Mpz::from_bytes_be(key_data);
|
||||
let public_key = DH_GENERATOR.powm(&private_key, &DH_PRIME);
|
||||
|
||||
Crypto {
|
||||
private_key: private_key,
|
||||
public_key: public_key,
|
||||
shared: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(&mut self, remote_key: &[u8], client_packet: &[u8], server_packet: &[u8]) {
|
||||
let shared_key = Mpz::from_bytes_be(remote_key).powm(&self.private_key, &DH_PRIME);
|
||||
|
||||
let mut data = Vec::with_capacity(0x54);
|
||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &shared_key.to_bytes_be());
|
||||
|
||||
for i in 1..6 {
|
||||
h.input(client_packet);
|
||||
h.input(server_packet);
|
||||
h.input(&[i]);
|
||||
data.write(&h.result().code()).unwrap();
|
||||
h.reset();
|
||||
}
|
||||
|
||||
h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &data[..0x14]);
|
||||
h.input(client_packet);
|
||||
h.input(server_packet);
|
||||
|
||||
self.shared = Some(SharedKeys{
|
||||
challenge: h.result().code().to_vec(),
|
||||
send_key: data[0x14..0x34].to_vec(),
|
||||
recv_key: data[0x34..0x54].to_vec()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> Vec<u8> {
|
||||
return self.public_key.to_bytes_be();
|
||||
}
|
||||
|
||||
pub fn shared(&self) -> &SharedKeys {
|
||||
match self.shared {
|
||||
Some(ref shared) => shared,
|
||||
None => panic!("ABC")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
src/main.rs
Normal file
28
src/main.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#![crate_name = "librespot"]
|
||||
|
||||
#![feature(plugin,core)]
|
||||
|
||||
#![plugin(mod_path)]
|
||||
#![plugin(protobuf_macros)]
|
||||
#[macro_use] extern crate lazy_static;
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate crypto;
|
||||
extern crate gmp;
|
||||
extern crate num;
|
||||
extern crate protobuf;
|
||||
extern crate rand;
|
||||
|
||||
mod connection;
|
||||
mod cryptoutil;
|
||||
mod protocol;
|
||||
mod session;
|
||||
mod util;
|
||||
|
||||
use session::Session;
|
||||
|
||||
fn main() {
|
||||
let mut s = Session::new();
|
||||
s.login();
|
||||
}
|
||||
|
3
src/protocol.rs
Normal file
3
src/protocol.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod_path! keyexchange (concat!(env!("OUT_DIR"), "/keyexchange.rs"));
|
||||
mod_path! authentication (concat!(env!("OUT_DIR"), "/authentication.rs"));
|
||||
|
78
src/session.rs
Normal file
78
src/session.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use connection::Connection;
|
||||
use cryptoutil::Crypto;
|
||||
use protocol;
|
||||
use util;
|
||||
use std::iter::{FromIterator,repeat};
|
||||
|
||||
use protobuf::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
pub struct Session {
|
||||
connection: Connection,
|
||||
crypto: Crypto,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new() -> Session {
|
||||
Session {
|
||||
connection: Connection::connect(),
|
||||
crypto: Crypto::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn login(&mut self) {
|
||||
let request = protobuf_init!(protocol::keyexchange::Request::new(), {
|
||||
data0 => {
|
||||
data0: 0x05,
|
||||
data1: 0x01,
|
||||
data2: 0x10800000000,
|
||||
},
|
||||
data1: 0,
|
||||
data2.data0 => {
|
||||
data0: self.crypto.public_key(),
|
||||
data1: 1,
|
||||
},
|
||||
random: util::rand_vec(&mut thread_rng(), 0x10),
|
||||
data4: vec![0x1e],
|
||||
data5: vec![0x08, 0x01]
|
||||
});
|
||||
|
||||
let init_client_packet =
|
||||
self.connection.send_packet_prefix(&[0,4], &request.write_to_bytes().unwrap());
|
||||
let init_server_packet =
|
||||
self.connection.recv_packet();
|
||||
|
||||
let response : protocol::keyexchange::Response =
|
||||
parse_from_bytes(&init_server_packet).unwrap();
|
||||
|
||||
protobuf_bind!(response, { data.data0.data0.data0: remote_key });
|
||||
|
||||
self.crypto.setup(&remote_key, &init_client_packet, &init_server_packet);
|
||||
|
||||
return;
|
||||
let appkey = vec![];
|
||||
let request = protobuf_init!(protocol::authentication::AuthRequest::new(), {
|
||||
credentials => {
|
||||
username: "USERNAME".to_string(),
|
||||
method: protocol::authentication::AuthRequest_LoginMethod::PASSWORD,
|
||||
password: b"PASSWORD".to_vec(),
|
||||
},
|
||||
data1 => {
|
||||
data0: 0,
|
||||
data1: 0,
|
||||
partner: "Partner blabla".to_string(),
|
||||
deviceid: "abc".to_string()
|
||||
},
|
||||
version: "master-v1.8.0-gad9e5b46".to_string(),
|
||||
data3 => {
|
||||
data0: 1,
|
||||
appkey1: appkey[0x1..0x81].to_vec(),
|
||||
appkey2: appkey[0x81..0x141].to_vec(),
|
||||
data3: "".to_string(),
|
||||
data4: Vec::from_iter(repeat(0).take(20))
|
||||
}
|
||||
});
|
||||
//println!("{:?}", response);
|
||||
}
|
||||
}
|
||||
|
20
src/util.rs
Normal file
20
src/util.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use rand::{Rng,Rand};
|
||||
|
||||
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
||||
let mut vec = Vec::with_capacity(size);
|
||||
|
||||
for _ in 0..size {
|
||||
vec.push(R::rand(rng));
|
||||
}
|
||||
|
||||
return vec
|
||||
}
|
||||
|
||||
pub fn alloc_buffer(size: usize) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(size);
|
||||
unsafe {
|
||||
vec.set_len(size);
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
Loading…
Reference in a new issue