Skip to content

Commit

Permalink
Merge pull request #421 from marco-prontera/fix-purpose-restriction
Browse files Browse the repository at this point in the history
[#419] Fix Purpose Restriction (narrowedVendors)
  • Loading branch information
sevriugin authored Sep 21, 2023
2 parents 205a69d + 5a9d1d8 commit f994e78
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 50 deletions.
27 changes: 16 additions & 11 deletions modules/core/src/encoder/field/PurposeRestrictionVectorEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ export class PurposeRestrictionVectorEncoder {
// if the vector is empty we'll just return a string with just the numRestricitons being 0
if (!prVector.isEmpty()) {

const gvlVendorIds = Array.from(prVector.gvl.vendorIds);
const nextGvlVendor = (vendorId, lastVendorId): number => {

const gvlHasVendorBetween = (vendorId: number, nextVendorId: number): boolean => {
for (let nextId = vendorId + 1; nextId <= lastVendorId; nextId++) {

const firstIndex = gvlVendorIds.indexOf(vendorId);
const nextIndex = gvlVendorIds.indexOf(nextVendorId);
if (prVector.gvl.vendorIds.has(nextId)) {

const res = nextIndex - firstIndex;
return nextId;

return res > 1;
}

}

return vendorId;

};

Expand Down Expand Up @@ -60,7 +63,7 @@ export class PurposeRestrictionVectorEncoder {
/**
* either end of the loop or there are GVL vendor IDs before the next one
*/
if (i === len - 1 || gvlHasVendorBetween(vendorId, vendors[i + 1])) {
if (i === len - 1 || vendors[i + 1] > nextGvlVendor(vendorId, vendors[len - 1])) {

/**
* it's a range entry if we've got something other than the start
Expand Down Expand Up @@ -141,13 +144,15 @@ export class PurposeRestrictionVectorEncoder {

}

// required to preserve the default behavior (includes also vendors ids that doesn't exist)
const vendorIds = Array.from({length: endVendorId - startOrOnlyVendorId + 1}, (_, index) => startOrOnlyVendorId + index);
vector.restrictPurposeToLegalBasis(purposeRestriction, vendorIds);
for ( let k: number = startOrOnlyVendorId; k <= endVendorId; k++) {

vector.add(k, purposeRestriction);

}

} else {

vector.restrictPurposeToLegalBasis(purposeRestriction, [startOrOnlyVendorId]);
vector.add(startOrOnlyVendorId, purposeRestriction);

}

Expand Down
82 changes: 43 additions & 39 deletions modules/core/src/model/PurposeRestrictionVector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,20 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector

switch (restrictionType) {

/**
* If the vendor has the purposeId in flexiblePurposes and it is
* listed as a legitimate interest purpose we can set the
* override to require consent.
*/
/**
* If the vendor has the purposeId in flexiblePurposes and it is
* listed as a legitimate interest purpose we can set the
* override to require consent.
*/
case RestrictionType.REQUIRE_CONSENT:
result = (vendor.flexiblePurposes.includes(purposeId) && vendor.legIntPurposes.includes(purposeId));
break;

/**
* If the vendor has the purposeId in flexiblePurposes and it is
* listed as a consent purpose we can set the
* override to require legitimate interest.
*/
/**
* If the vendor has the purposeId in flexiblePurposes and it is
* listed as a consent purpose we can set the
* override to require legitimate interest.
*/
case RestrictionType.REQUIRE_LI:
result = (vendor.flexiblePurposes.includes(purposeId) && vendor.purposes.includes(purposeId));
break;
Expand Down Expand Up @@ -131,30 +131,35 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector
* restrictPurposeToLegalBasis - adds all Vendors under a given Purpose Restriction
*
* @param {PurposeRestriction} purposeRestriction
* @param {number[]|null|undefined} vendorsIds
* @return {void}
*/
public restrictPurposeToLegalBasis(purposeRestriction: PurposeRestriction, vendorsIds: number[] = Array.from(this.gvl.vendorIds)): void {
public restrictPurposeToLegalBasis(purposeRestriction: PurposeRestriction): void {

const vendors = Array.from(this.gvl.vendorIds);
const hash: string = purposeRestriction.hash;
const lastEntry = vendors[vendors.length - 1];

/**
* Create an ordered array of vendor IDs from `1` (the minimum value for Vendor ID) to `lastEntry`
*/
const values = [...Array(lastEntry).keys()].map( (i) => i + 1);

if (!this.has(hash)) {

this.map.set(hash, new Set(vendorsIds));
this.map.set(hash, new Set(values)); // use static method `build` to create a `BST` from the ordered array of IDs
this.bitLength = 0;

} else {

const currentMap = this.map.get(hash);

for (const vendorId of vendorsIds) {
for (let i = 1; i <= lastEntry; i++) {

/**
* Previously I had a check here to remove a duplicate value, but because
* we're using a tree the value is guaranteed to be unique so there is no
* need to add an additional de-duplication here.
*/
currentMap.add(vendorId);

this.map.get(hash).add(i);

}

Expand Down Expand Up @@ -182,17 +187,17 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector

if (this.has(hash)) {

vendorIds = Array.from(this.map.get(hash));
vendorIds = Array.from(this.map.get(hash) as Set<number>);

}

} else {

const vendorSet = new Set<number>();

this.map.forEach((vendorIds: Set<number>): void => {
this.map.forEach((set: Set<number>): void => {

Array.from(vendorIds).forEach((vendorId: number): void => {
set.forEach((vendorId: number): void => {

vendorSet.add(vendorId);

Expand Down Expand Up @@ -262,50 +267,49 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector
*/
public getMaxVendorId(): number {

let result = 0;
let retr = 0;

this.map.forEach((purposeRestrictionVendorIds: Set<number>): void => {
this.map.forEach((set: Set<number>): void => {

const vendorIds = Array.from(purposeRestrictionVendorIds);
result = Math.max(vendorIds[vendorIds.length - 1], result);
retr = Math.max(Array.from(set)[set.size - 1], retr);

});

return result;
return retr;

}

public getRestrictions(vendorId?: number): PurposeRestriction[] {

const result: PurposeRestriction[] = [];
const retr: PurposeRestriction[] = [];

this.map.forEach((vendorIds: Set<number>, hash: string): void => {
this.map.forEach((set: Set<number>, hash: string): void => {

if (vendorId) {

if (vendorIds.has(vendorId)) {
if (set.has(vendorId)) {

result.push(PurposeRestriction.unHash(hash));
retr.push(PurposeRestriction.unHash(hash));

}

} else {

result.push(PurposeRestriction.unHash(hash));
retr.push(PurposeRestriction.unHash(hash));

}

});

return result;
return retr;

}

public getPurposes(): number[] {

const purposeIds = new Set<number>();

this.map.forEach((vendorIds: Set<number>, hash: string): void => {
this.map.forEach((set: Set<number>, hash: string): void => {

purposeIds.add(PurposeRestriction.unHash(hash).purposeId);

Expand All @@ -325,14 +329,14 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector
public remove(vendorId: number, purposeRestriction: PurposeRestriction): void {

const hash: string = purposeRestriction.hash;
const vendorIds: Set<number> | undefined = this.map.get(hash);
const set: Set<number> | undefined = this.map.get(hash);

if (vendorIds) {
if (set) {

vendorIds.delete(vendorId);
set.delete(vendorId);

// if it's empty let's delete the key so it doesn't show up empty
if (vendorIds.size == 0) {
if (set.size == 0) {

this.map.delete(hash);
this.bitLength = 0;
Expand Down Expand Up @@ -360,16 +364,16 @@ export class PurposeRestrictionVector extends Cloneable<PurposeRestrictionVector
* go through and remove some if they're not valid
*/

this.map.forEach((vendorIds: Set<number>, hash: string): void => {
this.map.forEach((set: Set<number>, hash: string): void => {

const purposeRestriction: PurposeRestriction = PurposeRestriction.unHash(hash);
const vendors: number[] = Array.from(vendorIds);
const vendors: number[] = Array.from(set);

vendors.forEach((vendorId: number): void => {

if (!this.isOkToHave(purposeRestriction.restrictionType, purposeRestriction.purposeId, vendorId)) {

vendorIds.delete(vendorId);
set.delete(vendorId);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {PurposeRestrictionVector, PurposeRestriction, RestrictionType} from '../
import {BitLength} from '../../../src/encoder';
import {GVL} from '../../../src/GVL';
import {makeRandomInt, sameDataDiffRef, GVLFactory} from '@iabtechlabtcf/testing';
import {BooleanEncoder} from '../../../lib/mjs';

const randomize = (ar: number[]): number[] => {

Expand Down Expand Up @@ -160,6 +161,102 @@ describe('encoder/field->PurposeRestrictionVectorEncoder', (): void => {

});

it('should create restriction groups and all with vendor range if only consecutive vendors from the GVL with narrowed vendors', (): void => {

const narrowVendors: number[] = randomize([
173, 1204, 458, 855, 1228, 788, 1189, 885, 136, 251, 178, 1060, 667, 138, 157,
517, 1020, 1025, 686, 737, 438, 1145, 956, 1126, 1122, 1003, 459, 948, 92, 58,
40, 830, 767, 649, 57, 953, 37, 377, 539, 1019, 50, 790, 39, 14, 93, 22, 81,
264, 565, 384, 224, 6, 66, 507, 410, 195, 27, 259, 785, 793, 148, 23, 780,
733, 354, 797, 394, 783, 561, 598, 956, 46, 907, 647, 983, 771, 801, 12, 508,
30, 87, 212, 462, 128, 185, 86, 625, 94, 163, 422, 792, 329, 945, 315, 810,
243, 285, 416, 77, 56, 591, 630, 868, 91, 1026, 875, 938, 440, 209, 707, 1029,
735, 852, 126, 1075, 434, 584, 110, 796, 168, 929, 8, 213, 183, 24, 1050, 312,
1, 120, 795, 100, 78, 323, 159, 119, 262, 870, 731, 328, 845, 758, 536, 967,
580, 755, 657, 98, 295, 1031, 881, 856, 431, 316, 131, 921, 606, 253, 10, 730,
946, 333, 452, 150, 278, 436, 991, 252, 294, 62, 747, 528, 63, 544, 424, 871,
67, 804, 254, 97, 109, 95, 511, 846, 153, 1081, 52, 102, 599, 228, 142, 475,
152, 703, 101, 230, 302, 898, 72, 34, 982, 521, 129, 468, 812, 373, 31, 509,
241, 883, 602, 69, 488, 385, 772, 559, 164, 139, 361, 766, 903, 140, 177, 297,
887, 952, 226, 76, 808, 835, 11, 60, 290, 192, 800, 787, 922, 759, 232, 71, 4,
16, 506, 350, 371, 84, 80, 111, 276, 261, 68, 82, 161, 45, 115, 531, 246, 134,
937, 61, 857, 104, 590, 13, 1077, 655, 165, 114, 484, 275, 42, 89, 786, 132,
44, 345, 577, 382, 21, 819, 423, 28, 242, 1015, 829, 831, 36, 162, 25, 512, 7,
744, 190, 380, 1009, 774, 284, 282, 281, 32, 70, 154, 210, 301,
]);

const prVector: PurposeRestrictionVector = new PurposeRestrictionVector();

prVector.gvl = GVLFactory.getVersion(17, 'v2.2') as unknown as GVL;

prVector.gvl.narrowVendorsTo(narrowVendors);

const restrictedPurposeIds = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
restrictedPurposeIds.forEach((el) => {

prVector.restrictPurposeToLegalBasis(
new PurposeRestriction(el, RestrictionType.REQUIRE_CONSENT),
);

});

/**
* ORDER:
* num pub restrictions
* purposeId
* restrictionType
* numEntries
* ----
* singleOrRange
* startVendorId
* endVendorId (may not be there)
*/
const encoded: string = PurposeRestrictionVectorEncoder.encode(prVector);
let index = 0;

expect(encoded, 'encoded string').not.to.be.empty;

// num restrictions
const numRestrictions: number = IntEncoder.decode(encoded.substr(index, BitLength.numRestrictions), BitLength.numRestrictions);

expect(numRestrictions, 'numRestrictions').to.equal(10);
index += BitLength.numRestrictions;

restrictedPurposeIds.forEach((purposeId) => {

// purposeId
const purpId: number = IntEncoder.decode(encoded.substr(index, BitLength.purposeId), BitLength.purposeId);

expect(purpId, 'purpId').to.equal(purposeId);
index += BitLength.purposeId;
// restrictionType
const restrictionType: number = IntEncoder.decode(encoded.substr(index, BitLength.restrictionType), BitLength.restrictionType);

expect(restrictionType, 'restrictionType').to.equal(RestrictionType.REQUIRE_CONSENT);
index += BitLength.restrictionType;

// numEntries
const numEntries: number = IntEncoder.decode(encoded.substr(index, BitLength.numEntries), BitLength.numEntries);
expect(numEntries, 'numEntries').to.equal(1);

index += BitLength.numEntries;

const isARange: boolean = BooleanEncoder.decode(encoded.substr(index, BitLength.anyBoolean));
expect(isARange, 'isARange').to.equal(true);
index += BitLength.anyBoolean;

const startVendorId: number = IntEncoder.decode(encoded.substr(index, BitLength.vendorId), BitLength.vendorId);
expect(startVendorId).to.equal(1);
index += BitLength.vendorId;

const endVendorId: number = IntEncoder.decode(encoded.substr(index, BitLength.vendorId), BitLength.vendorId);
expect(typeof endVendorId).to.equal('number');
index += BitLength.vendorId;

});

});

it('should create one restriction group and one vendor range', (): void => {

const purposeId = 2;
Expand Down
1 change: 1 addition & 0 deletions modules/testing/src/vendorlist/v2.2/vendor-list-v17.json

Large diffs are not rendered by default.

0 comments on commit f994e78

Please sign in to comment.