From 79b02956497c29b2721f261fc93ea3232ccd4a99 Mon Sep 17 00:00:00 2001 From: Chad Huff Date: Mon, 15 Jun 2026 09:28:59 -0600 Subject: [PATCH] Add support for Maryland (24), Indiana (25), Kentucky (26), Rhode Island (27) Implements four new MSPA state sections following the existing US-MN pattern: US-MD (Core + GPC), and US-IN/US-KY/US-RI (Core + Sensitive Data Consents). Each has per-state fields, segments, a section class, registration in Sections.ts / GppModel.ts, barrel exports, and per-section round-trip tests (registered in .mocharc.json) plus coverage in GppModel.test.ts. Co-Authored-By: Claude Opus 4.8 --- modules/cmpapi/.mocharc.json | 4 + modules/cmpapi/src/encoder/GppModel.ts | 40 +++++++ modules/cmpapi/src/encoder/field/UsInField.ts | 29 +++++ modules/cmpapi/src/encoder/field/UsKyField.ts | 29 +++++ modules/cmpapi/src/encoder/field/UsMdField.ts | 29 +++++ modules/cmpapi/src/encoder/field/UsRiField.ts | 29 +++++ modules/cmpapi/src/encoder/field/index.ts | 4 + .../cmpapi/src/encoder/section/Sections.ts | 14 ++- modules/cmpapi/src/encoder/section/UsIn.ts | 77 +++++++++++++ modules/cmpapi/src/encoder/section/UsKy.ts | 77 +++++++++++++ modules/cmpapi/src/encoder/section/UsMd.ts | 77 +++++++++++++ modules/cmpapi/src/encoder/section/UsRi.ts | 77 +++++++++++++ modules/cmpapi/src/encoder/section/index.ts | 4 + .../src/encoder/segment/UsInCoreSegment.ts | 103 ++++++++++++++++++ .../UsInSensitiveDataConsentSegment.ts | 73 +++++++++++++ .../src/encoder/segment/UsKyCoreSegment.ts | 102 +++++++++++++++++ .../UsKySensitiveDataConsentSegment.ts | 73 +++++++++++++ .../src/encoder/segment/UsMdCoreSegment.ts | 99 +++++++++++++++++ .../src/encoder/segment/UsMdGpcSegment.ts | 56 ++++++++++ .../src/encoder/segment/UsRiCoreSegment.ts | 102 +++++++++++++++++ .../UsRiSensitiveDataConsentSegment.ts | 73 +++++++++++++ modules/cmpapi/src/encoder/segment/index.ts | 8 ++ modules/cmpapi/test/GppModel.test.ts | 20 +++- .../cmpapi/test/encoder/section/UsIn.test.ts | 63 +++++++++++ .../cmpapi/test/encoder/section/UsKy.test.ts | 58 ++++++++++ .../cmpapi/test/encoder/section/UsMd.test.ts | 63 +++++++++++ .../cmpapi/test/encoder/section/UsRi.test.ts | 58 ++++++++++ 27 files changed, 1438 insertions(+), 3 deletions(-) create mode 100644 modules/cmpapi/src/encoder/field/UsInField.ts create mode 100644 modules/cmpapi/src/encoder/field/UsKyField.ts create mode 100644 modules/cmpapi/src/encoder/field/UsMdField.ts create mode 100644 modules/cmpapi/src/encoder/field/UsRiField.ts create mode 100644 modules/cmpapi/src/encoder/section/UsIn.ts create mode 100644 modules/cmpapi/src/encoder/section/UsKy.ts create mode 100644 modules/cmpapi/src/encoder/section/UsMd.ts create mode 100644 modules/cmpapi/src/encoder/section/UsRi.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsInCoreSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsInSensitiveDataConsentSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsKyCoreSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsKySensitiveDataConsentSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsMdCoreSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsMdGpcSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsRiCoreSegment.ts create mode 100644 modules/cmpapi/src/encoder/segment/UsRiSensitiveDataConsentSegment.ts create mode 100644 modules/cmpapi/test/encoder/section/UsIn.test.ts create mode 100644 modules/cmpapi/test/encoder/section/UsKy.test.ts create mode 100644 modules/cmpapi/test/encoder/section/UsMd.test.ts create mode 100644 modules/cmpapi/test/encoder/section/UsRi.test.ts diff --git a/modules/cmpapi/.mocharc.json b/modules/cmpapi/.mocharc.json index 851e047..c4a061a 100644 --- a/modules/cmpapi/.mocharc.json +++ b/modules/cmpapi/.mocharc.json @@ -33,6 +33,9 @@ "./test/encoder/section/UsDe.test.ts", "./test/encoder/section/UsFl.test.ts", "./test/encoder/section/UsIa.test.ts", + "./test/encoder/section/UsIn.test.ts", + "./test/encoder/section/UsKy.test.ts", + "./test/encoder/section/UsMd.test.ts", "./test/encoder/section/UsMn.test.ts", "./test/encoder/section/UsMt.test.ts", "./test/encoder/section/UsNat.test.ts", @@ -40,6 +43,7 @@ "./test/encoder/section/UsNh.test.ts", "./test/encoder/section/UsNj.test.ts", "./test/encoder/section/UsOr.test.ts", + "./test/encoder/section/UsRi.test.ts", "./test/encoder/section/UsTn.test.ts", "./test/encoder/section/UsTx.test.ts", "./test/encoder/section/UsUt.test.ts", diff --git a/modules/cmpapi/src/encoder/GppModel.ts b/modules/cmpapi/src/encoder/GppModel.ts index 20b105f..920aa5b 100644 --- a/modules/cmpapi/src/encoder/GppModel.ts +++ b/modules/cmpapi/src/encoder/GppModel.ts @@ -24,6 +24,10 @@ import { UsNh } from "./section/UsNh.js"; import { UsNj } from "./section/UsNj.js"; import { UsTn } from "./section/UsTn.js"; import { UsMn } from "./section/UsMn.js"; +import { UsMd } from "./section/UsMd.js"; +import { UsIn } from "./section/UsIn.js"; +import { UsKy } from "./section/UsKy.js"; +import { UsRi } from "./section/UsRi.js"; export class GppModel { private sections = new Map(); @@ -107,6 +111,18 @@ export class GppModel { } else if (sectionName === UsMn.NAME) { section = new UsMn(); this.sections.set(UsMn.NAME, section); + } else if (sectionName === UsMd.NAME) { + section = new UsMd(); + this.sections.set(UsMd.NAME, section); + } else if (sectionName === UsIn.NAME) { + section = new UsIn(); + this.sections.set(UsIn.NAME, section); + } else if (sectionName === UsKy.NAME) { + section = new UsKy(); + this.sections.set(UsKy.NAME, section); + } else if (sectionName === UsRi.NAME) { + section = new UsRi(); + this.sections.set(UsRi.NAME, section); } } else { section = this.sections.get(sectionName); @@ -342,6 +358,18 @@ export class GppModel { } else if (sectionIds[i] === UsMn.ID) { let section = new UsMn(encodedSections[i + 1]); sections.set(UsMn.NAME, section); + } else if (sectionIds[i] === UsMd.ID) { + let section = new UsMd(encodedSections[i + 1]); + sections.set(UsMd.NAME, section); + } else if (sectionIds[i] === UsIn.ID) { + let section = new UsIn(encodedSections[i + 1]); + sections.set(UsIn.NAME, section); + } else if (sectionIds[i] === UsKy.ID) { + let section = new UsKy(encodedSections[i + 1]); + sections.set(UsKy.NAME, section); + } else if (sectionIds[i] === UsRi.ID) { + let section = new UsRi(encodedSections[i + 1]); + sections.set(UsRi.NAME, section); } } } @@ -450,6 +478,18 @@ export class GppModel { } else if (sectionName === UsMn.NAME) { section = new UsMn(); this.sections.set(UsMn.NAME, section); + } else if (sectionName === UsMd.NAME) { + section = new UsMd(); + this.sections.set(UsMd.NAME, section); + } else if (sectionName === UsIn.NAME) { + section = new UsIn(); + this.sections.set(UsIn.NAME, section); + } else if (sectionName === UsKy.NAME) { + section = new UsKy(); + this.sections.set(UsKy.NAME, section); + } else if (sectionName === UsRi.NAME) { + section = new UsRi(); + this.sections.set(UsRi.NAME, section); } } else { section = this.sections.get(sectionName); diff --git a/modules/cmpapi/src/encoder/field/UsInField.ts b/modules/cmpapi/src/encoder/field/UsInField.ts new file mode 100644 index 0000000..b86e0cc --- /dev/null +++ b/modules/cmpapi/src/encoder/field/UsInField.ts @@ -0,0 +1,29 @@ +export enum UsInField { + MSPA_VERSION = "MspaVersion", + MSPA_COVERED_TRANSACTION = "MspaCoveredTransaction", + MSPA_MODE = "MspaMode", + PROCESSING_NOTICE = "ProcessingNotice", + SALE_OPT_OUT_NOTICE = "SaleOptOutNotice", + TARGETED_ADVERTISING_OPT_OUT_NOTICE = "TargetedAdvertisingOptOutNotice", + SALE_OPT_OUT = "SaleOptOut", + TARGETED_ADVERTISING_OPT_OUT = "TargetedAdvertisingOptOut", + KNOWN_CHILD_SENSITIVE_DATA_CONSENTS = "KnownChildSensitiveDataConsents", + ADDITIONAL_DATA_PROCESSING_CONSENT = "AdditionalDataProcessingConsent", + SENSITIVE_DATA_PROCESSING = "SensitiveDataProcessing", + SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED = "SensitiveDataConsentSegmentIncluded", +} + +export const UsIn_CORE_SEGMENT_FIELD_NAMES = [ + UsInField.MSPA_VERSION, + UsInField.MSPA_COVERED_TRANSACTION, + UsInField.MSPA_MODE, + UsInField.PROCESSING_NOTICE, + UsInField.SALE_OPT_OUT_NOTICE, + UsInField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, + UsInField.SALE_OPT_OUT, + UsInField.TARGETED_ADVERTISING_OPT_OUT, + UsInField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, + UsInField.ADDITIONAL_DATA_PROCESSING_CONSENT, +]; + +export const UsIn_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES = [UsInField.SENSITIVE_DATA_PROCESSING]; diff --git a/modules/cmpapi/src/encoder/field/UsKyField.ts b/modules/cmpapi/src/encoder/field/UsKyField.ts new file mode 100644 index 0000000..9bee37d --- /dev/null +++ b/modules/cmpapi/src/encoder/field/UsKyField.ts @@ -0,0 +1,29 @@ +export enum UsKyField { + MSPA_VERSION = "MspaVersion", + MSPA_COVERED_TRANSACTION = "MspaCoveredTransaction", + MSPA_MODE = "MspaMode", + PROCESSING_NOTICE = "ProcessingNotice", + SALE_OPT_OUT_NOTICE = "SaleOptOutNotice", + TARGETED_ADVERTISING_OPT_OUT_NOTICE = "TargetedAdvertisingOptOutNotice", + SALE_OPT_OUT = "SaleOptOut", + TARGETED_ADVERTISING_OPT_OUT = "TargetedAdvertisingOptOut", + KNOWN_CHILD_SENSITIVE_DATA_CONSENTS = "KnownChildSensitiveDataConsents", + ADDITIONAL_DATA_PROCESSING_CONSENT = "AdditionalDataProcessingConsent", + SENSITIVE_DATA_PROCESSING = "SensitiveDataProcessing", + SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED = "SensitiveDataConsentSegmentIncluded", +} + +export const UsKy_CORE_SEGMENT_FIELD_NAMES = [ + UsKyField.MSPA_VERSION, + UsKyField.MSPA_COVERED_TRANSACTION, + UsKyField.MSPA_MODE, + UsKyField.PROCESSING_NOTICE, + UsKyField.SALE_OPT_OUT_NOTICE, + UsKyField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, + UsKyField.SALE_OPT_OUT, + UsKyField.TARGETED_ADVERTISING_OPT_OUT, + UsKyField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, + UsKyField.ADDITIONAL_DATA_PROCESSING_CONSENT, +]; + +export const UsKy_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES = [UsKyField.SENSITIVE_DATA_PROCESSING]; diff --git a/modules/cmpapi/src/encoder/field/UsMdField.ts b/modules/cmpapi/src/encoder/field/UsMdField.ts new file mode 100644 index 0000000..bff99ba --- /dev/null +++ b/modules/cmpapi/src/encoder/field/UsMdField.ts @@ -0,0 +1,29 @@ +export enum UsMdField { + MSPA_VERSION = "MspaVersion", + MSPA_COVERED_TRANSACTION = "MspaCoveredTransaction", + MSPA_MODE = "MspaMode", + PROCESSING_NOTICE = "ProcessingNotice", + SALE_OPT_OUT_NOTICE = "SaleOptOutNotice", + TARGETED_ADVERTISING_OPT_OUT_NOTICE = "TargetedAdvertisingOptOutNotice", + SALE_OPT_OUT = "SaleOptOut", + TARGETED_ADVERTISING_OPT_OUT = "TargetedAdvertisingOptOut", + ADDITIONAL_DATA_PROCESSING_CONSENT = "AdditionalDataProcessingConsent", + + GPC_SEGMENT_TYPE = "GpcSegmentType", + GPC_SEGMENT_INCLUDED = "GpcSegmentIncluded", + GPC = "Gpc", +} + +export const UsMd_CORE_SEGMENT_FIELD_NAMES = [ + UsMdField.MSPA_VERSION, + UsMdField.MSPA_COVERED_TRANSACTION, + UsMdField.MSPA_MODE, + UsMdField.PROCESSING_NOTICE, + UsMdField.SALE_OPT_OUT_NOTICE, + UsMdField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, + UsMdField.SALE_OPT_OUT, + UsMdField.TARGETED_ADVERTISING_OPT_OUT, + UsMdField.ADDITIONAL_DATA_PROCESSING_CONSENT, +]; + +export const UsMd_GPC_SEGMENT_FIELD_NAMES = [UsMdField.GPC_SEGMENT_TYPE, UsMdField.GPC]; diff --git a/modules/cmpapi/src/encoder/field/UsRiField.ts b/modules/cmpapi/src/encoder/field/UsRiField.ts new file mode 100644 index 0000000..f40b47d --- /dev/null +++ b/modules/cmpapi/src/encoder/field/UsRiField.ts @@ -0,0 +1,29 @@ +export enum UsRiField { + MSPA_VERSION = "MspaVersion", + MSPA_COVERED_TRANSACTION = "MspaCoveredTransaction", + MSPA_MODE = "MspaMode", + PROCESSING_NOTICE = "ProcessingNotice", + SALE_OPT_OUT_NOTICE = "SaleOptOutNotice", + TARGETED_ADVERTISING_OPT_OUT_NOTICE = "TargetedAdvertisingOptOutNotice", + SALE_OPT_OUT = "SaleOptOut", + TARGETED_ADVERTISING_OPT_OUT = "TargetedAdvertisingOptOut", + KNOWN_CHILD_SENSITIVE_DATA_CONSENTS = "KnownChildSensitiveDataConsents", + ADDITIONAL_DATA_PROCESSING_CONSENT = "AdditionalDataProcessingConsent", + SENSITIVE_DATA_PROCESSING = "SensitiveDataProcessing", + SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED = "SensitiveDataConsentSegmentIncluded", +} + +export const UsRi_CORE_SEGMENT_FIELD_NAMES = [ + UsRiField.MSPA_VERSION, + UsRiField.MSPA_COVERED_TRANSACTION, + UsRiField.MSPA_MODE, + UsRiField.PROCESSING_NOTICE, + UsRiField.SALE_OPT_OUT_NOTICE, + UsRiField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, + UsRiField.SALE_OPT_OUT, + UsRiField.TARGETED_ADVERTISING_OPT_OUT, + UsRiField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, + UsRiField.ADDITIONAL_DATA_PROCESSING_CONSENT, +]; + +export const UsRi_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES = [UsRiField.SENSITIVE_DATA_PROCESSING]; diff --git a/modules/cmpapi/src/encoder/field/index.ts b/modules/cmpapi/src/encoder/field/index.ts index 8d58942..670ab40 100644 --- a/modules/cmpapi/src/encoder/field/index.ts +++ b/modules/cmpapi/src/encoder/field/index.ts @@ -10,6 +10,9 @@ export * from "./UsCtField.js"; export * from "./UsDeField.js"; export * from "./UsFlField.js"; export * from "./UsIaField.js"; +export * from "./UsInField.js"; +export * from "./UsKyField.js"; +export * from "./UsMdField.js"; export * from "./UsMnField.js"; export * from "./UsMtField.js"; export * from "./UsNatField.js"; @@ -17,6 +20,7 @@ export * from "./UsNeField.js"; export * from "./UsNhField.js"; export * from "./UsNjField.js"; export * from "./UsOrField.js"; +export * from "./UsRiField.js"; export * from "./UsTnField.js"; export * from "./UsTxField.js"; export * from "./UsUtField.js"; diff --git a/modules/cmpapi/src/encoder/section/Sections.ts b/modules/cmpapi/src/encoder/section/Sections.ts index 17e908e..f52a21a 100644 --- a/modules/cmpapi/src/encoder/section/Sections.ts +++ b/modules/cmpapi/src/encoder/section/Sections.ts @@ -18,6 +18,10 @@ import { UsNh } from "./UsNh.js"; import { UsNj } from "./UsNj.js"; import { UsTn } from "./UsTn.js"; import { UsMn } from "./UsMn.js"; +import { UsMd } from "./UsMd.js"; +import { UsIn } from "./UsIn.js"; +import { UsKy } from "./UsKy.js"; +import { UsRi } from "./UsRi.js"; export class Sections { public static SECTION_ID_NAME_MAP = new Map([ @@ -41,6 +45,10 @@ export class Sections { [UsNj.ID, UsNj.NAME], [UsTn.ID, UsTn.NAME], [UsMn.ID, UsMn.NAME], + [UsMd.ID, UsMd.NAME], + [UsIn.ID, UsIn.NAME], + [UsKy.ID, UsKy.NAME], + [UsRi.ID, UsRi.NAME], ]); public static SECTION_ORDER = [ TcfEuV2.NAME, @@ -62,6 +70,10 @@ export class Sections { UsNh.NAME, UsNj.NAME, UsTn.NAME, - UsMn.NAME + UsMn.NAME, + UsMd.NAME, + UsIn.NAME, + UsKy.NAME, + UsRi.NAME ]; } diff --git a/modules/cmpapi/src/encoder/section/UsIn.ts b/modules/cmpapi/src/encoder/section/UsIn.ts new file mode 100644 index 0000000..5d817b4 --- /dev/null +++ b/modules/cmpapi/src/encoder/section/UsIn.ts @@ -0,0 +1,77 @@ +import { UsInField } from "../field/UsInField.js"; +import { EncodableSegment } from "../segment/EncodableSegment.js"; +import { UsInCoreSegment } from "../segment/UsInCoreSegment.js"; +import { UsInSensitiveDataConsentSegment } from "../segment/UsInSensitiveDataConsentSegment.js"; +import { AbstractLazilyEncodableSection } from "./AbstractLazilyEncodableSection.js"; + +export class UsIn extends AbstractLazilyEncodableSection { + public static readonly ID = 25; + public static readonly VERSION = 1; + public static readonly NAME = "usin"; + + constructor(encodedString?: string) { + super(); + if (encodedString && encodedString.length > 0) { + this.decode(encodedString); + } + } + + //Overriden + public getId(): number { + return UsIn.ID; + } + + //Overriden + public getName(): string { + return UsIn.NAME; + } + + //Override + public getVersion(): number { + return UsIn.VERSION; + } + + //Overriden + protected initializeSegments(): EncodableSegment[] { + let segments: EncodableSegment[] = []; + segments.push(new UsInCoreSegment()); + segments.push(new UsInSensitiveDataConsentSegment()); + return segments; + } + + //Overriden + protected decodeSection(encodedString: string): EncodableSegment[] { + let segments: EncodableSegment[] = this.initializeSegments(); + + if (encodedString != null && encodedString.length !== 0) { + let encodedSegments = encodedString.split("."); + + if (encodedSegments.length > 0) { + segments[0].decode(encodedSegments[0]); + } + + if (encodedSegments.length > 1) { + segments[1].setFieldValue(UsInField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, true); + segments[1].decode(encodedSegments[1]); + } else { + segments[1].setFieldValue(UsInField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + } + } + + return segments; + } + + // Overriden + protected encodeSection(segments: EncodableSegment[]): string { + let encodedSegments: string[] = []; + + if (segments.length >= 1) { + encodedSegments.push(segments[0].encode()); + if (segments.length >= 2 && segments[1].getFieldValue(UsInField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED) === true) { + encodedSegments.push(segments[1].encode()); + } + } + + return encodedSegments.join("."); + } +} diff --git a/modules/cmpapi/src/encoder/section/UsKy.ts b/modules/cmpapi/src/encoder/section/UsKy.ts new file mode 100644 index 0000000..04eaee8 --- /dev/null +++ b/modules/cmpapi/src/encoder/section/UsKy.ts @@ -0,0 +1,77 @@ +import { UsKyField } from "../field/UsKyField.js"; +import { EncodableSegment } from "../segment/EncodableSegment.js"; +import { UsKyCoreSegment } from "../segment/UsKyCoreSegment.js"; +import { UsKySensitiveDataConsentSegment } from "../segment/UsKySensitiveDataConsentSegment.js"; +import { AbstractLazilyEncodableSection } from "./AbstractLazilyEncodableSection.js"; + +export class UsKy extends AbstractLazilyEncodableSection { + public static readonly ID = 26; + public static readonly VERSION = 1; + public static readonly NAME = "usky"; + + constructor(encodedString?: string) { + super(); + if (encodedString && encodedString.length > 0) { + this.decode(encodedString); + } + } + + //Overriden + public getId(): number { + return UsKy.ID; + } + + //Overriden + public getName(): string { + return UsKy.NAME; + } + + //Override + public getVersion(): number { + return UsKy.VERSION; + } + + //Overriden + protected initializeSegments(): EncodableSegment[] { + let segments: EncodableSegment[] = []; + segments.push(new UsKyCoreSegment()); + segments.push(new UsKySensitiveDataConsentSegment()); + return segments; + } + + //Overriden + protected decodeSection(encodedString: string): EncodableSegment[] { + let segments: EncodableSegment[] = this.initializeSegments(); + + if (encodedString != null && encodedString.length !== 0) { + let encodedSegments = encodedString.split("."); + + if (encodedSegments.length > 0) { + segments[0].decode(encodedSegments[0]); + } + + if (encodedSegments.length > 1) { + segments[1].setFieldValue(UsKyField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, true); + segments[1].decode(encodedSegments[1]); + } else { + segments[1].setFieldValue(UsKyField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + } + } + + return segments; + } + + // Overriden + protected encodeSection(segments: EncodableSegment[]): string { + let encodedSegments: string[] = []; + + if (segments.length >= 1) { + encodedSegments.push(segments[0].encode()); + if (segments.length >= 2 && segments[1].getFieldValue(UsKyField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED) === true) { + encodedSegments.push(segments[1].encode()); + } + } + + return encodedSegments.join("."); + } +} diff --git a/modules/cmpapi/src/encoder/section/UsMd.ts b/modules/cmpapi/src/encoder/section/UsMd.ts new file mode 100644 index 0000000..05c4abd --- /dev/null +++ b/modules/cmpapi/src/encoder/section/UsMd.ts @@ -0,0 +1,77 @@ +import { UsMdField } from "../field/UsMdField.js"; +import { EncodableSegment } from "../segment/EncodableSegment.js"; +import { UsMdCoreSegment } from "../segment/UsMdCoreSegment.js"; +import { UsMdGpcSegment } from "../segment/UsMdGpcSegment.js"; +import { AbstractLazilyEncodableSection } from "./AbstractLazilyEncodableSection.js"; + +export class UsMd extends AbstractLazilyEncodableSection { + public static readonly ID = 24; + public static readonly VERSION = 1; + public static readonly NAME = "usmd"; + + constructor(encodedString?: string) { + super(); + if (encodedString && encodedString.length > 0) { + this.decode(encodedString); + } + } + + //Overriden + public getId(): number { + return UsMd.ID; + } + + //Overriden + public getName(): string { + return UsMd.NAME; + } + + //Override + public getVersion(): number { + return UsMd.VERSION; + } + + //Overriden + protected initializeSegments(): EncodableSegment[] { + let segments: EncodableSegment[] = []; + segments.push(new UsMdCoreSegment()); + segments.push(new UsMdGpcSegment()); + return segments; + } + + //Overriden + protected decodeSection(encodedString: string): EncodableSegment[] { + let segments: EncodableSegment[] = this.initializeSegments(); + + if (encodedString != null && encodedString.length !== 0) { + let encodedSegments = encodedString.split("."); + + if (encodedSegments.length > 0) { + segments[0].decode(encodedSegments[0]); + } + + if (encodedSegments.length > 1) { + segments[1].setFieldValue(UsMdField.GPC_SEGMENT_INCLUDED, true); + segments[1].decode(encodedSegments[1]); + } else { + segments[1].setFieldValue(UsMdField.GPC_SEGMENT_INCLUDED, false); + } + } + + return segments; + } + + // Overriden + protected encodeSection(segments: EncodableSegment[]): string { + let encodedSegments: string[] = []; + + if (segments.length >= 1) { + encodedSegments.push(segments[0].encode()); + if (segments.length >= 2 && segments[1].getFieldValue(UsMdField.GPC_SEGMENT_INCLUDED) === true) { + encodedSegments.push(segments[1].encode()); + } + } + + return encodedSegments.join("."); + } +} diff --git a/modules/cmpapi/src/encoder/section/UsRi.ts b/modules/cmpapi/src/encoder/section/UsRi.ts new file mode 100644 index 0000000..89385ac --- /dev/null +++ b/modules/cmpapi/src/encoder/section/UsRi.ts @@ -0,0 +1,77 @@ +import { UsRiField } from "../field/UsRiField.js"; +import { EncodableSegment } from "../segment/EncodableSegment.js"; +import { UsRiCoreSegment } from "../segment/UsRiCoreSegment.js"; +import { UsRiSensitiveDataConsentSegment } from "../segment/UsRiSensitiveDataConsentSegment.js"; +import { AbstractLazilyEncodableSection } from "./AbstractLazilyEncodableSection.js"; + +export class UsRi extends AbstractLazilyEncodableSection { + public static readonly ID = 27; + public static readonly VERSION = 1; + public static readonly NAME = "usri"; + + constructor(encodedString?: string) { + super(); + if (encodedString && encodedString.length > 0) { + this.decode(encodedString); + } + } + + //Overriden + public getId(): number { + return UsRi.ID; + } + + //Overriden + public getName(): string { + return UsRi.NAME; + } + + //Override + public getVersion(): number { + return UsRi.VERSION; + } + + //Overriden + protected initializeSegments(): EncodableSegment[] { + let segments: EncodableSegment[] = []; + segments.push(new UsRiCoreSegment()); + segments.push(new UsRiSensitiveDataConsentSegment()); + return segments; + } + + //Overriden + protected decodeSection(encodedString: string): EncodableSegment[] { + let segments: EncodableSegment[] = this.initializeSegments(); + + if (encodedString != null && encodedString.length !== 0) { + let encodedSegments = encodedString.split("."); + + if (encodedSegments.length > 0) { + segments[0].decode(encodedSegments[0]); + } + + if (encodedSegments.length > 1) { + segments[1].setFieldValue(UsRiField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, true); + segments[1].decode(encodedSegments[1]); + } else { + segments[1].setFieldValue(UsRiField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + } + } + + return segments; + } + + // Overriden + protected encodeSection(segments: EncodableSegment[]): string { + let encodedSegments: string[] = []; + + if (segments.length >= 1) { + encodedSegments.push(segments[0].encode()); + if (segments.length >= 2 && segments[1].getFieldValue(UsRiField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED) === true) { + encodedSegments.push(segments[1].encode()); + } + } + + return encodedSegments.join("."); + } +} diff --git a/modules/cmpapi/src/encoder/section/index.ts b/modules/cmpapi/src/encoder/section/index.ts index e84b3a5..5f364b9 100644 --- a/modules/cmpapi/src/encoder/section/index.ts +++ b/modules/cmpapi/src/encoder/section/index.ts @@ -10,6 +10,9 @@ export * from "./UsCt.js"; export * from "./UsDe.js"; export * from "./UsFl.js"; export * from "./UsIa.js"; +export * from "./UsIn.js"; +export * from "./UsKy.js"; +export * from "./UsMd.js"; export * from "./UsMn.js"; export * from "./UsMt.js"; export * from "./UsNat.js"; @@ -17,6 +20,7 @@ export * from "./UsNe.js"; export * from "./UsNh.js"; export * from "./UsNj.js"; export * from "./UsOr.js"; +export * from "./UsRi.js"; export * from "./UsTn.js"; export * from "./UsTx.js"; export * from "./UsUt.js"; diff --git a/modules/cmpapi/src/encoder/segment/UsInCoreSegment.ts b/modules/cmpapi/src/encoder/segment/UsInCoreSegment.ts new file mode 100644 index 0000000..dbebacb --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsInCoreSegment.ts @@ -0,0 +1,103 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableFixedInteger } from "../datatype/EncodableFixedInteger.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsIn_CORE_SEGMENT_FIELD_NAMES } from "../field/UsInField.js"; +import { UsInField } from "../field/UsInField.js"; +import { UsIn } from "../section/UsIn.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsInCoreSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsIn_CORE_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 0 && n <= 2; + } + })(); + + const nonNullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 1 && n <= 2; + } + })(); + + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsInField.MSPA_VERSION.toString(), new EncodableFixedInteger(6, UsIn.VERSION)); + fields.put( + UsInField.MSPA_COVERED_TRANSACTION.toString(), + new EncodableFixedInteger(2, 1).withValidator(nonNullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.MSPA_MODE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.PROCESSING_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.SALE_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.TARGETED_ADVERTISING_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.SALE_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.TARGETED_ADVERTISING_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsInField.ADDITIONAL_DATA_PROCESSING_CONSENT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsInCoreSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsInSensitiveDataConsentSegment.ts b/modules/cmpapi/src/encoder/segment/UsInSensitiveDataConsentSegment.ts new file mode 100644 index 0000000..7da2c3c --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsInSensitiveDataConsentSegment.ts @@ -0,0 +1,73 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableBoolean } from "../datatype/EncodableBoolean.js"; +import { EncodableFixedIntegerList } from "../datatype/EncodableFixedIntegerList.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsIn_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES } from "../field/UsInField.js"; +import { UsInField } from "../field/UsInField.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsInSensitiveDataConsentSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsIn_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerListValidator = new (class implements Predicate { + test(l: number[]): boolean { + for (let i = 0; i < l.length; i++) { + let n = l[i]; + if (n < 0 || n > 2) { + return false; + } + } + return true; + } + })(); + + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsInField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED.toString(), new EncodableBoolean(true)); + fields.put( + UsInField.SENSITIVE_DATA_PROCESSING.toString(), + new EncodableFixedIntegerList(2, [0, 0, 0, 0, 0, 0, 0, 0]).withValidator( + nullableBooleanAsTwoBitIntegerListValidator + ) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsInSensitiveDataConsentSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsKyCoreSegment.ts b/modules/cmpapi/src/encoder/segment/UsKyCoreSegment.ts new file mode 100644 index 0000000..7b3c2b8 --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsKyCoreSegment.ts @@ -0,0 +1,102 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableFixedInteger } from "../datatype/EncodableFixedInteger.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsKy_CORE_SEGMENT_FIELD_NAMES } from "../field/UsKyField.js"; +import { UsKyField } from "../field/UsKyField.js"; +import { UsKy } from "../section/UsKy.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsKyCoreSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsKy_CORE_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 0 && n <= 2; + } + })(); + + const nonNullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 1 && n <= 2; + } + })(); + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsKyField.MSPA_VERSION.toString(), new EncodableFixedInteger(6, UsKy.VERSION)); + fields.put( + UsKyField.MSPA_COVERED_TRANSACTION.toString(), + new EncodableFixedInteger(2, 1).withValidator(nonNullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.MSPA_MODE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.PROCESSING_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.SALE_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.TARGETED_ADVERTISING_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.SALE_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.TARGETED_ADVERTISING_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsKyField.ADDITIONAL_DATA_PROCESSING_CONSENT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsKyCoreSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsKySensitiveDataConsentSegment.ts b/modules/cmpapi/src/encoder/segment/UsKySensitiveDataConsentSegment.ts new file mode 100644 index 0000000..a05c6a2 --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsKySensitiveDataConsentSegment.ts @@ -0,0 +1,73 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableBoolean } from "../datatype/EncodableBoolean.js"; +import { EncodableFixedIntegerList } from "../datatype/EncodableFixedIntegerList.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsKy_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES } from "../field/UsKyField.js"; +import { UsKyField } from "../field/UsKyField.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsKySensitiveDataConsentSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsKy_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerListValidator = new (class implements Predicate { + test(l: number[]): boolean { + for (let i = 0; i < l.length; i++) { + let n = l[i]; + if (n < 0 || n > 2) { + return false; + } + } + return true; + } + })(); + + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsKyField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED.toString(), new EncodableBoolean(true)); + fields.put( + UsKyField.SENSITIVE_DATA_PROCESSING.toString(), + new EncodableFixedIntegerList(2, [0, 0, 0, 0, 0, 0, 0, 0]).withValidator( + nullableBooleanAsTwoBitIntegerListValidator + ) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsKySensitiveDataConsentSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsMdCoreSegment.ts b/modules/cmpapi/src/encoder/segment/UsMdCoreSegment.ts new file mode 100644 index 0000000..2c92450 --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsMdCoreSegment.ts @@ -0,0 +1,99 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableFixedInteger } from "../datatype/EncodableFixedInteger.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsMd_CORE_SEGMENT_FIELD_NAMES } from "../field/UsMdField.js"; +import { UsMdField } from "../field/UsMdField.js"; +import { UsMd } from "../section/UsMd.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsMdCoreSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsMd_CORE_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 0 && n <= 2; + } + })(); + + const nonNullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 1 && n <= 2; + } + })(); + + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsMdField.MSPA_VERSION.toString(), new EncodableFixedInteger(6, UsMd.VERSION)); + fields.put( + UsMdField.MSPA_COVERED_TRANSACTION.toString(), + new EncodableFixedInteger(2, 1).withValidator(nonNullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.MSPA_MODE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.PROCESSING_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.SALE_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.TARGETED_ADVERTISING_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.SALE_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.TARGETED_ADVERTISING_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsMdField.ADDITIONAL_DATA_PROCESSING_CONSENT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsMdCoreSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsMdGpcSegment.ts b/modules/cmpapi/src/encoder/segment/UsMdGpcSegment.ts new file mode 100644 index 0000000..71468a5 --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsMdGpcSegment.ts @@ -0,0 +1,56 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableBoolean } from "../datatype/EncodableBoolean.js"; +import { EncodableFixedInteger } from "../datatype/EncodableFixedInteger.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsMd_GPC_SEGMENT_FIELD_NAMES } from "../field/UsMdField.js"; +import { UsMdField } from "../field/UsMdField.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsMdGpcSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsMd_GPC_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsMdField.GPC_SEGMENT_TYPE.toString(), new EncodableFixedInteger(2, 1)); + fields.put(UsMdField.GPC_SEGMENT_INCLUDED.toString(), new EncodableBoolean(true)); + fields.put(UsMdField.GPC.toString(), new EncodableBoolean(false)); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsMdGpcSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsRiCoreSegment.ts b/modules/cmpapi/src/encoder/segment/UsRiCoreSegment.ts new file mode 100644 index 0000000..2796b73 --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsRiCoreSegment.ts @@ -0,0 +1,102 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableFixedInteger } from "../datatype/EncodableFixedInteger.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsRi_CORE_SEGMENT_FIELD_NAMES } from "../field/UsRiField.js"; +import { UsRiField } from "../field/UsRiField.js"; +import { UsRi } from "../section/UsRi.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsRiCoreSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsRi_CORE_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 0 && n <= 2; + } + })(); + + const nonNullableBooleanAsTwoBitIntegerValidator = new (class implements Predicate { + test(n: number): boolean { + return n >= 1 && n <= 2; + } + })(); + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsRiField.MSPA_VERSION.toString(), new EncodableFixedInteger(6, UsRi.VERSION)); + fields.put( + UsRiField.MSPA_COVERED_TRANSACTION.toString(), + new EncodableFixedInteger(2, 1).withValidator(nonNullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.MSPA_MODE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.PROCESSING_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.SALE_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.TARGETED_ADVERTISING_OPT_OUT_NOTICE.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.SALE_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.TARGETED_ADVERTISING_OPT_OUT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + fields.put( + UsRiField.ADDITIONAL_DATA_PROCESSING_CONSENT.toString(), + new EncodableFixedInteger(2, 0).withValidator(nullableBooleanAsTwoBitIntegerValidator) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsRiCoreSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/UsRiSensitiveDataConsentSegment.ts b/modules/cmpapi/src/encoder/segment/UsRiSensitiveDataConsentSegment.ts new file mode 100644 index 0000000..fb3707d --- /dev/null +++ b/modules/cmpapi/src/encoder/segment/UsRiSensitiveDataConsentSegment.ts @@ -0,0 +1,73 @@ +import { AbstractBase64UrlEncoder } from "../base64/AbstractBase64UrlEncoder.js"; +import { CompressedBase64UrlEncoder } from "../base64/CompressedBase64UrlEncoder.js"; +import { BitStringEncoder } from "../bitstring/BitStringEncoder.js"; +import { EncodableBoolean } from "../datatype/EncodableBoolean.js"; +import { EncodableFixedIntegerList } from "../datatype/EncodableFixedIntegerList.js"; +import { Predicate } from "../datatype/validate/Predicate.js"; +import { DecodingError } from "../error/DecodingError.js"; +import { EncodableBitStringFields } from "../field/EncodableBitStringFields.js"; +import { UsRi_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES } from "../field/UsRiField.js"; +import { UsRiField } from "../field/UsRiField.js"; +import { AbstractLazilyEncodableSegment } from "./AbstractLazilyEncodableSegment.js"; + +export class UsRiSensitiveDataConsentSegment extends AbstractLazilyEncodableSegment { + private base64UrlEncoder: AbstractBase64UrlEncoder = CompressedBase64UrlEncoder.getInstance(); + private bitStringEncoder: BitStringEncoder = BitStringEncoder.getInstance(); + + constructor(encodedString?: string) { + super(); + if (encodedString) { + this.decode(encodedString); + } + } + + // overriden + public getFieldNames(): string[] { + return UsRi_SENSITIVE_DATA_CONSENT_SEGMENT_FIELD_NAMES; + } + + // overriden + protected initializeFields(): EncodableBitStringFields { + const nullableBooleanAsTwoBitIntegerListValidator = new (class implements Predicate { + test(l: number[]): boolean { + for (let i = 0; i < l.length; i++) { + let n = l[i]; + if (n < 0 || n > 2) { + return false; + } + } + return true; + } + })(); + + let fields: EncodableBitStringFields = new EncodableBitStringFields(); + fields.put(UsRiField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED.toString(), new EncodableBoolean(true)); + fields.put( + UsRiField.SENSITIVE_DATA_PROCESSING.toString(), + new EncodableFixedIntegerList(2, [0, 0, 0, 0, 0, 0, 0, 0]).withValidator( + nullableBooleanAsTwoBitIntegerListValidator + ) + ); + return fields; + } + + // overriden + protected encodeSegment(fields: EncodableBitStringFields): string { + let bitString: string = this.bitStringEncoder.encode(fields, this.getFieldNames()); + let encodedString: string = this.base64UrlEncoder.encode(bitString); + return encodedString; + } + + // overriden + protected decodeSegment(encodedString: string, fields: EncodableBitStringFields): void { + if (encodedString == null || encodedString.length === 0) { + this.fields.reset(fields); + } + try { + let bitString: string = this.base64UrlEncoder.decode(encodedString); + this.bitStringEncoder.decode(bitString, this.getFieldNames(), fields); + } catch (e) { + throw new DecodingError("Unable to decode UsRiSensitiveDataConsentSegment '" + encodedString + "'"); + } + } +} diff --git a/modules/cmpapi/src/encoder/segment/index.ts b/modules/cmpapi/src/encoder/segment/index.ts index 85cf8fb..35461f1 100644 --- a/modules/cmpapi/src/encoder/segment/index.ts +++ b/modules/cmpapi/src/encoder/segment/index.ts @@ -19,6 +19,12 @@ export * from "./UsDeGpcSegment.js"; export * from "./UsFlCoreSegment.js"; export * from "./UsIaCoreSegment.js"; export * from "./UsIaGpcSegment.js"; +export * from "./UsInCoreSegment.js"; +export * from "./UsInSensitiveDataConsentSegment.js"; +export * from "./UsKyCoreSegment.js"; +export * from "./UsKySensitiveDataConsentSegment.js"; +export * from "./UsMdCoreSegment.js"; +export * from "./UsMdGpcSegment.js"; export * from "./UsNatCoreSegment.js"; export * from "./UsNeCoreSegment.js"; export * from "./UsNeGpcSegment.js"; @@ -34,6 +40,8 @@ export * from "./UsNatGpcSegment.js"; export * from "./UspV1CoreSegment.js"; export * from "./UsOrCoreSegment.js"; export * from "./UsOrGpcSegment.js"; +export * from "./UsRiCoreSegment.js"; +export * from "./UsRiSensitiveDataConsentSegment.js"; export * from "./UsTnCoreSegment.js"; export * from "./UsTnGpcSegment.js"; export * from "./UsTxCoreSegment.js"; diff --git a/modules/cmpapi/test/GppModel.test.ts b/modules/cmpapi/test/GppModel.test.ts index 33a4e59..b008d02 100644 --- a/modules/cmpapi/test/GppModel.test.ts +++ b/modules/cmpapi/test/GppModel.test.ts @@ -60,6 +60,10 @@ describe("manifest.GppModel", (): void => { expect(gppModel.hasSection("usnj")).to.eql(false); expect(gppModel.hasSection("ustn")).to.eql(false); expect(gppModel.hasSection("usmn")).to.eql(false); + expect(gppModel.hasSection("usmd")).to.eql(false); + expect(gppModel.hasSection("usin")).to.eql(false); + expect(gppModel.hasSection("usky")).to.eql(false); + expect(gppModel.hasSection("usri")).to.eql(false); gppModel.setFieldValue("tcfeuv2", "Version", 2); gppModel.setFieldValue("tcfeuv2", "CmpId", 880); @@ -86,6 +90,10 @@ describe("manifest.GppModel", (): void => { gppModel.setFieldValue("usnj", "Version", 1); gppModel.setFieldValue("ustn", "Version", 1); gppModel.setFieldValue("usmn", "Version", 1); + gppModel.setFieldValue("usmd", "MspaVersion", 1); + gppModel.setFieldValue("usin", "MspaVersion", 1); + gppModel.setFieldValue("usky", "MspaVersion", 1); + gppModel.setFieldValue("usri", "MspaVersion", 1); expect(gppModel.hasSection("tcfeuv2")).to.eql(true); expect(gppModel.hasSection("tcfcav1")).to.eql(true); @@ -107,10 +115,14 @@ describe("manifest.GppModel", (): void => { expect(gppModel.hasSection("usnj")).to.eql(true); expect(gppModel.hasSection("ustn")).to.eql(true); expect(gppModel.hasSection("usmn")).to.eql(true); + expect(gppModel.hasSection("usmd")).to.eql(true); + expect(gppModel.hasSection("usin")).to.eql(true); + expect(gppModel.hasSection("usky")).to.eql(true); + expect(gppModel.hasSection("usri")).to.eql(true); let gppString = gppModel.encode(); expect(gppString).to.eql( - "DBACOYs~CPSG_8APSG_8ANwAAAENAAFgAAAAAAAAAAAAAAAAAAAA.IAAA.YAAAAAAAAAAA~BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA~1---~BAAAAAAAAABA.QA~BAAAAABA.QA~BAAAABA~BAAAAEA.QA~BAAAAAQA~BAAAAAEA.QA~BAAAAABA~BAAAAABA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BAAAAABA.QA~BAAAAAAAQA.QA~BAAAAAQA.QA~BAAAAAQA.QA" + "DBACOcGA~CPSG_8APSG_8ANwAAAENAAFgAAAAAAAAAAAAAAAAAAAA.IAAA.YAAAAAAAAAAA~BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA~1---~BAAAAAAAAABA.QA~BAAAAABA.QA~BAAAABA~BAAAAEA.QA~BAAAAAQA~BAAAAAEA.QA~BAAAAABA~BAAAAABA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BAAAAABA.QA~BAAAAAAAQA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BQAA.QA~BQAA.AAA~BQAA.AAA~BQAA.AAA" ); }); @@ -411,7 +423,7 @@ describe("manifest.GppModel", (): void => { it("should decode defaults from all sections", (): void => { let gppString = - "DBACOYs~CPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAAAAAA.QAAA.IAAA~BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA~1---~BAAAAAAAAABA.QA~BAAAAABA.QA~BAAAABA~BAAAAEA.QA~BAAAAAQA~BAAAAAEA.QA~BAAAAABA~BAAAAABA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BAAAAABA.QA~BAAAAAAAQA.QA~BAAAAAQA.QA~BAAAAAQA.QA"; + "DBACOcGA~CPSG_8APSG_8ANwAAAENAAFgAAAAAAAAAAAAAAAAAAAA.IAAA.YAAAAAAAAAAA~BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA~1---~BAAAAAAAAABA.QA~BAAAAABA.QA~BAAAABA~BAAAAEA.QA~BAAAAAQA~BAAAAAEA.QA~BAAAAABA~BAAAAABA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAABAA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BAAAAABA.QA~BAAAAAAAQA.QA~BAAAAAQA.QA~BAAAAAQA.QA~BQAA.QA~BQAA.AAA~BQAA.AAA~BQAA.AAA"; let gppModel = new GppModel(gppString); expect(gppModel.hasSection("tcfeuv2")).to.eql(true); @@ -434,6 +446,10 @@ describe("manifest.GppModel", (): void => { expect(gppModel.hasSection("usnj")).to.eql(true); expect(gppModel.hasSection("ustn")).to.eql(true); expect(gppModel.hasSection("usmn")).to.eql(true); + expect(gppModel.hasSection("usmd")).to.eql(true); + expect(gppModel.hasSection("usin")).to.eql(true); + expect(gppModel.hasSection("usky")).to.eql(true); + expect(gppModel.hasSection("usri")).to.eql(true); }); it("should decode uspv1 section", (): void => { diff --git a/modules/cmpapi/test/encoder/section/UsIn.test.ts b/modules/cmpapi/test/encoder/section/UsIn.test.ts new file mode 100644 index 0000000..138a172 --- /dev/null +++ b/modules/cmpapi/test/encoder/section/UsIn.test.ts @@ -0,0 +1,63 @@ +import { expect } from "chai"; +import { UsInField } from "../../../src/encoder/field/UsInField"; +import { UsIn } from "../../../src/encoder/section/UsIn"; + +describe("manifest.section.UsIn", (): void => { + it("should encode default", (): void => { + let usIn = new UsIn(); + expect(usIn.encode()).to.eql("BQAA.AAA"); + }); + + it("should encode with all fields set", (): void => { + let usIn = new UsIn(); + + usIn.setFieldValue(UsInField.MSPA_COVERED_TRANSACTION, 1); + usIn.setFieldValue(UsInField.MSPA_MODE, 1); + usIn.setFieldValue(UsInField.PROCESSING_NOTICE, 1); + usIn.setFieldValue(UsInField.SALE_OPT_OUT_NOTICE, 1); + usIn.setFieldValue(UsInField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, 1); + usIn.setFieldValue(UsInField.SALE_OPT_OUT, 1); + usIn.setFieldValue(UsInField.TARGETED_ADVERTISING_OPT_OUT, 1); + usIn.setFieldValue(UsInField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, 1); + usIn.setFieldValue(UsInField.ADDITIONAL_DATA_PROCESSING_CONSENT, 1); + usIn.setFieldValue(UsInField.SENSITIVE_DATA_PROCESSING, [2, 1, 0, 2, 1, 0, 2, 1]); + + expect(usIn.encode()).to.eql("BVVV.kkk"); + }); + + it("should encode default with sensitive data consent segment excluded", (): void => { + let usIn = new UsIn(); + usIn.setFieldValue(UsInField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + expect(usIn.encode()).to.eql("BQAA"); + }); + + it("should throw an error if invalid values are set", (): void => { + let usIn = new UsIn(); + + expect(function () { + usIn.setFieldValue(UsInField.MSPA_COVERED_TRANSACTION, 0); + }).to.throw(); + + expect(function () { + usIn.setFieldValue(UsInField.MSPA_MODE, 3); + }).to.throw(); + + expect(function () { + usIn.setFieldValue(UsInField.SENSITIVE_DATA_PROCESSING, [0, 1, 2, 3, 1, 2, 0, 1]); + }).to.throw(); + }); + + it("should decode", (): void => { + let usIn = new UsIn("BVVV.kkk"); + + expect(usIn.getFieldValue(UsInField.MSPA_COVERED_TRANSACTION)).to.eql(1); + expect(usIn.getFieldValue(UsInField.MSPA_MODE)).to.eql(1); + expect(usIn.getFieldValue(UsInField.SENSITIVE_DATA_PROCESSING)).to.eql([2, 1, 0, 2, 1, 0, 2, 1]); + }); + + it("should throw Error on garbage", (): void => { + expect(function () { + new UsIn("z").getFieldValue(UsInField.PROCESSING_NOTICE); + }).to.throw("Unable to decode UsInCoreSegment 'z'"); + }); +}); diff --git a/modules/cmpapi/test/encoder/section/UsKy.test.ts b/modules/cmpapi/test/encoder/section/UsKy.test.ts new file mode 100644 index 0000000..9622e19 --- /dev/null +++ b/modules/cmpapi/test/encoder/section/UsKy.test.ts @@ -0,0 +1,58 @@ +import { expect } from "chai"; +import { UsKyField } from "../../../src/encoder/field/UsKyField"; +import { UsKy } from "../../../src/encoder/section/UsKy"; + +describe("manifest.section.UsKy", (): void => { + it("should encode default", (): void => { + let usKy = new UsKy(); + expect(usKy.encode()).to.eql("BQAA.AAA"); + }); + + it("should encode with all fields set", (): void => { + let usKy = new UsKy(); + + usKy.setFieldValue(UsKyField.MSPA_COVERED_TRANSACTION, 1); + usKy.setFieldValue(UsKyField.MSPA_MODE, 1); + usKy.setFieldValue(UsKyField.PROCESSING_NOTICE, 1); + usKy.setFieldValue(UsKyField.SALE_OPT_OUT_NOTICE, 1); + usKy.setFieldValue(UsKyField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, 1); + usKy.setFieldValue(UsKyField.SALE_OPT_OUT, 1); + usKy.setFieldValue(UsKyField.TARGETED_ADVERTISING_OPT_OUT, 1); + usKy.setFieldValue(UsKyField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, 1); + usKy.setFieldValue(UsKyField.ADDITIONAL_DATA_PROCESSING_CONSENT, 1); + usKy.setFieldValue(UsKyField.SENSITIVE_DATA_PROCESSING, [2, 1, 0, 2, 1, 0, 2, 1]); + + expect(usKy.encode()).to.eql("BVVV.kkk"); + }); + + it("should encode default with sensitive data consent segment excluded", (): void => { + let usKy = new UsKy(); + usKy.setFieldValue(UsKyField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + expect(usKy.encode()).to.eql("BQAA"); + }); + + it("should throw an error if invalid values are set", (): void => { + let usKy = new UsKy(); + + expect(function () { + usKy.setFieldValue(UsKyField.MSPA_COVERED_TRANSACTION, 0); + }).to.throw(); + + expect(function () { + usKy.setFieldValue(UsKyField.SENSITIVE_DATA_PROCESSING, [0, 1, 2, 3, 1, 2, 0, 1]); + }).to.throw(); + }); + + it("should decode", (): void => { + let usKy = new UsKy("BVVV.kkk"); + + expect(usKy.getFieldValue(UsKyField.MSPA_COVERED_TRANSACTION)).to.eql(1); + expect(usKy.getFieldValue(UsKyField.SENSITIVE_DATA_PROCESSING)).to.eql([2, 1, 0, 2, 1, 0, 2, 1]); + }); + + it("should throw Error on garbage", (): void => { + expect(function () { + new UsKy("z").getFieldValue(UsKyField.PROCESSING_NOTICE); + }).to.throw("Unable to decode UsKyCoreSegment 'z'"); + }); +}); diff --git a/modules/cmpapi/test/encoder/section/UsMd.test.ts b/modules/cmpapi/test/encoder/section/UsMd.test.ts new file mode 100644 index 0000000..de98855 --- /dev/null +++ b/modules/cmpapi/test/encoder/section/UsMd.test.ts @@ -0,0 +1,63 @@ +import { expect } from "chai"; +import { UsMdField } from "../../../src/encoder/field/UsMdField"; +import { UsMd } from "../../../src/encoder/section/UsMd"; + +describe("manifest.section.UsMd", (): void => { + it("should encode default", (): void => { + let usMd = new UsMd(); + expect(usMd.encode()).to.eql("BQAA.QA"); + }); + + it("should encode with all fields set", (): void => { + let usMd = new UsMd(); + + usMd.setFieldValue(UsMdField.MSPA_COVERED_TRANSACTION, 1); + usMd.setFieldValue(UsMdField.MSPA_MODE, 1); + usMd.setFieldValue(UsMdField.PROCESSING_NOTICE, 1); + usMd.setFieldValue(UsMdField.SALE_OPT_OUT_NOTICE, 1); + usMd.setFieldValue(UsMdField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, 1); + usMd.setFieldValue(UsMdField.SALE_OPT_OUT, 1); + usMd.setFieldValue(UsMdField.TARGETED_ADVERTISING_OPT_OUT, 1); + usMd.setFieldValue(UsMdField.ADDITIONAL_DATA_PROCESSING_CONSENT, 1); + usMd.setFieldValue(UsMdField.GPC, true); + + expect(usMd.encode()).to.eql("BVVU.YA"); + }); + + it("should encode default with gpc segment excluded", (): void => { + let usMd = new UsMd(); + usMd.setFieldValue(UsMdField.GPC_SEGMENT_INCLUDED, false); + expect(usMd.encode()).to.eql("BQAA"); + }); + + it("should throw an error if invalid values are set", (): void => { + let usMd = new UsMd(); + + expect(function () { + usMd.setFieldValue(UsMdField.MSPA_COVERED_TRANSACTION, 0); + }).to.throw(); + + expect(function () { + usMd.setFieldValue(UsMdField.MSPA_MODE, 3); + }).to.throw(); + + expect(function () { + usMd.setFieldValue(UsMdField.PROCESSING_NOTICE, 3); + }).to.throw(); + }); + + it("should decode", (): void => { + let usMd = new UsMd("BVVU.YA"); + + expect(usMd.getFieldValue(UsMdField.MSPA_COVERED_TRANSACTION)).to.eql(1); + expect(usMd.getFieldValue(UsMdField.MSPA_MODE)).to.eql(1); + expect(usMd.getFieldValue(UsMdField.PROCESSING_NOTICE)).to.eql(1); + expect(usMd.getFieldValue(UsMdField.GPC)).to.eql(true); + }); + + it("should throw Error on garbage", (): void => { + expect(function () { + new UsMd("z").getFieldValue(UsMdField.PROCESSING_NOTICE); + }).to.throw("Unable to decode UsMdCoreSegment 'z'"); + }); +}); diff --git a/modules/cmpapi/test/encoder/section/UsRi.test.ts b/modules/cmpapi/test/encoder/section/UsRi.test.ts new file mode 100644 index 0000000..b594ecd --- /dev/null +++ b/modules/cmpapi/test/encoder/section/UsRi.test.ts @@ -0,0 +1,58 @@ +import { expect } from "chai"; +import { UsRiField } from "../../../src/encoder/field/UsRiField"; +import { UsRi } from "../../../src/encoder/section/UsRi"; + +describe("manifest.section.UsRi", (): void => { + it("should encode default", (): void => { + let usRi = new UsRi(); + expect(usRi.encode()).to.eql("BQAA.AAA"); + }); + + it("should encode with all fields set", (): void => { + let usRi = new UsRi(); + + usRi.setFieldValue(UsRiField.MSPA_COVERED_TRANSACTION, 1); + usRi.setFieldValue(UsRiField.MSPA_MODE, 1); + usRi.setFieldValue(UsRiField.PROCESSING_NOTICE, 1); + usRi.setFieldValue(UsRiField.SALE_OPT_OUT_NOTICE, 1); + usRi.setFieldValue(UsRiField.TARGETED_ADVERTISING_OPT_OUT_NOTICE, 1); + usRi.setFieldValue(UsRiField.SALE_OPT_OUT, 1); + usRi.setFieldValue(UsRiField.TARGETED_ADVERTISING_OPT_OUT, 1); + usRi.setFieldValue(UsRiField.KNOWN_CHILD_SENSITIVE_DATA_CONSENTS, 1); + usRi.setFieldValue(UsRiField.ADDITIONAL_DATA_PROCESSING_CONSENT, 1); + usRi.setFieldValue(UsRiField.SENSITIVE_DATA_PROCESSING, [2, 1, 0, 2, 1, 0, 2, 1]); + + expect(usRi.encode()).to.eql("BVVV.kkk"); + }); + + it("should encode default with sensitive data consent segment excluded", (): void => { + let usRi = new UsRi(); + usRi.setFieldValue(UsRiField.SENSITIVE_DATA_CONSENT_SEGMENT_INCLUDED, false); + expect(usRi.encode()).to.eql("BQAA"); + }); + + it("should throw an error if invalid values are set", (): void => { + let usRi = new UsRi(); + + expect(function () { + usRi.setFieldValue(UsRiField.MSPA_COVERED_TRANSACTION, 0); + }).to.throw(); + + expect(function () { + usRi.setFieldValue(UsRiField.SENSITIVE_DATA_PROCESSING, [0, 1, 2, 3, 1, 2, 0, 1]); + }).to.throw(); + }); + + it("should decode", (): void => { + let usRi = new UsRi("BVVV.kkk"); + + expect(usRi.getFieldValue(UsRiField.MSPA_COVERED_TRANSACTION)).to.eql(1); + expect(usRi.getFieldValue(UsRiField.SENSITIVE_DATA_PROCESSING)).to.eql([2, 1, 0, 2, 1, 0, 2, 1]); + }); + + it("should throw Error on garbage", (): void => { + expect(function () { + new UsRi("z").getFieldValue(UsRiField.PROCESSING_NOTICE); + }).to.throw("Unable to decode UsRiCoreSegment 'z'"); + }); +});