Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(MSS): Fix parsing of PIFF Sample Encryption Box #5432

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions demo/common/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,14 +920,6 @@ shakaAssets.testAssets = [
.addFeature(shakaAssets.Feature.MP4)
.addFeature(shakaAssets.Feature.LIVE)
.addFeature(shakaAssets.Feature.THUMBNAILS),
new ShakaDemoAssetInfo(
/* name= */ 'Microsoft Smooth Streaming',
/* iconUri= */ 'https://reference.dashif.org/dash.js/latest/samples/lib/img/mss-1.jpg',
/* manifestUri= */ 'https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest',
/* source= */ shakaAssets.Source.DASH_IF)
.addFeature(shakaAssets.Feature.MSS)
.addFeature(shakaAssets.Feature.HIGH_DEFINITION)
.addFeature(shakaAssets.Feature.MP4),
// End DASH-IF Assets }}}

// bitcodin assets {{{
Expand Down Expand Up @@ -1319,6 +1311,23 @@ shakaAssets.testAssets = [
.addFeature(shakaAssets.Feature.MP4)
.addFeature(shakaAssets.Feature.HIGH_DEFINITION)
.addLicenseServer('com.microsoft.playready', 'http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,ck:W31bfVt9W31bfVt9W31bfQ==,ckt:aescbc)'),
new ShakaDemoAssetInfo(
/* name= */ 'Super Speedway Trailer (MSS - Clear)',
/* iconUri= */ 'https://reference.dashif.org/dash.js/latest/samples/lib/img/mss-1.jpg',
/* manifestUri= */ 'https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest',
/* source= */ shakaAssets.Source.MICROSOFT)
.addFeature(shakaAssets.Feature.MSS)
.addFeature(shakaAssets.Feature.HIGH_DEFINITION)
.addFeature(shakaAssets.Feature.MP4),
new ShakaDemoAssetInfo(
/* name= */ 'Super Speedway Trailer (MSS - PlayReady)',
/* iconUri= */ 'https://reference.dashif.org/dash.js/latest/samples/lib/img/mss-1.jpg',
/* manifestUri= */ 'https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest',
/* source= */ shakaAssets.Source.MICROSOFT)
.addFeature(shakaAssets.Feature.MSS)
.addFeature(shakaAssets.Feature.HIGH_DEFINITION)
.addFeature(shakaAssets.Feature.MP4)
.addLicenseServer('com.microsoft.playready', 'https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:150)'),
// }}}

// MPEG-5 LCEVC assets {{{
Expand Down
2 changes: 2 additions & 0 deletions externs/isoboxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var ISOBoxerUtils;

/**
* @typedef {{
* _parsing: boolean,
* type: string,
* size: number,
* _parent: ISOBox,
Expand Down Expand Up @@ -123,6 +124,7 @@ var ISOBoxerUtils;
* sample_info_size: Array.<number>,
* data_offset: number
* }}
* @property {boolean} _parsing
* @property {string} type
* @property {number} size
* @property {ISOBox} _parent
Expand Down
14 changes: 12 additions & 2 deletions lib/mss/content_protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,22 @@ shaka.mss.ContentProtection = class {
* @private
*/
static getKID_(xml) {
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
// KID element is optional and no more than one is
// allowed inside the DATA element.
for (const elem of xml.getElementsByTagName('DATA')) {
const kid = shaka.util.XmlUtils.findChild(elem, 'KID');
if (kid) {
return kid.textContent;
// GUID: [DWORD, WORD, WORD, 8-BYTE]
const guidBytes = Uint8ArrayUtils.fromBase64(kid.textContent);
// Reverse byte order from little-endian to big-endian
const kidBytes = new Uint8Array([
guidBytes[3], guidBytes[2], guidBytes[1], guidBytes[0],
guidBytes[5], guidBytes[4],
guidBytes[7], guidBytes[6],
...guidBytes.slice(8),
]);
return Uint8ArrayUtils.toHex(kidBytes);
}
}

Expand Down Expand Up @@ -274,7 +284,7 @@ shaka.mss.ContentProtection = class {

for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const systemID = element.getAttribute('SystemID');
const systemID = element.getAttribute('SystemID').toLowerCase();
const keySystem = keySystemsBySystemId[systemID];
if (keySystem) {
const KID = ContentProtection.getPlayReadyKID_(element);
Expand Down
36 changes: 34 additions & 2 deletions lib/transmuxer/mss_transmuxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ shaka.transmuxer.MssTransmuxer = class {
}
});
// eslint-disable-next-line no-restricted-syntax
this.isoBoxer_.addBoxProcessor('senc', function() {
const sencProcessor = function() {
// eslint-disable-next-line no-invalid-this
const box = /** @type {!ISOBox} */(this);
box._procFullBox();
box._procField('sample_count', 'uint', 32);
if (box.flags & 1) {
box._procField('AlgorithmID', 'uint', 24);
box._procField('IV_size', 'uint', 8);
box._procFieldArray('KID', 16, 'uint', 8);
}
box._procField('sample_count', 'uint', 32);
// eslint-disable-next-line no-restricted-syntax
box._procEntries('entry', box.sample_count, function(entry) {
// eslint-disable-next-line no-invalid-this
Expand All @@ -125,6 +127,28 @@ shaka.transmuxer.MssTransmuxer = class {
});
}
});
};
this.isoBoxer_.addBoxProcessor('senc', sencProcessor);
// eslint-disable-next-line no-restricted-syntax
this.isoBoxer_.addBoxProcessor('uuid', function() {
const MssTransmuxer = shaka.transmuxer.MssTransmuxer;
// eslint-disable-next-line no-invalid-this
const box = /** @type {!ISOBox} */(this);
let isSENC = true;
for (let i = 0; i < 16; i++) {
if (box.usertype[i] !== MssTransmuxer.UUID_SENC_[i]) {
isSENC = false;
}
// Add support for other user types here
}

if (isSENC) {
if (box._parsing) {
box.type = 'sepiff'; // Rename it to be recognized later
}
// eslint-disable-next-line no-restricted-syntax, no-invalid-this
sencProcessor.call(/** @type {!ISOBox} */(this));
}
});
}

Expand Down Expand Up @@ -345,6 +369,14 @@ shaka.transmuxer.MssTransmuxer = class {
}
};

/**
* @private {!Uint8Array}
*/
shaka.transmuxer.MssTransmuxer.UUID_SENC_ = new Uint8Array([
0xA2, 0x39, 0x4F, 0x52, 0x5A, 0x9B, 0x4F, 0x14,
0xA2, 0x44, 0x6C, 0x42, 0x7C, 0x64, 0x8D, 0xF4,
]);

shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
'mss/audio/mp4',
() => new shaka.transmuxer.MssTransmuxer('mss/audio/mp4'),
Expand Down
54 changes: 53 additions & 1 deletion test/mss/mss_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ describe('MssParser Manifest', () => {

const aacCodecPrivateData = '1210';

// From https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264PR/S
// uperSpeedway_720.ism/Manifest
const protectionHeader = 'jAMAAAEAAQCCAzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0A' +
'bABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzA' +
'G8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQ' +
'BhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4' +
'AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZ' +
'AEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQ' +
'wBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AE' +
'sASQBEAD4AQQBtAGYAagBDAFQATwBQAGIARQBPAGwAMwBXAEQALwA1AG0AYwBlAGMAQQA' +
'9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBCAEcAdwAxAGEAWQBaADEA' +
'WQBYAE0APQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABDAFUAUwBUAE8ATQBBAFQAVABSA' +
'EkAQgBVAFQARQBTAD4APABJAEkAUwBfAEQAUgBNAF8AVgBFAFIAUwBJAE8ATgA+ADcALg' +
'AxAC4AMQAwADYANAAuADAAPAAvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4' +
'APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AEwAQQBfAFUAUgBM' +
'AD4AaAB0AHQAcAA6AC8ALwBwAGwAYQB5AHIAZQBhAGQAeQAuAGQAaQByAGUAYwB0AHQAY' +
'QBwAHMALgBuAGUAdAAvAHAAcgAvAHMAdgBjAC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAG' +
'UAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APABEAFMAXwBJAEQAPgBBAEgAKwA' +
'wADMAagB1AEsAYgBVAEcAYgBIAGwAMQBWAC8AUQBJAHcAUgBBAD0APQA8AC8ARABTAF8A' +
'SQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=';

/** @param {!shaka.extern.Manifest} manifest */
async function loadAllStreamsFor(manifest) {
const promises = [];
Expand Down Expand Up @@ -113,7 +134,7 @@ describe('MssParser Manifest', () => {
await Mss.testFails(source, error);
});

it('ive content ', async () => {
it('live content ', async () => {
const source = [
'<SmoothStreamingMedia Duration="1209510000" IsLive="true">',
' <StreamIndex Name="audio" Type="audio" Url="uri">',
Expand Down Expand Up @@ -417,4 +438,35 @@ describe('MssParser Manifest', () => {
expect(variant.audio).toBeTruthy();
expect(variant.video).toBeTruthy();
});

it('recognizes PlayReady System ID with mixed cases', async () => {
const manifestText = [
'<SmoothStreamingMedia Duration="1209510000">',
' <StreamIndex Type="video" Url="uri">',
' <QualityLevel Bitrate="2962000" CodecPrivateData="',
h264CodecPrivateData,
'" FourCC="H264" MaxHeight="720" MaxWidth="1280"/>',
' <c d="20020000"/>',
' </StreamIndex>',
' <Protection>',
' <ProtectionHeader SystemID="9a04F079-9840-4286-aB92-e65BE0885f95">',
protectionHeader,
' </ProtectionHeader>',
' </Protection>',
'</SmoothStreamingMedia>',
].join('\n');

fakeNetEngine.setResponseText('dummy://foo', manifestText);

/** @type {shaka.extern.Manifest} */
const manifest = await parser.start('dummy://foo', playerInterface);
const variant = manifest.variants[0];
expect(variant.video.drmInfos.length).toBe(1);
expect(variant.video.drmInfos[0].keySystem).toBe('com.microsoft.playready');
// Also able to parse KID correctly
expect(variant.video.drmInfos[0].keyIds.size).toBe(1);
// Expected KID: https://testweb.playready.microsoft.com/Content/Content2X
expect([...(variant.video.drmInfos[0].keyIds.values())][0]).toBe(
'09E367028F33436CA5DD60FFE6671E70'.toLowerCase());
});
});