CDNAudioFile.cpp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #include "CDNAudioFile.h"
  2. #include <string.h> // for memcpy
  3. #include <functional> // for __base
  4. #include <initializer_list> // for initializer_list
  5. #include <map> // for operator!=, operator==
  6. #include <string_view> // for string_view
  7. #include <type_traits> // for remove_extent_t
  8. #include "AccessKeyFetcher.h" // for AccessKeyFetcher
  9. #include "BellLogger.h" // for AbstractLogger
  10. #include "Crypto.h"
  11. #include "Logger.h" // for CSPOT_LOG
  12. #include "Packet.h" // for cspot
  13. #include "SocketStream.h" // for SocketStream
  14. #include "Utils.h" // for bigNumAdd, bytesToHexString, string...
  15. #include "WrappedSemaphore.h" // for WrappedSemaphore
  16. #ifdef BELL_ONLY_CJSON
  17. #include "cJSON.h"
  18. #else
  19. #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
  20. #include "nlohmann/json_fwd.hpp" // for json
  21. #endif
  22. using namespace cspot;
  23. CDNAudioFile::CDNAudioFile(const std::string& cdnUrl,
  24. const std::vector<uint8_t>& audioKey)
  25. : cdnUrl(cdnUrl), audioKey(audioKey) {
  26. this->crypto = std::make_unique<Crypto>();
  27. }
  28. size_t CDNAudioFile::getPosition() {
  29. return this->position;
  30. }
  31. void CDNAudioFile::seek(size_t newPos) {
  32. this->enableRequestMargin = true;
  33. this->position = newPos;
  34. }
  35. void CDNAudioFile::openStream() {
  36. CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str());
  37. // Open connection, read first 128 bytes
  38. this->httpConnection = bell::HTTPClient::get(
  39. this->cdnUrl,
  40. {bell::HTTPClient::RangeHeader::range(0, OPUS_HEADER_SIZE - 1)});
  41. this->httpConnection->stream().read((char*)header.data(), OPUS_HEADER_SIZE);
  42. this->totalFileSize =
  43. this->httpConnection->totalLength() - SPOTIFY_OPUS_HEADER;
  44. this->decrypt(header.data(), OPUS_HEADER_SIZE, 0);
  45. // Location must be dividable by 16
  46. size_t footerStartLocation =
  47. (this->totalFileSize - OPUS_FOOTER_PREFFERED + SPOTIFY_OPUS_HEADER) -
  48. (this->totalFileSize - OPUS_FOOTER_PREFFERED + SPOTIFY_OPUS_HEADER) % 16;
  49. this->footer = std::vector<uint8_t>(
  50. this->totalFileSize - footerStartLocation + SPOTIFY_OPUS_HEADER);
  51. this->httpConnection->get(
  52. cdnUrl, {bell::HTTPClient::RangeHeader::last(footer.size())});
  53. this->httpConnection->stream().read((char*)footer.data(),
  54. this->footer.size());
  55. this->decrypt(footer.data(), footer.size(), footerStartLocation);
  56. CSPOT_LOG(info, "Header and footer bytes received");
  57. this->position = 0;
  58. this->lastRequestPosition = 0;
  59. this->lastRequestCapacity = 0;
  60. }
  61. size_t CDNAudioFile::readBytes(uint8_t* dst, size_t bytes) {
  62. size_t offsetPosition = position + SPOTIFY_OPUS_HEADER;
  63. size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER;
  64. if (position + bytes >= this->totalFileSize) {
  65. return 0;
  66. }
  67. // // Opus tries to read header, use prefetched data
  68. if (offsetPosition < OPUS_HEADER_SIZE &&
  69. bytes + offsetPosition <= OPUS_HEADER_SIZE) {
  70. memcpy(dst, this->header.data() + offsetPosition, bytes);
  71. position += bytes;
  72. return bytes;
  73. }
  74. // // Opus tries to read footer, use prefetched data
  75. if (offsetPosition >= (actualFileSize - this->footer.size())) {
  76. size_t toReadBytes = bytes;
  77. if ((position + bytes) > this->totalFileSize) {
  78. // Tries to read outside of bounds, truncate
  79. toReadBytes = this->totalFileSize - position;
  80. }
  81. size_t footerOffset =
  82. offsetPosition - (actualFileSize - this->footer.size());
  83. memcpy(dst, this->footer.data() + footerOffset, toReadBytes);
  84. position += toReadBytes;
  85. return toReadBytes;
  86. }
  87. // Data not in the headers. Make sense of whats going on.
  88. // Position in bounds :)
  89. if (offsetPosition >= this->lastRequestPosition &&
  90. offsetPosition < this->lastRequestPosition + this->lastRequestCapacity) {
  91. size_t toRead = bytes;
  92. if ((toRead + offsetPosition) >
  93. this->lastRequestPosition + lastRequestCapacity) {
  94. toRead = this->lastRequestPosition + lastRequestCapacity - offsetPosition;
  95. }
  96. memcpy(dst, this->httpBuffer.data() + offsetPosition - lastRequestPosition,
  97. toRead);
  98. position += toRead;
  99. return toRead;
  100. } else {
  101. size_t requestPosition = (offsetPosition) - ((offsetPosition) % 16);
  102. if (this->enableRequestMargin && requestPosition > SEEK_MARGIN_SIZE) {
  103. requestPosition = (offsetPosition - SEEK_MARGIN_SIZE) -
  104. ((offsetPosition - SEEK_MARGIN_SIZE) % 16);
  105. this->enableRequestMargin = false;
  106. }
  107. this->httpConnection->get(
  108. cdnUrl, {bell::HTTPClient::RangeHeader::range(
  109. requestPosition, requestPosition + HTTP_BUFFER_SIZE - 1)});
  110. this->lastRequestPosition = requestPosition;
  111. this->lastRequestCapacity = this->httpConnection->contentLength();
  112. this->httpConnection->stream().read((char*)this->httpBuffer.data(),
  113. lastRequestCapacity);
  114. this->decrypt(this->httpBuffer.data(), lastRequestCapacity,
  115. this->lastRequestPosition);
  116. return readBytes(dst, bytes);
  117. }
  118. return bytes;
  119. }
  120. size_t CDNAudioFile::getSize() {
  121. return this->totalFileSize;
  122. }
  123. void CDNAudioFile::decrypt(uint8_t* dst, size_t nbytes, size_t pos) {
  124. auto calculatedIV = bigNumAdd(audioAESIV, pos / 16);
  125. this->crypto->aesCTRXcrypt(this->audioKey, calculatedIV, dst, nbytes);
  126. }