Skip to content

Fix TcfCaV1 (Canada) OptimizedRange + base64 encoding, with backwards-compatible decoding#107

Open
chuff wants to merge 4 commits into
IABTechLab:masterfrom
chuff:fix/canada-encoding
Open

Fix TcfCaV1 (Canada) OptimizedRange + base64 encoding, with backwards-compatible decoding#107
chuff wants to merge 4 commits into
IABTechLab:masterfrom
chuff:fix/canada-encoding

Conversation

@chuff

@chuff chuff commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Per the GPP Consent String Specification, the Canada (TcfCaV1) section encodes several fields as OptimizedRange / N-ArrayOfRanges (Fibonacci coded), and its disclosed-vendors segment should use the compressed base64 encoder. This corrects both and keeps existing strings decodable.

Fixes:

  1. VendorExpressConsent, VendorImpliedConsent, DisclosedVendors (OptimizedRange) — EncodableOptimizedFixedRangeEncodableOptimizedFibonacciRange.
  2. PubRestrictions (N-ArrayOfRanges(6,2), whose ids are an OptimizedRange) — new EncodableArrayOfOptimizedFibonacciRanges reusing the existing Fibonacci OptimizedRange for each record's ids. The fixed variant is left untouched for TCF EU.
  3. Disclosed-vendors segment base64 — TraditionalBase64UrlEncoderCompressedBase64UrlEncoder (the core and publisher-purposes segments already used compressed).

With all three, the output is now byte-identical to iabgpp-java for the same inputs (e.g. the disclosed segment goes from …IAGO5wAA to …IAGO5w).

Backwards compatibility

The TcfCaV1 core and disclosed-vendors segments decode by trying the spec-compliant (Fibonacci) interpretation first and, if it doesn't round-trip back to the input bitstring, falling back to the legacy (fixed-range) interpretation. base64 decode is padding-agnostic, so the traditional-vs-compressed change doesn't affect decoding. Strings produced by the previous encoder still decode correctly.

Note: backwards compatible on read; the encoder now emits Fibonacci/compressed output, so consumers still running the previous code will not read the new range-form strings — a deliberate wire-format correction.

Also fixes an incorrect decode error message in the disclosed-vendors segment (HeaderV1CoreSegmentTcfCaV1DisclosedVendorsSegment).

Test plan

  • Per-field encode/decode + round-trip tests (sparse IDs exercise the Fibonacci range path)
  • Backwards-compat decode tests using real pre-fix strings (vendors, PubRestrictions, traditional-base64 disclosed, plus a full real-world legacy string)
  • Verified against the compiled output (the repo's mocha/ts-node toolchain doesn't run under current Node; assertions were checked against tsc-compiled classes and match iabgpp-java byte-for-byte)

🤖 Generated with Claude Code

chuff and others added 4 commits June 9, 2026 07:13
Two Canada (TcfCaV1) encoding bugs:

1. VendorExpressConsent, VendorImpliedConsent, and DisclosedVendors are
   OptimizedRange per the spec (Fibonacci-coded in GPP), but used
   EncodableOptimizedFixedRange. Switch to EncodableOptimizedFibonacciRange.
2. The disclosed-vendors segment used TraditionalBase64UrlEncoder; the
   core and publisher-purposes segments use CompressedBase64UrlEncoder.
   Switch it to CompressedBase64UrlEncoder.

With both fixes the ES output is now byte-identical to iabgpp-java for the
same inputs (verified the disclosed segment goes from ...IAGO5wAA to
...IAGO5w, matching Java). Regenerate the affected test vectors and add a
round-trip test exercising the Fibonacci range path with sparse vendor IDs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per the GPP spec, PubRestrictions is N-ArrayOfRanges(6,2) where each
record's ids are an OptimizedRange (Fibonacci coded). It was using
EncodableArrayOfFixedIntegerRanges, which encodes ids as fixed-integer
ranges (the legacy ArrayOfRanges form shared with TCF EU).

Add EncodableArrayOfOptimizedFibonacciRanges, which reuses the existing
EncodableOptimizedFibonacciRange for each record's ids, export it, and
point TcfCaV1's PUB_RESTRICTIONS field at it. The fixed variant is left
untouched for TCF EU.

Regenerate the affected TcfCaV1 test vectors and extend the round-trip
test to cover PubRestrictions with sparse ids (Fibonacci range path).
The PubRestrictions string now matches iabgpp-java byte-for-byte.

Note: verified by Java/ES cross-implementation agreement and round-trip,
not yet against an external reference string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strings produced by the pre-fix encoder used fixed-integer ranges for the
OptimizedRange / N-ArrayOfRanges fields (VendorExpressConsent,
VendorImpliedConsent, DisclosedVendors, PubRestrictions), and a
traditional-base64 disclosed-vendors segment. Decoding those with the new
Fibonacci datatypes would misread the data.

The TcfCaV1 core and disclosed-vendors segments now decode by trying the
spec-compliant (Fibonacci) interpretation first and, if it does not
round-trip back to the input bitstring, falling back to the legacy
(fixed-range) interpretation. Whichever re-encodes to the original bits
is the one that produced the string. Decoded values are copied into the
current (Fibonacci) fields. (base64 decode is padding-agnostic, so the
traditional-vs-compressed difference does not affect decoding.)

Also fixes an incorrect decode error message in the disclosed-vendors
segment ("HeaderV1CoreSegment" -> "TcfCaV1DisclosedVendorsSegment").

Tests: decode real pre-fix strings (vendors, PubRestrictions, and the
traditional-base64 disclosed segment) and assert correct values.

Note: unlike iabgpp-java, re-encoding a decoded section here returns the
original string unchanged because AbstractLazilyEncodableSection.setFieldValue
does not mark the section dirty (pre-existing behavior); decoding is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iant

Adds a regression test using a real pre-fix (fixed-range) TcfCaV1 string:
verifies it decodes correctly (CmpId, language, 14 express + 46 implied
vendors) and that re-encoding (after marking the section dirty, the way
GppModel does) emits the spec-compliant Fibonacci form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant