Disallow using new Uint8Array with BufferSource.

Using "new Uint8Array" with a TypedArray creates a copy of the buffer;
this is unnecessarily expensive for large buffers.  This adds a rule
to disallow using it in favor of a new utility that correctly creates
a new "view" on the same buffer.

Note it is fine to pass an ArrayBuffer to "new Uint8Array" and it won't
copy; but there there are many cases where the type is BufferSource,
so it could be a TypedArray.  Unfortunately, there are many other cases
where we explicitly pass an ArrayBuffer; but the compiler rules don't
allow us to whitelist this case (since ArrayBuffer is part of
BufferSource).

Change-Id: I58696a85a9cbcc188c0b16919c9eeb63e56edca1
This commit is contained in:
Jacob Trimble
2019-08-20 10:36:22 -07:00
parent e2103d236a
commit 18b59c5294
18 changed files with 214 additions and 86 deletions
+25
View File
@@ -106,6 +106,31 @@ requirement: {
error_message: 'Use expect.toBe for primitives'
}
requirement: {
type: BANNED_CODE_PATTERN
value: '/** @param {BufferSource} a */'
'function template(a) { new Uint8Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Int8Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Uint8ClampedArray(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Unt16Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Int16Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Uint32Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Int32Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Float32Array(a) }'
value: '/** @param {BufferSource} a */'
'function template(a) { new Float64Array(a) }'
error_message: 'Use shaka.util.BufferUtils.view* instead.'
whitelist_regexp: 'lib/util/buffer_utils.js'
whitelist_regexp: 'lib/util/uint8array_utils.js'
whitelist_regexp: 'test/'
}
# Disallow console logging.
requirement: {
+1 -1
View File
@@ -903,7 +903,7 @@ shakaDemo.Main = class {
// Fetch the certificate, and apply it to the configuration.
const certificate = await this.requestCertificate_(
asset.certificateUri, netEngine);
const certArray = new Uint8Array(certificate);
const certArray = shaka.util.BufferUtils.toUint8(certificate);
for (const drmSystem of asset.licenseServers.keys()) {
config.drm.advanced[drmSystem] = config.drm.advanced[drmSystem] || {};
config.drm.advanced[drmSystem].serverCertificate = certArray;
+1 -1
View File
@@ -187,7 +187,7 @@ shaka.cast.CastUtils = class {
* @private
*/
static makeUint8Array_(obj) {
return new Uint8Array(obj['entries']);
return new Uint8Array(/** @type {!Array.<number>} */ (obj['entries']));
}
};
+3 -5
View File
@@ -216,10 +216,8 @@ shaka.dash.ContentProtection = class {
return [];
}
const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer(view);
const recordValue = new Uint8Array(
buffer, view.byteOffset + byteOffset, byteLength);
const recordValue = shaka.util.BufferUtils.toUint8(
view, byteOffset, byteLength);
records.push({
type: type,
value: recordValue,
@@ -232,7 +230,7 @@ shaka.dash.ContentProtection = class {
}
/**
* Parses an ArrayBuffer for PlayReady Objects. The data
* Parses a buffer for PlayReady Objects. The data
* should contain a 32-bit integer indicating the length of
* the PRO in bytes. Following that, a 16-bit integer for
* the number of PlayReady Object Records in the PRO. Lastly,
+1 -1
View File
@@ -2163,7 +2163,7 @@ shaka.hls.HlsParser = class {
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);
// The data encoded in the URI is a PSSH box to be used as init data.
const pssh = new Uint8Array(parsedData.data);
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.widevine.alpha', [
{initDataType: 'cenc', initData: pssh},
+4 -2
View File
@@ -19,6 +19,8 @@ goog.provide('shaka.media.IClosedCaptionParser');
goog.provide('shaka.media.MuxJSClosedCaptionParser');
goog.provide('shaka.media.NoopCaptionParser');
goog.require('shaka.util.BufferUtils');
/**
* The IClosedCaptionParser defines the interface to provide all operations for
@@ -78,7 +80,7 @@ shaka.media.MuxJSClosedCaptionParser = class {
init(data) {
const probe = muxjs.mp4.probe;
// Caption parser for Dash
const initBytes = new Uint8Array(data);
const initBytes = shaka.util.BufferUtils.toUint8(data);
this.videoTrackIds_ = probe.videoTrackIds(initBytes);
this.timescales_ = probe.timescale(initBytes);
this.muxCaptionParser_.init();
@@ -88,7 +90,7 @@ shaka.media.MuxJSClosedCaptionParser = class {
* @override
*/
parseFrom(data, onCaptions) {
const segmentBytes = new Uint8Array(data);
const segmentBytes = shaka.util.BufferUtils.toUint8(data);
const dashParsed = this.muxCaptionParser_.parse(
segmentBytes, this.videoTrackIds_, this.timescales_);
if (dashParsed && dashParsed.captions) {
+6 -4
View File
@@ -411,8 +411,8 @@ shaka.media.DrmEngine = class {
!this.offlineSessionIds_.length) {
// Explicit init data for any one stream or an offline session is
// sufficient to suppress 'encrypted' events for all streams.
const cb = (e) =>
this.newInitData(e.initDataType, new Uint8Array(e.initData));
const cb = (e) => this.newInitData(
e.initDataType, shaka.util.BufferUtils.toUint8(e.initData));
this.eventManager_.listen(this.video_, 'encrypted', cb);
}
}
@@ -890,7 +890,8 @@ shaka.media.DrmEngine = class {
// Suggestion: https://bit.ly/2JYcNTu
// Format: https://www.w3.org/TR/eme-initdata-keyids/
const initDataStr = JSON.stringify({'kids': keyIds});
const initData = new Uint8Array(StringUtils.toUTF8(initDataStr));
const initData =
shaka.util.BufferUtils.toUint8(StringUtils.toUTF8(initDataStr));
const initDatas = [{initData: initData, initDataType: 'keyids'}];
return {
@@ -1313,7 +1314,8 @@ shaka.media.DrmEngine = class {
hasExpiredKeys = true;
}
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(new Uint8Array(keyId));
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(
shaka.util.BufferUtils.toUint8(keyId));
this.keyStatusByKeyId_.set(keyIdHex, status);
});
+4 -6
View File
@@ -18,6 +18,7 @@
goog.provide('shaka.media.Transmuxer');
goog.require('goog.asserts');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.ManifestParserUtils');
@@ -160,7 +161,7 @@ shaka.media.Transmuxer = class {
this.transmuxedData_ = [];
this.captions_ = [];
const dataArray = new Uint8Array(data);
const dataArray = shaka.util.BufferUtils.toUint8(data);
this.muxTransmuxer_.push(dataArray);
this.muxTransmuxer_.flush();
@@ -190,11 +191,8 @@ shaka.media.Transmuxer = class {
*/
onTransmuxed_(segment) {
this.captions_ = segment.captions;
const segmentWithInit = new Uint8Array(segment.data.byteLength +
segment.initSegment.byteLength);
segmentWithInit.set(segment.initSegment, 0);
segmentWithInit.set(segment.data, segment.initSegment.byteLength);
this.transmuxedData_.push(segmentWithInit);
this.transmuxedData_.push(
shaka.util.Uint8ArrayUtils.concat(segment.initSegment, segment.data));
}
+4 -10
View File
@@ -335,9 +335,6 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
/** @private {!shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
/** @type {Uint8Array} */
this.certificate = null;
}
/** @override */
@@ -361,11 +358,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
/** @override */
setServerCertificate(serverCertificate) {
shaka.log.debug('PatchedMediaKeysApple.MediaKeys.setServerCertificate');
this.certificate =
serverCertificate ? new Uint8Array(serverCertificate) : null;
return Promise.resolve(true);
return Promise.resolve(false);
}
/**
@@ -475,7 +468,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession =
// It also only accepts Uint8Array, not ArrayBuffer, so explicitly
// make initData into a Uint8Array.
const session = this.nativeMediaKeys_.createSession(
'video/mp4', new Uint8Array(initData));
'video/mp4', shaka.util.BufferUtils.toUint8(initData));
this.nativeMediaKeySession_ = session;
this.sessionId = session.sessionId || '';
@@ -515,7 +508,8 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession =
try {
// Pass through to the native session.
this.nativeMediaKeySession_.update(new Uint8Array(response));
this.nativeMediaKeySession_.update(
shaka.util.BufferUtils.toUint8(response));
} catch (exception) {
this.updatePromise_.reject(exception);
}
+4 -3
View File
@@ -498,8 +498,8 @@ shaka.polyfill.PatchedMediaKeysMs.MediaKeySession =
// NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
// accepts Uint8Array.
this.nativeMediaKeySession_ = this.nativeMediaKeys_
.createSession('video/mp4', new Uint8Array(initData), null);
this.nativeMediaKeySession_ = this.nativeMediaKeys_.createSession(
'video/mp4', shaka.util.BufferUtils.toUint8(initData), null);
// Attach session event handlers here.
this.eventManager_.listen(this.nativeMediaKeySession_, 'mskeymessage',
@@ -538,7 +538,8 @@ shaka.polyfill.PatchedMediaKeysMs.MediaKeySession =
// Pass through to the native session.
// NOTE: IE11 takes either Uint8Array or ArrayBuffer, but Edge 12 only
// accepts Uint8Array.
this.nativeMediaKeySession_.update(new Uint8Array(response));
this.nativeMediaKeySession_.update(
shaka.util.BufferUtils.toUint8(response));
} catch (exception) {
this.updatePromise_.reject(exception);
}
+11 -8
View File
@@ -676,19 +676,22 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession =
if (this.type_ == 'persistent-license') {
const StringUtils = shaka.util.StringUtils;
if (!offlineSessionId) {
goog.asserts.assert(initData, 'expecting init data');
// Persisting the initial license.
// Prefix the init data with a tag to indicate persistence.
const prefix = StringUtils.toUTF8('PERSISTENT|');
const result =
new Uint8Array(prefix.byteLength + initData.byteLength);
result.set(new Uint8Array(prefix), 0);
result.set(new Uint8Array(initData), prefix.byteLength);
result.set(shaka.util.BufferUtils.toUint8(prefix), 0);
result.set(
shaka.util.BufferUtils.toUint8(initData),
prefix.byteLength);
mangledInitData = result;
} else {
// Loading a stored license.
// Prefix the init data (which is really a session ID) with a tag
// to indicate that we are loading a persisted session.
mangledInitData = new Uint8Array(
mangledInitData = shaka.util.BufferUtils.toUint8(
StringUtils.toUTF8('LOAD_SESSION|' + offlineSessionId));
}
} else {
@@ -697,11 +700,11 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession =
'expected temporary session');
goog.asserts.assert(!offlineSessionId,
'unexpected offline session ID');
mangledInitData = new Uint8Array(initData);
goog.asserts.assert(initData, 'expecting init data');
mangledInitData = shaka.util.BufferUtils.toUint8(initData);
}
goog.asserts.assert(mangledInitData,
'init data not set!');
goog.asserts.assert(mangledInitData, 'init data not set!');
} catch (exception) {
return Promise.reject(exception);
}
@@ -750,7 +753,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession =
*
* @param {!shaka.util.PublicPromise} promise The promise associated with
* this call.
* @param {?BufferSource} response
* @param {BufferSource} response
* @private
*/
update_(promise, response) {
@@ -787,7 +790,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession =
keyId = Uint8ArrayUtils.fromBase64(jwkSet.keys[0].kid);
} else {
// The key ID is not required.
key = new Uint8Array(response);
key = shaka.util.BufferUtils.toUint8(response);
keyId = null;
}
+6 -3
View File
@@ -19,6 +19,7 @@ goog.provide('shaka.text.TextEngine');
goog.require('goog.asserts');
goog.require('shaka.text.Cue');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.MimeUtils');
@@ -162,7 +163,8 @@ shaka.text.TextEngine = class {
// Parse the buffer and extract the first cue start time.
try {
const firstCue = this.parser_.parseFirstCue(new Uint8Array(buffer), time);
const firstCue = this.parser_.parseFirstCue(
shaka.util.BufferUtils.toUint8(buffer), time);
return firstCue.startTime;
} catch (exception) {
// This could be a failure from the parser itself (init segment required)
@@ -194,7 +196,7 @@ shaka.text.TextEngine = class {
}
if (startTime == null || endTime == null) {
this.parser_.parseInit(new Uint8Array(buffer));
this.parser_.parseInit(shaka.util.BufferUtils.toUint8(buffer));
return;
}
@@ -206,7 +208,8 @@ shaka.text.TextEngine = class {
};
// Parse the buffer and add the new cues.
const allCues = this.parser_.parseMedia(new Uint8Array(buffer), time);
const allCues = this.parser_.parseMedia(
shaka.util.BufferUtils.toUint8(buffer), time);
const cuesToAppend = allCues.filter((cue) => {
return cue.startTime >= this.appendWindowStart_ &&
cue.startTime < this.appendWindowEnd_;
+36 -8
View File
@@ -54,8 +54,8 @@ shaka.util.BufferUtils = class {
return true;
}
const uint8A = new Uint8Array(arr1);
const uint8B = new Uint8Array(arr2);
const uint8A = shaka.util.BufferUtils.toUint8(arr1);
const uint8B = shaka.util.BufferUtils.toUint8(arr2);
for (const i of shaka.util.Iterables.range(arr1.byteLength)) {
if (uint8A[i] != uint8B[i]) {
return false;
@@ -108,14 +108,42 @@ shaka.util.BufferUtils = class {
/**
* Creates a DataView over the given buffer.
* @param {BufferSource} buffer
* @param {number=} offset
* @param {number=} length
* @return {!DataView}
* @export
*/
static toDataView(buffer) {
if (buffer instanceof ArrayBuffer) {
return new DataView(buffer);
} else {
return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
static toDataView(buffer, offset = 0, length = Infinity) {
return shaka.util.BufferUtils.view_(buffer, offset, length, DataView);
}
/**
* Creates a new Uint8Array view on the same buffer.
* @param {BufferSource} data
* @param {number=} offset The offset from the beginning of this data's view
* to start the new view at.
* @param {number=} length The length of the new view.
* @return {!Uint8Array}
* @export
*/
static toUint8(data, offset = 0, length = Infinity) {
return shaka.util.BufferUtils.view_(data, offset, length, Uint8Array);
}
/**
* @param {BufferSource} data
* @param {number} offset
* @param {number} length
* @param {function(new:T, ArrayBuffer, number, number)} Type
* @return {!T}
* @template T
* @private
*/
static view_(data, offset, length, Type) {
const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer(data);
return new Type(
buffer,
(data.byteOffset || 0) + Math.min(offset, data.byteLength),
Math.max(0, Math.min(data.byteLength - offset, length)));
}
};
+4 -9
View File
@@ -198,11 +198,8 @@ shaka.util.DataViewReader = class {
throw this.outOfBounds_();
}
const value = new Uint8Array(
shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_),
this.dataView_.byteOffset + this.position_,
bytes);
const value =
shaka.util.BufferUtils.toUint8(this.dataView_, this.position_, bytes);
this.position_ += bytes;
return value;
}
@@ -270,10 +267,8 @@ shaka.util.DataViewReader = class {
this.position_ += 1;
}
const ret = new Uint8Array(
shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_),
this.dataView_.byteOffset + start,
this.position_ - start);
const ret = shaka.util.BufferUtils.toUint8(
this.dataView_, start, this.position_ - start);
// Skip string termination.
this.position_ += 1;
return shaka.util.StringUtils.fromUTF8(ret);
+5 -11
View File
@@ -81,10 +81,8 @@ shaka.util.EbmlParser = class {
size :
this.dataView_.byteLength - this.reader_.getPosition();
const dataView = new DataView(
shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_),
this.dataView_.byteOffset + this.reader_.getPosition(),
elementSize);
const dataView = shaka.util.BufferUtils.toDataView(
this.dataView_, this.reader_.getPosition(), elementSize);
this.reader_.skip(elementSize);
@@ -130,6 +128,7 @@ shaka.util.EbmlParser = class {
* @private
*/
parseVint_() {
const position = this.reader_.getPosition();
const firstByte = this.reader_.readUint8();
if (firstByte == 0) {
throw new shaka.util.Error(
@@ -142,13 +141,8 @@ shaka.util.EbmlParser = class {
const index = Math.floor(Math.log2(firstByte));
const numBytes = 8 - index;
goog.asserts.assert(numBytes <= 8 && numBytes >= 1, 'Incorrect log2 value');
const vint = new Uint8Array(numBytes);
for (const i of shaka.util.Iterables.range(numBytes)) {
vint[i] = i == 0 ? firstByte : this.reader_.readUint8();
}
return vint;
this.reader_.skip(numBytes - 1);
return shaka.util.BufferUtils.toUint8(this.dataView_, position, numBytes);
}
+10 -10
View File
@@ -35,7 +35,7 @@ shaka.util.FairPlayUtils = class {
* @export
*/
static defaultGetContentId(initData) {
const uint8 = new Uint8Array(initData);
const uint8 = shaka.util.BufferUtils.toUint8(initData);
const dataview = shaka.util.BufferUtils.toDataView(uint8);
// The first part is a 4 byte little-endian int, which is the length of
// the second part.
@@ -86,25 +86,25 @@ shaka.util.FairPlayUtils = class {
// composed of several parts. First, the raw init data we already got.
// Second, a 4-byte LE length followed by the content ID in UTF-16-LE.
// Third, a 4-byte LE length followed by the certificate.
/** @type {!Uint8Array} */
/** @type {BufferSource} */
let contentIdArray;
if (typeof contentId == 'string') {
contentIdArray = new Uint8Array(
shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true));
contentIdArray =
shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true);
} else {
contentIdArray = new Uint8Array(contentId);
contentIdArray = contentId;
}
const rebuiltInitData = new Uint8Array(
8 + initData.byteLength + contentIdArray.byteLength + cert.byteLength);
let offset = 0;
/** @param {!Uint8Array} array */
/** @param {BufferSource} array */
const append = (array) => {
rebuiltInitData.set(array, offset);
rebuiltInitData.set(shaka.util.BufferUtils.toUint8(array), offset);
offset += array.byteLength;
};
/** @param {!Uint8Array} array */
/** @param {BufferSource} array */
const appendWithLength = (array) => {
const view = shaka.util.BufferUtils.toDataView(rebuiltInitData);
const value = array.byteLength;
@@ -113,9 +113,9 @@ shaka.util.FairPlayUtils = class {
append(array);
};
append(new Uint8Array(initData));
append(initData);
appendWithLength(contentIdArray);
appendWithLength(new Uint8Array(cert));
appendWithLength(cert);
return rebuiltInitData;
}
+6 -2
View File
@@ -43,7 +43,7 @@ shaka.util.StringUtils = class {
return '';
}
let uint8 = new Uint8Array(data);
let uint8 = shaka.util.BufferUtils.toUint8(data);
// If present, strip off the UTF-8 BOM.
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
uint8 = uint8.subarray(3);
@@ -116,8 +116,11 @@ shaka.util.StringUtils = class {
*/
static fromBytesAutoDetect(data) {
const StringUtils = shaka.util.StringUtils;
if (!data) {
return '';
}
const uint8 = new Uint8Array(data);
const uint8 = shaka.util.BufferUtils.toUint8(data);
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
return StringUtils.fromUTF8(uint8);
} else if (uint8[0] == 0xfe && uint8[1] == 0xff) {
@@ -212,6 +215,7 @@ shaka.util.StringUtils = class {
// Check the browser for what chunk sizes it supports. Cache the result
// in an impl method to avoid checking several times.
if (!shaka.util.StringUtils.fromCharCodeImpl_) {
/** @param {number} size @return {boolean} */
const supportsChunkSize = (size) => {
try {
const buffer = new Uint8Array(size);
+83 -2
View File
@@ -43,8 +43,7 @@ describe('BufferUtils', () => {
expect(BufferUtils.equal(a, c)).toBe(false);
});
// TODO(modmaker): Fix comparisons of different types.
xit('compares different types', () => {
it('compares different types', () => {
const a = new Uint8Array([0, 1, 2, 3]);
const b = new DataView(new ArrayBuffer(4));
b.setUint16(0, 0x0001, false);
@@ -86,4 +85,86 @@ describe('BufferUtils', () => {
expect(BufferUtils.equal(a.buffer, d.buffer)).toBe(false);
});
});
describe('toUint8', () => {
it('allows passing ArrayBuffer', () => {
const buffer = new ArrayBuffer(10);
const value = BufferUtils.toUint8(buffer);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(0);
expect(value.byteLength).toBe(buffer.byteLength);
});
it('allows passing Uint8Array', () => {
const buffer = new Uint8Array(10);
const value = BufferUtils.toUint8(buffer);
expect(value).not.toBe(buffer);
expect(value.buffer).toBe(buffer.buffer);
expect(value.byteOffset).toBe(0);
expect(value.byteLength).toBe(buffer.byteLength);
});
it('allows passing partial Uint8Array', () => {
const buffer = new ArrayBuffer(10);
const view = new Uint8Array(buffer, 4, 4);
const value = BufferUtils.toUint8(view);
expect(value).not.toBe(view);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(4);
expect(value.byteLength).toBe(4);
});
it('allows setting offset/length with ArrayBuffer', () => {
const buffer = new ArrayBuffer(10);
const value = BufferUtils.toUint8(buffer, 3, 5);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(3);
expect(value.byteLength).toBe(5);
});
it('allows setting offset/length with Uint8Array', () => {
const buffer = new Uint8Array(10);
const value = BufferUtils.toUint8(buffer, 3, 5);
expect(value).not.toBe(buffer);
expect(value.buffer).toBe(buffer.buffer);
expect(value.byteOffset).toBe(3);
expect(value.byteLength).toBe(5);
});
it('allows setting offset/length with partial Uint8Array', () => {
const buffer = new ArrayBuffer(20);
const view = new Uint8Array(buffer, 5, 10);
const value = BufferUtils.toUint8(view, 3, 5);
expect(value).not.toBe(view);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(8);
expect(value.byteLength).toBe(5);
});
it('clamps offset to buffer', () => {
const buffer = new ArrayBuffer(10);
const value = BufferUtils.toUint8(buffer, 12);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(10);
expect(value.byteLength).toBe(0);
});
it('clamps offset to view', () => {
const buffer = new ArrayBuffer(20);
const view = new Uint8Array(buffer, 5, 10);
const value = BufferUtils.toUint8(view, 12);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(15);
expect(value.byteLength).toBe(0);
});
it('clamps length to view', () => {
const buffer = new ArrayBuffer(20);
const view = new Uint8Array(buffer, 5, 10);
const value = BufferUtils.toUint8(view, 4, 20);
expect(value.buffer).toBe(buffer);
expect(value.byteOffset).toBe(9);
expect(value.byteLength).toBe(6);
});
});
});