//----------------------------------------------------------------------------
//
// TSDuck - The MPEG Transport Stream Toolkit
// Copyright (c) 2005-2025, Thierry Lelegard
// BSD-2-Clause license, see LICENSE.txt file or https://tsduck.io/license
//
//----------------------------------------------------------------------------
//
//  Transport stream processor shared library:
//  Generic / sample / reference descrambler.
//  Can be used as a template for real conditional access systems.
//
//----------------------------------------------------------------------------

#include "tsAbstractDescrambler.h"
#include "tsPluginRepository.h"
#include "tsDuckProtocol.h"


//----------------------------------------------------------------------------
// Plugin definition
//----------------------------------------------------------------------------

namespace ts {
    class DescramblerPlugin: public AbstractDescrambler
    {
        TS_PLUGIN_CONSTRUCTORS(DescramblerPlugin);
    public:
        // Implementation of ProcessorPlugin interface.
        virtual bool getOptions() override;

    protected:
        // Implementation of AbstractDescrambler.
        virtual bool checkCADescriptor(CASID pmt_cas_id, const ByteBlock& priv) override;
        virtual bool checkECM(const Section& ecm) override;
        virtual bool decipherECM(const Section& ecm, CWData& cw_even, CWData& cw_odd) override;

    private:
        CASID          _cas_id = 0;
        duck::Protocol _protocol {};
    };
}

TS_REGISTER_PROCESSOR_PLUGIN(u"descrambler", ts::DescramblerPlugin);


//----------------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------------

ts::DescramblerPlugin::DescramblerPlugin(TSP* tsp_) :
    AbstractDescrambler(tsp_, u"Generic DVB descrambler")
{
    option(u"cas-id", 0, UINT16);
    help(u"cas-id",
         u"Specify the CA_system_id to filter when searching for ECM streams. Since "
         u"this descrambler is a demo tool using clear ECM's, it is unlikely that "
         u"other real ECM streams exist. So, by default, any ECM stream is used to "
         u"get the clear ECM's.\n\n"
         u"This plugin descrambles fixed PID's with fixed control words. As a demo, it can "
         u"also descramble services for which clear ECM's were generated using the utility "
         u"named tsecmg, a DVB SimulCrypt-compliant ECMG for test and demo.");
}


//----------------------------------------------------------------------------
// Plugin get options method.
//----------------------------------------------------------------------------

bool ts::DescramblerPlugin::getOptions()
{
    // Load plugin-specific command line arguments.
    _cas_id = intValue<uint16_t>(u"cas-id", 0);

    // The invoke superclass to actually start the descrambler.
    return AbstractDescrambler::getOptions();
}


//----------------------------------------------------------------------------
// Check a CA_descriptor from a PMT.
//----------------------------------------------------------------------------

bool ts::DescramblerPlugin::checkCADescriptor(CASID pmt_cas_id, const ByteBlock& priv)
{
    // In this demo descrambler, we accept all CAS id, unless one is specified.
    return _cas_id == 0 || pmt_cas_id == _cas_id;
}


//----------------------------------------------------------------------------
// Check if the descrambler may decipher an ECM.
//----------------------------------------------------------------------------

bool ts::DescramblerPlugin::checkECM(const Section& ecm)
{
    // In this demo descrambler, we do not add any further check.
    return true;
}


//----------------------------------------------------------------------------
// Decipher an ECM.
//----------------------------------------------------------------------------

bool ts::DescramblerPlugin::decipherECM(const Section& ecm, CWData& cw_even, CWData& cw_odd)
{
    // Clear returned ECM's.
    cw_even.cw.clear();
    cw_even.iv.clear();
    cw_odd.cw.clear();
    cw_odd.iv.clear();

    // The ECM content is the section payload.
    const uint8_t* const ecmData = ecm.payload();
    const size_t ecmSize = ecm.payloadSize();

    // Analyze ECM as a TLV message.
    tlv::MessageFactory mf(ecmData, ecmSize, _protocol);
    tlv::MessagePtr msg(mf.factory());

    // If this a valid ECM?
    std::shared_ptr<duck::ClearECM> clearECM(std::dynamic_pointer_cast<duck::ClearECM>(msg));
    if (clearECM == nullptr) {
        // Not a valid ECM as generated by tsecmg.
        const size_t dumpSize = std::min<size_t>(ecmSize, 16);
        error(u"received invalid ECM (%d bytes): %s%s", ecmSize, UString::Dump(ecmData, dumpSize, UString::SINGLE_LINE), dumpSize < ecmSize ? u" ..." : u"");
        return false;
    }
    else {
        cw_even.cw = clearECM->cw_even;
        cw_odd.cw = clearECM->cw_odd;
        verbose(u"ECM found, even CW: %s, odd CW: %s", UString::Dump(cw_even.cw, UString::COMPACT), UString::Dump(cw_odd.cw, UString::COMPACT));
        return true;
    }
}
