コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2018年7月15日 >>> 2018年7月6日
link もっと後

2018年7月15日

AArch64向けLinux開発環境の構築 その1

目次: GCC

AArch64(64ビットARM)向けのLinux開発環境を構築しました。超有名ツールの組み合わせだし、簡単だろうと思っていたのですが、意外とハマって1日掛かってしまったのでメモしておきます。使用するツールは下記の通りです。

  • クロスコンパイラ: crosstool-NG
  • カーネル: linux-next
  • ルートファイルシステム: buildroot
  • エミュレータ: qemu

クロスコンパイラ

クロスコンパイラを生成するためcrosstool-NG(GitHubへのリンク)を使います。crosstool-NGはARM向け以外にもクロスコンパイラやツールチェインの構築が簡単にできて便利です。

私の環境(Debian Testing)だとlibtool-binパッケージがインストールされていなくてハマったので、インストールしておくと良いかもしれません。

crosstool-NGコード取得、セットアップ
$ git clone https://github.com/crosstool-ng/crosstool-ng
$ cd crosstool-ng

$ ./bootstrap
INFO  :: *** Generating package version descriptions
INFO  :: Master packages: android-ndk autoconf automake avr-libc binutils cloog duma elf2flt expat gcc gdb gettext glibc-ports glibc gmp isl libelf libiconv libtool linux ltrace m4 make mingw-w64 mpc mpfr musl ncurses newlib strace uClibc zlib
INFO  :: Generating 'config/versions/android-ndk.in'
INFO  :: Generating 'config/versions/autoconf.in'
...(snip)...
INFO  :: Generating comp_libs.in (menu)
INFO  :: *** Gathering the list of data files to install
INFO  :: *** Running autoreconf
INFO  :: *** Done!

$ ./configure --enable-local
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
...(snip)...
config.status: creating config.h
config.status: config.h is unchanged
config.status: executing depfiles commands

$ make
/usr/bin/make  all-recursive
make[1]: Entering directory '/home/katsuhiro/share/projects/oss/crosstool-ng'
Making all in kconfig
make[2]: Entering directory '/home/katsuhiro/share/projects/oss/crosstool-ng/kconfig'
bison -y -l -b zconf -p zconf  -ozconf.c zconf.y
...(snip)...
/bin/mkdir -p docs && ( /bin/sed -e 's,[@]docdir[@],/usr/local/share/doc/crosstool-ng,g' -e 's,[@]pkgdatadir[@],/usr/local/share/crosstool-ng,g' -e 's,[@]pkglibexecdir[@],/usr/local/libexec/crosstool-ng,g' -e 's,[@]progname[@],'`echo ct-ng | sed 's,x,x,'`',g' | /bin/bash config.status --file=- ) < docs/ct-ng.1.in >docs/ct-ng.1-t && mv -f docs/ct-ng.1-t docs/ct-ng.1
make[2]: Leaving directory '/home/katsuhiro/share/projects/oss/crosstool-ng'
make[1]: Leaving directory '/home/katsuhiro/share/projects/oss/crosstool-ng'

カレントディレクトリにct-ngという名前のファイルが生成されます。通常はクロスコンパイラをインストールして使いますが、私はインストールしないで欲しい(適宜入れ替えたいから)ので、--enable-localオプションを付けています。

crosstool-NGビルド
$ ./ct-ng menuconfig
- Target options  --->
    Target Architecture (alpha)  --->
      armに変更する

    Bitness: (32-bit)  --->
      64-bitに変更する

- Operating System  --->
    Target OS (bare-metal)  --->
      linuxに変更する

- C compiler  --->
    C++ (NEW) を選択する

$ ./ct-ng build
[00:34] /

ビルド中は多少メッセージも出ますが、基本的に経過時間と棒がくるくる回るだけです。ログが見たい方は、ct-ngと同じディレクトリにあるbuild.logをtail -fなどで表示すると良いでしょう。

マシン性能によりますが、./ct-ng buildによるクロスコンパイラのビルドは1時間くらい掛かると思います。ビルドが終わると、ホームディレクトリにx-toolsというディレクトリが作られていると思います。AArch64用のクロスコンパイラ(gcc 8)をビルドした場合、x-tools以下は下記のようになっているはずです。

crosstool-NG生成物
$ cd ~/x-tools

$ ls
aarch64-unknown-linux-gnu

$ ls aarch64-unknown-linux-gnu
aarch64-unknown-linux-gnu  bin  build.log.bz2  include  lib  libexec  share

$ ls aarch64-unknown-linux-gnu/bin
aarch64-unknown-linux-gnu-addr2line     aarch64-unknown-linux-gnu-gcov-dump
aarch64-unknown-linux-gnu-ar            aarch64-unknown-linux-gnu-gcov-tool
aarch64-unknown-linux-gnu-as            aarch64-unknown-linux-gnu-gfortran
aarch64-unknown-linux-gnu-c++           aarch64-unknown-linux-gnu-gprof
aarch64-unknown-linux-gnu-c++filt       aarch64-unknown-linux-gnu-ld
aarch64-unknown-linux-gnu-cc            aarch64-unknown-linux-gnu-ld.bfd
aarch64-unknown-linux-gnu-cpp           aarch64-unknown-linux-gnu-ld.gold
aarch64-unknown-linux-gnu-ct-ng.config  aarch64-unknown-linux-gnu-ldd
aarch64-unknown-linux-gnu-dwp           aarch64-unknown-linux-gnu-nm
aarch64-unknown-linux-gnu-elfedit       aarch64-unknown-linux-gnu-objcopy
aarch64-unknown-linux-gnu-g++           aarch64-unknown-linux-gnu-objdump
aarch64-unknown-linux-gnu-gcc           aarch64-unknown-linux-gnu-populate
aarch64-unknown-linux-gnu-gcc-7.3.0     aarch64-unknown-linux-gnu-ranlib
aarch64-unknown-linux-gnu-gcc-ar        aarch64-unknown-linux-gnu-readelf
aarch64-unknown-linux-gnu-gcc-nm        aarch64-unknown-linux-gnu-size
aarch64-unknown-linux-gnu-gcc-ranlib    aarch64-unknown-linux-gnu-strings
aarch64-unknown-linux-gnu-gcov          aarch64-unknown-linux-gnu-strip

クロスコンパイラは~/x-tools/aarch64-unknown-linux-gnu/bin以下にあります。今後、このクロスコンパイラを使います。

カーネル

カーネルはLinuxの開発版linux-nextを使います(Gitリポジトリへのリンク)。StableカーネルやLTSカーネルも同じ手順で良いと思いますが、古いカーネルをビルドするときは、crosstool-NGでgcc 7かgcc 6を選択したほうが良いかもしれません(ビルド時に警告が出てくると邪魔なので…)。

linux-nextコード取得、セットアップ、ビルド
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
$ cd linux-next

$ export ARCH=arm64
$ export CROSS_COMPILE=~/x-tools/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-

$ make defconfig

$ make all
scripts/kconfig/conf  --syncconfig Kconfig
  WRAP    arch/arm64/include/generated/uapi/asm/ioctl.h
  WRAP    arch/arm64/include/generated/uapi/asm/errno.h
  WRAP    arch/arm64/include/generated/uapi/asm/ioctls.h
...(snip)...
  LD [M]  sound/soc/generic/snd-soc-simple-card-utils.ko
  LD [M]  sound/soc/generic/snd-soc-simple-card.ko
  LD [M]  sound/soc/sh/rcar/snd-soc-rcar.ko

マシン性能によりますが、カーネルのビルドは数十分くらい掛かると思います。AArch64用のカーネルをビルドした場合、arch/arm64/boot以下は下記のようになっているはずです。

linux-next生成物
$ cd arch/arm64/boot

$ ls
Image  Image.gz  Makefile  dts  install.sh

ビルドが終わると、arch/arm64/boot以下にImageという名前のファイルが作られていると思います。このイメージファイルを後で使います。

編集者:すずき(2023/09/24 11:42)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年7月8日

AndroidとMPEG2-TSその4

その1その2その3その4

もはや自分以外の誰得の内容なのか、わかりませんが、気にせず書きます。

その3にて「DescramblerImplを生成するのはMediaCasService::createDescrambler() のみ?のように見えます。」と書きました。この関数に至る経路がわかると、デスクランブラがどの暗号系を選択するのか?いつ選択するのか?などがわかるようになります。

で、今回はcreateDescrambler() が呼ばれる経路を見つけたのでメモします。しかしながらExtractorのsetMediaCas() を呼び出すのは誰なのか?まだ謎のままなので、いまいちスッキリしませんけども。

ExtractorのsetMediaCas()

//frameworks/av/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp

status_t MPEG2TSExtractor::setMediaCas(const HInterfaceToken &casToken) {
    HalToken halToken;
    halToken.setToExternal((uint8_t*)casToken.data(), casToken.size());
    sp<ICas> cas = ICas::castFrom(retrieveHalInterface(halToken));
    ALOGD("setMediaCas: %p", cas.get());

    status_t err = mParser->setMediaCas(cas); //★★★★mParser
    if (err == OK) {
        ALOGI("All tracks now have descramblers");
        init();
    }
    return err;
}

ここで出てくるmParserの型はATSParserでしたので、ATSParserの実装を見てみます。

ATSParser setMediaCas()

//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp

status_t ATSParser::setMediaCas(const sp<ICas> &cas) {
    status_t err = mCasManager->setMediaCas(cas); //★★★★mCasManager
    if (err != OK) {
        return err;
    }
    for (size_t i = 0; i < mPrograms.size(); ++i) {
        mPrograms.editItemAt(i)->updateCasSessions();
    }
    return OK;
}

ここで出てくるmCasManagerの型はATSParser::CasManagerでした。CasManagerを見てみましょう。

CasManager setMediaCas()

//frameworks/av/media/libstagefright/mpeg2ts/CasManager.cpp

status_t ATSParser::CasManager::setMediaCas(const sp<ICas> &cas) {
    if (cas == NULL) {
        ALOGE("setMediaCas: received NULL object");
        return BAD_VALUE;
    }
    if (mICas != NULL) {
        ALOGW("setMediaCas: already set");
        return ALREADY_EXISTS;
    }
    for (size_t index = 0; index < mProgramCasMap.size(); index++) {
        status_t err;
        if ((err = mProgramCasMap.editValueAt(
                index)->setMediaCas(cas, mCAPidToSessionIdMap)) != OK) { //★★★★mProgramCasMap
            return err;
        }
    }
    mICas = cas; //★★★★mICas
    return OK;
}

Extractorから渡されてきたcasは、CasManagerのメンバ変数mICasに保存されるようです。ちなみにこのmICasはその2のCasManager::parsePID() にて、mICas->processEcm(mCAPidToSessionIdMap[index], ecm); の呼び出し時に出てきました。ここで保存されていたんですね。

話を戻してmProgramCasMapはunsignedがキー、ProgramCasManagerのポインタが値のKeyedVectorです。

ProgramCasManager setMediaCas()

status_t ATSParser::CasManager::ProgramCasManager::setMediaCas(
        const sp<ICas> &cas, PidToSessionMap &sessionMap) {
    if (mHasProgramCas) {
        return initSession(cas, sessionMap, &mProgramCas); //★★★★
    }
    // TODO: share session among streams that has identical CA_descriptors.
    // For now, we open one session for each stream that has CA_descriptor.
    for (size_t index = 0; index < mStreamPidToCasMap.size(); index++) {
        status_t err = initSession(
                cas, sessionMap, &mStreamPidToCasMap.editValueAt(index)); //★★★★
        if (err != OK) {
            return err;
        }
    }
    return OK;
}

status_t ATSParser::CasManager::ProgramCasManager::initSession(
         const sp<ICas>& cas,
         PidToSessionMap &sessionMap,
         CasSession *session) {
    sp<IMediaCasService> casService = IMediaCasService::getService("default"); //★★★★IMediaCasService型
    if (casService == NULL) {
        ALOGE("Cannot obtain IMediaCasService");
        return NO_INIT;
    }
//...
    returnDescrambler = casService->createDescrambler(descriptor.mSystemID); //★★★★createDescrambler()
    if (!returnDescrambler.isOk()) {
        ALOGE("Failed to create descrambler: trans=%s",
                returnDescrambler.description().c_str());
        goto l_fail;
    }
    descramblerBase = (sp<IDescramblerBase>) returnDescrambler;
    if (descramblerBase == NULL) {
        ALOGE("Failed to create descrambler: null ptr");
        goto l_fail;
    }

やっとcreateDescrambler() が出てきました。casServiceはIMediaCasService型ですが、このインタフェースを実装しているクラスは1つしかなさそうです。

MediaCasService setMediaCas()

//hardware/interfaces/cas/1.0/default/MediaCasService.h

class MediaCasService : public IMediaCasService {
//...


//hardware/interfaces/cas/1.0/default/MediaCasService.cpp

Return<sp<IDescramblerBase>> MediaCasService::createDescrambler(int32_t CA_system_id) {

    ALOGV("%s: CA_system_id=%d", __FUNCTION__, CA_system_id);

    sp<IDescrambler> result;

    DescramblerFactory *factory;
    sp<SharedLibrary> library;
    if (mDescramblerLoader.findFactoryForScheme(
            CA_system_id, &library, &factory)) { //★★★★DescramblerPluginを探す処理はこの辺にありそう
        DescramblerPlugin *plugin = NULL;
        if (factory->createPlugin(CA_system_id, &plugin) == OK
                && plugin != NULL) {
            result = new DescramblerImpl(library, plugin); //★★★★DescramblerImplを生成している箇所があった
        }
    }

    return result;
}

ここでゴールのようです。まとめるとExtractorのsetMediaCas() を呼ぶと、

  • ATSParser::CasManager::parsePID() が呼び出しているmICas->processEcm() のmICasが設定される。
    つまりECMの処理方法が決まる
  • DescramblerImpl::mPluginHolderが設定される。
    つまりデスクランブルの処理方法が決まる
編集者:すずき(2018/07/17 22:44)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年7月7日

AndroidとMPEG2-TSその3

その1その2その3その4

デスクランブル処理はどこにあるんでしょう??それらしい処理を辿ってみましたが、本当にこの処理が動くのか、何を切っ掛けにデスクランブル処理が働き始めるのか、動かしてみないとわからなさそうです。うーん……。

デスクランブル処理Java側(たぶん)

//frameworks/base/media/java/android/media/MediaDescrambler.java

    public final int descramble(
            @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
            @NonNull MediaCodec.CryptoInfo cryptoInfo) {
//...
        try {
            return native_descramble(
                    cryptoInfo.key[0],
                    cryptoInfo.numSubSamples,
                    cryptoInfo.numBytesOfClearData,
                    cryptoInfo.numBytesOfEncryptedData,
                    srcBuf, srcBuf.position(), srcBuf.limit(),
                    dstBuf, dstBuf.position(), dstBuf.limit());
        } catch (ServiceSpecificException e) {
            MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
        } catch (RemoteException e) {
            cleanupAndRethrowIllegalState();
        }
        return -1;
    }

ここで呼び出しているnative_descramble() はnativeであると宣言されています。つまりJNI経由で呼び出します。メディアフレームワーク(android.media)のJNI実装はframeworks/base/media/jniに配置されているようです。

デスクランブル処理JNI側

//frameworks/base/media/jni/android_media_MediaDescrambler.cpp

static jint android_media_MediaDescrambler_native_descramble(
        JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
        jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
        jobject srcBuf, jint srcOffset, jint srcLimit,
        jobject dstBuf, jint dstOffset, jint dstLimit) {
    sp<JDescrambler> descrambler = getDescrambler(env, thiz); //★★★★descramblerはJDescrambler型
    if (descrambler == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Invalid descrambler object!");
        return -1;
    }

    hidl_vec<SubSample> subSamples;
    ssize_t totalLength = getSubSampleInfo(
            env, numSubSamples, numBytesOfClearDataObj,
            numBytesOfEncryptedDataObj, &subSamples);
    if (totalLength < 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                "Invalid subsample info!");
        return -1;
    }
//...
    err = descrambler->descramble(
            key, totalLength, subSamples,
            srcPtr, srcOffset, dstPtr, dstOffset,
            &status, &bytesWritten, &detailedError); //★★★★
//...


status_t JDescrambler::descramble(
        jbyte key,
        ssize_t totalLength,
        const hidl_vec<SubSample>& subSamples,
        const void *srcPtr,
        jint srcOffset,
        void *dstPtr,
        jint dstOffset,
        Status *status,
        uint32_t *bytesWritten,
        hidl_string *detailedError) {
//...
    auto err = mDescrambler->descramble(
            (ScramblingControl) key,
            subSamples,
            mDescramblerSrcBuffer,
            0,
            dstBuffer,
            0,
            [&status, &bytesWritten, &detailedError] (
                    Status _status, uint32_t _bytesWritten,
                    const hidl_string& _detailedError) {
                *status = _status;
                *bytesWritten = _bytesWritten;
                *detailedError = _detailedError;
            }); //★★★★
//...

ここで出てくるmDescramblerはIDescrambler型のポインタです。パッと見では、何がセットされているのか良くわかりませんが、このインタフェースを実装しているのは下記しかなさそうです。

デスクランブラ

//hardware/interfaces/cas/1.0/default/DescramblerImpl.h

class DescramblerImpl : public IDescrambler {
//...

//hardware/interfaces/cas/1.0/default/DescramblerImpl.cpp

Return<void> DescramblerImpl::descramble(
        ScramblingControl scramblingControl,
        const hidl_vec<SubSample>& subSamples,
        const SharedBuffer& srcBuffer,
        uint64_t srcOffset,
        const DestinationBuffer& dstBuffer,
        uint64_t dstOffset,
        descramble_cb _hidl_cb) {
    ALOGV("%s", __FUNCTION__);

    // Get a local copy of the shared_ptr for the plugin. Note that before
    // calling the HIDL callback, this shared_ptr must be manually reset,
    // since the client side could proceed as soon as the callback is called
    // without waiting for this method to go out of scope.
    std::shared_ptr<DescramblerPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★holder = mPluginHolder
    if (holder.get() == nullptr) {
        _hidl_cb(toStatus(INVALID_OPERATION), 0, NULL);
        return Void();
    }

//...

    // Casting hidl SubSample to DescramblerPlugin::SubSample, but need
    // to ensure structs are actually idential

    int32_t result = holder->descramble(
            dstBuffer.type != BufferType::SHARED_MEMORY,
            (DescramblerPlugin::ScramblingControl)scramblingControl,
            subSamples.size(),
            (DescramblerPlugin::SubSample*)subSamples.data(),
            srcPtr,
            srcOffset,
            dstPtr,
            dstOffset,
            NULL);
//...

このmPluginHolderはDescramblerPlugin型です。DescramblerImplが生成される時に設定されます。DescramblerImplを生成するのはMediaCasService::createDescrambler() のみ?のように見えます。

次に出てくるholderにはDescramblerPluginのポインタが入ります。Pluginの実装を探してみるとClearKeyにそれらしき処理があります。

Descrambler pluginの実装

//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp

ssize_t ClearKeyDescramblerPlugin::descramble(
        bool secure,
        ScramblingControl scramblingControl,
        size_t numSubSamples,
        const SubSample *subSamples,
        const void *srcPtr,
        int32_t srcOffset,
        void *dstPtr,
        int32_t dstOffset,
        AString *errorDetailMsg) {

    ALOGV("descramble: secure=%d, sctrl=%d, subSamples=%s, "
            "srcPtr=%p, dstPtr=%p, srcOffset=%d, dstOffset=%d",
          (int)secure, (int)scramblingControl,
          subSamplesToString(subSamples, numSubSamples).string(),
          srcPtr, dstPtr, srcOffset, dstOffset);

    if (mCASSession == NULL) {
        ALOGE("Uninitialized CAS session!");
        return ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED;
    }

    return mCASSession->decrypt(
            secure, scramblingControl,
            numSubSamples, subSamples,
            (uint8_t*)srcPtr + srcOffset,
            dstPtr == NULL ? NULL : ((uint8_t*)dstPtr + dstOffset),
            errorDetailMsg);
}

ここで出てくるmCASSessionはClearKeyCasSessionを指すようです。setMediaCasSession() にて設定されています。これは後で追ってみます。

ClearKeyによるデスクランブル

//av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp

// Decryption of a set of sub-samples
ssize_t ClearKeyCasSession::decrypt(
        bool secure, DescramblerPlugin::ScramblingControl scramblingControl,
        size_t numSubSamples, const DescramblerPlugin::SubSample *subSamples,
        const void *srcPtr, void *dstPtr, AString * /* errorDetailMsg */) {
        return ERROR_CAS_CANNOT_HANDLE;
    

    AES_KEY contentKey;

        // Hold lock to get the key only to avoid contention for decryption
        Mutex::Autolock _lock(mKeyLock);

        int32_t keyIndex = (scramblingControl & 1);
            ALOGE("decrypt: key %d is invalid", keyIndex);
            return ERROR_CAS_DECRYPT;
        
        contentKey = mKeyInfo[keyIndex].contentKey; //★★★★ClearKeyCasSession::updateECM() で設定する鍵だと思う
    
//...
        // Don't decrypt if len < AES_BLOCK_SIZE.
        // The last chunk shorter than AES_BLOCK_SIZE is not encrypted.
        if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled
                && subSamples[i].mNumBytesOfEncryptedData >= AES_BLOCK_SIZE) {
            err = decryptPayload(
                    contentKey,
                    numBytesinSubSample,
                    subSamples[i].mNumBytesOfClearData,
                    (char *)dst);
        }
//...

// Decryption of a TS payload
status_t ClearKeyCasSession::decryptPayload(
        const AES_KEY& key, size_t length, size_t offset, char* buffer) const {
    CHECK(buffer);

    // Invariant: only call decryptPayload with TS packets with at least 16
    // bytes of payload (AES_BLOCK_SIZE).

    CHECK(length >= offset + AES_BLOCK_SIZE);

    return TpBlockCtsDecrypt(key, length - offset, buffer + offset);
}

// AES-128 CBC-CTS decrypt optimized for Transport Packets. |key| is the AES
// key (odd key or even key), |length| is the data size, and |buffer| is the
// ciphertext to be decrypted in place.
status_t TpBlockCtsDecrypt(const AES_KEY& key, size_t length, char* buffer) {
    CHECK(buffer);
//...

CBCモードでAES暗号を復号しているようです。

まとめると、MediaDescrambler(ドキュメントはここ)のdescrambler() によって、スクランブルされたデータのデスクランブルができそう、ということがわかりました。

ドキュメントにそう書いてあるし、当たり前なんですけどね。新たにデスクランブラを足そうと思う人は、中身も知らないと足せないです。

編集者:すずき(2018/07/17 22:46)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年7月6日

AndroidとMPEG2-TSその2

その1その2その3その4

昨日の続き。スクランブルの掛かったストリームはmParser->mCasManagerに任せていました。mCasManagerはATSParser::CasManagerでしたので、実装を見てみます。

CasManager

//frameworks/av/media/libstagefright/mpeg2ts/CasManager.cpp

bool ATSParser::CasManager::parsePID(ABitReader *br, unsigned pid) {
    ssize_t index = mCAPidToSessionIdMap.indexOfKey(pid);
    if (index < 0) {
        return false;
    }
    hidl_vec<uint8_t> ecm;
    ecm.setToExternal((uint8_t*)br->data(), br->numBitsLeft() / 8);
    auto returnStatus = mICas->processEcm(mCAPidToSessionIdMap[index], ecm); //★★★★processEcm()
    if (!returnStatus.isOk() || (Status) returnStatus != Status::OK) {
        ALOGE("Failed to process ECM: trans=%s, status=%d",
                returnStatus.description().c_str(), (Status) returnStatus);
    }
    return true; // handled
}

謎のmICasがどこから来るかは、後で調べるとして、関数名processEcm() で探してみると、HALの方にコードがあります。

processEcm() の実装

//hardware/interfaces/cas/1.0/default/CasImpl.cpp

Return<Status> CasImpl::processEcm(
        const HidlCasSessionId &sessionId, const HidlCasData& ecm) {
    ALOGV("%s: sessionId=%s", __FUNCTION__,
            sessionIdToString(sessionId).string());
    std::shared_ptr<CasPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★CasPlugin
    if (holder.get() == nullptr) {
        return toStatus(INVALID_OPERATION);
    }

    return toStatus(holder->processEcm(sessionId, ecm));
}

想像するにCasPluginというクラスを派生させて処理を実装するのでしょう。探してみるとframeworks/av/drm/mediacas/plugins以下にclearkeyとmockという実装があります。

ClearKeyの実装

//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h

class ClearKeyCasPlugin : public CasPlugin {
...

//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp

status_t ClearKeyCasPlugin::processEcm(
        const CasSessionId &sessionId, const CasEcm& ecm) {
    ALOGV("processEcm: sessionId=%s", sessionIdToString(sessionId).string());
    sp<ClearKeyCasSession> session =
            ClearKeySessionLibrary::get()->findSession(sessionId);
    if (session == NULL) {
        return ERROR_CAS_SESSION_NOT_OPENED;
    }

    Mutex::Autolock lock(mKeyFetcherLock);

    return session->updateECM(mKeyFetcher.get(), (void*)ecm.data(), ecm.size()); //★★★★mKeyFetcher
}

status_t ClearKeyCasSession::updateECM(
        KeyFetcher *keyFetcher, void *ecm, size_t size) {
//...
    uint64_t asset_id;
    std::vector<KeyFetcher::KeyInfo> keys;
    status_t err = keyFetcher->ObtainKey(mEcmBuffer, &asset_id, &keys); //★★★★keyFetcher
    if (err != OK) {
        ALOGE("updateECM: failed to obtain key (err=%d)", err);
        return err;
    }

    ALOGV("updateECM: %zu key(s) found", keys.size());
    for (size_t keyIndex = 0; keyIndex < keys.size(); keyIndex++) {
        String8 str;

        const sp<ABuffer>& keyBytes = keys[keyIndex].key_bytes;
        CHECK(keyBytes->size() == kUserKeyLength);

        int result = AES_set_decrypt_key(
                reinterpret_cast<const uint8_t*>(keyBytes->data()),
                AES_BLOCK_SIZE * 8, &mKeyInfo[keyIndex].contentKey); //★★★★libsslの関数に渡して鍵を生成している?ようだ
//...


//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp

status_t ClearKeyFetcher::ObtainKey(const sp<ABuffer>& buffer,
        uint64_t* asset_id, std::vector<KeyInfo>* keys) {
//...

引数に渡しているmKeyFetcher(とget() が返すkeyFetcherも同様に)はKeyFetcher型のポインタでした。KeyFetcherを継承したClearKeyFetcher型のオブジェクトが格納されていました。

ClearKeyの仕組みは詳しく知りませんが、ClearKeyCasSession::updateECM() でAESの復号などをしていることと、AESの復号鍵はClearKeyFetcher::ObtainKey() がECMを読んで復号鍵を取得してくれるように見えました。

AndroidでECMの解読を行っている箇所が見つけられました。エレメンタリストリームのデスクランブルはどこで行っているのでしょうね…??

編集者:すずき(2018/07/17 22:45)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2018年7月15日 >>> 2018年7月6日
link もっと後

管理用メニュー

link 記事を新規作成

<2018>
<<<07>>>
1234567
891011121314
15161718192021
22232425262728
293031----

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 23年4月10日
    すずき (03/19 11:48)
    「[Linux - まとめリンク] 目次: Linuxカーネル、ドライバ関連。Linuxのstruct pageって何?Linu...」
  • link 24年3月18日
    すずき (03/19 11:47)
    「[画面のブランクを無効にする] 目次: LinuxROCK 3 model CのDebian bullseyeイメージは10分...」
  • link 24年3月3日
    すずき (03/19 11:07)
    「[解像度の設定を保存する] 目次: LinuxRaspberry Pi 3 Model B (以降RasPi 3B)のHDMI...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/19 11:48