Source: lib/msf/msf_utils.js

/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.msf.Utils');

goog.require('shaka.log');
goog.require('shaka.util.BufferUtils');


shaka.msf.Utils = class {
  /**
   * Parses a block of MoQT Properties into a property map. Object Properties
   * and Track Properties share this layout, so it serves both.
   *
   * Wire format โ€” a flat sequence of MOQT Key-Value-Pairs
   * (draft-ietf-moq-transport-18 ยง1.4.3) running to the end of the buffer;
   * whatever length prefix the block had was already consumed by the
   * transport layer:
   *
   *   delta type (vi64)
   *   value: vi64           when the resolved type is even
   *          length (vi64) + bytes  when the resolved type is odd
   *
   * Types are DELTA encoded against the previous type in the block, starting
   * from 0. Reading them as absolute is worse than dropping the trailing
   * properties: a delta can collide with a real type and bind an unrelated
   * property's value to it. With the LOC Timestamp and Timescale present the
   * deltas are 6 and 2, so an absolute read loses Timescale entirely.
   *
   * If parsing throws at any point the partial map built so far is returned,
   * so callers always receive a valid (possibly empty) map.
   *
   * @param {!Uint8Array} data
   * @param {!shaka.extern.MsfCodec} codec The negotiated draft's primitive
   *   codec. Draft-17 changed the var int encoding, so the same property
   *   bytes mean different numbers depending on which draft delivered them.
   * @return {!Map<bigint, bigint|!Uint8Array>}
   */
  static parseProperties(data, codec) {
    /** @type {!Map<bigint, bigint|!Uint8Array>} */
    const props = new Map();

    if (data.byteLength === 0) {
      return props;
    }

    /** @type {function(number):{value: bigint, bytesRead: number}} */
    const readVi64At =
        (offset) => shaka.msf.Utils.readVi64At(data, offset, codec);

    try {
      let offset = 0;
      /** @type {bigint} */
      let previousType = BigInt(0);
      while (offset < data.byteLength) {
        const deltaResult = readVi64At(offset);
        offset += deltaResult.bytesRead;
        const type = previousType + deltaResult.value;
        previousType = type;

        if (type % BigInt(2) === BigInt(0)) {
          // Even type โ†’ single vi64 value
          const valResult = readVi64At(offset);
          offset += valResult.bytesRead;
          props.set(type, valResult.value);
        } else {
          // Odd type โ†’ length-prefixed byte sequence
          const lenResult = readVi64At(offset);
          offset += lenResult.bytesRead;
          const len = Number(lenResult.value);
          props.set(type, shaka.util.BufferUtils.toUint8(data, offset, len));
          offset += len;
        }
      }
    } catch (e) {
      shaka.log.v2('Failed to parse MoQT properties, returning partial map',
          e);
    }

    return props;
  }

  /**
   * Reads one variable-length integer from `buffer` at byte `offset`, in
   * whichever encoding the negotiated draft uses. Draft-17 replaced the QUIC
   * two-bit size tag with a leading-ones count, so the same bytes mean
   * different numbers under the two.
   *
   * Synchronous equivalent of `Reader.u62WithSize()` in msf_classes.js.
   *
   * @param {!Uint8Array} buffer
   * @param {number} offset
   * @param {!shaka.extern.MsfCodec} codec
   * @return {{value: bigint, bytesRead: number}}
   */
  static readVi64At(buffer, offset, codec) {
    if (offset >= buffer.length) {
      throw new Error(`readVi64At: underflow at offset ${offset}`);
    }

    const bytesRead = codec.varIntLength(buffer[offset]);
    if (offset + bytesRead > buffer.length) {
      throw new Error(`readVi64At: need ${bytesRead} bytes`);
    }
    return {value: codec.decodeVarInt(
        buffer.subarray(offset, offset + bytesRead)), bytesRead};
  }
};

/**
 * @enum {number}
 */
shaka.msf.Utils.SetupOption = {
  AUTHORIZATION_TOKEN: 0x3,
  MAX_AUTH_TOKEN_CACHE_SIZE: 0x4,
  IMPLEMENTATION: 0x7,
};

/**
 * @typedef {{
 *   type: bigint,
 *   value: (bigint|!Uint8Array),
 * }}
 */
shaka.msf.Utils.KeyValuePair;


/**
 * Where an Object sits in a track. Defined in externs as
 * shaka.extern.MsfLocation, since it is part of the dialect plugin contract
 * and externs are compiled into builds that omit MSF entirely.
 *
 * @typedef {shaka.extern.MsfLocation}
 */
shaka.msf.Utils.Location;


/**
 * The shape delivered to subscription and fetch callbacks. Defined in externs
 * as shaka.extern.MsfObject, since it is part of the dialect plugin contract
 * and externs are compiled into builds that omit MSF entirely.
 *
 * @typedef {shaka.extern.MsfObject}
 */
shaka.msf.Utils.MOQObject;


/**
 * @typedef {shaka.extern.MsfObjectCallback}
 */
shaka.msf.Utils.ObjectCallback;


/**
 * @typedef {{
 *   namespace: Array<string>,
 *   trackName: string,
 *   trackAlias: bigint,
 *   requestId: bigint,
 *   callbacks: !Array<shaka.msf.Utils.ObjectCallback>,
 *   closed: boolean,
 * }}
 */
shaka.msf.Utils.TrackInfo;