Skip to content

Commit

Permalink
Clean up config merging, type check certs
Browse files Browse the repository at this point in the history
 - cleans up config merging now that abr.manager has been removed
   from the config
 - uses thing.constructor == Object instead of
   typeof(thing) == 'object', for better detection of anonymous
   objects
 - adds a default (empty) server certificate for type-checking
 - treats empty certs the same as null (no cert provided)

Fixes #784

Change-Id: Ie833a1b3bf484d5f12f3ebf6d513ed51740bdc44
  • Loading branch information
joeyparrish committed Jun 5, 2017
1 parent bb32073 commit e8af210
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 27 deletions.
6 changes: 4 additions & 2 deletions externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,10 @@ shakaExtern.DashContentProtectionCallback;
* audio.
* <i>Defaults to '', i.e., no specific robustness required.</i> <br>
* @property {Uint8Array} serverCertificate
* <i>Defaults to null, i.e., certificate will be requested from the license
* server if required.</i> <br>
* <i>Defaults to null.</i> <br>
* <i>An empty certificate (byteLength 0) will be treated as null.</i> <br>
* <i>A certificate will be requested from the license server if
* required.</i> <br>
* A key-system-specific server certificate used to encrypt license requests.
* Its use is optional and is meant as an optimization to avoid a round-trip
* to request a certificate.
Expand Down
3 changes: 2 additions & 1 deletion lib/media/drm_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ shaka.media.DrmEngine.prototype.attach = function(video) {
});

var setServerCertificate = null;
if (this.currentDrmInfo_.serverCertificate) {
if (this.currentDrmInfo_.serverCertificate &&
this.currentDrmInfo_.serverCertificate.length) {
setServerCertificate = this.mediaKeys_.setServerCertificate(
this.currentDrmInfo_.serverCertificate).then(function(supported) {
if (!supported) {
Expand Down
2 changes: 1 addition & 1 deletion lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,7 @@ shaka.Player.prototype.configOverrides_ = function() {
persistentStateRequired: false,
videoRobustness: '',
audioRobustness: '',
serverCertificate: null
serverCertificate: new Uint8Array(0)
}
};
};
Expand Down
36 changes: 21 additions & 15 deletions lib/util/config_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ shaka.util.ConfigUtils.mergeConfigObjects =
var subPath = path + '.' + k;
var subTemplate = ignoreKeys ? overrides[path] : template[k];

/**
* @type {boolean}
* If true, simply copy the object over and don't verify.
*/
var copyObject = !!({
'.abr.manager': true
})[subPath] || !!({
'serverCertificate': true
})[k];

// The order of these checks is important.
if (!ignoreKeys && !(k in destination)) {
shaka.log.error('Invalid config, unrecognized key ' + subPath);
Expand All @@ -63,17 +53,33 @@ shaka.util.ConfigUtils.mergeConfigObjects =
// destination config and replaced with a default from the template if
// possible.
if (subTemplate === undefined || ignoreKeys) {
// There is nothing in the template, so delete.
delete destination[k];
} else {
// There is something in the template, so go back to that.
destination[k] = subTemplate;
}
} else if (copyObject) {
destination[k] = source[k];
} else if (typeof destination[k] == 'object' &&
typeof source[k] == 'object') {
} else if (subTemplate.constructor == Object &&
source[k] &&
source[k].constructor == Object) {
// These are plain Objects with no other constructor.

if (!destination[k]) {
// The only way we should see a null destination is when ignoreKeys is
// true, so assert that it is.
goog.asserts.assert(ignoreKeys, 'Null destination without ignoreKeys!');
// Initialize the destination with the template so that normal merging
// and type-checking can happen.
destination[k] = subTemplate;
}

shaka.util.ConfigUtils.mergeConfigObjects(
destination[k], source[k], subTemplate, overrides, subPath);
} else if (typeof source[k] != typeof subTemplate) {
} else if (typeof source[k] != typeof subTemplate ||
source[k] == null ||
source[k].constructor != subTemplate.constructor) {
// The source is the wrong type. This check allows objects to be nulled,
// but does not allow null for any non-object fields.
shaka.log.error('Invalid config, wrong type for ' + subPath);
} else if (typeof destination[k] == 'function' &&
destination[k].length != source[k].length) {
Expand Down
10 changes: 5 additions & 5 deletions test/media/drm_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ describe('DrmEngine', function() {
});

it('sets server certificate if present in config', function(done) {
var cert = new Uint8Array(0);
var cert = new Uint8Array(1);
config.advanced['drm.abc'] = { serverCertificate: cert };
drmEngine.configure(config);

Expand All @@ -509,7 +509,7 @@ describe('DrmEngine', function() {

it('prefers server certificate from DrmInfo', function(done) {
var cert1 = new Uint8Array(5);
var cert2 = new Uint8Array(0);
var cert2 = new Uint8Array(1);
manifest.periods[0].variants[0].drmInfos[0].serverCertificate = cert1;

config.advanced['drm.abc'] = { serverCertificate: cert2 };
Expand Down Expand Up @@ -628,7 +628,7 @@ describe('DrmEngine', function() {
});

it('fails with an error if setServerCertificate fails', function(done) {
var cert = new Uint8Array(0);
var cert = new Uint8Array(1);
config.advanced['drm.abc'] = { serverCertificate: cert };
drmEngine.configure(config);

Expand Down Expand Up @@ -1268,7 +1268,7 @@ describe('DrmEngine', function() {
});

it('interrupts failed calls to setServerCertificate', function(done) {
var cert = new Uint8Array(0);
var cert = new Uint8Array(1);
config.advanced['drm.abc'] = { serverCertificate: cert };
drmEngine.configure(config);

Expand All @@ -1290,7 +1290,7 @@ describe('DrmEngine', function() {
});

it('interrupts successful calls to setServerCertificate', function(done) {
var cert = new Uint8Array(0);
var cert = new Uint8Array(1);
config.advanced['drm.abc'] = { serverCertificate: cert };
drmEngine.configure(config);

Expand Down
48 changes: 45 additions & 3 deletions test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,9 @@ describe('Player', function() {
});

newConfig = player.getConfiguration();
expect(newConfig.drm.advanced).toEqual(jasmine.objectContaining({
'ks1': { distinctiveIdentifierRequired: true }
}));
expect(newConfig.drm.advanced).toEqual({
'ks1': jasmine.objectContaining({ distinctiveIdentifierRequired: true })
});
expect(logErrorSpy).not.toHaveBeenCalled();
var lastGoodConfig = newConfig;

Expand Down Expand Up @@ -668,6 +668,48 @@ describe('Player', function() {
});
});

it('checks the type of serverCertificate', function() {
logErrorSpy.and.stub();

player.configure({
drm: {
advanced: {
'com.widevine.alpha': {
serverCertificate: null
}
}
}
});

expect(logErrorSpy).toHaveBeenCalledWith(
stringContaining('.serverCertificate'));

logErrorSpy.calls.reset();
player.configure({
drm: {
advanced: {
'com.widevine.alpha': {
serverCertificate: 'foobar'
}
}
}
});

expect(logErrorSpy).toHaveBeenCalledWith(
stringContaining('.serverCertificate'));
});

it('does not throw when null appears instead of an object', function() {
logErrorSpy.and.stub();

player.configure({
drm: { advanced: null }
});

expect(logErrorSpy).toHaveBeenCalledWith(
stringContaining('.drm.advanced'));
});

it('configures play and seek range for VOD', function(done) {
player.configure({playRangeStart: 5, playRangeEnd: 10});
var timeline = new shaka.media.PresentationTimeline(300, 0);
Expand Down

0 comments on commit e8af210

Please sign in to comment.