Skip to content

Fix TcfCaV1 (Canada) OptimizedRange encoding to use Fibonacci, with backwards-compatible decoding#105

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

Fix TcfCaV1 (Canada) OptimizedRange encoding to use Fibonacci, with backwards-compatible decoding#105
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, both of which use Fibonacci coding. They were using fixed-integer encoders. This corrects the encoding 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 that reuses the existing Fibonacci OptimizedRange for each record's ids. The fixed variant is left untouched for TCF EU, which legitimately keeps the legacy encoding.

(Base64 was already correct in this library — all TcfCaV1 segments use the compressed encoder. The traditional-vs-compressed issue only affected iabgpp-es.)

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. Strings produced by the previous encoder still decode correctly. Empty / bitfield-form data encodes identically either way, so only populated range-form data changes on the wire.

Note: this is backwards compatible on read (new code reads old strings). The encoder now emits Fibonacci strings, so consumers still running the previous code will not read the new range-form output — it is a deliberate wire-format correction for the affected fields.

Test plan

  • mvn test — full suite passes (368 tests)
  • 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, plus a full real-world legacy string)

🤖 Generated with Claude Code

chuff and others added 4 commits June 9, 2026 07:13
The Canada (TcfCaV1) VendorExpressConsent, VendorImpliedConsent, and
DisclosedVendors fields are OptimizedRange per the spec, which is
Fibonacci-coded in GPP. They were using EncodableOptimizedFixedRange
(the legacy OptimizedIntRange used by TCF EU); switch them to the
existing EncodableOptimizedFibonacciRange.

Regenerate the affected test vectors (only populated-vendor cases
change; empty/bitfield cases are encoder-agnostic) and add a round-trip
test that exercises the Fibonacci range path with sparse vendor IDs.

mvn test: 365 tests, 0 failures.

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 / OptimizedFibonacciRangeEncoder for each
record's ids, and point TcfCaV1's PUB_RESTRICTIONS field at it. The
fixed variant is left untouched for TCF EU.

Regenerate the affected TcfCaV1Test vectors and extend the round-trip
test to cover PubRestrictions with sparse ids (Fibonacci range path).

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

mvn test: 365 tests, 0 failures.

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). 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, so the in-memory representation is identical
regardless of which encoder produced the string.

Tests: decode real pre-fix strings (vendors, PubRestrictions) and assert
correct values.

mvn test: 367 tests, 0 failures.

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 emits the spec-compliant Fibonacci form.

mvn test: 368 tests, 0 failures.

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