UNPKG

@iabtechlabtcf/core

Version:

Ensures consistent encoding and decoding of TC Signals for the iab. Transparency and Consent Framework (TCF).

157 lines (156 loc) 7.15 kB
import { Vector } from '../../model/index.js'; import { BitLength } from '../index.js'; import { IntEncoder } from './IntEncoder.js'; import { BooleanEncoder } from './BooleanEncoder.js'; import { FixedVectorEncoder } from './FixedVectorEncoder.js'; import { VectorEncodingType } from './VectorEncodingType.js'; import { DecodingError } from '../../errors/index.js'; export class VendorVectorEncoder { static encode(value) { // collectors for range encoding const ranges = []; let range = []; // since both encodings need the maxId, start with that let retrString = IntEncoder.encode(value.maxId, BitLength.maxId); // bit field will be just the vendors as we walk through the vector let bitField = ''; let rangeIsSmaller; // some math const headerLength = BitLength.maxId + BitLength.encodingType; const bitFieldLength = headerLength + value.maxId; const minRangeLength = (BitLength.vendorId * 2 + BitLength.singleOrRange + BitLength.numEntries); // gets larger as we walk through the vector let rangeLength = headerLength + BitLength.numEntries; // walk through every value in the vector value.forEach((curValue, i) => { // build our bitfield no matter what bitField += BooleanEncoder.encode(curValue); /** * A range is a minimum of 45 bits, if the number of vendors in this * vector is less than 45 then we know that a bitfield encoding will be * shorter than any range encoding. * * The second check checks while we walk through the vector and abandons * building the ranges once it becomes larger */ rangeIsSmaller = (value.maxId > minRangeLength && rangeLength < bitFieldLength); /** * if the curValue is true and our rangeLength is less than the bitField * length, we'll continue to push these ranges into the array. Once the * ranges become a larger encoding there is no reason to continue * building the structure because we will be choosing the bitfield * encoding */ if (rangeIsSmaller && curValue) { /** * Look ahead to see if this is the last value in our range */ const nextValue = value.has(i + 1); // if there isn't a next value, then we'll wrap up this range if (!nextValue) { /** * this is the last value of the range, so we'll push it on to the * end into position 1 */ range.push(i); // add to the range length the additional vendorId rangeLength += BitLength.vendorId; // store the array in our bigger array ranges.push(range); // clear the array for the next range range = []; } else if (range.length === 0) { // this is the first value for this range range.push(i); // update our count with new range overhead rangeLength += BitLength.singleOrRange; rangeLength += BitLength.vendorId; } } }); if (rangeIsSmaller) { retrString += String(VectorEncodingType.RANGE); retrString += this.buildRangeEncoding(ranges); } else { retrString += String(VectorEncodingType.FIELD); retrString += bitField; } return retrString; } static decode(value, version) { let vector; let index = 0; const maxId = IntEncoder.decode(value.substr(index, BitLength.maxId), BitLength.maxId); index += BitLength.maxId; const encodingType = IntEncoder.decode(value.charAt(index), BitLength.encodingType); index += BitLength.encodingType; /** * Range is handled in batches so we'll need a different decoding scheme */ if (encodingType === VectorEncodingType.RANGE) { vector = new Vector(); if (version === 1) { if (value.substr(index, 1) === '1') { throw new DecodingError('Unable to decode default consent=1'); } // jump over the default encoding index++; } const numEntries = IntEncoder.decode(value.substr(index, BitLength.numEntries), BitLength.numEntries); index += BitLength.numEntries; // loop through each group of entries for (let i = 0; i < numEntries; i++) { // Ranges can represent a single id or a range of ids. const isIdRange = BooleanEncoder.decode(value.charAt(index)); index += BitLength.singleOrRange; /** * regardless of whether or not it's a single entry or range, the next * set of bits is a vendor ID */ const firstId = IntEncoder.decode(value.substr(index, BitLength.vendorId), BitLength.vendorId); index += BitLength.vendorId; // if it's a range, the next set of bits is the second id if (isIdRange) { const secondId = IntEncoder.decode(value.substr(index, BitLength.vendorId), BitLength.vendorId); index += BitLength.vendorId; // we'll need to set or unset all the vendor ids between the first and second for (let j = firstId; j <= secondId; j++) { vector.set(j); } } else { vector.set(firstId); } } } else { const bitField = value.substr(index, maxId); index += maxId; vector = FixedVectorEncoder.decode(bitField, maxId); } vector.bitLength = index; return vector; } static buildRangeEncoding(ranges) { // describe the number of entries to follow const numEntries = ranges.length; let rangeString = IntEncoder.encode(numEntries, BitLength.numEntries); // each range ranges.forEach((range) => { // is this range a single? const single = (range.length === 1); // first is the indicator of whether this is a single id or range (two) // 0 is single and range is 1 rangeString += BooleanEncoder.encode(!single); // second is the first (or only) vendorId rangeString += IntEncoder.encode(range[0], BitLength.vendorId); if (!single) { // add the second id if it exists rangeString += IntEncoder.encode(range[1], BitLength.vendorId); } }); return rangeString; } }