Search code examples
c++widevine

Widevine Session Update endless Loop


I am using libwidevinecdm.so from chrome to handle DRM protected data. I am currently successfully setting the widevine server certificate I get from the license server. I can also create a session with the pssh box of the media im trying to decode. So far everything is successful (all promises resolve fine).

(session is created like this: _cdm->CreateSessionAndGenerateRequest(promise_id, cdm::SessionType::kTemporary, cdm::InitDataType::kCenc, pssh_box.data(), static_cast<uint32_t>(pssh_box.size()));)

I am then getting a session message of type kLicenseRequest which I am forwarding to the respective license server. The license server responds with a valid response and the same amount of data as I can see in the browser when using Chrome. I am then passing this to my session like this:

        _cdm->UpdateSession(promise_id, session_id.data(), static_cast<uint32_t>(session_id.size()),
                            license_response.data(), static_cast<uint32_t>(license_response.size()));

The problem now is that this promise never resolves. It keeps posting the kLicenseRequest message over and over again to my session without ever returning. Does this mean my response is wrong? Or is this something else?

Br Yanick


Solution

  • The issue is caused by the fact, that everything in CreateSessionAndGenerateRequest is done synchronous - that means by the time CreateSessionAndGenerateRequest returns your promise will always be resolved.

    The CDM will emit the kLicenseRequest inside CreateSessionAndGenerateRequest and it doesn't do so in a "fire & forget" fashion, but the function waits there until you have returned from the cdm::Host_10::OnSessionMessage. Since my implementation of OnSessionMessage was creating a synchronous HTTP Request to the license server before - also synchronously - calling the UpdateSession the entire chain ended up to be blocking.

    So ultimately I was calling UpdateSession while still being inside CreateSessionAndGenerateRequest and I assume the CDM cannot handle this and reacts by creating a new session with the given ID and generating a request again, which of course triggered another UpdateSession and so on.

    Ultimately the simplest way to break the cycle was to make something asynchronous. I decided to launch a separate thread when receiving kLicenseRequest, wait for a few milliseconds to make sure that CreateSessionAndGenerateRequest has time to finish (not sure if that is really required) and then issue the request to the license server.

    The only change I had to do was adding the surrounding std::thread:

        void WidevineSession::forward_license_request(const std::vector<uint8_t> &data) {
            std::thread{
                    [=]() {
                        std::this_thread::sleep_for(std::chrono::milliseconds{100});
    
                        net::HttpRequest request{"POST", _license_server_url};
                        request.add_header("Authorization", fmt::format("Bearer {}", _access_token))
                                .byte_body(data);
    
                        const auto response = _client.execute(request);
                        if (response.status_code() != 200) {
                            log->error("Widevine license request not accepted by license server: {} {} ({})", response.status_code(), response.status_text(), utils::bytes_to_utf8(response.body()));
                            throw std::runtime_error{"Error requesting widevine license"};
                        }
    
                        log->info("Successfully requested widevine license from license server");
                        _adapter->update_session(this, _session_id, response.body());
                    }
            }.detach();
        }