readium / readium-sdk

A C++ ePub renderer SDK
BSD 3-Clause "New" or "Revised" License
385 stars 164 forks source link

Integration Issues with new and old ContentModule/Filter implementations #274

Closed rkwright closed 7 years ago

rkwright commented 7 years ago

This issue is a Discussion

Related issue(s) and/or pull request(s)

None

Expected Behaviour

N/A

Observed behaviour

Sony team is trying to integrate URMS with Readium but the design/implementation of ContentModule/Filters has changed and there are questions about the proper integration approach.

Sony/URMS team wrote: We have some questions after checking “feature/lcp” branch which seems to contain a lot of changes.

  1. Our solution does not use ContentModule approach, just one ContentFilter which is decrypting appropriate resources. Is this way still valid/supported or do we need to implement new ContentModule for this purpose ? The code snippet below contains some comments about non-encrypted loading(RED comment below) when no ContentModule is available. However it seems to be working with encrypted resources when suitable ContentFilter is registered. using commit [24e004a] in this branch.

    ContainerPtr Container::OpenContainer(const string &path) {

        ContainerPtr container = ContentModuleManager::Instance()->LoadContentAtPath(path);
    
        // 1: everything went well, we've got an encrypted EPUB handled by a ContentModule
        if (bool(container)) {
        return std::move(container);
    }
    
    // 2: no ContentModule was suitable, let's try non-encrypted loading // <— HERE
    container = OpenContainerForContentModule(path);
    if (bool(container)) {
        return std::move(container);
    } else {
        return nullptr;
    }
    
    // 3: there's always the option of a raised exception, which the caller captures to 
    degrade gracefully

    }

  2. Some of our customers will probably try to use more DRM systems in one Application. For example, would be possible to make working solution with LCP and standalone Marlin ContentFilter or is needed to implement one more ContentModule for Marlin and register both in ContentModuleManager? Basically what is suggested way to implement multi-DRM support using one Readium-SDK instance.

Test file(s)

N/A

Product

SDK

Additional information

None yet

danielweck commented 7 years ago

At this point in time, the feature/lcp branch of the readium-sdk GitHub repository ( https://github.com/readium/readium-sdk/tree/feature/lcp ) contains many changes that do not directly relate to LCP / DRM functionality, but that were nonetheless seen as important / necessary in order to efficiently debug the LCP "client lib" implementation, and to work around some compiler toolchain limitations. For example (just from the top of my head), we removed dead code / unused functionality, we disabled thread usage, and we eliminated the levels of indirections that managed smart pointer allocations (templated C++ code).

I plan to break down the code diff into separate manageable pieces, to narrow down the DRM scope (essentially: "Content Filter" and "Content Module"). I appreciate that right now, a Pull Request could not realistically be created based on the existing bulk code changes.

The key API "entry-points" are RegisterFilter():
https://github.com/readium/readium-sdk/blob/feature/lcp/ePub3/ePub/filter_manager_impl.cpp#L37
...and RegisterContentModule() (the Content Module would in turn invokes RegisterFilter()):
https://github.com/readium/readium-sdk/blob/feature/lcp/ePub3/ePub/content_module_manager.cpp#L63

Although it is recommended to use the "Content Module" facility to bootstrap multiple DRM schemes, it is technically possible to directly use the lower-level "Content Filter" construct. However, an implementor would pretty much have to write from scratch the equivalent of Readium's "Content Module" in order to manage several "Content Filters".

The ContentModuleManager::LoadContentAtPath() function "dispatches" the actual resource decryption requests to actual compatible Content Module / Filters:
https://github.com/readium/readium-sdk/blob/feature/lcp/ePub3/ePub/content_module_manager.cpp#L141

    ContainerPtr
    ContentModuleManager::LoadContentAtPath(const string& path)
    {
        if (_known_modules.empty())
        {
            return nullptr;
        }

        for (auto& item : _known_modules)
        {
            std::shared_ptr<ContentModule> modulePtr = item.second;

            ContainerPtr container = modulePtr->ProcessFile(path);

            if (bool(container)) {

                // Singleton FilterManagerImpl::RegisterFilter() uses .emplace() with unique keys,
                // so ContentFilters with the same name => only the first inserted one is preserved (subsequent insertions ignored).
                modulePtr->RegisterContentFilters();

                // Problem: container was loaded okay, but if Navigation Document was encrypted,
                // then because the ContentFilters were not registered yet:
                // the NavDoc XHTML silently failed to load (populate the internal ReadiumSDK data structures)
                // as the byte buffers were scrambled. So, we need to either reload the entire EPUB, or reload only the navdoc.
                std::shared_ptr<Package> package = container->DefaultPackage();
                package->Unpack_Finally(true);

                return std::move(container);
            }
        }

        return nullptr;
    }

Each concrete (registered) Content Module's ProcessFile() function call determines whether a particular DRM scheme handles a given EPUB publication. If no DRM module is capable of handling the loading EPUB, or if the EPUB is not encrypted, or if no Content Modules are registered at all, then OpenContainerForContentModule() is invoked to completely bypass the DRM pipeline, as shown in Container::OpenContainer():

https://github.com/readium/readium-sdk/blob/feature/lcp/ePub3/ePub/container.cpp#L169

    ContainerPtr Container::OpenContainer(const string &path) {

        ContainerPtr container = ContentModuleManager::Instance()->LoadContentAtPath(path);

        // 1: everything went well, we've got an encrypted EPUB handled by a ContentModule
        if (bool(container)) {
            return std::move(container);
        }

        // 2: no ContentModule was suitable, let's try non-encrypted loading
        container = OpenContainerForContentModule(path);
        if (bool(container)) {
            return std::move(container);
        } else {
            return nullptr;
        }

        // 3: there's always the option of a raised exception, which the caller captures to degrade gracefully
    }
rkwright commented 7 years ago

Now documented here. Closing