RDK Documentation (Open Sourced RDK Components)
drmSessionTest.cpp
1 #include <vector>
2 #include <iostream>
3 #include <iomanip>
4 #include <algorithm>
5 #include <iterator>
6 #include <memory>
7 
10 #include "AampVgdrmHelper.h"
11 #include "AampClearKeyHelper.h"
12 #include "AampHlsOcdmBridge.h"
13 #include "open_cdm.h"
14 
15 #include "aampMocks.h"
16 #include "opencdmMocks.h"
17 #include "curlMocks.h"
18 
19 #include "drmTestUtils.h"
20 
21 #include "CppUTest/TestHarness.h"
22 #include "CppUTestExt/MockSupport.h"
23 
25 {
26  std::string response;
27  MockCurlOpts opts;
28  unsigned int callCount;
29 
30  TestCurlResponse(std::string response) :
31  response(response),
32  callCount(0) {}
33 
34  std::vector<std::string> getHeaders() const
35  {
36  std::vector<std::string> headerList;
37 
38  for (int i = 0; i < opts.headerCount; ++i)
39  {
40  headerList.push_back(std::string(opts.headers[i]));
41  }
42  std::sort(headerList.begin(), headerList.end());
43  return headerList;
44  }
45 };
46 
48 {
49  std::string url;
50  std::string challenge;
51 
52  MockChallengeData(std::string url = "", std::string challenge = "") :
53  url(url),
54  challenge(challenge) {}
55 };
56 
57 TEST_GROUP(AampDrmSessionTests)
58 {
59  PrivateInstanceAAMP *mAamp;
60  OpenCDMSession* mOcdmSession = (OpenCDMSession*)0x0CD12345;
61  OpenCDMSystem* mOcdmSystem = (OpenCDMSystem*)0x0CDACC12345;
62  std::map<std::string, TestCurlResponse> mCurlResponses;
63  MockChallengeData mMockChallengeData;
64  AampDRMSessionManager *sessionManager;
65  AampLogManager mLogging;
66  int mMaxDrmSessions = 2;
67 
68  // The URL AAMP uses to fetch the session token
69  const std::string mSessionTokenUrl = "http://localhost:50050/authService/getSessionToken";
70  // A fixed session token response - any tests which require a session token can use this,
71  // since the manager is singleton and will only ever fetch it once. Test order is non-deterministic,
72  // so it's not guaranteed which test will actually trigger the fetch
73  const std::string mSessionTokenResponse = "{\"token\":\"SESSIONTOKEN\", \"status\":0}";
74 
75  AampDRMSessionManager* getSessionManager()
76  {
77  if (sessionManager == nullptr) {
78  sessionManager = new AampDRMSessionManager(&mLogging, mMaxDrmSessions);
79  }
80  return sessionManager;
81  }
82 
83  AampDrmSession* createDrmSessionForHelper(std::shared_ptr<AampDrmHelper> drmHelper, const char *keySystem)
84  {
85  AampDRMSessionManager *sessionManager = getSessionManager();
86  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
87 
88  mock("OpenCDM").expectOneCall("opencdm_create_system")
89  .withParameter("keySystem", keySystem)
90  .andReturnValue(mOcdmSystem);
91 
92  mock("OpenCDM").expectOneCall("opencdm_construct_session")
93  .withOutputParameterReturning("session", &mOcdmSession, sizeof(mOcdmSession))
94  .andReturnValue(0);
95  AampDrmSession *drmSession = sessionManager->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
96  mock().checkExpectations();
97  CHECK_TEXT(drmSession != NULL, "Session creation failed");
98 
99  return drmSession;
100  }
101 
102  AampDrmSession* createDashDrmSession(const std::string testKeyData, const std::string psshStr, DrmMetaDataEventPtr& event)
103  {
104  std::vector<uint8_t> testKeyDataVec(testKeyData.begin(), testKeyData.end());
105  return createDashDrmSession(testKeyDataVec, psshStr, event);
106  }
107 
108  AampDrmSession* createDashDrmSession(const std::vector<uint8_t> testKeyData, const std::string psshStr, DrmMetaDataEventPtr& event)
109  {
110  AampDRMSessionManager *sessionManager = getSessionManager();
111 
112  mock("OpenCDM").expectOneCall("opencdm_create_system")
113  .withParameter("keySystem", "com.microsoft.playready")
114  .andReturnValue(mOcdmSystem);
115 
116  mock("OpenCDM").expectOneCall("opencdm_construct_session")
117  .withOutputParameterReturning("session", &mOcdmSession, sizeof(mOcdmSession))
118  .andReturnValue(0);
119 
120  mock("OpenCDM").expectOneCall("opencdm_session_update")
121  .withMemoryBufferParameter("keyMessage", testKeyData.data(), testKeyData.size())
122  .withIntParameter("keyLength", testKeyData.size())
123  .andReturnValue(0);
124 
125  AampDrmSession *drmSession = sessionManager->createDrmSession("9a04f079-9840-4286-ab92-e65be0885f95", eMEDIAFORMAT_DASH,
126  (const unsigned char*)psshStr.c_str(), psshStr.length(), eMEDIATYPE_VIDEO, mAamp, event);
127  CHECK_TEXT(drmSession != NULL, "Session creation failed");
128 
129  return drmSession;
130  }
131 
132  std::shared_ptr<AampHlsOcdmBridge> createBridgeForHelper(std::shared_ptr<AampVgdrmHelper> drmHelper)
133  {
134  return std::make_shared<AampHlsOcdmBridge>(&mLogging, createDrmSessionForHelper(drmHelper, "net.vgdrm"));
135  }
136 
137  DrmMetaDataEventPtr createDrmMetaDataEvent()
138  {
139  return std::make_shared<DrmMetaDataEvent>(AAMP_TUNE_FAILURE_UNKNOWN, "", 0, 0, false);
140  }
141 
142  void setupCurlPerformResponse(std::string response)
143  {
144  static string responseStr = response;
145 
146  MockCurlSetPerformCallback([](CURL *curl, MockCurlWriteCallback writeCallback, void* writeData, void* userData) {
147  writeCallback((char*)responseStr.c_str(), 1, responseStr.size(), writeData);
148  }, this);
149  }
150 
151  void setupCurlPerformResponses(const std::map<std::string, std::string>& responses)
152  {
153  for (auto& response : responses)
154  {
155  mCurlResponses.emplace(response.first, TestCurlResponse(response.second));
156  }
157 
158  MockCurlSetPerformCallback([](CURL *curl, MockCurlWriteCallback writeCallback, void* writeData, void* userData) {
159  const auto *responseMap = (const std::map<std::string, TestCurlResponse>*)userData;
160  const MockCurlOpts *curlOpts = MockCurlGetOpts();
161 
162  // Check if there is a response setup for this URL
163  auto iter = responseMap->find(curlOpts->url);
164  if (iter != responseMap->end())
165  {
166  TestCurlResponse *curlResponse = const_cast<TestCurlResponse*>(&iter->second);
167  curlResponse->opts = *curlOpts; // Taking a copy of the opts so we can check them later
168  curlResponse->callCount++;
169  // Issue the write callback with the user-provided response
170  writeCallback((char*)curlResponse->response.c_str(), 1, curlResponse->response.size(), writeData);
171  }
172  }, &mCurlResponses);
173  }
174 
175  const TestCurlResponse* getCurlPerformResponse(std::string url)
176  {
177  auto iter = mCurlResponses.find(url);
178  if (iter != mCurlResponses.end())
179  {
180  return &iter->second;
181  }
182 
183  return nullptr;
184  }
185 
186  void checkHeaders()
187  {
188  CHECK_EQUAL(1, 0);
189  }
190 
191  void setupChallengeCallbacks(const MockChallengeData& challengeData = MockChallengeData("challenge.example", "OCDM_CHALLENGE_DATA"))
192  {
193  mMockChallengeData = challengeData;
194  MockOpenCdmCallbacks callbacks = {NULL, NULL};
195  callbacks.constructSessionCallback = [](const MockOpenCdmSessionInfo *mockSessionInfo, void *mockUserData) {
196  MockChallengeData *pChallengeData = (MockChallengeData*)mockUserData;
197  // OpenCDM should come back to us with a URL + challenge payload.
198  // The content of these shouldn't matter for us, since we use the request info from the DRM helper instead
199  const char* url = pChallengeData->url.c_str();
200  const std::string challenge = pChallengeData->challenge;
201  mockSessionInfo->callbacks.process_challenge_callback((OpenCDMSession*)mockSessionInfo->session,
202  mockSessionInfo->userData, url, (const uint8_t*)challenge.c_str(), challenge.size());
203  };
204 
205  callbacks.sessionUpdateCallback = [](const MockOpenCdmSessionInfo *mockSessionInfo, const uint8_t keyMessage[], const uint16_t keyLength) {
206  mockSessionInfo->callbacks.key_update_callback((OpenCDMSession*)mockSessionInfo->session, mockSessionInfo->userData, keyMessage, keyLength);
207  mockSessionInfo->callbacks.keys_updated_callback((OpenCDMSession*)mockSessionInfo->session, mockSessionInfo->userData);
208  };
209  MockOpenCdmSetCallbacks(callbacks, &mMockChallengeData);
210  }
211 
212  void setupChallengeCallbacksForExternalLicense()
213  {
214  MockOpenCdmCallbacks callbacks = {NULL, NULL};
215  callbacks.constructSessionCallback = [](const MockOpenCdmSessionInfo *mockSessionInfo, void *mockUserData) {
216  // OpenCDM should come back to us with a URL + challenge payload.
217  // The content of these shouldn't matter for us, since we use the request info from the DRM helper instead
218  const char* url = "test";
219  uint8_t challenge[] = {'A'};
220  mockSessionInfo->callbacks.process_challenge_callback((OpenCDMSession*)mockSessionInfo->session, mockSessionInfo->userData, url, challenge, 1);
221 
222  // For DRM's which perform license acquistion outside the AampDRMSessionManager context(AampLicenseRequest::DRM_RETRIEVE)
223  // there wont be an opencdm_session_update call,hence trigger the keys_updated_callback as well within this callback
224  mockSessionInfo->callbacks.key_update_callback((OpenCDMSession*)mockSessionInfo->session, mockSessionInfo->userData, nullptr, 0);
225  mockSessionInfo->callbacks.keys_updated_callback((OpenCDMSession*)mockSessionInfo->session, mockSessionInfo->userData);
226  };
227 
228  MockOpenCdmSetCallbacks(callbacks, NULL);
229  }
230 
231  void setup()
232  {
233  MockAampReset();
234  MockCurlReset();
235  MockOpenCdmReset();
236  mAamp = new PrivateInstanceAAMP(gpGlobalConfig);
237  }
238 
239  void teardown()
240  {
241  if (sessionManager != nullptr)
242  {
243  sessionManager->clearSessionData();
244  SAFE_DELETE(sessionManager);
245  }
246 
247  if (NULL != mAamp)
248  {
249  delete mAamp;
250  mAamp = NULL;
251  }
252 
253  MockAampReset();
254  MockCurlReset();
255  MockOpenCdmReset();
256  }
257 };
258 
259 #ifndef USE_SECCLIENT
260 TEST(AampDrmSessionTests, TestVgdrmSessionDecrypt)
261 {
262  DrmInfo drmInfo;
263  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
264  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
265 
266  // The key used in the decrypt call should be picked up from the DRM helper
267  // (URI to key conversion is tested elsewhere)
268  std::vector<uint8_t> expectedKeyId;
269  drmHelper->getKey(expectedKeyId);
270  LONGS_EQUAL(16, expectedKeyId.size());
271 
272  setupChallengeCallbacksForExternalLicense();
273 
274  AampDrmSession *drmSession = createDrmSessionForHelper(drmHelper, "net.vgdrm");
275  STRCMP_EQUAL("net.vgdrm", drmSession->getKeySystem().c_str());
276 
277  const uint8_t testIv[] = {'T', 'E', 'S', 'T', 'I', 'V'};
278  const uint8_t payloadData[] = {'E', 'N', 'C', 'R', 'Y', 'P', 'T', 'E', 'D'};
279  const uint8_t decryptedData[] = {'C', 'L', 'E', 'A', 'R', 'D', 'A', 'T', 'A', 'O', 'U', 'T'};
280  uint8_t *pOpaqueData = NULL;
281 
282  // The actual data transfer happens in shared memory for VGDRM,
283  // so data in/out is just expected to contain the size of the data
284  uint32_t expectedDataIn = sizeof(payloadData);
285  uint32_t expectedDataOut = sizeof(decryptedData);
286 
287  // Check the call to decrypt on the DrmSession leads to a opencdm_session_decrypt call
288  // with the correct parameters (IV & payload provided here and key taken from the helper)
289  mock("OpenCDM").expectOneCall("opencdm_session_decrypt")
290  .withPointerParameter("session", mOcdmSession)
291  .withMemoryBufferParameter("encrypted", (unsigned char*)&expectedDataIn, sizeof(expectedDataIn))
292  .withOutputParameterReturning("encrypted", &expectedDataOut, sizeof(expectedDataOut))
293  .withIntParameter("encryptedLength", sizeof(expectedDataIn))
294  .withPointerParameter("iv", (void*)testIv)
295  .withIntParameter("ivLength", sizeof(testIv))
296  .withMemoryBufferParameter("keyId", (unsigned char*)&expectedKeyId[0], expectedKeyId.size())
297  .withIntParameter("keyIdLength", expectedKeyId.size())
298  .andReturnValue(0);
299 
300  LONGS_EQUAL(0, drmSession->decrypt(testIv, sizeof(testIv), payloadData, sizeof(payloadData), &pOpaqueData));
301  mock().checkExpectations();
302 }
303 
304 TEST(AampDrmSessionTests, TestDecryptFromHlsOcdmBridgeNoKey)
305 {
306  DrmInfo drmInfo;
307  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
308  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
309 
310  setupChallengeCallbacksForExternalLicense();
311 
312  shared_ptr<AampHlsOcdmBridge> hlsOcdmBridge = createBridgeForHelper(drmHelper);
313 
314  uint8_t payloadData[] = {'E', 'N', 'C', 'R', 'Y', 'P', 'T', 'E', 'D'};
315 
316  // Key not ready, (SetDecryptInfo not called on bridge)
317  // so opencdm_session_decrypt should not be called and an error status should be returned
318  mock("OpenCDM").expectNoCall("opencdm_session_decrypt");
319  LONGS_EQUAL(eDRM_ERROR, hlsOcdmBridge->Decrypt(PROFILE_BUCKET_DECRYPT_VIDEO, payloadData, sizeof(payloadData)));
320  mock().checkExpectations();
321 }
322 
323 TEST(AampDrmSessionTests, TestDecryptFromHlsOcdmBridge)
324 {
325  DrmInfo drmInfo;
326  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
327  std::string testIv = "TESTIV0123456789";
328  drmInfo.iv = (unsigned char*)testIv.c_str();
329 
330  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
331  std::vector<uint8_t> expectedKeyId;
332  drmHelper->getKey(expectedKeyId);
333  LONGS_EQUAL(16, expectedKeyId.size());
334 
335  setupChallengeCallbacksForExternalLicense();
336 
337  shared_ptr<AampHlsOcdmBridge> hlsOcdmBridge = createBridgeForHelper(drmHelper);
338 
339  uint8_t payloadData[] = {'E', 'N', 'C', 'R', 'Y', 'P', 'T', 'E', 'D'};
340  const uint8_t decryptedData[] = {'C', 'L', 'E', 'A', 'R', 'D', 'A', 'T', 'A', 'O', 'U', 'T'};
341 
342  hlsOcdmBridge->SetDecryptInfo(mAamp, &drmInfo);
343 
344  // The actual data transfer happens in shared memory for VGDRM,
345  // so data in/out is just expected to contain the size of the data
346  uint32_t expectedDataIn = sizeof(payloadData);
347  uint32_t expectedDataOut = sizeof(decryptedData);
348 
349  // Check the call to decrypt on the HlsOcdmBridge leads to a opencdm_session_decrypt call
350  // with the correct parameters (IV from SetDecryptInfo, payload provided here and key taken from the helper)
351  mock("OpenCDM").expectOneCall("opencdm_session_decrypt")
352  .withPointerParameter("session", mOcdmSession)
353  .withMemoryBufferParameter("encrypted", (unsigned char*)&expectedDataIn, sizeof(expectedDataIn))
354  .withOutputParameterReturning("encrypted", &expectedDataOut, sizeof(expectedDataOut))
355  .withIntParameter("encryptedLength", sizeof(expectedDataIn))
356  .withPointerParameter("iv", (void*)drmInfo.iv)
357  .withIntParameter("ivLength", testIv.size())
358  .withMemoryBufferParameter("keyId", (unsigned char*)&expectedKeyId[0], expectedKeyId.size())
359  .withIntParameter("keyIdLength", expectedKeyId.size())
360  .andReturnValue(0);
361 
362  LONGS_EQUAL(eDRM_SUCCESS, hlsOcdmBridge->Decrypt(PROFILE_BUCKET_DECRYPT_VIDEO, payloadData, sizeof(payloadData)));
363  mock().checkExpectations();
364 }
365 
366 TEST(AampDrmSessionTests, TestClearKeyLicenseAcquisition)
367 {
368  // Setup Curl and OpenCDM mocks. We expect that curl_easy_perform will be called to fetch
369  // the key and that OpenCDM will be called to construct the session and handle the fetched key.
370  std::string testKeyData = "TESTKEYDATA";
371  setupCurlPerformResponse(testKeyData);
372  setupChallengeCallbacks();
373 
374  // Begin test
375  DrmInfo drmInfo;
376  drmInfo.method = eMETHOD_AES_128;
377  drmInfo.manifestURL = "http://example.com/assets/test.m3u8";
378  drmInfo.keyURI = "file.key";
379  std::shared_ptr<AampClearKeyHelper> drmHelper = std::make_shared<AampClearKeyHelper>(drmInfo, &mLogging);
380 
381  // We expect the key data to be transformed by the helper before being passed to opencdm_session_update.
382  // Thus we call the helper ourselves here (with the data our mock Curl will return) so we know what to expect.
383  // Note: testing of the transformation is done in the helper tests, here we just want to ensure that whatever
384  // the helper returns is what OpenCDM gets.
385  const shared_ptr<DrmData> expectedDrmData = make_shared<DrmData>((unsigned char *)testKeyData.c_str(), testKeyData.size());
386  drmHelper->transformLicenseResponse(expectedDrmData);
387 
388  mock("OpenCDM").expectOneCall("opencdm_session_update")
389  .withMemoryBufferParameter("keyMessage", (const unsigned char*) expectedDrmData->getData().c_str(), (size_t)expectedDrmData->getDataLength())
390  .withIntParameter("keyLength", expectedDrmData->getDataLength())
391  .andReturnValue(0);
392 
393  AampDrmSession *drmSession = createDrmSessionForHelper(drmHelper, "org.w3.clearkey");
394  STRCMP_EQUAL("org.w3.clearkey", drmSession->getKeySystem().c_str());
395 
396  const MockCurlOpts *curlOpts = MockCurlGetOpts();
397  // Key URL should been constructed based on manifestURL and keyURI from the DrmInfo
398  STRCMP_EQUAL("http://example.com/assets/file.key", curlOpts->url);
399  // POST is used
400  LONGS_EQUAL(0L, curlOpts->httpGet);
401 }
402 
403 TEST(AampDrmSessionTests, TestOcdmCreateSystemFailure)
404 {
405  DrmInfo drmInfo;
406  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
407  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
408  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
409 
410  // Causing opencdm_create_system to fail - we should not go on to call opencdm_construct_session
411  mOcdmSystem = nullptr;
412  mock("OpenCDM").expectOneCall("opencdm_create_system")
413  .withParameter("keySystem", "net.vgdrm")
414  .andReturnValue(mOcdmSystem);
415  mock("OpenCDM").expectNoCall("opencdm_construct_session");
416  AampDrmSession *drmSession = getSessionManager()->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
417  mock().checkExpectations();
418 }
419 
420 TEST(AampDrmSessionTests, TestOcdmConstructSessionFailure)
421 {
422  DrmInfo drmInfo;
423  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
424  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
425  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
426 
427  AampDRMSessionManager *sessionManager = getSessionManager();
428 
429  mock("OpenCDM").expectOneCall("opencdm_create_system")
430  .withParameter("keySystem", "net.vgdrm")
431  .andReturnValue(mOcdmSystem);
432 
433  // Causing opencdm_construct_session to fail - createDrmSession should return a NULL session
434  mOcdmSession = nullptr;
435  mock("OpenCDM").expectOneCall("opencdm_construct_session")
436  .withOutputParameterReturning("session", &mOcdmSession, sizeof(mOcdmSession))
437  .andReturnValue((int)ERROR_KEYSYSTEM_NOT_SUPPORTED);
438  AampDrmSession *drmSession = sessionManager->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
439  mock().checkExpectations();
440  CHECK_TEXT(drmSession == NULL, "Session creation unexpectedly succeeded");
441 }
442 
443 TEST(AampDrmSessionTests, TestMultipleSessionsDifferentKey)
444 {
445  std::string testKeyData = "TESTKEYDATA";
446  setupCurlPerformResponse(testKeyData);
447  setupChallengeCallbacks();
448 
449  DrmInfo drmInfo;
450  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
451  std::shared_ptr<AampVgdrmHelper> drmHelper1 = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
452 
453  const shared_ptr<DrmData> expectedDrmData = make_shared<DrmData>((unsigned char *)testKeyData.c_str(), testKeyData.size());
454  drmHelper1->transformLicenseResponse(expectedDrmData);
455 
456  setupChallengeCallbacksForExternalLicense();
457 
458  // 1st time around - expecting standard session creation
459  AampDrmSession *drmSession1 = createDrmSessionForHelper(drmHelper1, "net.vgdrm");
460  STRCMP_EQUAL("net.vgdrm", drmSession1->getKeySystem().c_str());
461 
462  // 2nd time around - expecting another new session to be created
463  drmInfo.keyURI = "81701500000811367b131dd025ab0a7bd8d20c1314151600";
464  std::shared_ptr<AampVgdrmHelper> drmHelper2 = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
465  AampDrmSession *drmSession2 = createDrmSessionForHelper(drmHelper2, "net.vgdrm");
466  STRCMP_EQUAL("net.vgdrm", drmSession2->getKeySystem().c_str());
467  CHECK_FALSE(drmSession1 == drmSession2);
468 }
469 
470 TEST(AampDrmSessionTests, TestMultipleSessionsSameKey)
471 {
472  std::string testKeyData = "TESTKEYDATA";
473  setupCurlPerformResponse(testKeyData);
474  setupChallengeCallbacks();
475 
476  DrmInfo drmInfo;
477  drmInfo.method = eMETHOD_AES_128;
478  drmInfo.manifestURL = "http://example.com/assets/test.m3u8";
479  drmInfo.keyURI = "file.key";
480  std::shared_ptr<AampClearKeyHelper> drmHelper = std::make_shared<AampClearKeyHelper>(drmInfo, &mLogging);
481 
482  const shared_ptr<DrmData> expectedDrmData = make_shared<DrmData>((unsigned char *)testKeyData.c_str(), testKeyData.size());
483  drmHelper->transformLicenseResponse(expectedDrmData);
484 
485  // Only 1 session update called expected, since a single session should be shared
486  mock("OpenCDM").expectOneCall("opencdm_session_update")
487  .withMemoryBufferParameter("keyMessage", (const unsigned char *)expectedDrmData->getData().c_str(), (size_t)expectedDrmData->getDataLength())
488  .withIntParameter("keyLength", expectedDrmData->getDataLength())
489  .andReturnValue(0);
490 
491  // 1st time around - expecting standard session creation
492  AampDrmSession *drmSession1 = createDrmSessionForHelper(drmHelper, "org.w3.clearkey");
493  STRCMP_EQUAL("org.w3.clearkey", drmSession1->getKeySystem().c_str());
494 
495  // 2nd time around - expecting the existing session will be shared, so no OCDM session created
496  AampDRMSessionManager *sessionManager = getSessionManager();
497  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
498  mock("OpenCDM").expectNoCall("opencdm_create_system");
499  mock("OpenCDM").expectNoCall("opencdm_construct_session");
500  AampDrmSession *drmSession2 = sessionManager->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
501  CHECK_EQUAL(drmSession1, drmSession2);
502 
503  // Clear out the sessions. Now a new OCDM session is expected again
504  sessionManager->clearSessionData();
505 
506  mock("OpenCDM").expectOneCall("opencdm_session_update")
507  .withMemoryBufferParameter("keyMessage", (const unsigned char *)expectedDrmData->getData().c_str(), (size_t)expectedDrmData->getDataLength())
508  .withIntParameter("keyLength", expectedDrmData->getDataLength())
509  .andReturnValue(0);
510 
511  AampDrmSession *drmSession3 = createDrmSessionForHelper(drmHelper, "org.w3.clearkey");
512  STRCMP_EQUAL("org.w3.clearkey", drmSession3->getKeySystem().c_str());
513 }
514 
515 TEST(AampDrmSessionTests, TestDashPlayReadySession)
516 {
517  string prLicenseServerURL = "http://licenseserver.example/license";
518  const std::string testKeyData = "TESTKEYDATA";
519  const std::string testChallengeData = "PLAYREADY_CHALLENGE_DATA";
520 
521  // Setting a PlayReady license server URL in the global config. This
522  // should get used to request the license
523  gpGlobalConfig->SetConfigValue<std::string>(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, prLicenseServerURL);
524 
525  // Setting up Curl callbacks for the session token and license requests
526  setupCurlPerformResponses({
527  {mSessionTokenUrl, mSessionTokenResponse},
528  {prLicenseServerURL, testKeyData}
529  });
530 
531  setupChallengeCallbacks(MockChallengeData("playready.example", testChallengeData));
532 
533  // PSSH string which will get passed to the helper for parsing, so needs to be in valid format
534  const std::string psshStr =
535  "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\">"
536  "<DATA>"
537  "<KID>16bytebase64enckeydata==</KID>"
538  "</DATA>"
539  "</WRMHEADER>";
540 
541  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
542  AampDrmSession *drmSession = createDashDrmSession(testKeyData, psshStr, event);
543  STRCMP_EQUAL("com.microsoft.playready", drmSession->getKeySystem().c_str());
544 
545  const TestCurlResponse *licenseResponse = getCurlPerformResponse(prLicenseServerURL);
546  CHECK_EQUAL(1, licenseResponse->callCount);
547 
548  // Check the post data set on Curl, this should be the challenge data returned by OCDM
549  CHECK_EQUAL(testChallengeData.size(), licenseResponse->opts.postFieldSize);
550  CHECK_EQUAL(testChallengeData, licenseResponse->opts.postFields);
551 }
552 
553 TEST(AampDrmSessionTests, TestDashPlayReadySessionNoCkmPolicy)
554 {
555  string prLicenseServerURL = "http://licenseserver.example/license";
556  std::string testKeyData = "TESTKEYDATA";
557 
558  // Setting a PlayReady license server URL in the global config. This
559  // should get used to request the license
560  gpGlobalConfig->SetConfigValue<std::string>(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, prLicenseServerURL);
561 
562  // Setting up Curl callbacks for the session token and license requests
563  setupCurlPerformResponses({
564  {mSessionTokenUrl, mSessionTokenResponse},
565  {prLicenseServerURL, testKeyData}
566  });
567 
568  setupChallengeCallbacks();
569 
570  // PSSH string which will get passed to the helper for parsing, so needs to be in valid format
571  const std::string psshStr =
572  "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\">"
573  "<DATA>"
574  "<KID>16bytebase64enckeydata==</KID>"
575  "</DATA>"
576  "</WRMHEADER>";
577 
578  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
579  AampDrmSession *drmSession = createDashDrmSession(testKeyData, psshStr, event);
580  CHECK_TEXT(drmSession != NULL, "Session creation failed");
581  STRCMP_EQUAL("com.microsoft.playready", drmSession->getKeySystem().c_str());
582 
583  const MockCurlOpts *curlOpts = MockCurlGetOpts();
584 
585  // Check license URL from the global config was used
586  std::string url;
588  STRCMP_EQUAL(url.c_str(), curlOpts->url);
589 
590  // Check the post data set on Curl. Since we didn't pass any metadata (ckm:policy) in the init data,
591  // we expect the challenge data returned by OCDM to just be used as-is
592  CHECK_TRUE(curlOpts->postFieldSize > 0);
593  STRNCMP_EQUAL("OCDM_CHALLENGE_DATA", curlOpts->postFields, curlOpts->postFieldSize);
594 }
595 
596 TEST(AampDrmSessionTests, TestSessionBadChallenge)
597 {
598  DrmInfo drmInfo;
599  drmInfo.method = eMETHOD_AES_128;
600  drmInfo.manifestURL = "http://example.com/assets/test.m3u8";
601  drmInfo.keyURI = "file.key";
602  std::shared_ptr<AampClearKeyHelper> drmHelper = std::make_shared<AampClearKeyHelper>(drmInfo, &mLogging);
603 
604  // Cause OpenCDM to return an empty challenge. This should cause an error
605  setupChallengeCallbacks(MockChallengeData("", ""));
606 
607  mock("OpenCDM").expectOneCall("opencdm_create_system")
608  .withParameter("keySystem", "org.w3.clearkey")
609  .andReturnValue(mOcdmSystem);
610 
611  mock("OpenCDM").expectOneCall("opencdm_construct_session")
612  .withOutputParameterReturning("session", &mOcdmSession, sizeof(mOcdmSession))
613  .andReturnValue(0);
614 
615  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
616  AampDrmSession *drmSession = getSessionManager()->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
617  POINTERS_EQUAL(NULL, drmSession);
618  CHECK_EQUAL(AAMP_TUNE_DRM_CHALLENGE_FAILED, event->getFailure());
619 }
620 
621 TEST(AampDrmSessionTests, TestSessionBadLicenseResponse)
622 {
623  DrmInfo drmInfo;
624  drmInfo.method = eMETHOD_AES_128;
625  drmInfo.manifestURL = "http://example.com/assets/test.m3u8";
626  drmInfo.keyURI = "file.key";
627  std::shared_ptr<AampClearKeyHelper> drmHelper = std::make_shared<AampClearKeyHelper>(drmInfo, &mLogging);
628 
629  // Make curl return empty data for the key. This should cause an error
630  setupCurlPerformResponses({
631  {"http://example.com/assets/file.key", ""}
632  });
633 
634  mock("OpenCDM").expectOneCall("opencdm_create_system")
635  .withParameter("keySystem", "org.w3.clearkey")
636  .andReturnValue(mOcdmSystem);
637 
638  mock("OpenCDM").expectOneCall("opencdm_construct_session")
639  .withOutputParameterReturning("session", &mOcdmSession, sizeof(mOcdmSession))
640  .andReturnValue(0);
641  setupChallengeCallbacks();
642 
643  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
644  AampDrmSession *drmSession = getSessionManager()->createDrmSession(drmHelper, event, mAamp, eMEDIATYPE_VIDEO);
645  POINTERS_EQUAL(NULL, drmSession);
646  CHECK_EQUAL(AAMP_TUNE_LICENCE_REQUEST_FAILED, event->getFailure());
647 }
648 
649 TEST(AampDrmSessionTests, TestDashSessionBadPssh)
650 {
651  std::string testKeyData = "TESTKEYDATA";
652 
653  // Pass a bad PSSH string. This should cause an error
654  const std::string psshStr = "bad data with no KID";
655 
656  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
657  AampDrmSession *drmSession = getSessionManager()->createDrmSession("9a04f079-9840-4286-ab92-e65be0885f95", eMEDIAFORMAT_DASH,
658  (const unsigned char*)psshStr.c_str(), psshStr.length(), eMEDIATYPE_VIDEO, mAamp, event);
659  POINTERS_EQUAL(NULL, drmSession);
660  CHECK_EQUAL(AAMP_TUNE_CORRUPT_DRM_METADATA, event->getFailure());
661 }
662 
663 TEST(AampDrmSessionTests, TestDrmMessageCallback)
664 {
665  DrmInfo drmInfo;
666  drmInfo.keyURI = "81701500000810367b131dd025ab0a7bd8d20c1314151600";
667  std::shared_ptr<AampVgdrmHelper> drmHelper = std::make_shared<AampVgdrmHelper>(drmInfo, &mLogging);
668  setupChallengeCallbacksForExternalLicense();
669  AampDrmSession *drmSession = createDrmSessionForHelper(drmHelper, "net.vgdrm");
670  STRCMP_EQUAL("net.vgdrm", drmSession->getKeySystem().c_str());
671 
672  struct DrmMessageTestData
673  {
674  std::string prefix;
675  std::string payload;
676  bool callbackExpected;
677  };
678 
679  const std::vector<DrmMessageTestData> testData = {
680  // 'individualization-request' or '3' should be accepted
681  {"individualization-request:Type:", "{\"watermark\":{\"display\":false}}", true},
682  {"3:Type:", "{\"watermark\":{\"display\":false}}", true},
683  {"individualization-request:Type:", "payload is opaque so could be anything", true},
684 
685  // Wrong/no type - shouldn't trigger callback, shouldn't cause a problem
686  {"4:Type:", "{\"watermark\":{\"display\":false}}", false},
687  {"", "just a random string", false},
688  };
689 
690  for (const auto& testCase : testData)
691  {
692  const std::string challengeData = testCase.prefix + testCase.payload;
693 
694  if (testCase.callbackExpected)
695  {
696  mock("Aamp").expectOneCall("individualization").withStringParameter("payload", testCase.payload.c_str());
697  }
698  else
699  {
700  mock("Aamp").expectNoCall("individualization");
701  }
702 
703  MockOpenCdmSessionInfo *cdmInfo = MockOpenCdmGetSessionInfo();
704  cdmInfo->callbacks.process_challenge_callback((OpenCDMSession*)cdmInfo->session,
705  cdmInfo->userData,
706  "", // Empty URL, not needed for this callback
707  (const uint8_t*)challengeData.data(),
708  (const uint16_t)challengeData.size());
709  }
710 }
711 
712 #else /* USE_SECCLIENT */
713 TEST(AampDrmSessionTests, TestDashPlayReadySessionSecClient)
714 {
715  std::string prLicenseServerURL = "http://licenseserver.example/license";
716  std::string expectedServiceHostUrl = "licenseserver.example";
717 
718  // Setting a PlayReady license server URL in the global config. This
719  // should get used to request the license
720  gpGlobalConfig->SetConfigValue<std::string>(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, prLicenseServerURL);
721 
722  // Setting up Curl repsonse for the session token
723  setupCurlPerformResponses({
724  {mSessionTokenUrl, mSessionTokenResponse}
725  });
726 
727  setupChallengeCallbacks();
728 
729  // PSSH string which will get passed to the helper for parsing, so needs to be in valid format
730  const std::string psshStr =
731  "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\">"
732  "<DATA>"
733  "<KID>16bytebase64enckeydata==</KID>"
734  "<ckm:policy xmlns:ckm=\"urn:ccp:ckm\">policy</ckm:policy>"
735  "</DATA>"
736  "</WRMHEADER>";
737 
738  std::string expectedKeyData = "TESTSECKEYDATA";
739  size_t respLength = expectedKeyData.length();
740  const char *respPtr = expectedKeyData.c_str();
741 
742  // The license should be acquired using the secure client, rather than curl
743  mock("SecClient").expectOneCall("AcquireLicense")
744  .withStringParameter("serviceHostUrl", expectedServiceHostUrl.c_str())
745  .withOutputParameterReturning("licenseResponse", &respPtr, sizeof(respPtr))
746  .withOutputParameterReturning("licenseResponseLength", &respLength, sizeof(respLength));
747 
748  DrmMetaDataEventPtr event = createDrmMetaDataEvent();
749  AampDrmSession *drmSession = createDashDrmSession(expectedKeyData, psshStr, event);
750  CHECK_TEXT(drmSession != NULL, "Session creation failed");
751  STRCMP_EQUAL("com.microsoft.playready", drmSession->getKeySystem().c_str());
752 }
753 #endif /* USE_SECCLIENT */
AampDRMSessionManager.h
Header file for DRM session manager.
TestCurlResponse
Definition: drmSessionTest.cpp:24
DrmInfo::keyURI
std::string keyURI
Definition: AampDrmInfo.h:87
AampConfig::SetConfigValue
void SetConfigValue(ConfigPriority owner, AAMPConfigSettings cfg, const T &value)
SetConfigValue - Set function to set bool/int/long data type configuration.
Definition: AampConfig.cpp:830
AampDRMSessionManager::createDrmSession
AampDrmSession * createDrmSession(const char *systemId, MediaFormat mediaFormat, const unsigned char *initDataPtr, uint16_t dataLength, MediaType streamType, PrivateInstanceAAMP *aamp, DrmMetaDataEventPtr e, const unsigned char *contentMetadata=nullptr, bool isPrimarySession=false)
Creates and/or returns the DRM session corresponding to keyId (Present in initDataPtr) AampDRMSession...
Definition: AampDRMSessionManager.cpp:875
eMEDIATYPE_VIDEO
@ eMEDIATYPE_VIDEO
Definition: AampMediaType.h:39
DrmInfo::manifestURL
std::string manifestURL
Definition: AampDrmInfo.h:86
AAMP_TUNE_DRM_CHALLENGE_FAILED
@ AAMP_TUNE_DRM_CHALLENGE_FAILED
Definition: AampEvent.h:125
DrmInfo::iv
unsigned char * iv
Definition: AampDrmInfo.h:84
gpGlobalConfig
AampConfig * gpGlobalConfig
Global configuration.
Definition: main_aamp.cpp:48
PROFILE_BUCKET_DECRYPT_VIDEO
@ PROFILE_BUCKET_DECRYPT_VIDEO
Definition: AampProfiler.h:62
AampDRMSessionManager
Controller for managing DRM sessions.
Definition: AampDRMSessionManager.h:145
eMEDIAFORMAT_DASH
@ eMEDIAFORMAT_DASH
Definition: AampDrmMediaFormat.h:35
AAMP_TUNE_FAILURE_UNKNOWN
@ AAMP_TUNE_FAILURE_UNKNOWN
Definition: AampEvent.h:147
AampHlsOcdmBridge.h
Handles OCDM bridge to validate DRM License.
AampLogManager
AampLogManager Class.
Definition: AampLogManager.h:150
AampDrmSession::decrypt
virtual int decrypt(GstBuffer *keyIDBuffer, GstBuffer *ivBuffer, GstBuffer *buffer, unsigned subSampleCount, GstBuffer *subSamplesBuffer, GstCaps *caps=NULL)
Function to decrypt GStreamer stream buffer.
Definition: AampDrmSession.cpp:55
_MockCurlOpts
Definition: curlMocks.h:12
AampConfig::GetConfigValue
bool GetConfigValue(AAMPConfigSettings cfg, std::string &value)
GetConfigValue - Gets configuration for string data type.
Definition: AampConfig.cpp:748
eDRM_SUCCESS
@ eDRM_SUCCESS
Definition: HlsDrmBase.h:37
DrmInfo
DRM information required to decrypt.
Definition: AampDrmInfo.h:47
_MockSessionInfo
Definition: opencdmMocks.h:6
_MockOpenCdmCallbacks
Definition: opencdmMocks.h:16
aampdrmsessionfactory.h
Header file for AampDrmSessionFactory.
AampDrmSession
Base class for DRM sessions.
Definition: AampDrmSession.h:69
eMETHOD_AES_128
@ eMETHOD_AES_128
Definition: AampDrmInfo.h:39
AAMP_TUNE_LICENCE_REQUEST_FAILED
@ AAMP_TUNE_LICENCE_REQUEST_FAILED
Definition: AampEvent.h:127
AampDRMSessionManager::clearSessionData
void clearSessionData()
Clean up the memory used by session variables.
Definition: AampDRMSessionManager.cpp:155
AAMP_TUNE_CORRUPT_DRM_METADATA
@ AAMP_TUNE_CORRUPT_DRM_METADATA
Definition: AampEvent.h:134
AampDrmSession::getKeySystem
string getKeySystem()
Get the DRM System, ie, UUID for PlayReady WideVine etc..
Definition: AampDrmSession.cpp:47
PrivateInstanceAAMP
Class representing the AAMP player's private instance, which is not exposed to outside world.
Definition: priv_aamp.h:640
MockChallengeData
Definition: drmSessionTest.cpp:47
DrmInfo::method
DrmMethod method
Definition: AampDrmInfo.h:79
eDRM_ERROR
@ eDRM_ERROR
Definition: HlsDrmBase.h:38
eAAMPConfig_PRLicenseServerUrl
@ eAAMPConfig_PRLicenseServerUrl
Definition: AampConfig.h:299