move to new cspot

This commit is contained in:
philippe44
2023-03-25 16:48:41 -07:00
parent c712b78931
commit 008c36facf
2983 changed files with 465270 additions and 13569 deletions

View File

@@ -0,0 +1,32 @@
#pragma once
#include <functional>
#include <memory>
#include "CSpotContext.h"
#include "Utils.h"
namespace cspot {
class AccessKeyFetcher {
public:
AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx);
~AccessKeyFetcher();
typedef std::function<void(std::string)> Callback;
void getAccessKey(Callback callback);
private:
const std::string CLIENT_ID =
"65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id
const std::string SCOPES =
"streaming,user-library-read,user-library-modify,user-top-read,user-read-"
"recently-played"; // Required access scopes
std::shared_ptr<cspot::Context> ctx;
bool isExpired();
std::string accessKey;
long long int expiresAt;
};
} // namespace cspot

View File

@@ -1,21 +1,24 @@
#ifndef APRESOLVE_H
#define APRESOLVE_H
#pragma once
#include <memory>
#include <string>
class ApResolve {
private:
std::string getApList();
#include "HTTPClient.h"
#include "nlohmann/json.hpp"
public:
ApResolve();
/**
namespace cspot {
class ApResolve {
private:
std::string apOverride;
public:
ApResolve(std::string apOverride);
/**
* @brief Connects to spotify's servers and returns first valid ap address
*
* @return std::string Address in form of url:port
*/
std::string fetchFirstApAddress();
std::string fetchFirstApAddress();
};
#endif
} // namespace cspot

View File

@@ -1,82 +0,0 @@
#ifndef AUDIOCHUNK_H
#define AUDIOCHUNK_H
#include <memory>
#include <vector>
#include <string>
#include <algorithm>
#include "platform/WrappedSemaphore.h"
#include "Crypto.h"
#include "Utils.h"
#include <mutex>
class AudioChunk {
private:
/**
* @brief Calculates a correct IV by performing bignum addition.
*
* @param num Number to add to IV.
* @return std::vector<uint8_t>
*/
std::vector<uint8_t> getIVSum(uint32_t num);
size_t decryptedCount = 0;
size_t oldStartPos;
public:
std::unique_ptr<Crypto> crypto;
std::vector<uint8_t> decryptedData;
std::vector<uint8_t> audioKey;
bool keepInMemory = false;
std::mutex dataAccessMutex;
uint32_t startPosition;
uint32_t endPosition;
uint16_t seqId;
size_t headerFileSize = -1;
bool isLoaded = false;
bool isFailed = false;
/**
* @brief Triggered when audiochunk is fully downloaded and decrypted.
*/
std::unique_ptr<WrappedSemaphore> isLoadedSemaphore;
/**
* @brief
*/
std::unique_ptr<WrappedSemaphore> isHeaderFileSizeLoadedSemaphore;
/**
* Decrypts data and writes it to the target buffer
* @param target data buffer to write to
* @param offset data offset
* @param nbytes number of bytes to read
*/
void readData(uint8_t *target, size_t offset, size_t nbytes);
/**
* @brief AudioChunk handles all audiochunk related operations.
*
* @param seqId Sequence id of requested chunk
* @param audioKey Audio key used for decryption of audio data
* @param startPosition Start position of current chunk in audio file
* @param predictedEndPosition Predicted end position of given chunk. This is not final positon.
*/
AudioChunk(uint16_t seqId, std::vector<uint8_t> &audioKey, uint32_t startPosition, uint32_t predictedEndPosition);
~AudioChunk();
/**
* @brief Appends incoming chunked data to local cache.
*
* @param data encrypted binary audio data.
*/
void appendData(const std::vector<uint8_t> &data);
/**
* @brief Sets loaded status on the chunk
*
*/
void finalize();
};
#endif

View File

@@ -1,58 +0,0 @@
#ifndef AUDIOCHUNKMANAGER_H
#define AUDIOCHUNKMANAGER_H
#include <memory>
#include <atomic>
#include <algorithm>
#include <mutex>
#include "Utils.h"
#include "AudioChunk.h"
#include "Queue.h"
#include "BellTask.h"
#define DATA_SIZE_HEADER 24
#define DATA_SIZE_FOOTER 2
class AudioChunkManager : public bell::Task {
std::vector<std::shared_ptr<AudioChunk>> chunks;
bell::Queue<std::pair<std::vector<uint8_t>, bool>> audioChunkDataQueue;
void runTask();
public:
AudioChunkManager();
std::atomic<bool> isRunning = false;
std::mutex runningMutex;
std::mutex chunkMutex;
/**
* @brief Registers a new audio chunk request.
*
* Registering an audiochunk will trigger a request to spotify servers.
* All the incoming data will be redirected to this given audiochunk.
*
* @param seqId sequence identifier of given audio chunk.
* @param audioKey audio key of given file, used for decryption.
* @param startPos start position of audio chunk
* @param endPos end position of audio chunk. end - pos % 4 must be 0.
* @return std::shared_ptr<AudioChunk> registered audio chunk. Does not contain the data yet.
*/
std::shared_ptr<AudioChunk> registerNewChunk(uint16_t seqId, std::vector<uint8_t> &audioKey, uint32_t startPos, uint32_t endPos);
/**
* @brief Pushes binary data from spotify's servers containing audio chunks.
*
* This method pushes received data to a queue that is then received by manager's thread.
* That thread parses the data and passes it to a matching audio chunk.
*
* @param data binary data received from spotify's servers
* @param failed whenever given chunk request failed
*/
void handleChunkData(std::vector<uint8_t>& data, bool failed = false);
/**
* @brief Fails all requested chunks, used for reconnection.
*/
void failAllChunks();
void close();
};
#endif

View File

@@ -0,0 +1,46 @@
#pragma once
#include <algorithm>
#include <climits>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include "Crypto.h"
#include "Logger.h"
#include "NanoPBHelper.h"
#include "Utils.h"
#include "protobuf/authentication.pb.h"
#include "protobuf/keyexchange.pb.h"
namespace cspot {
class AuthChallenges {
public:
AuthChallenges();
~AuthChallenges();
std::vector<uint8_t> shanSendKey = {};
std::vector<uint8_t> shanRecvKey = {};
std::vector<uint8_t> prepareAuthPacket(std::vector<uint8_t>& authBlob,
int authType,
const std::string& deviceId,
const std::string& username);
std::vector<uint8_t> solveApHello(std::vector<uint8_t>& helloPacket,
std::vector<uint8_t>& data);
std::vector<uint8_t> prepareClientHello();
private:
const long long SPOTIFY_VERSION = 0x10800000000;
ClientResponseEncrypted authRequest;
ClientResponsePlaintext clientResPlaintext;
ClientHello clientHello;
APResponseMessage apResponse;
std::unique_ptr<Crypto> crypto;
};
} // namespace cspot

View File

@@ -0,0 +1,92 @@
#pragma once
#include <cstddef>
#include <memory>
#include "Crypto.h"
#include "WrappedSemaphore.h"
#include "Logger.h"
#include "Utils.h"
#include "CSpotContext.h"
#include "AccessKeyFetcher.h"
namespace cspot {
class CDNTrackStream {
public:
CDNTrackStream(std::shared_ptr<cspot::AccessKeyFetcher>);
~CDNTrackStream();
enum class Status { INITIALIZING, HAS_DATA, HAS_URL, FAILED };
struct TrackInfo {
std::string trackId;
std::string name;
std::string album;
std::string artist;
std::string imageUrl;
int duration;
};
TrackInfo trackInfo;
Status status;
std::unique_ptr<bell::WrappedSemaphore> trackReady;
void fetchFile(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& audioKey);
void fail();
void openStream();
size_t readBytes(uint8_t* dst, size_t bytes);
size_t getPosition();
size_t getSize();
void seek(size_t position);
private:
const int OPUS_HEADER_SIZE = 8 * 1024;
const int OPUS_FOOTER_PREFFERED = 1024 * 12; // 12K should be safe
const int SEEK_MARGIN_SIZE = 1024 * 4;
const int HTTP_BUFFER_SIZE = 1024 * 14;
const int SPOTIFY_OPUS_HEADER = 167;
// Used to store opus metadata, speeds up read
std::vector<uint8_t> header = std::vector<uint8_t>(OPUS_HEADER_SIZE);
std::vector<uint8_t> footer;
// General purpose buffer to read data
std::vector<uint8_t> httpBuffer = std::vector<uint8_t>(HTTP_BUFFER_SIZE);
// AES IV for decrypting the audio stream
const std::vector<uint8_t> audioAESIV = {0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb,
0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64,
0x3f, 0x63, 0x0d, 0x93};
std::unique_ptr<Crypto> crypto;
std::shared_ptr<cspot::AccessKeyFetcher> accessKeyFetcher;
std::unique_ptr<bell::HTTPClient::Response> httpConnection;
bool isConnected = false;
size_t position = 0; // Spotify header size
size_t totalFileSize = 0;
size_t lastRequestPosition = 0;
size_t lastRequestCapacity = 0;
bool enableRequestMargin = false;
std::string cdnUrl;
std::vector<uint8_t> trackId;
std::vector<uint8_t> audioKey;
void decrypt(uint8_t* dst, size_t nbytes, size_t pos);
};
} // namespace cspot

View File

@@ -0,0 +1,41 @@
#pragma once
#include <memory>
#include "MercurySession.h"
#include "TimeProvider.h"
#include "protobuf/metadata.pb.h"
namespace cspot {
struct Context {
struct ConfigState {
// Setup default bitrate to 160
AudioFormat audioFormat = AudioFormat::AudioFormat_OGG_VORBIS_160;
std::string deviceId;
std::string deviceName;
int volume;
std::string username;
std::string countryCode;
};
ConfigState config;
std::shared_ptr<TimeProvider> timeProvider;
std::shared_ptr<cspot::MercurySession> session;
static std::shared_ptr<Context> createFromBlob(std::shared_ptr<LoginBlob> blob) {
auto ctx = std::make_shared<Context>();
ctx->timeProvider = std::make_shared<TimeProvider>();
ctx->session = std::make_shared<MercurySession>(ctx->timeProvider);
ctx->config.deviceId = blob->getDeviceId();
ctx->config.deviceName = blob->getDeviceName();
ctx->config.volume = 0;
ctx->config.username = blob->getUserName();
return ctx;
}
};
} // namespace cspot

View File

@@ -1,67 +0,0 @@
#ifndef CHUNKEDAUDIOSTREAM_H
#define CHUNKEDAUDIOSTREAM_H
#include <iostream>
#include <vector>
#include <fstream>
#include <array>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <atomic>
#include "ivorbisfile.h"
#include "MercuryManager.h"
#include "AudioSink.h"
#include "AudioChunk.h"
#include "platform/WrappedMutex.h"
#include "ChunkedByteStream.h"
#define SPOTIFY_HEADER_SIZE 167
#define BUFFER_SIZE 0x20000 * 1.5
typedef std::function<void(uint8_t *, size_t)> pcmDataCallback;
enum class Whence
{
START,
CURRENT,
END
};
class ChunkedAudioStream
{
private:
// Vorbis related
OggVorbis_File vorbisFile;
ov_callbacks vorbisCallbacks;
int currentSection;
// Audio data
uint32_t duration;
bool loadingChunks = false;
uint16_t lastSequenceId = 0;
std::shared_ptr<MercuryManager> manager;
std::vector<uint8_t> fileId;
uint32_t startPositionMs;
public:
ChunkedAudioStream(std::vector<uint8_t> fileId, std::vector<uint8_t> audioKey, uint32_t duration, std::shared_ptr<MercuryManager> manager, uint32_t startPositionMs, bool isPaused);
~ChunkedAudioStream();
std::shared_ptr<ChunkedByteStream> byteStream;
std::function<void()> streamFinishedCallback;
std::atomic<bool> isPaused = false;
std::atomic<bool> isRunning = false;
std::atomic<bool> finished = false;
pcmDataCallback pcmCallback;
std::shared_ptr<AudioSink> audioSink;
WrappedMutex seekMutex;
std::vector<uint8_t> read(size_t bytes);
void seekMs(uint32_t positionMs);
void seek(size_t pos, Whence whence);
void startPlaybackLoop(uint8_t *pcmOut, size_t pcmOut_len);
};
#endif

View File

@@ -1,115 +0,0 @@
#ifndef CSPOT_CHUNKEDBYTESTREAM_H
#define CSPOT_CHUNKEDBYTESTREAM_H
#include "ByteStream.h"
#include "BellLogger.h"
#include <memory>
#include "MercuryManager.h"
#include "stdint.h"
class ChunkedByteStream : public bell::ByteStream {
private:
// AES key used for data decryption
std::vector<uint8_t> audioKey;
// Spotify internal fileId
std::vector<uint8_t> fileId;
// Buffer for storing currently read chunks
std::vector<std::shared_ptr<AudioChunk>> chunks;
// Current position of the read pointer
size_t pos = 0;
size_t fileSize = -1;
std::mutex readMutex;
std::atomic<bool> loadAheadEnabled = false;
std::shared_ptr<MercuryManager> mercuryManager;
/**
* Returns an audio chunk for given position.
* @param position requested position
* @return matching audio chunk, or nullptr if no chunk is available
*/
std::shared_ptr<AudioChunk> getChunkForPosition(size_t position);
/**
* Requests a new audio chunk from mercury manager. Returns its structure immediately.
* @param position index of a chunk to request
* @return requested chunk
*/
std::shared_ptr<AudioChunk> requestChunk(uint16_t position);
/**
* Tries to read data from a given audio chunk.
* @param buffer destination buffer
* @param bytes number of bytes to read
* @param chunk `AudioChunk` to read from
* @return number of bytes read
*/
size_t attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr<AudioChunk> chunk);
public:
ChunkedByteStream(std::shared_ptr<MercuryManager> manager);
~ChunkedByteStream() {};
/**
* Requests first chunk from the file, and then fills file information based on its header
*/
void fetchFileInformation();
/**
* Enables / disables load-ahead of chunks.
* @param loadAhead true to enable load ahead
*/
void setEnableLoadAhead(bool loadAhead);
/**
* Sets information about given spotify file, necessary for chunk request
* @param fileId id of given audio file
* @param audioKey audio key used for decryption
*/
void setFileInfo(std::vector<uint8_t>& fileId, std::vector<uint8_t>& audioKey);
// ---- ByteStream methods ----
/**
* Reads given amount of bytes from stream. Data is OPUS encoded.
* @param buffer buffer to read into
* @param size amount of bytes to read
* @return amount of bytes read
*/
size_t read(uint8_t *buf, size_t nbytes);
/**
* Seeks to given position in stream
* @param pos position to seek to
*/
void seek(size_t pos);
/**
* skip given amount of bytes in stream.
* @param nbytes amount of bytes to skip
*/
size_t skip(size_t nbytes);
/**
* Returns current position in stream.
* @return position
*/
size_t position();
/**
* Returns size of the file
* @return bytes in file
*/
size_t size();
/**
* Close the reader
*/
void close();
};
#endif //CSPOT_CHUNKEDBYTESTREAM_H

View File

@@ -1,27 +0,0 @@
#ifndef CONFIGJSON_H
#define CONFIGJSON_H
#include <memory>
#include <iostream>
#include "FileHelper.h"
#include "protobuf/metadata.pb.h"
class ConfigJSON
{
private:
std::shared_ptr<FileHelper> _file;
std::string _jsonFileName;
public:
ConfigJSON(std::string jsonFileName, std::shared_ptr<FileHelper> file);
bool load();
bool save();
uint16_t volume;
std::string deviceName;
std::string apOverride;
AudioFormat format;
};
extern std::shared_ptr<ConfigJSON> configMan;
#endif

View File

@@ -1,11 +1,11 @@
#ifndef CONSTANTPARAMETERS_H
#define CONSTANTPARAMETERS_H
#pragma once
#define MAX_VOLUME 65536
// variable weakly set in ZeroconfAuthentificator.cpp
extern char deviceId[];
namespace cspot {
// Hardcoded information sent to spotify servers
const char * const informationString = "cspot-player";
const char * const brandName = "cspot";
@@ -14,4 +14,4 @@ const char * const protocolVersion = "2.7.1";
const char * const defaultDeviceName = "CSpot";
const char * const swVersion = "1.0.0";
#endif
}

View File

@@ -1,16 +0,0 @@
#ifndef FILEHELPER_H
#define FILEHELPER_H
#include <iostream>
#include <fstream>
class FileHelper
{
public:
FileHelper() {}
virtual ~FileHelper() {}
virtual bool readFile(std::string filename, std::string &fileContent) = 0;
virtual bool writeFile(std::string filename, std::string fileContent) = 0;
};
#endif

View File

@@ -1,465 +0,0 @@
// AUTOGENERATED FILE, DO NOT EDIT BY HAND
#ifndef PB_KEYEXCHANGE_H
#define PB_KEYEXCHANGE_H
#include <memory>
#include <vector>
#include <PbCommon.h>
#include <PbWriter.h>
#include <PbReader.h>
enum class Product : uint32_t {
PRODUCT_CLIENT = 0,
PRODUCT_LIBSPOTIFY = 1,
PRODUCT_MOBILE = 2,
PRODUCT_PARTNER = 3,
PRODUCT_LIBSPOTIFY_EMBEDDED = 5
};
enum class Platform : uint32_t {
PLATFORM_WIN32_X86 = 0,
PLATFORM_OSX_X86 = 1,
PLATFORM_LINUX_X86 = 2,
PLATFORM_IPHONE_ARM = 3,
PLATFORM_S60_ARM = 4,
PLATFORM_OSX_PPC = 5,
PLATFORM_ANDROID_ARM = 6,
PLATFORM_WINDOWS_CE_ARM = 7,
PLATFORM_LINUX_X86_64 = 8,
PLATFORM_OSX_X86_64 = 9,
PLATFORM_PALM_ARM = 10,
PLATFORM_LINUX_SH = 11,
PLATFORM_FREEBSD_X86 = 12,
PLATFORM_FREEBSD_X86_64 = 13,
PLATFORM_BLACKBERRY_ARM = 14,
PLATFORM_SONOS = 15,
PLATFORM_LINUX_MIPS = 16,
PLATFORM_LINUX_ARM = 17,
PLATFORM_LOGITECH_ARM = 18,
PLATFORM_LINUX_BLACKFIN = 19,
PLATFORM_WP7_ARM = 20,
PLATFORM_ONKYO_ARM = 21,
PLATFORM_QNXNTO_ARM = 22,
PLATFORM_BCO_ARM = 23
};
enum class Cryptosuite : uint32_t {
CRYPTO_SUITE_SHANNON = 0,
CRYPTO_SUITE_RC4_SHA1_HMAC = 1
};
class LoginCryptoDiffieHellmanChallenge : public BaseProtobufMessage {
private:
public:
LoginCryptoDiffieHellmanChallenge() {};
std::vector<uint8_t> gs;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
reader->decodeVector(gs);
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
writer->addVector(10, gs);
}
};
class LoginCryptoChallengeUnion : public BaseProtobufMessage {
private:
public:
LoginCryptoChallengeUnion() {};
LoginCryptoDiffieHellmanChallenge diffie_hellman;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
diffie_hellman.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
diffie_hellman.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
}
};
class LoginCryptoDiffieHellmanHello : public BaseProtobufMessage {
private:
public:
LoginCryptoDiffieHellmanHello() {};
std::vector<uint8_t> gc;
uint32_t server_keys_known;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
reader->decodeVector(gc);
break;
case 20:
server_keys_known = reader->decodeVarInt<uint32_t>();
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
writer->addVector(10, gc);
writer->addVarInt(20, server_keys_known);
}
};
class LoginCryptoHelloUnion : public BaseProtobufMessage {
private:
public:
LoginCryptoHelloUnion() {};
LoginCryptoDiffieHellmanHello diffie_hellman;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
diffie_hellman.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
diffie_hellman.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
}
};
class BuildInfo : public BaseProtobufMessage {
private:
public:
BuildInfo() {};
Product product;
Platform platform;
uint64_t version;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
product = static_cast<Product>(reader->decodeVarInt<uint32_t>());
break;
case 30:
platform = static_cast<Platform>(reader->decodeVarInt<uint32_t>());
break;
case 40:
version = reader->decodeVarInt<uint64_t>();
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
writer->addVarInt(10, static_cast<uint32_t>(product));
writer->addVarInt(30, static_cast<uint32_t>(platform));
writer->addVarInt(40, version);
}
};
class FeatureSet : public BaseProtobufMessage {
private:
public:
FeatureSet() {};
bool autoupdate2;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 1:
autoupdate2 = reader->decodeVarInt<bool>();
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
writer->addVarInt(1, autoupdate2);
}
};
class APChallenge : public BaseProtobufMessage {
private:
public:
APChallenge() {};
LoginCryptoChallengeUnion login_crypto_challenge;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
login_crypto_challenge.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
login_crypto_challenge.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
}
};
class APResponseMessage : public BaseProtobufMessage {
private:
public:
APResponseMessage() {};
APChallenge challenge;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
challenge.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
challenge.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
}
};
class LoginCryptoDiffieHellmanResponse : public BaseProtobufMessage {
private:
public:
LoginCryptoDiffieHellmanResponse() {};
std::vector<uint8_t> hmac;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
reader->decodeVector(hmac);
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
writer->addVector(10, hmac);
}
};
class LoginCryptoResponseUnion : public BaseProtobufMessage {
private:
public:
LoginCryptoResponseUnion() {};
LoginCryptoDiffieHellmanResponse diffie_hellman;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
diffie_hellman.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
diffie_hellman.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
}
};
class CryptoResponseUnion : public BaseProtobufMessage {
private:
public:
CryptoResponseUnion() {};
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
}
};
class PoWResponseUnion : public BaseProtobufMessage {
private:
public:
PoWResponseUnion() {};
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
}
};
class ClientResponsePlaintext : public BaseProtobufMessage {
private:
public:
ClientResponsePlaintext() {};
LoginCryptoResponseUnion login_crypto_response;
PoWResponseUnion pow_response;
CryptoResponseUnion crypto_response;
bool decodeField(std::shared_ptr<PbReader> reader) {
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
login_crypto_response.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
case 20:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
pow_response.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
case 30:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
crypto_response.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
login_crypto_response.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
lastMessagePosition = writer->startMessage();
pow_response.encodeWithWriter(writer);
writer->finishMessage(20, lastMessagePosition);
lastMessagePosition = writer->startMessage();
crypto_response.encodeWithWriter(writer);
writer->finishMessage(30, lastMessagePosition);
}
};
class ClientHello : public BaseProtobufMessage {
private:
public:
ClientHello() {};
BuildInfo build_info;
LoginCryptoHelloUnion login_crypto_hello;
std::vector<Cryptosuite> cryptosuites_supported;
std::vector<uint8_t> client_nonce;
std::vector<uint8_t> padding;
FeatureSet feature_set;
bool decodeField(std::shared_ptr<PbReader> reader) {
if (firstField) {
cryptosuites_supported.clear();
}
switch (reader->currentTag)
{
case 10:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
build_info.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
case 50:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
login_crypto_hello.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
case 30:
cryptosuites_supported.push_back(Cryptosuite());
cryptosuites_supported[cryptosuites_supported.size()-1] = static_cast<Cryptosuite>(reader->decodeVarInt<uint32_t>());
break;
case 60:
reader->decodeVector(client_nonce);
break;
case 70:
reader->decodeVector(padding);
break;
case 80:
lastMessagePosition = reader->pos + reader->decodeVarInt<uint32_t>();
feature_set.parseWithReader(reader);
reader->maxPosition = lastMessagePosition;
break;
default:
return false;
}
return true;
}
void encodeWithWriter(std::shared_ptr<PbWriter> writer) {
lastMessagePosition = writer->startMessage();
build_info.encodeWithWriter(writer);
writer->finishMessage(10, lastMessagePosition);
lastMessagePosition = writer->startMessage();
login_crypto_hello.encodeWithWriter(writer);
writer->finishMessage(50, lastMessagePosition);
lastMessagePosition = writer->startMessage();
for (int i = 0; i < cryptosuites_supported.size(); i++) {
writer->encodeVarInt(static_cast<uint32_t>(cryptosuites_supported[i]));
}
writer->finishMessage(30, lastMessagePosition);
writer->addVector(60, client_nonce);
writer->addVector(70, padding);
lastMessagePosition = writer->startMessage();
feature_set.encodeWithWriter(writer);
writer->finishMessage(80, lastMessagePosition);
}
};
#endif

View File

@@ -1,5 +1,4 @@
#ifndef LOGGER_H
#define LOGGER_H
#pragma once
#include <BellLogger.h>
@@ -8,5 +7,3 @@
{ \
bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \
} while (0)
#endif

View File

@@ -1,35 +1,49 @@
#ifndef LOGINBLOB_H
#define LOGINBLOB_H
#pragma once
#include <vector>
#include <memory>
#include <iostream>
#include <map>
#include <memory>
#include <nlohmann/json.hpp>
#include <vector>
#include "ConstantParameters.h"
#include "Crypto.h"
#include "protobuf/authentication.pb.h"
class LoginBlob
{
private:
int blobSkipPosition = 0;
std::unique_ptr<Crypto> crypto;
namespace cspot {
class LoginBlob {
private:
int blobSkipPosition = 0;
std::unique_ptr<Crypto> crypto;
std::string name, deviceId;
uint32_t readBlobInt(const std::vector<uint8_t>& loginData);
std::vector<uint8_t> decodeBlob(const std::vector<uint8_t>& blob,
const std::vector<uint8_t>& sharedKey);
std::vector<uint8_t> decodeBlobSecondary(const std::vector<uint8_t>& blob,
const std::string& username,
const std::string& deviceId);
uint32_t readBlobInt(const std::vector<uint8_t>& loginData);
std::vector<uint8_t> decodeBlob(const std::vector<uint8_t>& blob, const std::vector<uint8_t>& sharedKey);
std::vector<uint8_t> decodeBlobSecondary(const std::vector<uint8_t>& blob, const std::string& username, const std::string& deviceId);
public:
LoginBlob(std::string name);
std::vector<uint8_t> authData;
std::string username = "";
int authType;
public:
LoginBlob();
std::vector<uint8_t> authData;
std::string username;
int authType;
// Loading
void loadZeroconfQuery(std::map<std::string, std::string>& queryParams);
void loadZeroconf(const std::vector<uint8_t>& blob,
const std::vector<uint8_t>& sharedKey,
const std::string& deviceId, const std::string& username);
void loadUserPass(const std::string& username, const std::string& password);
void loadJson(const std::string& json);
// Loading
void loadZeroconf(const std::vector<uint8_t>& blob, const std::vector<uint8_t>& sharedKey, const std::string& deviceId, const std::string& username);
void loadUserPass(const std::string& username, const std::string& password);
void loadJson(const std::string& json);
std::string buildZeroconfInfo();
std::string getDeviceId();
std::string getDeviceName();
std::string getUserName();
std::string toJson();
std::string toJson();
};
#endif
} // namespace cspot

View File

@@ -1,103 +0,0 @@
#ifndef MERCURY_MANAGER_H
#define MERCURY_MANAGER_H
#include <map>
#include <string>
#include <functional>
#include <vector>
#include <mutex>
#include "ShannonConnection.h"
#include "MercuryResponse.h"
#include "Packet.h"
#include "Utils.h"
#include "MercuryManager.h"
#include "AudioChunk.h"
#include "AudioChunkManager.h"
#include <atomic>
#include "BellTask.h"
#include "platform/WrappedSemaphore.h"
#include "TimeProvider.h"
#include "Session.h"
#include <NanoPBHelper.h>
#include "protobuf/mercury.pb.h"
#include <stdint.h>
#include <memory>
#define AUDIOCHUNK_TIMEOUT_MS 5 * 1000
#define RECONNECTION_RETRY_MS 5 * 1000
#define PING_TIMEOUT_MS 2 * 60 * 1000 + 5000
typedef std::function<void(std::unique_ptr<MercuryResponse>)> mercuryCallback;
typedef std::function<void(bool, std::vector<uint8_t>)> audioKeyCallback;
typedef std::function<void()> voidCallback;
#define AUDIO_CHUNK_SIZE 0x20000
enum class MercuryType : uint8_t
{
SUB = 0xb3,
UNSUB = 0xb4,
SUBRES = 0xb5,
SEND = 0xb2,
GET = 0xFF, // Shitty workaround, it's value is actually same as SEND
PING = 0x04,
PONG_ACK = 0x4a,
AUDIO_CHUNK_REQUEST_COMMAND = 0x08,
AUDIO_CHUNK_SUCCESS_RESPONSE = 0x09,
AUDIO_CHUNK_FAILURE_RESPONSE = 0x0A,
AUDIO_KEY_REQUEST_COMMAND = 0x0C,
AUDIO_KEY_SUCCESS_RESPONSE = 0x0D,
AUDIO_KEY_FAILURE_RESPONSE = 0x0E,
COUNTRY_CODE_RESPONSE = 0x1B,
};
extern std::map<MercuryType, std::string> MercuryTypeMap;
class MercuryManager : public bell::Task
{
private:
Header tempMercuryHeader;
std::map<uint64_t, mercuryCallback> callbacks;
std::mutex reconnectionMutex;
std::mutex runningMutex;
std::mutex stopMutex;
std::map<std::string, mercuryCallback> subscriptions;
std::unique_ptr<Session> session;
std::shared_ptr<LoginBlob> lastAuthBlob;
std::unique_ptr<AudioChunkManager> audioChunkManager;
std::vector<std::unique_ptr<Packet>> queue;
std::unique_ptr<WrappedSemaphore> queueSemaphore;
unsigned long long lastRequestTimestamp = -1;
unsigned long long lastPingTimestamp = -1;
uint64_t sequenceId;
uint32_t audioKeySequence;
audioKeyCallback keyCallback;
void runTask();
public:
MercuryManager(std::unique_ptr<Session> session);
~MercuryManager();
std::atomic<bool> isRunning = false;
voidCallback reconnectedCallback;
uint16_t audioChunkSequence;
std::shared_ptr<TimeProvider> timeProvider;
char countryCode[2];
bool timeoutHandler();
uint64_t execute(MercuryType method, std::string uri, mercuryCallback &callback, mercuryCallback &subscription, mercuryParts &payload);
uint64_t execute(MercuryType method, std::string uri, mercuryCallback &callback, mercuryCallback &subscription);
uint64_t execute(MercuryType method, std::string uri, mercuryCallback &callback, mercuryParts &payload);
uint64_t execute(MercuryType method, std::string uri, mercuryCallback &callback);
void updateQueue();
void stop();
void handleQueue();
void requestAudioKey(std::vector<uint8_t> trackId, std::vector<uint8_t> fileId, audioKeyCallback &audioCallback);
std::shared_ptr<AudioChunk> fetchAudioChunk(std::vector<uint8_t> fileId, std::vector<uint8_t> &audioKey, uint16_t index);
std::shared_ptr<AudioChunk> fetchAudioChunk(std::vector<uint8_t> fileId, std::vector<uint8_t> &audioKey, uint32_t startPos, uint32_t endPos);
void unregisterAudioCallback(uint16_t seqId);
void unregisterMercuryCallback(uint64_t seqId);
void freeAudioKeyCallback();
void reconnect();
};
#endif

View File

@@ -1,30 +0,0 @@
#ifndef MERCURYRESPONSE_H
#define MERCURYRESPONSE_H
#include <map>
#include <string>
#include <functional>
#include <vector>
#include <NanoPBHelper.h>
#include "protobuf/mercury.pb.h"
#include "Utils.h"
typedef std::vector<std::vector<uint8_t>> mercuryParts;
class MercuryResponse
{
private:
void parseResponse(std::vector<uint8_t> &data);
std::vector<uint8_t> data;
public:
MercuryResponse(std::vector<uint8_t> &data);
~MercuryResponse();
void decodeHeader();
Header mercuryHeader;
uint8_t flags;
mercuryParts parts;
uint64_t sequenceId;
};
#endif

View File

@@ -0,0 +1,128 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "BellTask.h"
#include "Logger.h"
#include "NanoPBHelper.h"
#include "Packet.h"
#include "Queue.h"
#include "Session.h"
#include "TimeProvider.h"
#include "Utils.h"
#include "protobuf/mercury.pb.h"
namespace cspot {
class MercurySession : public bell::Task, public cspot::Session {
public:
MercurySession(std::shared_ptr<cspot::TimeProvider> timeProvider);
~MercurySession();
typedef std::vector<std::vector<uint8_t>> DataParts;
struct Response {
Header mercuryHeader;
uint8_t flags;
DataParts parts;
uint64_t sequenceId;
bool fail;
};
typedef std::function<void(Response&)> ResponseCallback;
typedef std::function<void(bool, const std::vector<uint8_t>&)> AudioKeyCallback;
typedef std::function<void()> ConnectionEstabilishedCallback;
enum class RequestType : uint8_t {
SUB = 0xb3,
UNSUB = 0xb4,
SUBRES = 0xb5,
SEND = 0xb2,
GET = 0xFF, // Shitty workaround, it's value is actually same as SEND
PING = 0x04,
PONG_ACK = 0x4a,
AUDIO_CHUNK_REQUEST_COMMAND = 0x08,
AUDIO_CHUNK_SUCCESS_RESPONSE = 0x09,
AUDIO_CHUNK_FAILURE_RESPONSE = 0x0A,
AUDIO_KEY_REQUEST_COMMAND = 0x0C,
AUDIO_KEY_SUCCESS_RESPONSE = 0x0D,
AUDIO_KEY_FAILURE_RESPONSE = 0x0E,
COUNTRY_CODE_RESPONSE = 0x1B,
};
std::unordered_map<RequestType, std::string> RequestTypeMap = {
{RequestType::GET, "GET"},
{RequestType::SEND, "SEND"},
{RequestType::SUB, "SUB"},
{RequestType::UNSUB, "UNSUB"},
};
void handlePacket();
uint64_t executeSubscription(RequestType type, const std::string& uri,
ResponseCallback callback,
ResponseCallback subscription, DataParts& parts);
uint64_t executeSubscription(RequestType type, const std::string& uri,
ResponseCallback callback,
ResponseCallback subscription) {
DataParts parts = {};
return this->executeSubscription(type, uri, callback, subscription, parts);
}
uint64_t execute(RequestType type, const std::string& uri,
ResponseCallback callback) {
return this->executeSubscription(type, uri, callback, nullptr);
}
uint64_t execute(RequestType type, const std::string& uri,
ResponseCallback callback, DataParts& parts) {
return this->executeSubscription(type, uri, callback, nullptr, parts);
}
void requestAudioKey(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback);
std::string getCountryCode();
void disconnect();
void setConnectedHandler(ConnectionEstabilishedCallback callback);
bool triggerTimeout() override;
private:
const int PING_TIMEOUT_MS = 2 * 60 * 1000 + 5000;
std::shared_ptr<cspot::TimeProvider> timeProvider;
Header tempMercuryHeader = {};
ConnectionEstabilishedCallback connectionReadyCallback = nullptr;
bell::Queue<cspot::Packet> packetQueue;
void runTask() override;
void reconnect();
std::unordered_map<uint64_t, ResponseCallback> callbacks;
std::unordered_map<std::string, ResponseCallback> subscriptions;
AudioKeyCallback audioKeyCallback;
uint64_t sequenceId = 1;
uint32_t audioKeySequence = 1;
unsigned long long timestampDiff;
unsigned long long lastPingTimestamp = -1;
std::string countryCode = "";
std::mutex isRunningMutex;
std::atomic<bool> isRunning = false;
std::atomic<bool> isReconnecting = false;
std::atomic<bool> executeEstabilishedCallback = false;
void failAllPending();
Response decodeResponse(const std::vector<uint8_t>& data);
};
} // namespace cspot

View File

@@ -1,17 +1,11 @@
#ifndef PACKET_H
#define PACKET_H
#pragma once
#include <vector>
#include <cstdint>
#include <vector>
class Packet
{
private:
public:
Packet(uint8_t command, const std::vector<uint8_t> &data);
uint8_t command;
std::vector<uint8_t> data;
namespace cspot {
struct Packet {
uint8_t command;
std::vector<uint8_t> data;
};
#endif
} // namespace cspot

View File

@@ -5,32 +5,45 @@
#include <ws2tcpip.h>
#include "win32shim.h"
#else
#include "sys/socket.h"
#include <netdb.h>
#include <unistd.h>
#include "sys/socket.h"
#include <netinet/in.h>
#endif
#include <functional>
#include <vector>
#include <string>
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
#include "Packet.h"
#include "Utils.h"
typedef std::function<bool()> timeoutCallback;
class PlainConnection
{
public:
PlainConnection();
~PlainConnection();
namespace cspot {
class PlainConnection {
public:
PlainConnection();
~PlainConnection();
/**
* @brief Connect to the given AP address
*
* @param apAddress The AP url to connect to
*/
void connect(const std::string& apAddress);
void close();
timeoutCallback timeoutHandler;
std::vector<uint8_t> sendPrefixPacket(const std::vector<uint8_t>& prefix,
const std::vector<uint8_t>& data);
std::vector<uint8_t> recvPacket();
void readBlock(const uint8_t* dst, size_t size);
size_t writeBlock(const std::vector<uint8_t>& data);
private:
int apSock;
void connectToAp(std::string apAddress);
void closeSocket();
timeoutCallback timeoutHandler;
std::vector<uint8_t> sendPrefixPacket(const std::vector<uint8_t> &prefix, const std::vector<uint8_t> &data);
std::vector<uint8_t> recvPacket();
std::vector<uint8_t> readBlock(size_t size);
size_t writeBlock(const std::vector<uint8_t> &data);
};
} // namespace cspot
#endif

View File

@@ -1,87 +1,83 @@
#ifndef PLAYERSTATE_H
#define PLAYERSTATE_H
#pragma once
#include <vector>
#include <NanoPBHelper.h>
#include <memory>
#include <string>
#include "Utils.h"
#include "TimeProvider.h"
#include <vector>
#include "CSpotContext.h"
#include "ConstantParameters.h"
#include "CspotAssert.h"
#include "TrackReference.h"
#include "ConfigJSON.h"
#include <NanoPBHelper.h>
#include "TimeProvider.h"
#include "Utils.h"
#include "protobuf/spirc.pb.h"
enum class PlaybackState {
Playing,
Stopped,
Loading,
Paused
};
namespace cspot {
class PlayerState
{
private:
uint32_t seqNum = 0;
uint8_t capabilityIndex = 0;
std::vector<uint8_t> frameData;
std::shared_ptr<TimeProvider> timeProvider;
std::shared_ptr<ConfigJSON> config;
class PlaybackState {
private:
std::shared_ptr<cspot::Context> ctx;
uint32_t seqNum = 0;
uint8_t capabilityIndex = 0;
std::vector<uint8_t> frameData;
void addCapability(CapabilityType typ, int intValue = -1, std::vector<std::string> stringsValue = std::vector<std::string>());
public:
Frame innerFrame;
Frame remoteFrame;
void addCapability(
CapabilityType typ, int intValue = -1,
std::vector<std::string> stringsValue = std::vector<std::string>());
/**
public:
Frame innerFrame;
Frame remoteFrame;
enum class State { Playing, Stopped, Loading, Paused };
/**
* @brief Player state represents the current state of player.
*
* Responsible for keeping track of player's state. Doesn't control the playback itself.
*
* @param timeProvider synced time provider
*/
PlayerState(std::shared_ptr<TimeProvider> timeProvider);
~PlayerState();
PlaybackState(std::shared_ptr<cspot::Context> ctx);
/**
~PlaybackState();
/**
* @brief Updates state according to current playback state.
*
* @param state playback state
*/
void setPlaybackState(const PlaybackState state);
void setPlaybackState(const PlaybackState::State state);
/**
/**
* @brief Sets player activity
*
* @param isActive activity status
*/
void setActive(bool isActive);
void setActive(bool isActive);
/**
/**
* @brief Simple getter
*
* @return true player is active
* @return false player is inactive
*/
bool isActive();
bool isActive();
/**
/**
* @brief Updates local track position.
*
* @param position position in milliseconds
*/
void updatePositionMs(uint32_t position);
void updatePositionMs(uint32_t position);
/**
/**
* @brief Sets local volume on internal state.
*
* @param volume volume between 0 and UINT16 max
*/
void setVolume(uint32_t volume);
void setVolume(uint32_t volume);
/**
/**
* @brief Enables queue shuffling.
*
* Sets shuffle parameter on local frame, and in case shuffling is enabled,
@@ -89,50 +85,56 @@ public:
*
* @param shuffle whenever should shuffle
*/
void setShuffle(bool shuffle);
void setShuffle(bool shuffle);
/**
/**
* @brief Enables repeat
*
* @param repeat should repeat param
*/
void setRepeat(bool repeat);
void setRepeat(bool repeat);
/**
/**
* @brief Updates local track queue from remote data.
*/
void updateTracks();
void updateTracks();
/**
/**
* @brief Changes playback to next queued track.
*
* Will go back to first track if current track is last track in queue.
* In that case, it will pause if repeat is disabled.
*/
bool nextTrack();
bool nextTrack();
/**
/**
* @brief Changes playback to previous queued track.
*
* Will stop if current track is the first track in queue and repeat is disabled.
* If repeat is enabled, it will loop back to the last track in queue.
*/
void prevTrack();
void prevTrack();
/**
* @brief Gets the current track reference.
*
* @return std::shared_ptr<TrackReference> pointer to track reference
*/
std::shared_ptr<TrackReference> getCurrentTrack();
/**
* @brief Gets the current track reference.
*
* @return std::shared_ptr<TrackReference> pointer to track reference
*/
TrackRef* getCurrentTrackRef();
/**
/**
* @brief Gets reference to next track in queue, or nullptr if there is no next track.
*
* @return std::shared_ptr<TrackReference> pointer to track reference
*/
TrackRef* getNextTrackRef();
/**
* @brief Encodes current frame into binary data via protobuf.
*
* @param typ message type to include in frame type
* @return std::vector<uint8_t> binary frame data
*/
std::vector<uint8_t> encodeCurrentFrame(MessageType typ);
std::vector<uint8_t> encodeCurrentFrame(MessageType typ);
};
#endif
} // namespace cspot

View File

@@ -1,51 +0,0 @@
#ifndef PLAYER_H
#define PLAYER_H
#include <vector>
#include <string>
#include <math.h>
#include <functional>
#include <atomic>
#include <mutex>
#include "Utils.h"
#include "MercuryManager.h"
#include "TrackReference.h"
#include "Session.h"
#include "SpotifyTrack.h"
#include "AudioSink.h"
#include <mutex>
#include "BellTask.h"
class Player : public bell::Task {
private:
std::shared_ptr<MercuryManager> manager;
SpotifyTrack *currentTrack = nullptr;
SpotifyTrack *nextTrack = nullptr;
std::shared_ptr<AudioSink> audioSink;
std::mutex loadTrackMutex;
WrappedMutex nextTrackMutex;
WrappedMutex currentTrackMutex;
void runTask();
public:
Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSink> audioSink);
std::function<void()> endOfFileCallback;
int volume = 255;
uint32_t logVolume;
bool needFlush = false;
std::atomic<bool> isRunning = false;
trackChangedCallback trackChanged;
std::mutex runningMutex;
void setVolume(uint32_t volume);
void handleLoad(std::shared_ptr<TrackReference> track, std::function<void(bool)> &trackLoadedCallback, uint32_t position_ms, bool isPaused);
void pause();
void cancelCurrentTrack();
void seekMs(size_t positionMs);
void feedPCM(uint8_t *data, size_t len);
void play();
void stop();
};
#endif

View File

@@ -1,52 +1,44 @@
#ifndef SESSION_H
#define SESSION_H
#pragma once
#include <vector>
#include <random>
#include <memory>
#include <functional>
#include <climits>
#include <algorithm>
#include "Utils.h"
#include "stdlib.h"
#include "ShannonConnection.h"
#include "LoginBlob.h"
#include "ApResolve.h"
#include "PlainConnection.h"
#include "Packet.h"
#include "ConstantParameters.h"
#include "Crypto.h"
#include "NanoPBHelper.h"
#include "protobuf/authentication.pb.h"
#include "protobuf/keyexchange.pb.h"
#include <functional>
#include <memory>
#include <vector>
#include "ApResolve.h"
#include "AuthChallenges.h"
#include "ConstantParameters.h"
#include "Logger.h"
#include "LoginBlob.h"
#include "Packet.h"
#include "PlainConnection.h"
#include "ShannonConnection.h"
#include "Utils.h"
#include "protobuf/mercury.pb.h"
#define SPOTIFY_VERSION 0x10800000000
#define LOGIN_REQUEST_COMMAND 0xAB
#define AUTH_SUCCESSFUL_COMMAND 0xAC
#define AUTH_DECLINED_COMMAND 0xAD
class Session
{
private:
ClientResponseEncrypted authRequest;
ClientResponsePlaintext clientResPlaintext;
ClientHello clientHello;
APResponseMessage apResponse;
namespace cspot {
class Session {
protected:
std::unique_ptr<cspot::AuthChallenges> challenges;
std::shared_ptr<cspot::PlainConnection> conn;
std::shared_ptr<LoginBlob> authBlob;
std::shared_ptr<PlainConnection> conn;
std::unique_ptr<Crypto> crypto;
std::vector<uint8_t> sendClientHelloRequest();
void processAPHelloResponse(std::vector<uint8_t> &helloPacket);
std::string deviceId = "142137fd329622137a14901634264e6f332e2411";
public:
Session();
~Session();
std::shared_ptr<ShannonConnection> shanConn;
std::shared_ptr<LoginBlob> authBlob;
void connect(std::unique_ptr<PlainConnection> connection);
void connectWithRandomAp();
void close();
std::vector<uint8_t> authenticate(std::shared_ptr<LoginBlob> blob);
public:
Session();
~Session();
std::shared_ptr<cspot::ShannonConnection> shanConn;
void connect(std::unique_ptr<cspot::PlainConnection> connection);
void connectWithRandomAp();
void close();
virtual bool triggerTimeout() = 0;
std::vector<uint8_t> authenticate(std::shared_ptr<LoginBlob> blob);
};
#endif
} // namespace cspot

View File

@@ -1,47 +1,41 @@
#ifndef SHANNONCONNECTION_H
#define SHANNONCONNECTION_H
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "win32shim.h"
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <sys/types.h>
#include <string>
#include <memory>
#include <cstdint>
#include "platform/WrappedMutex.h"
#include "Utils.h"
#include "Shannon.h"
#include "PlainConnection.h"
#include <memory>
#include <string>
#include <vector>
#include "Packet.h"
#include "PlainConnection.h"
#include "Shannon.h"
#include <mutex>
#include "Utils.h"
#include "Logger.h"
#define MAC_SIZE 4
namespace cspot {
class ShannonConnection {
private:
std::unique_ptr<Shannon> sendCipher;
std::unique_ptr<Shannon> recvCipher;
uint32_t sendNonce = 0;
uint32_t recvNonce = 0;
std::vector<uint8_t> cipherPacket(uint8_t cmd, std::vector<uint8_t>& data);
std::mutex writeMutex;
std::mutex readMutex;
class ShannonConnection
{
private:
std::unique_ptr<Shannon> sendCipher;
std::unique_ptr<Shannon> recvCipher;
uint32_t sendNonce = 0;
uint32_t recvNonce = 0;
std::vector<uint8_t> cipherPacket(uint8_t cmd, std::vector<uint8_t> &data);
WrappedMutex writeMutex;
WrappedMutex readMutex;
public:
ShannonConnection();
~ShannonConnection();
void wrapConnection(std::shared_ptr<PlainConnection> conn, std::vector<uint8_t> &sendKey, std::vector<uint8_t> &recvKey);
void sendPacket(uint8_t cmd, std::vector<uint8_t> &data);
std::shared_ptr<PlainConnection> conn;
std::unique_ptr<Packet> recvPacket();
public:
ShannonConnection();
~ShannonConnection();
void wrapConnection(std::shared_ptr<PlainConnection> conn,
std::vector<uint8_t>& sendKey,
std::vector<uint8_t>& recvKey);
void sendPacket(uint8_t cmd, std::vector<uint8_t>& data);
std::shared_ptr<PlainConnection> conn;
Packet recvPacket();
};
} // namespace cspot
#endif

View File

@@ -1,112 +0,0 @@
#ifndef SPIRCCONTROLLER_H
#define SPIRCCONTROLLER_H
#include <vector>
#include <string>
#include <functional>
#include "Utils.h"
#include "MercuryManager.h"
#include "Session.h"
#include "PlayerState.h"
#include "SpotifyTrack.h"
#include "ConstantParameters.h"
#include "Player.h"
#include "ConfigJSON.h"
#include <cassert>
#include <variant>
enum class CSpotEventType {
PLAY_PAUSE,
VOLUME,
TRACK_INFO,
DISC,
NEXT,
PREV,
SEEK,
LOAD,
PLAYBACK_START
};
struct CSpotEvent {
CSpotEventType eventType;
std::variant<TrackInfo, int, bool> data;
};
typedef std::function<void(CSpotEvent&)> cspotEventHandler;
class SpircController {
private:
std::shared_ptr<MercuryManager> manager;
std::string username;
bool firstFrame = true;
std::unique_ptr<Player> player;
std::unique_ptr<PlayerState> state;
std::shared_ptr<AudioSink> audioSink;
std::shared_ptr<ConfigJSON> config;
cspotEventHandler eventHandler;
void sendCmd(MessageType typ);
void notify();
void sendEvent(CSpotEventType eventType, std::variant<TrackInfo, int, bool> data = 0);
void handleFrame(std::vector<uint8_t> &data);
void loadTrack(uint32_t position_ms = 0, bool isPaused = 0);
public:
SpircController(std::shared_ptr<MercuryManager> manager, std::string username, std::shared_ptr<AudioSink> audioSink);
~SpircController();
void subscribe();
/**
* @brief Pauses / Plays current song
*
* Calling this function will pause or resume playback, setting the
* necessary state value and notifying spotify SPIRC.
*
* @param pause if true pause content, play otherwise
*/
void setPause(bool pause, bool notifyPlayer = true);
/**
* @brief Toggle Play/Pause
*/
void playToggle();
/**
* @brief Notifies spotify servers about volume change
*
* @param volume int between 0 and `MAX_VOLUME`
*/
void setRemoteVolume(int volume);
/**
* @brief Set device volume and notifies spotify
*
* @param volume int between 0 and `MAX_VOLUME`
*/
void setVolume(int volume);
/**
* @brief change volume by a given value
*
* @param volume int between 0 and `MAX_VOLUME`
*/
void adjustVolume(int by);
/**
* @brief Goes back to previous track and notfies spotify SPIRC
*/
void prevSong();
/**
* @brief Skips to next track and notfies spotify SPIRC
*/
void nextSong();
void setEventHandler(cspotEventHandler handler);
void stopPlayer();
/**
* @brief Disconnect players and notify
*/
void disconnect();
};
#endif

View File

@@ -0,0 +1,78 @@
#pragma once
#include <memory>
#include "BellTask.h"
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "PlaybackState.h"
#include "TrackPlayer.h"
#include "TrackProvider.h"
#include "protobuf/spirc.pb.h"
namespace cspot {
class SpircHandler {
public:
SpircHandler(std::shared_ptr<cspot::Context> ctx);
enum class EventType {
PLAY_PAUSE,
VOLUME,
TRACK_INFO,
DISC,
NEXT,
PREV,
SEEK,
DEPLETED,
FLUSH,
PLAYBACK_START
};
typedef std::variant<CDNTrackStream::TrackInfo, int, bool> EventData;
struct Event {
EventType eventType;
EventData data;
};
typedef std::function<void(std::unique_ptr<Event>)> EventHandler;
void subscribeToMercury();
std::shared_ptr<TrackPlayer> getTrackPlayer();
void setEventHandler(EventHandler handler);
void setPause(bool pause);
void nextSong();
void previousSong();
void notifyAudioReachedPlayback();
void updatePositionMs(uint32_t position);
void setRemoteVolume(int volume);
void loadTrackFromURI(const std::string& uri);
void disconnect();
private:
std::shared_ptr<cspot::Context> ctx;
std::shared_ptr<cspot::TrackPlayer> trackPlayer;
EventHandler eventHandler = nullptr;
cspot::PlaybackState playbackState;
CDNTrackStream::TrackInfo currentTrackInfo;
bool isTrackFresh = true;
bool isRequestedFromLoad = false;
bool isNextTrackPreloaded = false;
uint32_t nextTrackPosition = 0;
void sendCmd(MessageType typ);
void sendEvent(EventType type);
void sendEvent(EventType type, EventData data);
void handleFrame(std::vector<uint8_t>& data);
void notify();
};
} // namespace cspot

View File

@@ -1,58 +0,0 @@
#ifndef SPOTIFYTRACK_H
#define SPOTIFYTRACK_H
#include <memory>
#include <vector>
#include <iostream>
#include "MercuryManager.h"
#include "Utils.h"
#include "MercuryResponse.h"
#include <fstream>
#include "Crypto.h"
#include <functional>
#include "ChunkedAudioStream.h"
#include "TrackReference.h"
#include "NanoPBHelper.h"
#include "protobuf/metadata.pb.h"
#include <cassert>
#include <atomic>
struct TrackInfo {
std::string name;
std::string album;
std::string artist;
std::string imageUrl;
int duration;
};
typedef std::function<void(TrackInfo&)> trackChangedCallback;
class SpotifyTrack
{
private:
std::shared_ptr<MercuryManager> manager;
void trackInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused);
void episodeInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused);
void requestAudioKey(std::vector<uint8_t> fileId, std::vector<uint8_t> trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused);
bool countryListContains(char *countryList, char *country);
bool canPlayTrack(int altIndex);
Track trackInfo;
Episode episodeInfo;
std::vector<uint8_t> fileId;
std::vector<uint8_t> currentChunkData;
std::vector<uint8_t> currentChunkHeader;
public:
SpotifyTrack(std::shared_ptr<MercuryManager> manager, std::shared_ptr<TrackReference> trackRef, uint32_t position_ms, bool isPaused);
~SpotifyTrack();
uint64_t reqSeqNum = -1;
std::atomic<bool> loaded = false;
std::function<void()> loadedTrackCallback;
std::unique_ptr<ChunkedAudioStream> audioStream;
trackChangedCallback trackInfoReceived;
audioKeyCallback audioKeyLambda;
mercuryCallback responseLambda;
};
#endif

View File

@@ -1,34 +1,34 @@
#ifndef TIMEPROVIDER_H
#define TIMEPROVIDER_H
#pragma once
#include <vector>
#include <stdint.h>
#include <vector>
class TimeProvider
{
private:
unsigned long long timestampDiff;
#include "Utils.h"
public:
/**
namespace cspot {
class TimeProvider {
private:
unsigned long long timestampDiff;
public:
/**
* @brief Bypasses the need for NTP server sync by syncing with spotify's servers
*
*/
TimeProvider();
TimeProvider();
/**
/**
* @brief Syncs the TimeProvider with spotify server's timestamp
*
* @param pongPacket pong packet containing timestamp
*/
void syncWithPingPacket(const std::vector<uint8_t>& pongPacket);
/**
void syncWithPingPacket(const std::vector<uint8_t>& pongPacket);
/**
* @brief Get current timestamp synced with spotify servers
*
* @return unsigned long long timestamp
*/
unsigned long long getSyncedTimestamp();
unsigned long long getSyncedTimestamp();
};
#endif
} // namespace cspot

View File

@@ -0,0 +1,76 @@
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <atomic>
#include <BellUtils.h>
#include <WrappedSemaphore.h>
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "TrackProvider.h"
#include "TrackReference.h"
#ifdef BELL_VORBIS_FLOAT
#include "vorbis/vorbisfile.h"
#else
#include "ivorbisfile.h"
#endif
namespace cspot {
class TrackPlayer : bell::Task {
public:
typedef std::function<void()> TrackLoadedCallback;
typedef std::function<size_t(uint8_t*, size_t, std::string_view, size_t)> DataCallback;
typedef std::function<void()> EOFCallback;
typedef std::function<bool()> isAiringCallback;
TrackPlayer(std::shared_ptr<cspot::Context> ctx, isAiringCallback, EOFCallback, TrackLoadedCallback);
~TrackPlayer();
void loadTrackFromRef(TrackReference& ref, size_t playbackMs, bool startAutomatically);
void setDataCallback(DataCallback callback);
CDNTrackStream::TrackInfo getCurrentTrackInfo();
void seekMs(size_t ms);
void stopTrack();
// Vorbis codec callbacks
size_t _vorbisRead(void* ptr, size_t size, size_t nmemb);
size_t _vorbisClose();
int _vorbisSeek(int64_t offset, int whence);
long _vorbisTell();
void destroy();
private:
std::shared_ptr<cspot::Context> ctx;
std::shared_ptr<cspot::TrackProvider> trackProvider;
std::shared_ptr<cspot::CDNTrackStream> currentTrackStream;
size_t sequence = std::time(nullptr);
std::unique_ptr<bell::WrappedSemaphore> playbackSemaphore;
TrackLoadedCallback trackLoaded;
DataCallback dataCallback = nullptr;
EOFCallback eofCallback;
isAiringCallback isAiring;
// Playback control
std::atomic<bool> currentSongPlaying;
std::mutex playbackMutex;
std::mutex seekMutex;
// Vorbis related
OggVorbis_File vorbisFile;
ov_callbacks vorbisCallbacks;
int currentSection;
std::vector<uint8_t> pcmBuffer = std::vector<uint8_t>(1024);
size_t playbackPosition = 0;
bool autoStart = false;
std::atomic<bool> isRunning = true;
std::mutex runningMutex;
void runTask() override;
};
} // namespace cspot

View File

@@ -0,0 +1,35 @@
#pragma once
#include <memory>
#include "AccessKeyFetcher.h"
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "TrackReference.h"
#include "protobuf/metadata.pb.h"
#include "protobuf/spirc.pb.h"
namespace cspot {
class TrackProvider {
public:
TrackProvider(std::shared_ptr<cspot::Context> ctx);
~TrackProvider();
std::shared_ptr<CDNTrackStream> loadFromTrackRef(TrackReference& trackRef);
private:
std::shared_ptr<AccessKeyFetcher> accessKeyFetcher;
std::shared_ptr<cspot::Context> ctx;
std::unique_ptr<cspot::CDNTrackStream> cdnStream;
Track trackInfo;
std::weak_ptr<CDNTrackStream> currentTrackReference;
TrackReference trackIdInfo;
void queryMetadata();
void onMetadataResponse(MercurySession::Response& res);
void fetchFile(const std::vector<uint8_t>& fileId,
const std::vector<uint8_t>& trackId);
bool canPlayTrack(int index);
};
} // namespace cspot

View File

@@ -1,33 +1,52 @@
#ifndef TRACKREFERENCE_H
#define TRACKREFERENCE_H
#pragma once
#include <string_view>
#include <vector>
#include "NanoPBHelper.h"
#include "Utils.h"
#include "protobuf/spirc.pb.h"
#include <NanoPBHelper.h>
#include <iostream>
#include <string>
namespace cspot {
class TrackReference
{
private:
std::string alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::vector<uint8_t> base62Decode(std::string uri);
struct TrackReference {
static constexpr auto base62Alphabet =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public:
TrackReference(TrackRef *ref);
~TrackReference();
// Resolved track GID
std::vector<uint8_t> gid;
std::vector<uint8_t> gid;
// Type identifier
enum class Type { TRACK, EPISODE };
Type type;
bool isEpisode = false;
static TrackReference fromTrackRef(TrackRef* ref) {
TrackReference trackRef;
if (ref->gid != nullptr) {
// For tracks, the GID is already in the protobuf
trackRef.gid = pbArrayToVector(ref->gid);
trackRef.type = Type::TRACK;
} else {
// Episode GID is being fetched via base62 encoded URI
auto uri = std::string(ref->uri);
auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size());
/**
* @brief Returns an uri that can be allowed to query track information.
*
* @return std::string
*/
std::string getMercuryRequestUri();
trackRef.gid = {0};
std::string_view alphabet(base62Alphabet);
for (int x = 0; x < uri.size(); x++) {
size_t d = alphabet.find(uri[x]);
trackRef.gid = bigNumMultiply(trackRef.gid, 62);
trackRef.gid = bigNumAdd(trackRef.gid, d);
}
}
return trackRef;
}
static TrackReference fromGID(std::vector<uint8_t> gid, bool isEpisode) {
TrackReference trackRef;
trackRef.gid = gid;
trackRef.type = isEpisode ? Type::EPISODE : Type::TRACK;
return trackRef;
}
};
#endif
} // namespace cspot

View File

@@ -9,6 +9,7 @@
#include <unistd.h>
#include "sys/socket.h"
#include <netdb.h>
#include <netinet/in.h>
#endif
#include <cstdint>
#include <cstring>
@@ -18,6 +19,9 @@
#include <sstream>
#include <iostream>
#include <iomanip>
#include <memory>
#include <string>
#include <stdexcept>
#define HMAC_SHA1_BLOCKSIZE 64
@@ -36,6 +40,8 @@ unsigned long long getCurrentTimestamp();
*/
uint64_t hton64(uint64_t value);
std::vector<uint8_t> bigNumDivide(std::vector<uint8_t> num, int n);
/**
* @brief Performs big number multiplication on two numbers
*
@@ -59,6 +65,13 @@ unsigned char h2int(char c);
std::string urlDecode(std::string str);
/**
* @brief Converts provided hex string into binary data
*
* @param s string containing hex data
* @return std::vector<uint8_t> vector containing binary data
*/
std::vector<uint8_t> stringHexToBytes(const std::string &s);
/**
* @brief Converts provided bytes into a human readable hex string
@@ -66,7 +79,7 @@ std::string urlDecode(std::string str);
* @param bytes vector containing binary data
* @return std::string string containing hex representation of inputted data
*/
std::string bytesToHexString(std::vector<uint8_t> &bytes);
std::string bytesToHexString(const std::vector<uint8_t> &bytes);
/**
* @brief Extracts given type from binary data
@@ -99,5 +112,15 @@ std::vector<uint8_t> pack(T data)
return rawData;
}
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
auto size = static_cast<size_t>( size_s );
std::unique_ptr<char[]> buf( new char[ size ] );
std::snprintf( buf.get(), size, format.c_str(), args ... );
return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}
#endif

View File

@@ -1,56 +0,0 @@
#ifndef ZEROCONFAUTHENTICATOR_H
#define ZEROCONFAUTHENTICATOR_H
#include <vector>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <string>
#include <BaseHTTPServer.h>
#include <cstdlib>
#include "Utils.h"
#include "LoginBlob.h"
#include "Crypto.h"
#include "BellTask.h"
#include "ConstantParameters.h"
#ifdef ESP_PLATFORM
#include "mdns.h"
#elif defined(_WIN32)
#include "mdnssvc.h"
#else
#include "dns_sd.h"
#include <unistd.h>
#endif
#ifndef SOCK_NONBLOCK
#define SOCK_NONBLOCK O_NONBLOCK
#endif
#define SERVER_PORT_MAX 65535 // Max usable tcp port
#define SERVER_PORT_MIN 1024 // 0-1024 services ports
typedef std::function<void(std::shared_ptr<LoginBlob>)> authCallback;
class ZeroconfAuthenticator {
private:
#ifdef _WIN32
struct mdnsd* service;
#endif
int serverPort;
bool authorized = false;
std::unique_ptr<Crypto> crypto;
std::shared_ptr<bell::BaseHTTPServer> server;
authCallback gotBlobCallback;
void startServer();
std::string buildJsonInfo();
void handleAddUser(std::map<std::string, std::string>& queryMap);
void registerZeroconf();
std::string getParameterFromUrlEncoded(std::string data, std::string param);
public:
ZeroconfAuthenticator(authCallback callback, std::shared_ptr<bell::BaseHTTPServer> httpServer);
void registerHandlers();
};
#endif