Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions bitutils/bitutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
func ParseByte4(data []byte, bitStartIndex uint) (byte, error) {
startByte := bitStartIndex / 8
bitStartOffset := bitStartIndex % 8

if bitStartOffset < 5 {
if uint(len(data)) < (startByte + 1) {
return 0, fmt.Errorf("ParseByte4 expected 4 bits to start at bit %d, but the consent string was only %d bytes long", bitStartIndex, len(data))
}

return (data[startByte] & (0xf0 >> bitStartOffset)) >> (4 - bitStartOffset), nil
}

if uint(len(data)) < (startByte+2) && bitStartOffset > 4 {
return 0, fmt.Errorf("ParseByte4 expected 4 bits to start at bit %d, but the consent string was only %d bytes long (needs second byte)", bitStartIndex, len(data))
}
Expand All @@ -23,26 +26,31 @@ func ParseByte4(data []byte, bitStartIndex uint) (byte, error) {
bitsConsumed := 8 - bitStartOffset
overflow := 4 - bitsConsumed
rightBits := (data[startByte+1] & (0xf0 << (4 - overflow))) >> (8 - overflow)

return leftBits | rightBits, nil
}

// ParseByte8 parses 8 bits of data from the data array, starting at the given index
func ParseByte8(data []byte, bitStartIndex uint) (byte, error) {
startByte := bitStartIndex / 8
bitStartOffset := bitStartIndex % 8

if bitStartOffset == 0 {
if uint(len(data)) < (startByte + 1) {
return 0, fmt.Errorf("ParseByte8 expected 8 bits to start at bit %d, but the consent string was only %d bytes long", bitStartIndex, len(data))
}

return data[startByte], nil
}

if uint(len(data)) < (startByte + 2) {
return 0, fmt.Errorf("ParseByte8 expected 8 bitst to start at bit %d, but the consent string was only %d bytes long", bitStartIndex, len(data))
}

leftBits := (data[startByte] & (0xff >> bitStartOffset)) << bitStartOffset
shiftComplement := 8 - bitStartOffset
rightBits := (data[startByte+1] & (0xff << shiftComplement)) >> shiftComplement

return leftBits | rightBits, nil
}

Expand All @@ -55,6 +63,7 @@ func ParseUInt12(data []byte, bitStartIndex uint) (uint16, error) {
if endOffset > 0 {
endByte++
}

if uint(len(data)) < endByte {
return 0, fmt.Errorf("ParseUInt12 expected a 12-bit int to start at bit %d, but the consent string was only %d bytes long",
bitStartIndex, len(data))
Expand All @@ -64,23 +73,28 @@ func ParseUInt12(data []byte, bitStartIndex uint) (uint16, error) {
if err != nil {
return 0, fmt.Errorf("ParseUInt12 error on left byte: %s", err)
}

rightByte, err := ParseByte8(data, bitStartIndex+4)
if err != nil {
return 0, fmt.Errorf("ParseUInt12 error on right byte: %s", err)
}

return binary.BigEndian.Uint16([]byte{leftByte, rightByte}), nil
}

// ParseUInt16 parses a 16-bit integer from the data array, starting at the given index
func ParseUInt16(data []byte, bitStartIndex uint) (uint16, error) {
startByte := bitStartIndex / 8
bitStartOffset := bitStartIndex % 8

if bitStartOffset == 0 {
if uint(len(data)) < (startByte + 2) {
return 0, fmt.Errorf("ParseUInt16 expected a 16-bit int to start at bit %d, but the consent string was only %d bytes long", bitStartIndex, len(data))
}

return binary.BigEndian.Uint16(data[startByte : startByte+2]), nil
}

if uint(len(data)) < (startByte + 3) {
return 0, fmt.Errorf("ParseUInt16 expected a 16-bit int to start at bit %d, but the consent string was only %d bytes long", bitStartIndex, len(data))
}
Expand All @@ -89,9 +103,11 @@ func ParseUInt16(data []byte, bitStartIndex uint) (uint16, error) {
if err != nil {
return 0, fmt.Errorf("ParseUInt16 error on left byte: %s", err)
}

rightByte, err := ParseByte8(data, bitStartIndex+8)
if err != nil {
return 0, fmt.Errorf("ParseUInt16 error on right byte: %s", err)
}

return binary.BigEndian.Uint16([]byte{leftByte, rightByte}), nil
}
18 changes: 12 additions & 6 deletions bitutils/bitutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,43 +120,49 @@ func assertNilError(t *testing.T, err error) {
}
}

func assertStringsEqual(t *testing.T, expected string, actual string) {
func assertStringsEqual(t *testing.T, expected, actual string) {
t.Helper()

if actual != expected {
t.Errorf("Strings were not equal. Expected %s, actual %s", expected, actual)
}
}

func assertBytesEqual(t *testing.T, expected byte, actual byte) {
func assertBytesEqual(t *testing.T, expected, actual byte) {
t.Helper()

if actual != expected {
t.Errorf("bytes were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertUInt8sEqual(t *testing.T, expected uint8, actual uint8) {
func assertUInt8sEqual(t *testing.T, expected, actual uint8) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertUInt16sEqual(t *testing.T, expected uint16, actual uint16) {
func assertUInt16sEqual(t *testing.T, expected, actual uint16) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertIntsEqual(t *testing.T, expected int, actual int) {
func assertIntsEqual(t *testing.T, expected, actual int) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertBoolsEqual(t *testing.T, expected bool, actual bool) {
func assertBoolsEqual(t *testing.T, expected, actual bool) {
t.Helper()

if actual != expected {
t.Errorf("Bools were not equal. Expected %t, actual %t", expected, actual)
}
Expand Down
6 changes: 2 additions & 4 deletions consentconstants/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@ package consentconstants

import "errors"

var (
// ErrEmptyDecodedConsent error raised when the consent string is empty
ErrEmptyDecodedConsent = errors.New("decoded consent cannot be empty")
)
// ErrEmptyDecodedConsent error raised when the consent string is empty
var ErrEmptyDecodedConsent = errors.New("decoded consent cannot be empty")
6 changes: 3 additions & 3 deletions consentconstants/features.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package consentconstants

// SpecialFeature is one of the IAB GDPR special features. These appear in:
// 1. `root.specialFeatures[i]` of the vendor list: https://vendorlist.consensu.org/vendorlist.json
// 2. SpecialFeatureOptIns of the Consent string: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-
type SpecialFeature uint8
// 1. `root.specialFeatures[i]` of the vendor list: https://vendorlist.consensu.org/vendorlist.json
// 2. SpecialFeatureOptIns of the Consent string: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-
type SpecialFeature uint8
4 changes: 2 additions & 2 deletions consentconstants/purposes.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package consentconstants

// Purpose is one of the IAB GDPR purposes. These appear in:
// 1. `root.purposes[i]` of the vendor list: https://vendorlist.consensu.org/vendorlist.json
// 2. PurposesAllowed of the Consent string: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-
// 1. `root.purposes[i]` of the vendor list: https://vendorlist.consensu.org/vendorlist.json
// 2. PurposesAllowed of the Consent string: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-
type Purpose uint8

// TCF 1 Purposes:
Expand Down
34 changes: 23 additions & 11 deletions vendorconsent/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,30 +66,36 @@ func BenchmarkParse(b *testing.B) {
for _, c := range testcases {
all = append(all, c.consent)
}

b.Run("all testcases", func(b *testing.B) {
// on https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
// section "A note on compiler optimisations"
// we have a warning about the compiler may eliminate
// ParseString function call, to prevent this we assign the result to
// some variables out of the for loop scope
// on https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
// section "A note on compiler optimisations"
// we have a warning about the compiler may eliminate
// ParseString function call, to prevent this we assign the result to
// some variables out of the for loop scope
var consent api.VendorConsents

var err error

max := len(all)
for n := 0; n < b.N; n++ {
consent, err = vendorconsent.ParseString(all[n%max])
}

_ = consent
_ = err
})

for _, tc := range testcases {
tc := tc
b.Run(fmt.Sprintf("case %s", tc.label), func(b *testing.B) {
b.Run("case "+tc.label, func(b *testing.B) {
var consent api.VendorConsents

var err error
for n := 0; n < b.N; n++ {
consent, err = vendorconsent.ParseString(tc.consent)
}

_ = consent
_ = err
})
Expand All @@ -101,17 +107,21 @@ var consentFile string
func init() {
flag.StringVar(&consentFile, "consent-file", "", "ascii consent file")
}

func BenchmarkVerify(b *testing.B) {
if consentFile == "" {
b.SkipNow()
}

readFile, err := os.Open(consentFile)
if err != nil {
b.FailNow() // abort
}

defer readFile.Close()
fileScanner := bufio.NewScanner(readFile)
fileScanner.Split(bufio.ScanLines)

var consents []string

for fileScanner.Scan() {
Expand All @@ -120,16 +130,18 @@ func BenchmarkVerify(b *testing.B) {

max := len(consents)
b.Run(fmt.Sprintf("testing just parsing %d consents/string", max), func(b *testing.B) {
// on https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
// section "A note on compiler optimisations"
// we have a warning about the compiler may eliminate
// ParseString function call, to prevent this we assign the result to
// some variables out of the for loop scope
// on https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
// section "A note on compiler optimisations"
// we have a warning about the compiler may eliminate
// ParseString function call, to prevent this we assign the result to
// some variables out of the for loop scope
var consent api.VendorConsents

var err error
for n := 0; n < b.N; n++ {
consent, err = vendorconsent.ParseString(consents[n%max])
}

_ = consent
_ = err
})
Expand Down
4 changes: 3 additions & 1 deletion vendorconsent/consent20_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,17 @@ func TestParseEmptyString(t *testing.T) {
}
}

func assertInvalid20(t *testing.T, urlEncodedString string, expectError string) {
func assertInvalid20(t *testing.T, urlEncodedString, expectError string) {
t.Helper()

data, err := base64.RawURLEncoding.DecodeString(urlEncodedString)
assertNilError(t, err)
assertInvalidBytes20(t, data, expectError)
}

func assertInvalidBytes20(t *testing.T, data []byte, expectError string) {
t.Helper()

if consent, err := tcf2.Parse(data); err == nil {
t.Errorf("base64 URL-encoded string %s was considered valid, but shouldn't be. MaxVendorID: %d. len(data): %d", base64.RawURLEncoding.EncodeToString(data), consent.MaxVendorID(), len(data))
} else if err.Error() != expectError {
Expand Down
28 changes: 18 additions & 10 deletions vendorconsent/consent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,17 @@ func TestParseValidString(t *testing.T) {
assertUInt16sEqual(t, 14, parsed.VendorListVersion())
}

func assertInvalid(t *testing.T, urlEncodedString string, expectError string) {
func assertInvalid(t *testing.T, urlEncodedString, expectError string) {
t.Helper()

data, err := base64.RawURLEncoding.DecodeString(urlEncodedString)
assertNilError(t, err)
assertInvalidBytes(t, data, expectError)
}

func assertInvalidBytes(t *testing.T, data []byte, expectError string) {
t.Helper()

if consent, err := tcf1.Parse(data); err == nil {
t.Errorf("base64 URL-encoded string %s was considered valid, but shouldn't be. MaxVendorID: %d. len(data): %d", base64.RawURLEncoding.EncodeToString(data), consent.MaxVendorID(), len(data))
} else if err.Error() != expectError {
Expand All @@ -103,50 +105,58 @@ func assertInvalidBytes(t *testing.T, data []byte, expectError string) {
func decode(t *testing.T, encodedString string) []byte {
data, err := base64.RawURLEncoding.DecodeString(encodedString)
assertNilError(t, err)

return data
}

func assertStringsEqual(t *testing.T, expected string, actual string) {
func assertStringsEqual(t *testing.T, expected, actual string) {
t.Helper()

if actual != expected {
t.Errorf("Strings were not equal. Expected %s, actual %s", expected, actual)
}
}

func assertUInt8sEqual(t *testing.T, expected uint8, actual uint8) {
func assertUInt8sEqual(t *testing.T, expected, actual uint8) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertUInt16sEqual(t *testing.T, expected uint16, actual uint16) {
func assertUInt16sEqual(t *testing.T, expected, actual uint16) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertIntsEqual(t *testing.T, expected int, actual int) {
func assertIntsEqual(t *testing.T, expected, actual int) {
t.Helper()

if actual != expected {
t.Errorf("Ints were not equal. Expected %d, actual %d", expected, actual)
}
}

func assertBoolsEqual(t *testing.T, expected bool, actual bool) {
func assertBoolsEqual(t *testing.T, expected, actual bool) {
t.Helper()

if actual != expected {
t.Errorf("Bools were not equal. Expected %t, actual %t", expected, actual)
}
}

func buildMap(keys ...uint) map[uint]struct{} {
var s struct{}

m := make(map[uint]struct{}, len(keys))
for _, key := range keys {
m[key] = s
}

return m
}

Expand All @@ -160,13 +170,11 @@ func assertNilError(t *testing.T, err error) {
func isSet(data []byte, bitIndex uint) bool {
byteIndex := bitIndex / 8
bitOffset := bitIndex % 8

return byteToBool(data[byteIndex] & (0x80 >> bitOffset))
}

// byteToBool returns false if val is 0, and true otherwise
func byteToBool(val byte) bool {
if val == 0 {
return false
}
return true
return val != 0
}
Loading