mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 13:07:03 +03:00
move to new cspot
This commit is contained in:
32
components/spotify/cspot/include/AccessKeyFetcher.h
Normal file
32
components/spotify/cspot/include/AccessKeyFetcher.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
46
components/spotify/cspot/include/AuthChallenges.h
Normal file
46
components/spotify/cspot/include/AuthChallenges.h
Normal 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
|
||||
92
components/spotify/cspot/include/CDNTrackStream.h
Normal file
92
components/spotify/cspot/include/CDNTrackStream.h
Normal 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
|
||||
41
components/spotify/cspot/include/CSpotContext.h
Normal file
41
components/spotify/cspot/include/CSpotContext.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
128
components/spotify/cspot/include/MercurySession.h
Normal file
128
components/spotify/cspot/include/MercurySession.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
78
components/spotify/cspot/include/SpircHandler.h
Normal file
78
components/spotify/cspot/include/SpircHandler.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
76
components/spotify/cspot/include/TrackPlayer.h
Normal file
76
components/spotify/cspot/include/TrackPlayer.h
Normal 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
|
||||
35
components/spotify/cspot/include/TrackProvider.h
Normal file
35
components/spotify/cspot/include/TrackProvider.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user