Skip to content

4.X: fix bitfield off-by-one encoding - #108

Open
yuzawa-san wants to merge 1 commit into
IABTechLab:4.Xfrom
yuzawa-san:4.X-roundtrip-check
Open

yuzawa-san wants to merge 1 commit into
IABTechLab:4.Xfrom
yuzawa-san:4.X-roundtrip-check

Conversation

@yuzawa-san

Copy link
Copy Markdown
Contributor

this addresses the feedback done in #83 (comment)

from one of the tcf specs:

PurposeConsent
Bitfield(24)

One bit for each Purpose:
1 Consent
0 No Consent
The user’s consent value for each Purpose established on the legal basis of consent.

The Purposes are numerically identified and published in the Global Vendor List. From left to right, Purpose 1 maps to the 0th bit, purpose 24 maps to the bit at index 23. Special Purposes are a different ID space and not included in this field.

@Tejasshack

Copy link
Copy Markdown
Collaborator

PR 108 — Fixed Bitfield

  1. The encoding change matches the specification.
    The spec defines bit 0 as ID 1, bit 1 as ID 2, and so on. The change from containsInt(i) to containsInt(i + 1) correctly aligns encoding with the existing decode behavior. This is not a spec issue.

  2. Actual bug: the last valid ID cannot be stored through the API.
    EncodableFixedBitfield.initialize() creates the IntegerSet with adjustment = 0 and to = numElements, so the set only accepts IDs 0..numElements-1. After the encoding fix, the valid IDs are 1..numElements.

  3. Example:
    For Bitfield(24), IDs 1..23 work, but ID 24 is rejected by IntegerSet.addInt() and is never stored. Therefore, Purpose 24 cannot be set through setFieldValue().

  4. Recommended fix:
    Initialize the set with adjustment = 1:

    @Override
    protected IntegerSet initialize() {
      return new IntegerSet(new BitSet(numElements), 0, numElements, 1);
    }
  5. Add a boundary round-trip test.
    Please verify the highest ID, not only middle values:

    Set<Integer> purposeConsents = Set.of(1, 24);
    // encode -> decode -> assert equality

    The same boundary check should be added for Bitfield(12) so Special Feature Opt-In ID 12 also survives setFieldValue().

Main point: PR 108 is spec-correct on bit indexing; the remaining issue is an off-by-one storage bug that prevents the highest valid ID from being set.

Specification for reference
https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform

@Tejasshack
Tejasshack self-requested a review September 21, 2026 20:55
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.

2 participants