123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- #include "CDNAudioFile.h"
- #include <string.h> // for memcpy
- #include <functional> // for __base
- #include <initializer_list> // for initializer_list
- #include <map> // for operator!=, operator==
- #include <string_view> // for string_view
- #include <type_traits> // for remove_extent_t
- #include "AccessKeyFetcher.h" // for AccessKeyFetcher
- #include "BellLogger.h" // for AbstractLogger
- #include "Crypto.h"
- #include "Logger.h" // for CSPOT_LOG
- #include "Packet.h" // for cspot
- #include "SocketStream.h" // for SocketStream
- #include "Utils.h" // for bigNumAdd, bytesToHexString, string...
- #include "WrappedSemaphore.h" // for WrappedSemaphore
- #ifdef BELL_ONLY_CJSON
- #include "cJSON.h"
- #else
- #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
- #include "nlohmann/json_fwd.hpp" // for json
- #endif
- using namespace cspot;
- CDNAudioFile::CDNAudioFile(const std::string& cdnUrl,
- const std::vector<uint8_t>& audioKey)
- : cdnUrl(cdnUrl), audioKey(audioKey) {
- this->crypto = std::make_unique<Crypto>();
- }
- size_t CDNAudioFile::getPosition() {
- return this->position;
- }
- void CDNAudioFile::seek(size_t newPos) {
- this->enableRequestMargin = true;
- this->position = newPos;
- }
- void CDNAudioFile::openStream() {
- CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str());
- // Open connection, read first 128 bytes
- this->httpConnection = bell::HTTPClient::get(
- this->cdnUrl,
- {bell::HTTPClient::RangeHeader::range(0, OPUS_HEADER_SIZE - 1)});
- this->httpConnection->stream().read((char*)header.data(), OPUS_HEADER_SIZE);
- this->totalFileSize =
- this->httpConnection->totalLength() - SPOTIFY_OPUS_HEADER;
- this->decrypt(header.data(), OPUS_HEADER_SIZE, 0);
- // Location must be dividable by 16
- size_t footerStartLocation =
- (this->totalFileSize - OPUS_FOOTER_PREFFERED + SPOTIFY_OPUS_HEADER) -
- (this->totalFileSize - OPUS_FOOTER_PREFFERED + SPOTIFY_OPUS_HEADER) % 16;
- this->footer = std::vector<uint8_t>(
- this->totalFileSize - footerStartLocation + SPOTIFY_OPUS_HEADER);
- this->httpConnection->get(
- cdnUrl, {bell::HTTPClient::RangeHeader::last(footer.size())});
- this->httpConnection->stream().read((char*)footer.data(),
- this->footer.size());
- this->decrypt(footer.data(), footer.size(), footerStartLocation);
- CSPOT_LOG(info, "Header and footer bytes received");
- this->position = 0;
- this->lastRequestPosition = 0;
- this->lastRequestCapacity = 0;
- }
- size_t CDNAudioFile::readBytes(uint8_t* dst, size_t bytes) {
- size_t offsetPosition = position + SPOTIFY_OPUS_HEADER;
- size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER;
- if (position + bytes >= this->totalFileSize) {
- return 0;
- }
- // // Opus tries to read header, use prefetched data
- if (offsetPosition < OPUS_HEADER_SIZE &&
- bytes + offsetPosition <= OPUS_HEADER_SIZE) {
- memcpy(dst, this->header.data() + offsetPosition, bytes);
- position += bytes;
- return bytes;
- }
- // // Opus tries to read footer, use prefetched data
- if (offsetPosition >= (actualFileSize - this->footer.size())) {
- size_t toReadBytes = bytes;
- if ((position + bytes) > this->totalFileSize) {
- // Tries to read outside of bounds, truncate
- toReadBytes = this->totalFileSize - position;
- }
- size_t footerOffset =
- offsetPosition - (actualFileSize - this->footer.size());
- memcpy(dst, this->footer.data() + footerOffset, toReadBytes);
- position += toReadBytes;
- return toReadBytes;
- }
- // Data not in the headers. Make sense of whats going on.
- // Position in bounds :)
- if (offsetPosition >= this->lastRequestPosition &&
- offsetPosition < this->lastRequestPosition + this->lastRequestCapacity) {
- size_t toRead = bytes;
- if ((toRead + offsetPosition) >
- this->lastRequestPosition + lastRequestCapacity) {
- toRead = this->lastRequestPosition + lastRequestCapacity - offsetPosition;
- }
- memcpy(dst, this->httpBuffer.data() + offsetPosition - lastRequestPosition,
- toRead);
- position += toRead;
- return toRead;
- } else {
- size_t requestPosition = (offsetPosition) - ((offsetPosition) % 16);
- if (this->enableRequestMargin && requestPosition > SEEK_MARGIN_SIZE) {
- requestPosition = (offsetPosition - SEEK_MARGIN_SIZE) -
- ((offsetPosition - SEEK_MARGIN_SIZE) % 16);
- this->enableRequestMargin = false;
- }
- this->httpConnection->get(
- cdnUrl, {bell::HTTPClient::RangeHeader::range(
- requestPosition, requestPosition + HTTP_BUFFER_SIZE - 1)});
- this->lastRequestPosition = requestPosition;
- this->lastRequestCapacity = this->httpConnection->contentLength();
- this->httpConnection->stream().read((char*)this->httpBuffer.data(),
- lastRequestCapacity);
- this->decrypt(this->httpBuffer.data(), lastRequestCapacity,
- this->lastRequestPosition);
- return readBytes(dst, bytes);
- }
- return bytes;
- }
- size_t CDNAudioFile::getSize() {
- return this->totalFileSize;
- }
- void CDNAudioFile::decrypt(uint8_t* dst, size_t nbytes, size_t pos) {
- auto calculatedIV = bigNumAdd(audioAESIV, pos / 16);
- this->crypto->aesCTRXcrypt(this->audioKey, calculatedIV, dst, nbytes);
- }
|