big merge

This commit is contained in:
Philippe G
2021-12-18 21:04:23 -08:00
parent 955692f8ad
commit 898998efb0
583 changed files with 84472 additions and 1965 deletions

View File

@@ -0,0 +1,21 @@
#ifndef APRESOLVE_H
#define APRESOLVE_H
#include <string>
class ApResolve {
private:
std::string getApList();
public:
ApResolve();
/**
* @brief Connects to spotify's servers and returns first valid ap address
*
* @return std::string Address in form of url:port
*/
std::string fetchFirstApAddress();
};
#endif

View File

@@ -0,0 +1,74 @@
#ifndef AUDIOCHUNK_H
#define AUDIOCHUNK_H
#include <memory>
#include <vector>
#include <string>
#include <algorithm>
#include "pthread.h"
#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);
public:
std::unique_ptr<Crypto> crypto;
std::vector<uint8_t> decryptedData;
std::vector<uint8_t> audioKey;
bool keepInMemory = false;
pthread_mutex_t loadingMutex;
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;
/**
* @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(std::vector<uint8_t> &data);
/**
* @brief Performs AES CTR decryption of received data.
*
*/
void decrypt();
};
#endif

View File

@@ -0,0 +1,57 @@
#ifndef AUDIOCHUNKMANAGER_H
#define AUDIOCHUNKMANAGER_H
#include <memory>
#include <atomic>
#include <algorithm>
#include <mutex>
#include "Utils.h"
#include "AudioChunk.h"
#include "Queue.h"
#include "Task.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;
/**
* @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,18 @@
#ifndef AUDIOSINK_H
#define AUDIOSINK_H
#include <stdint.h>
#include <vector>
class AudioSink
{
public:
AudioSink() {}
virtual ~AudioSink() {}
virtual void feedPCMFrames(std::vector<uint8_t> &data) = 0;
virtual void volumeChanged(uint16_t volume) {}
bool softwareVolumeControl = true;
bool usign = false;
};
#endif

View File

@@ -0,0 +1,75 @@
#ifndef CHUNKEDAUDIOSTREAM_H
#define CHUNKEDAUDIOSTREAM_H
#include <iostream>
#include <vector>
#include <fstream>
#include <array>
#include <unistd.h>
#include <atomic>
#include "ivorbisfile.h"
#include "MercuryManager.h"
#include "AudioSink.h"
#include "AudioChunk.h"
#include "platform/WrappedMutex.h"
#define SPOTIFY_HEADER_SIZE 167
#define BUFFER_SIZE 0x20000 * 1.5
typedef std::function<void(std::vector<uint8_t>&)> pcmDataCallback;
enum class Whence
{
START,
CURRENT,
END
};
class ChunkedAudioStream
{
private:
// Vorbis related
OggVorbis_File vorbisFile;
ov_callbacks vorbisCallbacks;
int currentSection;
// Audio chunking
std::vector<uint8_t> audioKey;
std::vector<std::shared_ptr<AudioChunk>> chunks;
// 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;
std::shared_ptr<AudioChunk> requestChunk(size_t chunkIndex);
void fetchTraillingPacket();
std::shared_ptr<AudioChunk> findChunkForPosition(size_t position);
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();
int requestedChunkIndex = 0;
std::function<void()> streamFinishedCallback;
size_t pos = SPOTIFY_HEADER_SIZE; // size of some spotify header
uint32_t fileSize;
uint32_t readBeforeSeek = 0;
bool loadingMeta = true;
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();
};
#endif

View File

@@ -0,0 +1,26 @@
#ifndef CONFIGJSON_H
#define CONFIGJSON_H
#include <memory>
#include <iostream>
#include "FileHelper.h"
#include "ProtoHelper.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;
AudioFormat format;
};
extern std::shared_ptr<ConfigJSON> configMan;
#endif

View File

@@ -0,0 +1,15 @@
#ifndef CONSTANTPARAMETERS_H
#define CONSTANTPARAMETERS_H
#define MAX_VOLUME 65536
// Hardcoded information sent to spotify servers
const char * const deviceId = "162137fd329622137a14901634264e6f332e2422";
const char * const informationString = "cspot";
const char * const brandName = "corn";
const char * const versionString = "cspot-1.0";
const char * const protocolVersion = "2.7.1";
const char * const defaultDeviceName = "CSpot";
const char * const swVersion = "1.0.0";
#endif

View File

@@ -0,0 +1,16 @@
#ifndef CSPOT_ASSERT_H
#define CSPOT_ASSERT_H
#include <stdio.h>
#include <cassert>
#define CSPOT_ASSERT(CONDITION, MESSAGE) \
do \
{ \
if (!(CONDITION)) \
{ \
printf("At %s in %s:%d\n Assertion %s failed: %s", __func__, __FILE__, __LINE__, #CONDITION, MESSAGE); \
abort(); \
} \
} while (0)
#endif

View File

@@ -0,0 +1,16 @@
#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

@@ -0,0 +1,465 @@
// 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

@@ -0,0 +1,12 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <BellLogger.h>
#define CSPOT_LOG(type, ...) \
do \
{ \
bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \
} while (0)
#endif

View File

@@ -0,0 +1,35 @@
#ifndef LOGINBLOB_H
#define LOGINBLOB_H
#include <vector>
#include <memory>
#include <iostream>
#include "Crypto.h"
#include "ProtoHelper.h"
class LoginBlob
{
private:
int blobSkipPosition = 0;
std::unique_ptr<Crypto> crypto;
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::vector<uint8_t> authData;
std::string username;
int authType;
// 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 toJson();
};
#endif

View File

@@ -0,0 +1,100 @@
#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 "ProtoHelper.h"
#include "MercuryManager.h"
#include "AudioChunk.h"
#include "AudioChunkManager.h"
#include <atomic>
#include "Task.h"
#include "platform/WrappedSemaphore.h"
#include "TimeProvider.h"
#include "Session.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:
std::map<uint64_t, mercuryCallback> callbacks;
std::mutex reconnectionMutex;
std::mutex runningMutex;
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;
std::atomic<bool> isRunning = false;
uint64_t sequenceId;
uint32_t audioKeySequence;
audioKeyCallback keyCallback;
void runTask();
public:
MercuryManager(std::unique_ptr<Session> session);
voidCallback reconnectedCallback;
uint16_t audioChunkSequence;
std::shared_ptr<TimeProvider> timeProvider;
std::string countryCode;
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

@@ -0,0 +1,28 @@
#ifndef MERCURYRESPONSE_H
#define MERCURYRESPONSE_H
#include <map>
#include <string>
#include <functional>
#include <vector>
#include "ProtoHelper.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);
void decodeHeader();
Header mercuryHeader;
uint8_t flags;
mercuryParts parts;
uint64_t sequenceId;
};
#endif

View File

@@ -0,0 +1,17 @@
#ifndef PACKET_H
#define PACKET_H
#include <vector>
#include <cstdint>
class Packet
{
private:
public:
Packet(uint8_t command, std::vector<uint8_t> &data);
uint8_t command;
std::vector<uint8_t> data;
};
#endif

View File

@@ -0,0 +1,42 @@
#ifndef PBCOMMON_H
#define PBCOMMON_H
#include <cstdint>
#include <PbWriter.h>
#include <optional>
#include <PbReader.h>
class BaseProtobufMessage
{
private:
public:
bool firstField = true;
uint32_t lastMessagePosition;
void parseFromVector(std::vector<uint8_t> const &rawData)
{
auto reader = std::make_shared<PbReader>(rawData);
parseWithReader(reader);
}
void encodeToVector(std::vector<uint8_t> &rawData)
{
auto writer = std::make_shared<PbWriter>(rawData);
encodeWithWriter(writer);
}
void parseWithReader(std::shared_ptr<PbReader> reader)
{
firstField = true;
while (reader->next())
{
if (!decodeField(reader))
{
reader->skip();
} else {
firstField = false;
}
}
}
virtual void encodeWithWriter(std::shared_ptr<PbWriter> writer) = 0;
virtual bool decodeField(std::shared_ptr<PbReader> reader) = 0;
};
#endif

View File

@@ -0,0 +1,42 @@
#ifndef PBREADER_H
#define PBREADER_H
#include <vector>
#include <string>
#include <memory>
#include <PbWireType.h>
class PbReader
{
private:
std::vector<uint8_t> const &rawData;
uint32_t currentWireValue = 0;
uint64_t skipVarIntDump = 0;
uint32_t nextFieldLength = 0;
int64_t decodeZigzag(uint64_t value);
public:
PbReader(std::vector<uint8_t> const &rawData);
uint32_t maxPosition = 0;
PbWireType currentWireType = PbWireType::unknown;
uint32_t currentTag = 0;
uint32_t pos = 0;
template <typename T>
T decodeVarInt();
template <typename T>
T decodeFixed();
template <typename T>
T decodeSVarInt();
void decodeString(std::string &target);
void decodeVector(std::vector<uint8_t> &target);
bool next();
void skip();
void resetMaxPosition();
};
#endif

View File

@@ -0,0 +1,15 @@
#ifndef PBWIRETYPE_H
#define PBWIRETYPE_H
#include <cstdint>
enum class PbWireType : uint32_t
{
varint = 0, // int32/64, uint32/64, sint32/64, bool, enum
fixed64 = 1, // fixed64, sfixed64, double
length_delimited = 2, // string, bytes, nested messages, packed repeated fields
fixed32 = 5, // fixed32, sfixed32, float
unknown = 99
};
#endif

View File

@@ -0,0 +1,39 @@
#ifndef PBWRITER_H
#define PBWRITER_H
#include <vector>
#include <string>
#include <memory>
#include <PbWireType.h>
class PbWriter
{
private:
std::vector<uint8_t> &rawData;
uint32_t pos;
uint32_t msgStartPos = 0;
void encodeVarInt(uint32_t low, uint32_t high, int32_t atIndex = 0);
uint32_t encodeZigzag32(int32_t value);
uint64_t encodeZigzag64(int64_t value);
public:
PbWriter(std::vector<uint8_t> &rawData);
template <typename T>
void encodeVarInt(T, int32_t atIndex = 0);
template <typename T>
void encodeFixed(T);
void addSVarInt32(uint32_t tag, int32_t);
void addSVarInt64(uint32_t tag, int64_t);
void addString(uint32_t tag, std::string &target);
void addVector(uint32_t tag, std::vector<uint8_t> &target);
template <typename T>
void addVarInt(uint32_t tag, T intType);
void addBool(uint32_t tag, bool value);
void addField(uint32_t tag, PbWireType wiretype);
uint32_t startMessage();
void finishMessage(uint32_t tag, uint32_t pos);
};
#endif

View File

@@ -0,0 +1,31 @@
#ifndef PLAINCONNECTION_H
#define PLAINCONNECTION_H
#include "sys/socket.h"
#include <functional>
#include <vector>
#include <string>
#include <cstdint>
#include <netdb.h>
#include <unistd.h>
#include "Packet.h"
#include "Utils.h"
typedef std::function<bool()> timeoutCallback;
class PlainConnection
{
public:
PlainConnection();
~PlainConnection();
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);
};
#endif

View File

@@ -0,0 +1,50 @@
#ifndef PLAYER_H
#define PLAYER_H
#include <vector>
#include <string>
#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 "Queue.h"
#include "Task.h"
class Player : public bell::Task {
private:
std::shared_ptr<MercuryManager> manager;
std::shared_ptr<SpotifyTrack> currentTrack = nullptr;
std::shared_ptr<AudioSink> audioSink;
std::mutex loadTrackMutex;
// @TODO: Use some actual structure here
bell::Queue<std::shared_ptr<SpotifyTrack>> trackQueue;
void runTask();
public:
Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSink> audioSink);
std::function<void()> endOfFileCallback;
int volume = 255;
uint32_t logVolume;
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()> &trackLoadedCallback, uint32_t position_ms, bool isPaused);
void pause();
void cancelCurrentTrack();
void seekMs(size_t positionMs);
void feedPCM(std::vector<uint8_t> &data);
void play();
void stop();
};
#endif

View File

@@ -0,0 +1,135 @@
#ifndef PLAYERSTATE_H
#define PLAYERSTATE_H
#include <vector>
#include <memory>
#include <string>
#include "ProtoHelper.h"
#include "Utils.h"
#include "TimeProvider.h"
#include "ConstantParameters.h"
#include "CspotAssert.h"
#include "TrackReference.h"
#include "ConfigJSON.h"
enum class PlaybackState {
Playing,
Stopped,
Loading,
Paused
};
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;
void addCapability(CapabilityType typ, int intValue = -1, std::vector<std::string> stringsValue = std::vector<std::string>());
public:
Frame innerFrame = Frame();
Frame remoteFrame = Frame();
/**
* @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);
/**
* @brief Updates state according to current playback state.
*
* @param state playback state
*/
void setPlaybackState(const PlaybackState state);
/**
* @brief Sets player activity
*
* @param isActive activity status
*/
void setActive(bool isActive);
/**
* @brief Simple getter
*
* @return true player is active
* @return false player is inactive
*/
bool isActive();
/**
* @brief Updates local track position.
*
* @param position position in milliseconds
*/
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);
/**
* @brief Enables queue shuffling.
*
* Sets shuffle parameter on local frame, and in case shuffling is enabled,
* it will randomize the entire local queue.
*
* @param shuffle whenever should shuffle
*/
void setShuffle(bool shuffle);
/**
* @brief Enables repeat
*
* @param repeat should repeat param
*/
void setRepeat(bool repeat);
/**
* @brief Updates local track queue from remote data.
*/
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();
/**
* @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();
/**
* @brief Gets the current track reference.
*
* @return std::shared_ptr<TrackReference> pointer to track reference
*/
std::shared_ptr<TrackReference> getCurrentTrack();
/**
* @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);
};
#endif

View File

@@ -0,0 +1,37 @@
#ifndef PROTOBUF_H
#define PROTOBUF_H
#include <iostream>
#include <memory>
#include "protobuf.h"
#include <PbReader.h>
#include <PbCommon.h>
std::optional<AnyRef> findFieldWithProtobufTag(AnyRef ref, uint32_t tag);
void decodeField(std::shared_ptr<PbReader> reader, AnyRef any);
void decodeProtobuf(std::shared_ptr<PbReader> reader, AnyRef any);
void encodeProtobuf(std::shared_ptr<PbWriter> writer, AnyRef any, uint32_t protobufTag = 0);
template <typename T>
std::vector<uint8_t> encodePb(T & data)
{
auto ref = AnyRef::of(&data);
std::vector<uint8_t> rawData;;
auto writer = std::make_shared<PbWriter>(rawData);
encodeProtobuf(writer, ref);
return rawData;
}
template <typename T>
T decodePb(std::vector<uint8_t> & bytes)
{
T data = {};
auto ref = AnyRef::of(&data);
auto writer = std::make_shared<PbReader>(bytes);
decodeProtobuf(writer, ref);
return data;
}
#endif

View File

@@ -0,0 +1,49 @@
#ifndef SESSION_H
#define SESSION_H
#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 "ProtoHelper.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;
std::shared_ptr<PlainConnection> conn;
std::unique_ptr<Crypto> crypto;
std::vector<uint8_t> sendClientHelloRequest();
void processAPHelloResponse(std::vector<uint8_t> &helloPacket);
public:
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);
};
#endif

View File

@@ -0,0 +1,43 @@
#ifndef SHANNON_H
#define SHANNON_H
#include <cstdint>
#include <vector>
class Shannon
{
public:
static constexpr unsigned int N = 16;
void key(const std::vector<uint8_t> &key); /* set key */
void nonce(const std::vector<uint8_t> &nonce); /* set Init Vector */
void stream(std::vector<uint8_t> &buf); /* stream cipher */
void maconly(std::vector<uint8_t> &buf); /* accumulate MAC */
void encrypt(std::vector<uint8_t> &buf); /* encrypt + MAC */
void decrypt(std::vector<uint8_t> &buf); /* decrypt + MAC */
void finish(std::vector<uint8_t> &buf); /* finalise MAC */
private:
static constexpr unsigned int FOLD = Shannon::N;
static constexpr unsigned int INITKONST = 0x6996c53a;
static constexpr unsigned int KEYP = 13;
uint32_t R[Shannon::N];
uint32_t CRC[Shannon::N];
uint32_t initR[Shannon::N];
uint32_t konst;
uint32_t sbuf;
uint32_t mbuf;
int nbuf;
static uint32_t sbox1(uint32_t w);
static uint32_t sbox2(uint32_t w);
void cycle();
void crcfunc(uint32_t i);
void macfunc(uint32_t i);
void initState();
void saveState();
void reloadState();
void genkonst();
void diffuse();
void loadKey(const std::vector<uint8_t> &key);
};
#endif

View File

@@ -0,0 +1,41 @@
#ifndef SHANNONCONNECTION_H
#define SHANNONCONNECTION_H
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <netdb.h>
#include <memory>
#include <cstdint>
#include "platform/WrappedMutex.h"
#include "Utils.h"
#include "Shannon.h"
#include "PlainConnection.h"
#include "Packet.h"
#define MAC_SIZE 4
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();
};
#endif

View File

@@ -0,0 +1,104 @@
#ifndef SPIRCCONTROLLER_H
#define SPIRCCONTROLLER_H
#include <vector>
#include <string>
#include <functional>
#include "Utils.h"
#include "MercuryManager.h"
#include "ProtoHelper.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,
};
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;
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);
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();
};
#endif

View File

@@ -0,0 +1,54 @@
#ifndef SPOTIFYTRACK_H
#define SPOTIFYTRACK_H
#include <memory>
#include <vector>
#include <iostream>
#include "MercuryManager.h"
#include "ProtoHelper.h"
#include "Utils.h"
#include "MercuryResponse.h"
#include <fstream>
#include "Crypto.h"
#include <functional>
#include "ChunkedAudioStream.h"
#include "TrackReference.h"
#include <cassert>
struct TrackInfo {
std::string name;
std::string album;
std::string artist;
std::string imageUrl;
};
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(std::string countryList, std::string country);
bool canPlayTrack(std::vector<Restriction> &restrictions);
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::function<void()> loadedTrackCallback;
std::unique_ptr<ChunkedAudioStream> audioStream;
trackChangedCallback trackInfoReceived;
audioKeyCallback audioKeyLambda;
mercuryCallback responseLambda;
};
#endif

View File

@@ -0,0 +1,34 @@
#ifndef TIMEPROVIDER_H
#define TIMEPROVIDER_H
#include <vector>
#include <stdint.h>
class TimeProvider
{
private:
unsigned long long timestampDiff;
public:
/**
* @brief Bypasses the need for NTP server sync by syncing with spotify's servers
*
*/
TimeProvider();
/**
* @brief Syncs the TimeProvider with spotify server's timestamp
*
* @param pongPacket pong packet containing timestamp
*/
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();
};
#endif

View File

@@ -0,0 +1,34 @@
#ifndef TRACKREFERENCE_H
#define TRACKREFERENCE_H
#include <vector>
#include "Utils.h"
#include "ProtoHelper.h"
#include <iostream>
#include <string>
class TrackReference
{
private:
std::string alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::vector<uint8_t> base62Decode(std::string uri);
public:
TrackReference(TrackRef *ref);
~TrackReference();
TrackRef* ref;
std::vector<uint8_t> gid;
bool isEpisode = false;
/**
* @brief Returns an uri that can be allowed to query track information.
*
* @return std::string
*/
std::string getMercuryRequestUri();
};
#endif

View File

@@ -0,0 +1,98 @@
#ifndef UTILS_H
#define UTILS_H
#include <unistd.h>
#include <vector>
#include "sys/socket.h"
#include <cstdint>
#include <netdb.h>
#include <cstring>
#include <memory>
#include <chrono>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#define HMAC_SHA1_BLOCKSIZE 64
/**
* @brief Returns current timestamp
*
* @return unsigned long long resulting timestamp in milliseconds from unix time zero
*/
unsigned long long getCurrentTimestamp();
/**
* @brief portable 64bit equivalent of htons / htonl. aka endianess swap
*
* @param value input value to swap
* @return uint64_t swapped result
*/
uint64_t hton64(uint64_t value);
/**
* @brief Performs big number multiplication on two numbers
*
* @param num big num in vector format
* @param n secondary number
* @return std::vector<uint8_t> resulting number
*/
std::vector<uint8_t> bigNumMultiply(std::vector<uint8_t> num, int n);
/**
* @brief Performs big number addition on two numbers
*
* @param num big num in vector format
* @param n secondary number
* @return std::vector<uint8_t> resulting number
*/
std::vector<uint8_t> bigNumAdd(std::vector<uint8_t> num, int n);
unsigned char h2int(char c);
std::string urlDecode(std::string str);
/**
* @brief Converts provided bytes into a human readable hex string
*
* @param bytes vector containing binary data
* @return std::string string containing hex representation of inputted data
*/
std::string bytesToHexString(std::vector<uint8_t> &bytes);
/**
* @brief Extracts given type from binary data
*
* @tparam T type to extract
* @param v vector containing binary data to extract from
* @param pos position offset
* @return T extracted type
*/
template <typename T>
T extract(const std::vector<unsigned char> &v, int pos)
{
T value;
memcpy(&value, &v[pos], sizeof(T));
return value;
}
/**
* @brief Packs given type into binary data
*
* @tparam T type of data to pack
* @param data data to pack
* @return std::vector<uint8_t> resulting vector containing binary data
*/
template <typename T>
std::vector<uint8_t> pack(T data)
{
std::vector<std::uint8_t> rawData( (std::uint8_t*)&data, (std::uint8_t*)&(data) + sizeof(T));
return rawData;
}
#endif

View File

@@ -0,0 +1,49 @@
#ifndef ZEROCONFAUTHENTICATOR_H
#define ZEROCONFAUTHENTICATOR_H
#include <vector>
#include <unistd.h>
#include <string>
#include <BaseHTTPServer.h>
#include <cstdlib>
#include "Utils.h"
#include "LoginBlob.h"
#include "Crypto.h"
#include "Task.h"
#include "ConstantParameters.h"
#ifdef ESP_PLATFORM
#include "mdns.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:
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