From e2537474533b6a7f64ce171af35fec4b640a6fbd Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Tue, 5 Apr 2016 17:28:09 -0700 Subject: [PATCH] Add support for 'cbcs' and 'cens' protection schemes Issue #78 Change-Id: I9f71b9a92067e2f6b388092494a7d6a84986cdc0 --- packager/app/widevine_encryption_flags.cc | 8 +- packager/media/base/decrypt_config.cc | 10 +- packager/media/base/decrypt_config.h | 13 +- packager/media/base/decryptor_source.cc | 16 ++ .../formats/mp4/encrypting_fragmenter.cc | 66 +++++-- .../media/formats/mp4/encrypting_fragmenter.h | 14 +- .../formats/mp4/key_rotation_fragmenter.cc | 22 ++- .../formats/mp4/key_rotation_fragmenter.h | 6 + packager/media/formats/mp4/segmenter.cc | 56 +++++- .../media/formats/mp4/track_run_iterator.cc | 20 +- .../mp4/track_run_iterator_unittest.cc | 179 ++++++++++++++++-- 11 files changed, 355 insertions(+), 55 deletions(-) diff --git a/packager/app/widevine_encryption_flags.cc b/packager/app/widevine_encryption_flags.cc index 1517a8d4bd..80bbcd224f 100644 --- a/packager/app/widevine_encryption_flags.cc +++ b/packager/app/widevine_encryption_flags.cc @@ -57,8 +57,12 @@ DEFINE_int32(crypto_period_duration, "rotation is enabled."); DEFINE_string(protection_scheme, "cenc", - "Choose protection scheme. Currently support cenc and cbc1. " - "Default is cenc."); + "Choose protection scheme, 'cenc' or 'cbc1' or pattern-based " + "protection schemes 'cens' or 'cbcs'. Note that if a " + "pattern-based protection scheme only applies to video stream; " + "audio stream will be encrypted using the corresponding " + "non-pattern-based encryption schemes, i.e. 'cenc' for 'cens', " + "'cbc1' for 'cbcs'."); namespace edash_packager { diff --git a/packager/media/base/decrypt_config.cc b/packager/media/base/decrypt_config.cc index 882d5ed5c2..c2c7f3e7b8 100644 --- a/packager/media/base/decrypt_config.cc +++ b/packager/media/base/decrypt_config.cc @@ -12,16 +12,20 @@ namespace media { DecryptConfig::DecryptConfig(const std::vector& key_id, const std::vector& iv, const std::vector& subsamples) - : DecryptConfig(key_id, iv, subsamples, FOURCC_cenc) {} + : DecryptConfig(key_id, iv, subsamples, FOURCC_cenc, 0, 0) {} DecryptConfig::DecryptConfig(const std::vector& key_id, const std::vector& iv, const std::vector& subsamples, - FourCC protection_scheme) + FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block) : key_id_(key_id), iv_(iv), subsamples_(subsamples), - protection_scheme_(protection_scheme) { + protection_scheme_(protection_scheme), + crypt_byte_block_(crypt_byte_block), + skip_byte_block_(skip_byte_block) { CHECK_GT(key_id.size(), 0u); } diff --git a/packager/media/base/decrypt_config.h b/packager/media/base/decrypt_config.h index 73194d1afa..b1a3ed8600 100644 --- a/packager/media/base/decrypt_config.h +++ b/packager/media/base/decrypt_config.h @@ -60,10 +60,16 @@ class DecryptConfig { /// in size to the sum of the subsample sizes. /// @param protection_scheme specifies the protection scheme: 'cenc', 'cens', /// 'cbc1', 'cbcs'. + /// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in + /// pattern based encryption, 'cens' and 'cbcs'. Ignored otherwise. + /// @param skip_byte_block indicates number of unencrypted blocks (16-byte) + /// in pattern based encryption, 'cens' and 'cbcs'. Ignored otherwise. DecryptConfig(const std::vector& key_id, const std::vector& iv, const std::vector& subsamples, - FourCC protection_scheme); + FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block); ~DecryptConfig(); @@ -71,6 +77,8 @@ class DecryptConfig { const std::vector& iv() const { return iv_; } const std::vector& subsamples() const { return subsamples_; } FourCC protection_scheme() const { return protection_scheme_; } + uint8_t crypt_byte_block() const { return crypt_byte_block_; } + uint8_t skip_byte_block() const { return skip_byte_block_; } private: const std::vector key_id_; @@ -83,6 +91,9 @@ class DecryptConfig { const std::vector subsamples_; const FourCC protection_scheme_; + // For pattern-based protection schemes, like CENS and CBCS. + const uint8_t crypt_byte_block_; + const uint8_t skip_byte_block_; DISALLOW_COPY_AND_ASSIGN(DecryptConfig); }; diff --git a/packager/media/base/decryptor_source.cc b/packager/media/base/decryptor_source.cc index c23177545a..16ed5d4460 100644 --- a/packager/media/base/decryptor_source.cc +++ b/packager/media/base/decryptor_source.cc @@ -9,6 +9,7 @@ #include "packager/base/logging.h" #include "packager/base/stl_util.h" #include "packager/media/base/aes_decryptor.h" +#include "packager/media/base/aes_pattern_cryptor.h" namespace edash_packager { namespace media { @@ -47,6 +48,21 @@ bool DecryptorSource::DecryptSampleBuffer(const DecryptConfig* decrypt_config, case FOURCC_cbc1: aes_decryptor.reset(new AesCbcDecryptor(kNoPadding, kChainAcrossCalls)); break; + case FOURCC_cens: + aes_decryptor.reset( + new AesPatternCryptor(decrypt_config->crypt_byte_block(), + decrypt_config->skip_byte_block(), + AesPatternCryptor::kDontUseConstantIv, + scoped_ptr(new AesCtrDecryptor))); + break; + case FOURCC_cbcs: + aes_decryptor.reset( + new AesPatternCryptor(decrypt_config->crypt_byte_block(), + decrypt_config->skip_byte_block(), + AesPatternCryptor::kUseConstantIv, + scoped_ptr(new AesCbcDecryptor( + kNoPadding, kChainAcrossCalls)))); + break; default: LOG(ERROR) << "Unsupported protection scheme: " << decrypt_config->protection_scheme(); diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index d28d08f205..c6b7817eae 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -9,6 +9,7 @@ #include #include "packager/media/base/aes_encryptor.h" +#include "packager/media/base/aes_pattern_cryptor.h" #include "packager/media/base/buffer_reader.h" #include "packager/media/base/key_source.h" #include "packager/media/base/media_sample.h" @@ -63,26 +64,37 @@ EncryptingFragmenter::EncryptingFragmenter( TrackFragment* traf, scoped_ptr encryption_key, int64_t clear_time, - FourCC protection_scheme) + FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block) : Fragmenter(traf), info_(info), encryption_key_(encryption_key.Pass()), nalu_length_size_(GetNaluLengthSize(*info)), video_codec_(GetVideoCodec(*info)), clear_time_(clear_time), - protection_scheme_(protection_scheme) { + protection_scheme_(protection_scheme), + crypt_byte_block_(crypt_byte_block), + skip_byte_block_(skip_byte_block) { DCHECK(encryption_key_); - if (video_codec_ == kCodecVP8) { - vpx_parser_.reset(new VP8Parser); - } else if (video_codec_ == kCodecVP9) { - vpx_parser_.reset(new VP9Parser); - } else if (video_codec_ == kCodecH264) { - header_parser_.reset(new H264VideoSliceHeaderParser); - } else if (video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1) { - header_parser_.reset(new H265VideoSliceHeaderParser); - } else if (nalu_length_size_ > 0) { - LOG(WARNING) << "Unknown video codec '" << video_codec_ - << "', whole subsamples will be encrypted."; + switch (video_codec_) { + case kCodecVP8: + vpx_parser_.reset(new VP8Parser); + break; + case kCodecVP9: + vpx_parser_.reset(new VP9Parser); + break; + case kCodecH264: + header_parser_.reset(new H264VideoSliceHeaderParser); + break; + case kCodecHVC1: + FALLTHROUGH_INTENDED; + case kCodecHEV1: + header_parser_.reset(new H265VideoSliceHeaderParser); + break; + default: + LOG(WARNING) << "Unknown video codec '" << video_codec_ + << "', whole subsamples will be encrypted."; } } @@ -153,6 +165,11 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() { // The offset will be adjusted in Segmenter after knowing moof size. traf()->auxiliary_offset.offsets.push_back(0); + // For 'cbcs' scheme, Constant IVs SHALL be used. + const size_t per_sample_iv_size = + (protection_scheme_ == FOURCC_cbcs) ? 0 : encryptor_->iv().size(); + traf()->sample_encryption.iv_size = per_sample_iv_size; + // Optimize saiz box. SampleAuxiliaryInformationSize& saiz = traf()->auxiliary_size; saiz.sample_count = traf()->runs[0].sample_sizes.size(); @@ -165,9 +182,11 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() { // |sample_info_sizes| table is filled in only for subsample encryption, // otherwise |sample_info_size| is just the IV size. DCHECK(!IsSubsampleEncryptionRequired()); - saiz.default_sample_info_size = encryptor_->iv().size(); + saiz.default_sample_info_size = per_sample_iv_size; } - traf()->sample_encryption.iv_size = encryptor_->iv().size(); + // It should only happen with full sample encryption + constant iv, which is + // not a legal combination. + CHECK(!saiz.sample_info_sizes.empty() || saiz.default_sample_info_size != 0); } Status EncryptingFragmenter::CreateEncryptor() { @@ -180,6 +199,19 @@ Status EncryptingFragmenter::CreateEncryptor() { case FOURCC_cbc1: encryptor.reset(new AesCbcEncryptor(kNoPadding, kChainAcrossCalls)); break; + case FOURCC_cens: + encryptor.reset( + new AesPatternCryptor(crypt_byte_block(), skip_byte_block(), + AesPatternCryptor::kDontUseConstantIv, + scoped_ptr(new AesCtrEncryptor))); + break; + case FOURCC_cbcs: + encryptor.reset( + new AesPatternCryptor(crypt_byte_block(), skip_byte_block(), + AesPatternCryptor::kUseConstantIv, + scoped_ptr(new AesCbcEncryptor( + kNoPadding, kChainAcrossCalls)))); + break; default: return Status(error::MUXER_FAILURE, "Unsupported protection scheme."); } @@ -202,7 +234,9 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { DCHECK(encryptor_); SampleEncryptionEntry sample_encryption_entry; - sample_encryption_entry.initialization_vector = encryptor_->iv(); + // For 'cbcs' scheme, Constant IVs SHALL be used. + if (protection_scheme_ != FOURCC_cbcs) + sample_encryption_entry.initialization_vector = encryptor_->iv(); uint8_t* data = sample->writable_data(); if (IsSubsampleEncryptionRequired()) { if (vpx_parser_) { diff --git a/packager/media/formats/mp4/encrypting_fragmenter.h b/packager/media/formats/mp4/encrypting_fragmenter.h index e960d5bd52..1b17738157 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.h +++ b/packager/media/formats/mp4/encrypting_fragmenter.h @@ -32,11 +32,17 @@ class EncryptingFragmenter : public Fragmenter { /// track's timescale. /// @param protection_scheme specifies the protection scheme: 'cenc', 'cens', /// 'cbc1', 'cbcs'. + /// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in + /// pattern based encryption. + /// @param skip_byte_block indicates number of unencrypted blocks (16-byte) + /// in pattern based encryption. EncryptingFragmenter(scoped_refptr info, TrackFragment* traf, scoped_ptr encryption_key, int64_t clear_time, - FourCC protection_scheme); + FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block); ~EncryptingFragmenter() override; @@ -62,6 +68,8 @@ class EncryptingFragmenter : public Fragmenter { const EncryptionKey* encryption_key() const { return encryption_key_.get(); } AesCryptor* encryptor() { return encryptor_.get(); } FourCC protection_scheme() const { return protection_scheme_; } + uint8_t crypt_byte_block() const { return crypt_byte_block_; } + uint8_t skip_byte_block() const { return skip_byte_block_; } void set_encryption_key(scoped_ptr encryption_key) { encryption_key_ = encryption_key.Pass(); @@ -83,7 +91,9 @@ class EncryptingFragmenter : public Fragmenter { const uint8_t nalu_length_size_; const VideoCodec video_codec_; int64_t clear_time_; - FourCC protection_scheme_; + const FourCC protection_scheme_; + const uint8_t crypt_byte_block_; + const uint8_t skip_byte_block_; scoped_ptr vpx_parser_; scoped_ptr header_parser_; diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index 80ff991b58..db763586c2 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -25,12 +25,16 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, int64_t crypto_period_duration, int64_t clear_time, FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block, MuxerListener* muxer_listener) : EncryptingFragmenter(info, traf, scoped_ptr(new EncryptionKey()), clear_time, - protection_scheme), + protection_scheme, + crypt_byte_block, + skip_byte_block), moof_(moof), encryption_key_source_(encryption_key_source), track_type_(track_type), @@ -105,10 +109,18 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption( // Fill in SampleGroupDescription box information. traf()->sample_group_description.grouping_type = FOURCC_seig; traf()->sample_group_description.entries.resize(1); - traf()->sample_group_description.entries[0].is_protected = 1; - traf()->sample_group_description.entries[0].per_sample_iv_size = - encryptor()->iv().size(); - traf()->sample_group_description.entries[0].key_id = encryption_key()->key_id; + auto& sample_group_entry = traf()->sample_group_description.entries[0]; + sample_group_entry.is_protected = 1; + if (protection_scheme() == FOURCC_cbcs) { + // For 'cbcs' scheme, Constant IVs SHALL be used. + sample_group_entry.per_sample_iv_size = 0; + sample_group_entry.constant_iv = encryptor()->iv(); + } else { + sample_group_entry.per_sample_iv_size = encryptor()->iv().size(); + } + sample_group_entry.crypt_byte_block = crypt_byte_block(); + sample_group_entry.skip_byte_block = skip_byte_block(); + sample_group_entry.key_id = encryption_key()->key_id; // Fill in SampleToGroup box information. traf()->sample_to_group.grouping_type = FOURCC_seig; diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.h b/packager/media/formats/mp4/key_rotation_fragmenter.h index 821d140d4f..ee138bb7f5 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.h +++ b/packager/media/formats/mp4/key_rotation_fragmenter.h @@ -34,6 +34,10 @@ class KeyRotationFragmenter : public EncryptingFragmenter { /// track's timescale. /// @param protection_scheme specifies the protection scheme: 'cenc', 'cens', /// 'cbc1', 'cbcs'. + /// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in + /// pattern based encryption. + /// @param skip_byte_block indicates number of unencrypted blocks (16-byte) + /// in pattern based encryption. /// @param muxer_listener is a pointer to MuxerListener for notifying /// muxer related events. This may be null. KeyRotationFragmenter(MovieFragment* moof, @@ -44,6 +48,8 @@ class KeyRotationFragmenter : public EncryptingFragmenter { int64_t crypto_period_duration, int64_t clear_time, FourCC protection_scheme, + uint8_t crypt_byte_block, + uint8_t skip_byte_block, MuxerListener* muxer_listener); ~KeyRotationFragmenter() override; diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 484deed050..d4ff55c4ae 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -26,6 +26,9 @@ namespace media { namespace mp4 { namespace { +// For pattern-based encryption. +const uint8_t kCryptByteBlock = 1u; +const uint8_t kSkipByteBlock = 9u; const size_t kCencKeyIdSize = 16u; @@ -47,6 +50,18 @@ uint64_t Rescale(uint64_t time_in_old_scale, return static_cast(time_in_old_scale) / old_scale * new_scale; } +uint8_t GetCryptByteBlock(FourCC protection_scheme) { + return (protection_scheme == FOURCC_cbcs || protection_scheme == FOURCC_cens) + ? kCryptByteBlock + : 0; +} + +uint8_t GetSkipByteBlock(FourCC protection_scheme) { + return (protection_scheme == FOURCC_cbcs || protection_scheme == FOURCC_cens) + ? kSkipByteBlock + : 0; +} + void GenerateSinf(const EncryptionKey& encryption_key, FourCC old_type, FourCC protection_scheme, @@ -60,7 +75,18 @@ void GenerateSinf(const EncryptionKey& encryption_key, auto& track_encryption = sinf->info.track_encryption; track_encryption.default_is_protected = 1; DCHECK(!encryption_key.iv.empty()); - track_encryption.default_per_sample_iv_size = encryption_key.iv.size(); + if (protection_scheme == FOURCC_cbcs) { + // ISO/IEC 23001-7:2016 10.4.1 + // For 'cbcs' scheme, Constant IVs SHALL be used. + track_encryption.default_per_sample_iv_size = 0; + track_encryption.default_constant_iv = encryption_key.iv; + } else { + track_encryption.default_per_sample_iv_size = encryption_key.iv.size(); + } + track_encryption.default_crypt_byte_block = + GetCryptByteBlock(protection_scheme); + track_encryption.default_skip_byte_block = + GetSkipByteBlock(protection_scheme); track_encryption.default_kid = encryption_key.key_id; } @@ -159,6 +185,16 @@ Status Segmenter::Initialize(const std::vector& streams, continue; } + FourCC local_protection_scheme = protection_scheme; + if (streams[i]->info()->stream_type() != kStreamVideo) { + // Pattern encryption should only be used with video. Replaces with the + // corresponding non-pattern encryption scheme. + if (protection_scheme == FOURCC_cbcs) + local_protection_scheme = FOURCC_cbc1; + else if (protection_scheme == FOURCC_cens) + local_protection_scheme = FOURCC_cenc; + } + KeySource::TrackType track_type = GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels); SampleDescription& description = @@ -170,10 +206,12 @@ Status Segmenter::Initialize(const std::vector& streams, encryption_key.key_id.assign( kKeyRotationDefaultKeyId, kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId)); - if (!AesCryptor::GenerateRandomIv(protection_scheme, &encryption_key.iv)) + if (!AesCryptor::GenerateRandomIv(local_protection_scheme, + &encryption_key.iv)) { return Status(error::INTERNAL_ERROR, "Failed to generate random iv."); + } GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds, - protection_scheme, &description); + local_protection_scheme, &description); if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady( kInitialEncryptionInfo, encryption_key.key_id, @@ -185,7 +223,8 @@ Status Segmenter::Initialize(const std::vector& streams, encryption_key_source, track_type, crypto_period_duration_in_seconds * streams[i]->info()->time_scale(), clear_lead_in_seconds * streams[i]->info()->time_scale(), - protection_scheme, muxer_listener_); + local_protection_scheme, GetCryptByteBlock(local_protection_scheme), + GetSkipByteBlock(local_protection_scheme), muxer_listener_); continue; } @@ -195,12 +234,14 @@ Status Segmenter::Initialize(const std::vector& streams, if (!status.ok()) return status; if (encryption_key->iv.empty()) { - if (!AesCryptor::GenerateRandomIv(protection_scheme, &encryption_key->iv)) + if (!AesCryptor::GenerateRandomIv(local_protection_scheme, + &encryption_key->iv)) { return Status(error::INTERNAL_ERROR, "Failed to generate random iv."); + } } GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds, - protection_scheme, &description); + local_protection_scheme, &description); if (moov_->pssh.empty()) { moov_->pssh.resize(encryption_key->key_system_info.size()); @@ -218,7 +259,8 @@ Status Segmenter::Initialize(const std::vector& streams, fragmenters_[i] = new EncryptingFragmenter( streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(), clear_lead_in_seconds * streams[i]->info()->time_scale(), - protection_scheme); + local_protection_scheme, GetCryptByteBlock(local_protection_scheme), + GetSkipByteBlock(local_protection_scheme)); } // Choose the first stream if there is no VIDEO. diff --git a/packager/media/formats/mp4/track_run_iterator.cc b/packager/media/formats/mp4/track_run_iterator.cc index f57e55357a..86e1597d87 100644 --- a/packager/media/formats/mp4/track_run_iterator.cc +++ b/packager/media/formats/mp4/track_run_iterator.cc @@ -606,10 +606,22 @@ scoped_ptr TrackRunIterator::GetDecryptConfig() { FourCC protection_scheme = is_audio() ? audio_description().sinf.type.type : video_description().sinf.type.type; - return scoped_ptr( - new DecryptConfig(track_encryption().default_kid, - sample_encryption_entry.initialization_vector, - sample_encryption_entry.subsamples, protection_scheme)); + std::vector iv = sample_encryption_entry.initialization_vector; + if (iv.empty()) { + if (protection_scheme != FOURCC_cbcs) { + LOG(WARNING) + << "Constant IV should only be used with 'cbcs' protection scheme."; + } + iv = track_encryption().default_constant_iv; + if (iv.empty()) { + LOG(ERROR) << "IV cannot be empty."; + return scoped_ptr(); + } + } + return scoped_ptr(new DecryptConfig( + track_encryption().default_kid, iv, sample_encryption_entry.subsamples, + protection_scheme, track_encryption().default_crypt_byte_block, + track_encryption().default_skip_byte_block)); } } // namespace mp4 diff --git a/packager/media/formats/mp4/track_run_iterator_unittest.cc b/packager/media/formats/mp4/track_run_iterator_unittest.cc index 5169dbf7f4..855d918487 100644 --- a/packager/media/formats/mp4/track_run_iterator_unittest.cc +++ b/packager/media/formats/mp4/track_run_iterator_unittest.cc @@ -19,6 +19,9 @@ const int kSumAscending1 = 45; const int kAudioScale = 48000; const int kVideoScale = 25; +const uint8_t kDefaultCryptByteBlock = 2; +const uint8_t kDefaultSkipByteBlock = 8; + const uint8_t kAuxInfo[] = { // Sample 1: IV (no subsumples). 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, @@ -29,7 +32,8 @@ const uint8_t kAuxInfo[] = { // Sample 2: Subsample 1. 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, // Sample 2: Subsample 2. - 0x00, 0x03, 0x00, 0x00, 0x00, 0x04}; + 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, +}; const uint8_t kSampleEncryptionDataWithSubsamples[] = { // Sample count. @@ -47,7 +51,8 @@ const uint8_t kSampleEncryptionDataWithSubsamples[] = { // Sample 2: Subsample 1. 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, // Sample 2: Subsample 2. - 0x00, 0x03, 0x00, 0x00, 0x00, 0x04}; + 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, +}; const uint8_t kSampleEncryptionDataWithoutSubsamples[] = { // Sample count. @@ -55,13 +60,37 @@ const uint8_t kSampleEncryptionDataWithoutSubsamples[] = { // Sample 1: IV. 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, // Sample 2: IV. - 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32}; + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32, +}; + +const uint8_t kSampleEncryptionDataWithConstantIvAndSubsamples[] = { + // Sample count. + 0x00, 0x00, 0x00, 0x02, + // Sample 1: Subsample count. + 0x00, 0x01, + // Sample 1: Subsample 1. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + // Sample 2: Subsample count. + 0x00, 0x02, + // Sample 2: Subsample 1. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + // Sample 2: Subsample 2. + 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, +}; + +const uint8_t kSampleEncryptionDataWithConstantIvWithoutSubsamples[] = { + // Sample count. + 0x00, 0x00, 0x00, 0x02, +}; const char kIv1[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31}; const char kIv2[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32}; +const char kConstantIv[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x33}; -const uint8_t kKeyId[] = {0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, - 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44}; +const uint8_t kKeyId[] = { + 0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44, +}; } // namespace @@ -142,7 +171,7 @@ class TrackRunIteratorTest : public testing::Test { } // Update the first sample description of a Track to indicate encryption - void AddEncryption(Track* track) { + void AddEncryption(FourCC protection_scheme, Track* track) { SampleDescription* stsd = &track->media.information.sample_table.description; ProtectionSchemeInfo* sinf; @@ -152,9 +181,20 @@ class TrackRunIteratorTest : public testing::Test { sinf = &stsd->audio_entries[0].sinf; } - sinf->type.type = FOURCC_cenc; + sinf->type.type = protection_scheme; sinf->info.track_encryption.default_is_protected = 1; - sinf->info.track_encryption.default_per_sample_iv_size = 8; + // Use constant IV for CBCS protection scheme. + if (protection_scheme == FOURCC_cbcs) { + sinf->info.track_encryption.default_per_sample_iv_size = 0; + sinf->info.track_encryption.default_constant_iv.assign( + kConstantIv, kConstantIv + arraysize(kConstantIv)); + sinf->info.track_encryption.default_crypt_byte_block = + kDefaultCryptByteBlock; + sinf->info.track_encryption.default_skip_byte_block = + kDefaultSkipByteBlock; + } else { + sinf->info.track_encryption.default_per_sample_iv_size = 8; + } sinf->info.track_encryption.default_kid.assign(kKeyId, kKeyId + arraysize(kKeyId)); } @@ -202,6 +242,39 @@ class TrackRunIteratorTest : public testing::Test { } } + void AddSampleEncryptionWithConstantIv(uint8_t use_subsample_flag, + TrackFragment* frag) { + frag->sample_encryption.iv_size = 0; + frag->sample_encryption.flags = use_subsample_flag; + if (use_subsample_flag) { + frag->sample_encryption.sample_encryption_data.assign( + kSampleEncryptionDataWithConstantIvAndSubsamples, + kSampleEncryptionDataWithConstantIvAndSubsamples + + arraysize(kSampleEncryptionDataWithConstantIvAndSubsamples)); + } else { + frag->sample_encryption.sample_encryption_data.assign( + kSampleEncryptionDataWithConstantIvWithoutSubsamples, + kSampleEncryptionDataWithConstantIvWithoutSubsamples + + arraysize(kSampleEncryptionDataWithConstantIvWithoutSubsamples)); + } + + // Update sample sizes and aux info header. + frag->runs.resize(1); + frag->runs[0].sample_count = 2; + if (use_subsample_flag) { + // Update sample sizes to match with subsample entries above. + frag->runs[0].sample_sizes[0] = 3; + frag->runs[0].sample_sizes[1] = 10; + // Set aux info header. + frag->auxiliary_offset.offsets.push_back(0); + frag->auxiliary_size.sample_count = 2; + frag->auxiliary_size.sample_info_sizes.push_back(16); + frag->auxiliary_size.sample_info_sizes.push_back(22); + } else { + // No aux info needed for constant iv and full sample encryption. + } + } + void SetAscending(std::vector* vec) { vec->resize(10); for (size_t i = 0; i < vec->size(); i++) @@ -364,7 +437,7 @@ TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) { TEST_F(TrackRunIteratorTest, DecryptConfigTestWithSampleEncryptionAndSubsample) { - AddEncryption(&moov_.tracks[1]); + AddEncryption(FOURCC_cenc, &moov_.tracks[1]); iter_.reset(new TrackRunIterator(&moov_)); MovieFragment moof = CreateFragment(); @@ -402,7 +475,7 @@ TEST_F(TrackRunIteratorTest, TEST_F(TrackRunIteratorTest, DecryptConfigTestWithSampleEncryptionAndNoSubsample) { - AddEncryption(&moov_.tracks[1]); + AddEncryption(FOURCC_cenc, &moov_.tracks[1]); iter_.reset(new TrackRunIterator(&moov_)); MovieFragment moof = CreateFragment(); @@ -432,8 +505,84 @@ TEST_F(TrackRunIteratorTest, EXPECT_EQ(config->subsamples().size(), 0u); } +TEST_F(TrackRunIteratorTest, + DecryptConfigTestWithSampleEncryptionAndConstantIvAndSubsample) { + AddEncryption(FOURCC_cbcs, &moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_)); + + MovieFragment moof = CreateFragment(); + AddSampleEncryptionWithConstantIv(SampleEncryption::kUseSubsampleEncryption, + &moof.tracks[1]); + + ASSERT_TRUE(iter_->Init(moof)); + // The run for track 2 will be the second, which is parsed according to + // data_offset. + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 2u); + + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[1].runs[0].data_offset); + scoped_ptr config = iter_->GetDecryptConfig(); + EXPECT_EQ(FOURCC_cbcs, config->protection_scheme()); + EXPECT_EQ(kDefaultCryptByteBlock, config->crypt_byte_block()); + EXPECT_EQ(kDefaultSkipByteBlock, config->skip_byte_block()); + EXPECT_EQ(std::vector(kKeyId, kKeyId + arraysize(kKeyId)), + config->key_id()); + EXPECT_EQ( + std::vector(kConstantIv, kConstantIv + arraysize(kConstantIv)), + config->iv()); + EXPECT_EQ(config->subsamples().size(), 1u); + EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u); + EXPECT_EQ(config->subsamples()[0].cipher_bytes, 2u); + iter_->AdvanceSample(); + config = iter_->GetDecryptConfig(); + EXPECT_EQ( + std::vector(kConstantIv, kConstantIv + arraysize(kConstantIv)), + config->iv()); + EXPECT_EQ(config->subsamples().size(), 2u); + EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u); + EXPECT_EQ(config->subsamples()[0].cipher_bytes, 2u); + EXPECT_EQ(config->subsamples()[1].clear_bytes, 3u); + EXPECT_EQ(config->subsamples()[1].cipher_bytes, 4u); +} + +TEST_F(TrackRunIteratorTest, + DecryptConfigTestWithSampleEncryptionAndConstantIvAndNoSubsample) { + AddEncryption(FOURCC_cbcs, &moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_)); + + MovieFragment moof = CreateFragment(); + AddSampleEncryptionWithConstantIv(!SampleEncryption::kUseSubsampleEncryption, + &moof.tracks[1]); + + ASSERT_TRUE(iter_->Init(moof)); + // The run for track 2 will be the second, which is parsed according to + // data_offset. + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 2u); + + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[1].runs[0].data_offset); + scoped_ptr config = iter_->GetDecryptConfig(); + EXPECT_EQ(FOURCC_cbcs, config->protection_scheme()); + EXPECT_EQ(std::vector(kKeyId, kKeyId + arraysize(kKeyId)), + config->key_id()); + EXPECT_EQ( + std::vector(kConstantIv, kConstantIv + arraysize(kConstantIv)), + config->iv()); + EXPECT_EQ(config->subsamples().size(), 0u); + iter_->AdvanceSample(); + config = iter_->GetDecryptConfig(); + EXPECT_EQ( + std::vector(kConstantIv, kConstantIv + arraysize(kConstantIv)), + config->iv()); + EXPECT_EQ(config->subsamples().size(), 0u); +} + TEST_F(TrackRunIteratorTest, DecryptConfigTestWithAuxInfo) { - AddEncryption(&moov_.tracks[1]); + AddEncryption(FOURCC_cenc, &moov_.tracks[1]); iter_.reset(new TrackRunIterator(&moov_)); MovieFragment moof = CreateFragment(); @@ -470,8 +619,8 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTestWithAuxInfo) { // It is legal for aux info blocks to be shared among multiple formats. TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) { - AddEncryption(&moov_.tracks[0]); - AddEncryption(&moov_.tracks[1]); + AddEncryption(FOURCC_cenc, &moov_.tracks[0]); + AddEncryption(FOURCC_cenc, &moov_.tracks[1]); iter_.reset(new TrackRunIterator(&moov_)); MovieFragment moof = CreateFragment(); @@ -512,8 +661,8 @@ TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) { // byte 10000: track 1, run 2 data // byte 20000: track 1, run 1 aux info TEST_F(TrackRunIteratorTest, UnexpectedOrderingTest) { - AddEncryption(&moov_.tracks[0]); - AddEncryption(&moov_.tracks[1]); + AddEncryption(FOURCC_cenc, &moov_.tracks[0]); + AddEncryption(FOURCC_cenc, &moov_.tracks[1]); iter_.reset(new TrackRunIterator(&moov_)); MovieFragment moof = CreateFragment();