MythTV master
HLSStream.cpp
Go to the documentation of this file.
1#include "HLSStream.h"
2
3#include <algorithm>
4#include <array>
5#include <cstdint>
6#include <cstring>
7#include <utility>
8
9#include "libmythbase/mythconfig.h"
10
11#if CONFIG_LIBCRYPTO
12#include <openssl/aes.h>
13#include <openssl/evp.h>
14#endif
15
16#include <QMutexLocker>
17
19
20#ifdef HLS_USE_MYTHDOWNLOADMANAGER
21#include "HLSReader.h"
22#endif
23
24#define LOC QString("HLSRecstream[%1]: ").arg(m_inputId)
25
26HLSRecStream::HLSRecStream(int inputId, int seq, uint64_t bitrate, QString m3u8_url, QString segment_base_url)
27 : m_inputId(inputId),
28 m_id(seq),
29 m_bitrate(bitrate),
30 m_m3u8Url(std::move(m3u8_url)),
31 m_segmentBaseUrl(std::move(segment_base_url))
32{
33 LOG(VB_RECORD, LOG_DEBUG, LOC + "ctor");
34}
35
37{
38 LOG(VB_RECORD, LOG_DEBUG, LOC + "dtor");
39#if CONFIG_LIBCRYPTO
40 AESKeyMap::iterator Iaes;
41
42 for (Iaes = m_aesKeys.begin(); Iaes != m_aesKeys.end(); ++Iaes)
43 delete *Iaes;
44#endif // CONFIG_LIBCRYPTO
45}
46
47QString HLSRecStream::toString(void) const
48{
49 return QString("%1 bitrate %2").arg(Id()).arg(Bitrate());
50}
51
52#if CONFIG_LIBCRYPTO
53bool HLSRecStream::DownloadKey(MythSingleDownload& downloader,
54 const QString& keypath, HLS_AES_KEY* aeskey) const
55{
56 QByteArray key;
57
58#ifdef HLS_USE_MYTHDOWNLOADMANAGER // MythDownloadManager leaks memory
59 bool ret = HLSReader::DownloadURL(keypath, &key);
60#else
61 QUrl keyUrl { keypath };
62 if (!keyUrl.isValid())
63 {
64 LOG(VB_RECORD, LOG_ERR, LOC + "Invalid key path: " + keypath);
65 return false;
66 }
67 bool ret = downloader.DownloadURL(keyUrl, &key);
68#endif
69
70 if (!ret || key.size() != AES128_KEY_SIZE)
71 {
72 if (ret)
73 {
74 LOG(VB_RECORD, LOG_ERR, LOC +
75 QString("The AES key loaded doesn't have the right size (%1)")
76 .arg(key.size()));
77 }
78 else
79 {
80 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to download AES key: " +
81 downloader.ErrorString());
82 }
83 return false;
84 }
85 memcpy(aeskey->key.data(), key.constData(), AES128_KEY_SIZE);
86
87 LOG(VB_RECORD, LOG_DEBUG, LOC + "Downloaded AES key");
88
89 return true;
90}
91
92// AES decryption based on OpenSSL example found on
93// https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
94//
95int HLSRecStream::Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
96 unsigned char *iv, unsigned char *plaintext) const
97{
98 int len = 0;
99
100 int plaintext_len = 0;
101
102 /* Create and initialise the context */
103 EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
104 if(!ctx)
105 {
106 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to create and initialize cipher context");
107 return 0;
108 }
109
110 /*
111 * Initialise the decryption operation. IMPORTANT - ensure you use a key
112 * and IV size appropriate for your cipher
113 * In this example we are using 128 bit AES (i.e. a 128 bit key). The
114 * IV size for *most* modes is the same as the block size. For AES this
115 * is 128 bits
116 */
117 if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
118 {
119 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to initialize decryption operation");
120 return 0;
121 }
122
123 /*
124 * Provide the message to be decrypted, and obtain the plaintext output.
125 * EVP_DecryptUpdate can be called multiple times if necessary.
126 */
127 if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
128 {
129 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to decrypt");
130 return 0;
131 }
132 plaintext_len = len;
133
134 /*
135 * Finalise the decryption. Further plaintext bytes may be written at
136 * this stage.
137 */
138 if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
139 {
140 LOG(VB_RECORD, LOG_ERR, LOC + "Failed to finalize decryption" +
141 QString(" len:%1").arg(len) +
142 QString(" plaintext_len:%1").arg(plaintext_len) );
143 return 0;
144 }
145 plaintext_len += len;
146
147 /* Clean up */
148 EVP_CIPHER_CTX_free(ctx);
149
150 return plaintext_len;
151}
152
153bool HLSRecStream::DecodeData(MythSingleDownload& downloader,
154 const QByteArray& IV, const QString& keypath,
155 QByteArray& data, int64_t sequence)
156{
157 LOG(VB_RECORD, LOG_INFO, LOC + "DecodeData:" +
158 QString(" IV.size():%1").arg(IV.size()) +
159 QString(" keypath:%1..%2").arg(keypath.left(20),keypath.right(20)) +
160 QString(" sequence:%1").arg(sequence));
161
162 AESKeyMap::iterator Ikey = m_aesKeys.find(keypath);
163 if (Ikey == m_aesKeys.end())
164 {
165 auto* key = new HLS_AES_KEY;
166 DownloadKey(downloader, keypath, key);
167 Ikey = m_aesKeys.insert(keypath, key);
168 if (Ikey == m_aesKeys.end())
169 {
170 LOG(VB_RECORD, LOG_ERR, LOC +
171 "DecodeData: Unable to add AES key to map");
172 return false;
173 }
174 }
175
176 /* Decrypt data using AES-128 */
177 std::array<uint8_t,AES_BLOCK_SIZE> iv {};
178 auto *decrypted_data = new uint8_t[data.size()];
179 if (IV.isEmpty())
180 {
181 /*
182 * If the EXT-X-KEY tag does not have the IV attribute,
183 * implementations MUST use the sequence number of the
184 * media file as the IV when encrypting or decrypting that
185 * media file. The big-endian binary representation of
186 * the sequence number SHALL be placed in a 16-octet
187 * buffer and padded (on the left) with zeros.
188 */
189 iv[15] = sequence & 0xff;
190 iv[14] = (sequence >> 8) & 0xff;
191 iv[13] = (sequence >> 16) & 0xff;
192 iv[12] = (sequence >> 24) & 0xff;
193 }
194 else
195 {
196 std::ranges::copy(std::as_const(IV), iv.data());
197 }
198
199 int aeslen = data.size() & ~0xf;
200 if (aeslen != data.size())
201 {
202 LOG(VB_RECORD, LOG_WARNING, LOC +
203 QString("Data size %1 not multiple of 16 bytes, rounding to %2")
204 .arg(data.size()).arg(aeslen));
205 }
206
207 int plaintext_len = Decrypt((unsigned char*)data.constData(), aeslen, (*Ikey)->key.data(),
208 iv.data(), decrypted_data);
209
210 LOG(VB_RECORD, LOG_INFO, LOC +
211 QString("Segment data.size()):%1 plaintext_len:%2")
212 .arg(data.size()).arg(plaintext_len));
213
214 data = QByteArray(reinterpret_cast<char*>(decrypted_data), plaintext_len);
215 delete[] decrypted_data;
216
217 return true;
218}
219#endif // CONFIG_LIBCRYPTO
220
221void HLSRecStream::AverageBandwidth(int64_t bandwidth)
222{
223 // Average the last 20 segments
224 if (m_bandwidthSegs.size() > 19)
225 m_sumBandwidth -= m_bandwidthSegs.dequeue();
226 m_bandwidthSegs.enqueue(bandwidth);
227 m_sumBandwidth += bandwidth;
229}
230
231std::chrono::seconds HLSRecStream::Duration(void) const
232{
233 QMutexLocker lock(&m_lock);
234 return m_duration;
235}
236
238{
239 return this->Bitrate() < b.Bitrate();
240}
241
243{
244 return this->Bitrate() > b.Bitrate();
245}
#define LOC
Definition: HLSStream.cpp:24
static constexpr uint8_t AES128_KEY_SIZE
Definition: HLSStream.h:19
hls_aes_key_st HLS_AES_KEY
Definition: HLSStream.h:23
std::chrono::seconds Duration(void) const
Definition: HLSStream.cpp:231
bool operator>(const HLSRecStream &b) const
Definition: HLSStream.cpp:242
std::chrono::seconds m_duration
Definition: HLSStream.h:97
int64_t m_bandwidth
Definition: HLSStream.h:100
~HLSRecStream(void)
Definition: HLSStream.cpp:36
double m_sumBandwidth
Definition: HLSStream.h:101
QString toString(void) const
Definition: HLSStream.cpp:47
uint64_t Bitrate(void) const
Definition: HLSStream.h:48
HLSRecStream(int inputId, int seq, uint64_t bitrate, QString m3u8_url, QString segment_base_url)
Definition: HLSStream.cpp:26
int Id(void) const
Definition: HLSStream.h:40
bool operator<(const HLSRecStream &b) const
Definition: HLSStream.cpp:237
QQueue< int64_t > m_bandwidthSegs
Definition: HLSStream.h:102
uint64_t AverageBandwidth(void) const
Definition: HLSStream.h:47
QMutex m_lock
Definition: HLSStream.h:106
QString ErrorString(void) const
bool DownloadURL(const QUrl &url, QByteArray *buffer, std::chrono::seconds timeout=30s, uint redirs=0, qint64 maxsize=0, QString *final_url=nullptr)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
std::array< uint8_t, AES128_KEY_SIZE > key
Definition: HLSStream.h:21