Skip to content

Commit

Permalink
TCF 2.0: Fix legitimate interest vendor parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxNode authored Aug 12, 2020
2 parents 475ddb5 + 6cd748c commit 1ec0458
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 8 deletions.
8 changes: 8 additions & 0 deletions vendorconsent/tcf2/bitfield.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ type consentBitField struct {
maxVendorID uint16
}

func (f *consentBitField) MaxVendorID() uint16 {
if f == nil {
return 0
}

return f.maxVendorID
}

func (f *consentBitField) VendorConsent(id uint16) bool {
if id < 1 || id > f.maxVendorID {
return false
Expand Down
5 changes: 5 additions & 0 deletions vendorconsent/tcf2/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ConsentMetadata struct {
}

type vendorConsentsResolver interface {
MaxVendorID() uint16
VendorConsent(id uint16) bool
}

Expand Down Expand Up @@ -118,6 +119,10 @@ func (c ConsentMetadata) ConsentLanguage() string {
return string([]byte{leftChar + 65, rightChar + 65}) // Unicode A-Z is 65-90
}

func (c ConsentMetadata) VendorLegitInterestMaxID() uint16 {
return c.vendorLegitimateInterests.MaxVendorID()
}

func (c ConsentMetadata) VendorListVersion() uint16 {
// The vendor list version is stored in bits 120 - 131
rightByte := ((c.data[16] & 0xf0) >> 4) | ((c.data[15] & 0x0f) << 4)
Expand Down
16 changes: 13 additions & 3 deletions vendorconsent/tcf2/rangesection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import (

func parseRangeSection(metadata ConsentMetadata, maxVendorID uint16, startbit uint) (*rangeSection, uint, error) {
data := metadata.data
// This makes an int from bits 230-241

if len(data) < 31 {
return nil, 0, fmt.Errorf("vendor consent strings using RangeSections require at least 31 bytes. Got %d", len(data))
}
numEntries, err := bitutils.ParseUInt12(data, 230)

// This makes an int from bits [startBit, startBit + 12)
numEntries, err := bitutils.ParseUInt12(data, startbit)
if err != nil {
return nil, 0, err
}

// Parse out the "exceptions" here.
currentOffset := uint(242)
currentOffset := startbit + 12
consents := make([]rangeConsent, numEntries)
for i := uint16(0); i < numEntries; i++ {
thisConsent, bitsConsumed, err := parseRangeConsent(data, currentOffset, maxVendorID)
Expand Down Expand Up @@ -98,6 +100,14 @@ type rangeSection struct {
maxVendorID uint16
}

func (p *rangeSection) MaxVendorID() uint16 {
if p == nil {
return 0
}

return p.maxVendorID
}

// VendorConsents implementation
func (p rangeSection) VendorConsent(id uint16) bool {
if id < 1 || id > p.maxVendorID {
Expand Down
23 changes: 18 additions & 5 deletions vendorconsent/tcf2/rangesection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import (
func TestRangeSectionConsent(t *testing.T) {
// String built using http://iabtcf.com/#/encode
// This sample encodes a mix of Single- and Range-typed consent exceptions.
consent, err := Parse(decode(t, "COyiILmOyiILmADACHENAPCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAAAAAA"))
consent, err := Parse(decode(t, "COyfVVoOyfVVoADACHENAwCAAAAAAAAAAAAAE5QBgALgAqgD8AQACSwEygJyAnSAMABgAFkAgQCDASeAmYBOgAA"))
assertNilError(t, err)
assertUInt8sEqual(t, 2, consent.Version())
assertUInt16sEqual(t, 3, consent.CmpID())
assertUInt16sEqual(t, 2, consent.CmpVersion())
assertUInt8sEqual(t, 7, consent.ConsentScreen())
assertStringsEqual(t, "EN", consent.ConsentLanguage())
assertUInt16sEqual(t, 15, consent.VendorListVersion())
assertUInt16sEqual(t, 48, consent.VendorListVersion())
assertUInt16sEqual(t, 626, consent.MaxVendorID())

// The above encoder doesn't support setting purposes.
Expand All @@ -27,11 +27,24 @@ func TestRangeSectionConsent(t *testing.T) {

vendorsWithConsent := buildMap(23, 42, 126, 127, 128, 587, 613, 626)
for i := uint16(1); i <= consent.MaxVendorID(); i++ {
_, ok := vendorsWithConsent[uint(i)]
if ok != consent.VendorConsent(i) {
_, expected := vendorsWithConsent[uint(i)]
actual := consent.VendorConsent(i)
if expected != actual {
fmt.Printf("Vendor: %d failed\n", i)
}
assertBoolsEqual(t, ok, consent.VendorConsent(i))
assertBoolsEqual(t, expected, actual)
}

// TODO func VendorLegitInterest() should be added to api.VendorConsents
consentMetadata := consent.(ConsentMetadata)
vendorsLegitimateInterestWithConsent := buildMap(24, 44, 129, 130, 131, 591, 614, 628)
for i := uint16(1); i <= consentMetadata.VendorLegitInterestMaxID(); i++ {
_, expected := vendorsLegitimateInterestWithConsent[uint(i)]
actual := consentMetadata.VendorLegitInterest(i)
if expected != actual {
fmt.Printf("VendorLegitInterest: %d failed\n", i)
}
assertBoolsEqual(t, expected, actual)
}
}

Expand Down

0 comments on commit 1ec0458

Please sign in to comment.