mirror of
https://github.com/cubixle/iabtcfv2.git
synced 2026-04-30 15:28:44 +01:00
Encode and decode TCString
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
type Bits struct {
|
||||||
|
position uint
|
||||||
|
bytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
bytePows = []byte{128, 64, 32, 16, 8, 4, 2, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBits(bytes []byte) *Bits {
|
||||||
|
return &Bits{bytes: bytes, position: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) ReadBool() bool {
|
||||||
|
byteIndex := b.position / 8
|
||||||
|
bitIndex := b.position % 8
|
||||||
|
b.position++
|
||||||
|
|
||||||
|
return (b.bytes[byteIndex] & bytePows[bitIndex]) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) WriteBool(v bool) {
|
||||||
|
byteIndex := b.position / 8
|
||||||
|
shift := (byteIndex+1)*8 - b.position - 1
|
||||||
|
b.position++
|
||||||
|
|
||||||
|
if v {
|
||||||
|
b.bytes[byteIndex] |= 1 << uint(shift)
|
||||||
|
} else {
|
||||||
|
b.bytes[byteIndex] &^= 1 << uint(shift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) ReadInt(n uint) int {
|
||||||
|
v := 0
|
||||||
|
for i, shift := uint(0), n-1; i < n; i++ {
|
||||||
|
if b.ReadBool() {
|
||||||
|
v += 1 << uint(shift)
|
||||||
|
}
|
||||||
|
shift--
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) WriteInt(v int, n uint) {
|
||||||
|
b.WriteNumber(int64(v), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bits) WriteNumber(v int64, n uint) {
|
||||||
|
startOffset := int(b.position)
|
||||||
|
for i := int(n) - 1; i >= 0; i-- {
|
||||||
|
index := startOffset + i
|
||||||
|
byteIndex := index / 8
|
||||||
|
shift := (byteIndex+1)*8 - index - 1
|
||||||
|
b.bytes[byteIndex] |= byte(v%2) << uint(shift)
|
||||||
|
v /= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
b.position += n
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DecodeSegmentType(s string) int {
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(b)
|
||||||
|
return e.ReadInt(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(s string) (t *TCData, err error) {
|
||||||
|
t = &TCData{}
|
||||||
|
for _, v := range strings.Split(s, ".") {
|
||||||
|
segmentType := DecodeSegmentType(v)
|
||||||
|
if segmentType == 1 {
|
||||||
|
segment, err := DecodeDisclosedVendors(v)
|
||||||
|
if err == nil {
|
||||||
|
t.DisclosedVendors = segment
|
||||||
|
}
|
||||||
|
} else if segmentType == 2 {
|
||||||
|
segment, err := DecodeAllowedVendors(v)
|
||||||
|
if err == nil {
|
||||||
|
t.AllowedVendors = segment
|
||||||
|
}
|
||||||
|
} else if segmentType == 3 {
|
||||||
|
segment, err := DecodePubllisherTC(v)
|
||||||
|
if err == nil {
|
||||||
|
t.PublisherTC = segment
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
segment, err := DecodeCoreString(v)
|
||||||
|
if err == nil {
|
||||||
|
t.CoreString = segment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.CoreString == nil {
|
||||||
|
return nil, fmt.Errorf("invalid TC string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeCoreString(s string) (c *CoreString, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(b)
|
||||||
|
|
||||||
|
c = &CoreString{}
|
||||||
|
c.Version = e.ReadInt(6)
|
||||||
|
c.Created = e.ReadTime()
|
||||||
|
c.LastUpdated = e.ReadTime()
|
||||||
|
c.CmpId = e.ReadInt(12)
|
||||||
|
c.CmpVersion = e.ReadInt(12)
|
||||||
|
c.ConsentScreen = e.ReadInt(6)
|
||||||
|
c.ConsentLanguage = e.ReadIsoCode()
|
||||||
|
c.VendorListVersion = e.ReadInt(12)
|
||||||
|
c.TcfPolicyVersion = e.ReadInt(6)
|
||||||
|
c.IsServiceSpecific = e.ReadBool()
|
||||||
|
c.UseNonStandardStacks = e.ReadBool()
|
||||||
|
c.SpecialFeatureOptIns = e.ReadBitField(12)
|
||||||
|
c.PurposesConsent = e.ReadBitField(24)
|
||||||
|
c.PurposesLITransparency = e.ReadBitField(24)
|
||||||
|
c.PurposeOneTreatment = e.ReadBool()
|
||||||
|
c.PublisherCC = e.ReadIsoCode()
|
||||||
|
|
||||||
|
c.MaxVendorId = e.ReadInt(16)
|
||||||
|
c.IsRangeEncoding = e.ReadBool()
|
||||||
|
if c.IsRangeEncoding {
|
||||||
|
c.NumEntries = e.ReadInt(12)
|
||||||
|
c.RangeEntries = e.ReadRangeEntries(uint(c.NumEntries))
|
||||||
|
} else {
|
||||||
|
c.VendorsConsent = e.ReadBitField(uint(c.MaxVendorId))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.MaxVendorIdLI = e.ReadInt(16)
|
||||||
|
c.IsRangeEncodingLI = e.ReadBool()
|
||||||
|
if c.IsRangeEncodingLI {
|
||||||
|
c.NumEntriesLI = e.ReadInt(12)
|
||||||
|
c.RangeEntriesLI = e.ReadRangeEntries(uint(c.NumEntriesLI))
|
||||||
|
} else {
|
||||||
|
c.VendorsLITransparency = e.ReadBitField(uint(c.MaxVendorIdLI))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.NumPubRestrictions = e.ReadInt(12)
|
||||||
|
c.PubRestrictions = e.ReadPubRestrictions(uint(c.NumPubRestrictions))
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeDisclosedVendors(s string) (d *DisclosedVendors, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(b)
|
||||||
|
|
||||||
|
d = &DisclosedVendors{}
|
||||||
|
d.SegmentType = e.ReadInt(3)
|
||||||
|
d.MaxVendorId = e.ReadInt(16)
|
||||||
|
d.IsRangeEncoding = e.ReadBool()
|
||||||
|
if d.IsRangeEncoding {
|
||||||
|
d.NumEntries = e.ReadInt(12)
|
||||||
|
d.RangeEntries = e.ReadRangeEntries(uint(d.NumEntries))
|
||||||
|
} else {
|
||||||
|
d.DisclosedVendors = e.ReadBitField(uint(d.MaxVendorId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.SegmentType != 1 {
|
||||||
|
err = fmt.Errorf("disclosed vendors segment type must be 1")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeAllowedVendors(s string) (a *AllowedVendors, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(b)
|
||||||
|
|
||||||
|
a = &AllowedVendors{}
|
||||||
|
a.SegmentType = e.ReadInt(3)
|
||||||
|
a.MaxVendorId = e.ReadInt(16)
|
||||||
|
a.IsRangeEncoding = e.ReadBool()
|
||||||
|
if a.IsRangeEncoding {
|
||||||
|
a.NumEntries = e.ReadInt(12)
|
||||||
|
a.RangeEntries = e.ReadRangeEntries(uint(a.NumEntries))
|
||||||
|
} else {
|
||||||
|
a.AllowedVendors = e.ReadBitField(uint(a.MaxVendorId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.SegmentType != 2 {
|
||||||
|
err = fmt.Errorf("allowed vendors segment type must be 2")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodePubllisherTC(s string) (p *PublisherTC, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(b)
|
||||||
|
|
||||||
|
p = &PublisherTC{}
|
||||||
|
p.SegmentType = e.ReadInt(3)
|
||||||
|
p.PubPurposesConsent = e.ReadBitField(24)
|
||||||
|
p.PubPurposesLITransparency = e.ReadBitField(24)
|
||||||
|
p.NumCustomPurposes = e.ReadInt(6)
|
||||||
|
p.CustomPurposesConsent = e.ReadBitField(uint(p.NumCustomPurposes))
|
||||||
|
p.CustomPurposesLITransparency = e.ReadBitField(uint(p.NumCustomPurposes))
|
||||||
|
|
||||||
|
if p.SegmentType != 3 {
|
||||||
|
err = fmt.Errorf("allowed vendors segment type must be 3")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
str := "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA.IF3EXySoGY2tho2YVFzBEIYwfJxyigMgShgQIsS0NQIeFLBoGPiAAHBGYJAQAGBAkkACBAQIsHGBMCQABgAgRiRCMQEGMDzNIBIBAggkbY0FACCVmnkHS3ZCY70-6u__QA.elAAAAAAAWA"
|
||||||
|
|
||||||
|
data, err := Decode(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("TC String should be decoded without error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := data.ToTCString()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("Encode() should be produce a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeInvalid(t *testing.T) {
|
||||||
|
str := "IF3EXySoGY2tho2YVFzBEIYwfJxyigMgShgQIsS0NQIeFLBoGPiAAHBGYJAQAGBAkkACBAQIsHGBMCQABgAgRiRCMQEGMDzNIBIBAggkbY0FACCVmnkHS3ZCY70-6u__QA.elAAAAAAAWA"
|
||||||
|
|
||||||
|
_, err := Decode(str)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("TC String should not be decoded: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeCoreString(t *testing.T) {
|
||||||
|
str := "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA"
|
||||||
|
|
||||||
|
if DecodeSegmentType(str) != 0 {
|
||||||
|
t.Errorf("Segment type should be 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
segment, err := DecodeCoreString(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Segment should be decoded without error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("Encode() should be produce a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeDisclosedVendors(t *testing.T) {
|
||||||
|
str := "IF3EXySoGY2tho2YVFzBEIYwfJxyigMgShgQIsS0NQIeFLBoGPiAAHBGYJAQAGBAkkACBAQIsHGBMCQABgAgRiRCMQEGMDzNIBIBAggkbY0FACCVmnkHS3ZCY70-6u__QA"
|
||||||
|
|
||||||
|
if DecodeSegmentType(str) != 1 {
|
||||||
|
t.Errorf("Segment type should be 1")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
segment, err := DecodeDisclosedVendors(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Segment should be decoded without error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment.IsVendorDisclosed(1) {
|
||||||
|
t.Errorf("Vendor 1 should not be disclosed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !segment.IsVendorDisclosed(9) {
|
||||||
|
t.Errorf("Vendor 9 should be disclosed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("Encode() should be produce a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeAllowedVendors(t *testing.T) {
|
||||||
|
str := "QF3QAgABAA1A"
|
||||||
|
|
||||||
|
if DecodeSegmentType(str) != 2 {
|
||||||
|
t.Errorf("Segment type should be 2")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
segment, err := DecodeAllowedVendors(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Segment should be decoded without error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment.IsVendorAllowed(10) {
|
||||||
|
t.Errorf("Vendor 10 should not be disclosed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !segment.IsVendorAllowed(53) {
|
||||||
|
t.Errorf("Vendor 53 should be disclosed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("Encode() should be produce a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodePublisherTC(t *testing.T) {
|
||||||
|
str := "elAAAAAAAWA"
|
||||||
|
|
||||||
|
if DecodeSegmentType(str) != 3 {
|
||||||
|
t.Errorf("Segment type should be 3")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
segment, err := DecodePubllisherTC(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Segment should be decoded without error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !segment.IsPurposeAllowed(1) {
|
||||||
|
t.Errorf("Purpose 1 should be allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if segment.NumCustomPurposes != 2 {
|
||||||
|
t.Errorf("NumCustomPurposes should be 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("Encode() should be produce a string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
+335
@@ -0,0 +1,335 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
str := "COxSKBCOxSKCCBcABCENAgCMAPzAAEPAAAqIDaQBQAMgAgABqAR0A2gDaQAwAMgAgANoAAA.IDaQBQAMgAgABqAR0A2g.QF3QAgABAA1A.eEAAAAAAAUA"
|
||||||
|
data := &TCData{
|
||||||
|
CoreString: &CoreString{
|
||||||
|
Version: 2,
|
||||||
|
Created: timeFromDeciSeconds(15859228738),
|
||||||
|
LastUpdated: timeFromDeciSeconds(15859228802),
|
||||||
|
CmpId: 92,
|
||||||
|
CmpVersion: 1,
|
||||||
|
ConsentScreen: 2,
|
||||||
|
ConsentLanguage: "EN",
|
||||||
|
VendorListVersion: 32,
|
||||||
|
TcfPolicyVersion: 2,
|
||||||
|
IsServiceSpecific: false,
|
||||||
|
UseNonStandardStacks: false,
|
||||||
|
SpecialFeatureOptIns: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
},
|
||||||
|
PurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
3: true,
|
||||||
|
4: true,
|
||||||
|
5: true,
|
||||||
|
6: true,
|
||||||
|
9: true,
|
||||||
|
10: true,
|
||||||
|
},
|
||||||
|
PurposesLITransparency: map[int]bool{
|
||||||
|
2: true,
|
||||||
|
7: true,
|
||||||
|
8: true,
|
||||||
|
9: true,
|
||||||
|
10: true,
|
||||||
|
},
|
||||||
|
PurposeOneTreatment: false,
|
||||||
|
PublisherCC: "FR",
|
||||||
|
MaxVendorId: 436,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
VendorsConsent: map[int]bool{},
|
||||||
|
NumEntries: 5,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 285,
|
||||||
|
EndVendorID: 285,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxVendorIdLI: 436,
|
||||||
|
IsRangeEncodingLI: true,
|
||||||
|
VendorsLITransparency: map[int]bool{},
|
||||||
|
NumEntriesLI: 3,
|
||||||
|
RangeEntriesLI: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumPubRestrictions: 0,
|
||||||
|
},
|
||||||
|
DisclosedVendors: &DisclosedVendors{
|
||||||
|
SegmentType: 1,
|
||||||
|
MaxVendorId: 436,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
NumEntries: 5,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 285,
|
||||||
|
EndVendorID: 285,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AllowedVendors: &AllowedVendors{
|
||||||
|
SegmentType: 2,
|
||||||
|
MaxVendorId: 750,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
NumEntries: 2,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 2,
|
||||||
|
EndVendorID: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PublisherTC: &PublisherTC{
|
||||||
|
SegmentType: 3,
|
||||||
|
PubPurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
7: true,
|
||||||
|
},
|
||||||
|
PubPurposesLITransparency: map[int]bool{},
|
||||||
|
NumCustomPurposes: 2,
|
||||||
|
CustomPurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
},
|
||||||
|
CustomPurposesLITransparency: map[int]bool{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := data.ToTCString()
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestEncodeCoreString(t *testing.T) {
|
||||||
|
str := "COxSKBCOxSKCCBcABCENAgCMAPzAAEPAAAqIDaQBQAMgAgABqAR0A2gDaQAwAMgAgANoAAA"
|
||||||
|
segment := &CoreString{
|
||||||
|
Version: 2,
|
||||||
|
Created: timeFromDeciSeconds(15859228738),
|
||||||
|
LastUpdated: timeFromDeciSeconds(15859228802),
|
||||||
|
CmpId: 92,
|
||||||
|
CmpVersion: 1,
|
||||||
|
ConsentScreen: 2,
|
||||||
|
ConsentLanguage: "EN",
|
||||||
|
VendorListVersion: 32,
|
||||||
|
TcfPolicyVersion: 2,
|
||||||
|
IsServiceSpecific: false,
|
||||||
|
UseNonStandardStacks: false,
|
||||||
|
SpecialFeatureOptIns: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
},
|
||||||
|
PurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
3: true,
|
||||||
|
4: true,
|
||||||
|
5: true,
|
||||||
|
6: true,
|
||||||
|
9: true,
|
||||||
|
10: true,
|
||||||
|
},
|
||||||
|
PurposesLITransparency: map[int]bool{
|
||||||
|
2: true,
|
||||||
|
7: true,
|
||||||
|
8: true,
|
||||||
|
9: true,
|
||||||
|
10: true,
|
||||||
|
},
|
||||||
|
PurposeOneTreatment: false,
|
||||||
|
PublisherCC: "FR",
|
||||||
|
MaxVendorId: 436,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
VendorsConsent: map[int]bool{},
|
||||||
|
NumEntries: 5,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 285,
|
||||||
|
EndVendorID: 285,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxVendorIdLI: 436,
|
||||||
|
IsRangeEncodingLI: true,
|
||||||
|
VendorsLITransparency: map[int]bool{},
|
||||||
|
NumEntriesLI: 3,
|
||||||
|
RangeEntriesLI: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumPubRestrictions: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDisclosedVendors(t *testing.T) {
|
||||||
|
str := "IDaQBQAMgAgABqAR0A2g"
|
||||||
|
segment := &DisclosedVendors{
|
||||||
|
SegmentType: 1,
|
||||||
|
MaxVendorId: 436,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
NumEntries: 5,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 25,
|
||||||
|
EndVendorID: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 32,
|
||||||
|
EndVendorID: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 285,
|
||||||
|
EndVendorID: 285,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 436,
|
||||||
|
EndVendorID: 436,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeAllowedVendors(t *testing.T) {
|
||||||
|
str := "QF3QAgABAA1A"
|
||||||
|
segment := &AllowedVendors{
|
||||||
|
SegmentType: 2,
|
||||||
|
MaxVendorId: 750,
|
||||||
|
IsRangeEncoding: true,
|
||||||
|
NumEntries: 2,
|
||||||
|
RangeEntries: []*RangeEntry{
|
||||||
|
{
|
||||||
|
StartVendorID: 2,
|
||||||
|
EndVendorID: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StartVendorID: 53,
|
||||||
|
EndVendorID: 53,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodePublisherTC(t *testing.T) {
|
||||||
|
str := "eEAAAAAAAUA"
|
||||||
|
segment := &PublisherTC{
|
||||||
|
SegmentType: 3,
|
||||||
|
PubPurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
2: true,
|
||||||
|
7: true,
|
||||||
|
},
|
||||||
|
PubPurposesLITransparency: map[int]bool{},
|
||||||
|
NumCustomPurposes: 2,
|
||||||
|
CustomPurposesConsent: map[int]bool{
|
||||||
|
1: true,
|
||||||
|
},
|
||||||
|
CustomPurposesLITransparency: map[int]bool{},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := segment.Encode()
|
||||||
|
if result != str {
|
||||||
|
t.Errorf("Encode() should produce the same string: in = %s, out = %s", str, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeFromDeciSeconds(deciseconds int64) time.Time {
|
||||||
|
return time.Unix(deciseconds/10, (deciseconds%10)*int64(time.Millisecond*100)).UTC()
|
||||||
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
decisecondsPerSecond = 10
|
||||||
|
nanosecondsPerDecisecond = int64(time.Millisecond * 100)
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCEncoder struct {
|
||||||
|
*Bits
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCEncoder(src []byte) *TCEncoder {
|
||||||
|
return &TCEncoder{NewBits(src)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) ReadTime() time.Time {
|
||||||
|
var ds = int64(r.ReadInt(36))
|
||||||
|
return time.Unix(ds/decisecondsPerSecond, (ds%decisecondsPerSecond)*nanosecondsPerDecisecond).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) WriteTime(v time.Time) {
|
||||||
|
r.WriteNumber(v.UnixNano()/nanosecondsPerDecisecond, 36)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) ReadIsoCode() string {
|
||||||
|
var buf = make([]byte, 0, 2)
|
||||||
|
for i := uint(0); i < 2; i++ {
|
||||||
|
buf = append(buf, byte(r.ReadInt(6))+'A')
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) WriteIsoCode(v string) {
|
||||||
|
for _, char := range v {
|
||||||
|
r.WriteInt(int(byte(char)-'A'), 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) ReadBitField(n uint) map[int]bool {
|
||||||
|
var m = make(map[int]bool)
|
||||||
|
for i := uint(0); i < n; i++ {
|
||||||
|
if r.ReadBool() {
|
||||||
|
m[int(i)+1] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) WriteRangeEntries(entries []*RangeEntry) {
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
r.WriteBool(true)
|
||||||
|
r.WriteInt(entry.StartVendorID, 16)
|
||||||
|
r.WriteInt(entry.EndVendorID, 16)
|
||||||
|
} else {
|
||||||
|
r.WriteBool(false)
|
||||||
|
r.WriteInt(entry.StartVendorID, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) ReadRangeEntries(n uint) []*RangeEntry {
|
||||||
|
var ret = make([]*RangeEntry, 0, n)
|
||||||
|
for i := uint(0); i < n; i++ {
|
||||||
|
var isRange = r.ReadBool()
|
||||||
|
var start, end int
|
||||||
|
start = r.ReadInt(16)
|
||||||
|
if isRange {
|
||||||
|
end = r.ReadInt(16)
|
||||||
|
} else {
|
||||||
|
end = start
|
||||||
|
}
|
||||||
|
ret = append(ret, &RangeEntry{StartVendorID: start, EndVendorID: end})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) WritePubRestrictions(entries []*PubRestriction) {
|
||||||
|
for _, entry := range entries {
|
||||||
|
r.WriteInt(entry.PurposeId, 6)
|
||||||
|
r.WriteInt(entry.RestrictionType, 2)
|
||||||
|
r.WriteInt(len(entry.RangeEntries), 12)
|
||||||
|
r.WriteRangeEntries(entry.RangeEntries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TCEncoder) ReadPubRestrictions(n uint) []*PubRestriction {
|
||||||
|
var ret = make([]*PubRestriction, 0, n)
|
||||||
|
for i := uint(0); i < n; i++ {
|
||||||
|
var purposeId = r.ReadInt(6)
|
||||||
|
var restrictionType = r.ReadInt(2)
|
||||||
|
var numEntries = r.ReadInt(12)
|
||||||
|
var rangeEntries = r.ReadRangeEntries(uint(numEntries))
|
||||||
|
ret = append(ret, &PubRestriction{PurposeId: purposeId,
|
||||||
|
RestrictionType: restrictionType,
|
||||||
|
NumEntries: numEntries,
|
||||||
|
RangeEntries: rangeEntries,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AllowedVendors struct {
|
||||||
|
SegmentType int
|
||||||
|
MaxVendorId int
|
||||||
|
IsRangeEncoding bool
|
||||||
|
AllowedVendors map[int]bool
|
||||||
|
NumEntries int
|
||||||
|
RangeEntries []*RangeEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AllowedVendors) IsVendorAllowed(id int) bool {
|
||||||
|
if a.IsRangeEncoding {
|
||||||
|
for _, entry := range a.RangeEntries {
|
||||||
|
if entry.StartVendorID <= id && id <= entry.EndVendorID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.AllowedVendors[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AllowedVendors) Encode() string {
|
||||||
|
bitSize := 20
|
||||||
|
|
||||||
|
if a.IsRangeEncoding {
|
||||||
|
bitSize += 12
|
||||||
|
entriesSize := len(a.RangeEntries)
|
||||||
|
for _, entry := range a.RangeEntries {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
entriesSize += 16 * 2
|
||||||
|
} else {
|
||||||
|
entriesSize += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitSize += entriesSize
|
||||||
|
} else {
|
||||||
|
bitSize += a.MaxVendorId
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(make([]byte, bitSize/8))
|
||||||
|
if bitSize%8 != 0 {
|
||||||
|
e = NewTCEncoder(make([]byte, bitSize/8+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(a.SegmentType, 3)
|
||||||
|
e.WriteInt(a.MaxVendorId, 16)
|
||||||
|
e.WriteBool(a.IsRangeEncoding)
|
||||||
|
if a.IsRangeEncoding {
|
||||||
|
e.WriteInt(len(a.RangeEntries), 12)
|
||||||
|
e.WriteRangeEntries(a.RangeEntries)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < a.MaxVendorId; i++ {
|
||||||
|
e.WriteBool(a.IsVendorAllowed(i + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(e.bytes)
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CoreString struct {
|
||||||
|
Version int
|
||||||
|
Created time.Time
|
||||||
|
LastUpdated time.Time
|
||||||
|
CmpId int
|
||||||
|
CmpVersion int
|
||||||
|
ConsentScreen int
|
||||||
|
ConsentLanguage string
|
||||||
|
VendorListVersion int
|
||||||
|
TcfPolicyVersion int
|
||||||
|
IsServiceSpecific bool
|
||||||
|
UseNonStandardStacks bool
|
||||||
|
SpecialFeatureOptIns map[int]bool
|
||||||
|
PurposesConsent map[int]bool
|
||||||
|
PurposesLITransparency map[int]bool
|
||||||
|
PurposeOneTreatment bool
|
||||||
|
PublisherCC string
|
||||||
|
MaxVendorId int
|
||||||
|
IsRangeEncoding bool
|
||||||
|
VendorsConsent map[int]bool
|
||||||
|
NumEntries int
|
||||||
|
RangeEntries []*RangeEntry
|
||||||
|
MaxVendorIdLI int
|
||||||
|
IsRangeEncodingLI bool
|
||||||
|
VendorsLITransparency map[int]bool
|
||||||
|
NumEntriesLI int
|
||||||
|
RangeEntriesLI []*RangeEntry
|
||||||
|
NumPubRestrictions int
|
||||||
|
PubRestrictions []*PubRestriction
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubRestriction struct {
|
||||||
|
PurposeId int
|
||||||
|
RestrictionType int
|
||||||
|
NumEntries int
|
||||||
|
RangeEntries []*RangeEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type RangeEntry struct {
|
||||||
|
StartVendorID int
|
||||||
|
EndVendorID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) IsSpecialFeatureAllowed(id int) bool {
|
||||||
|
return c.SpecialFeatureOptIns[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) IsPurposeAllowed(id int) bool {
|
||||||
|
return c.PurposesConsent[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) IsPurposeLIAllowed(id int) bool {
|
||||||
|
return c.PurposesLITransparency[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) IsVendorAllowed(id int) bool {
|
||||||
|
if c.IsRangeEncoding {
|
||||||
|
for _, entry := range c.RangeEntries {
|
||||||
|
if entry.StartVendorID <= id && id <= entry.EndVendorID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.VendorsConsent[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) IsVendorLIAllowed(id int) bool {
|
||||||
|
if c.IsRangeEncodingLI {
|
||||||
|
for _, entry := range c.RangeEntriesLI {
|
||||||
|
if entry.StartVendorID <= id && id <= entry.EndVendorID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.VendorsLITransparency[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CoreString) Encode() string {
|
||||||
|
bitSize := 230
|
||||||
|
|
||||||
|
if c.IsRangeEncoding {
|
||||||
|
bitSize += 12
|
||||||
|
entriesSize := len(c.RangeEntries)
|
||||||
|
for _, entry := range c.RangeEntries {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
entriesSize += 16 * 2
|
||||||
|
} else {
|
||||||
|
entriesSize += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitSize += +entriesSize
|
||||||
|
} else {
|
||||||
|
bitSize += c.MaxVendorId
|
||||||
|
}
|
||||||
|
|
||||||
|
bitSize += 16
|
||||||
|
if c.IsRangeEncodingLI {
|
||||||
|
bitSize += 12
|
||||||
|
entriesSize := len(c.RangeEntriesLI)
|
||||||
|
for _, entry := range c.RangeEntriesLI {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
entriesSize += 16 * 2
|
||||||
|
} else {
|
||||||
|
entriesSize += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitSize += entriesSize
|
||||||
|
} else {
|
||||||
|
bitSize += c.MaxVendorIdLI
|
||||||
|
}
|
||||||
|
|
||||||
|
bitSize += 12
|
||||||
|
for _, res := range c.PubRestrictions {
|
||||||
|
entriesSize := 20
|
||||||
|
for _, entry := range res.RangeEntries {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
entriesSize += 16 * 2
|
||||||
|
} else {
|
||||||
|
entriesSize += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitSize += entriesSize
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(make([]byte, bitSize/8))
|
||||||
|
if bitSize%8 != 0 {
|
||||||
|
e = NewTCEncoder(make([]byte, bitSize/8+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(c.Version, 6)
|
||||||
|
e.WriteTime(c.Created)
|
||||||
|
e.WriteTime(c.LastUpdated)
|
||||||
|
e.WriteInt(c.CmpId, 12)
|
||||||
|
e.WriteInt(c.CmpVersion, 12)
|
||||||
|
e.WriteInt(c.ConsentScreen, 6)
|
||||||
|
e.WriteIsoCode(c.ConsentLanguage)
|
||||||
|
e.WriteInt(c.VendorListVersion, 12)
|
||||||
|
e.WriteInt(c.TcfPolicyVersion, 6)
|
||||||
|
e.WriteBool(c.IsServiceSpecific)
|
||||||
|
e.WriteBool(c.UseNonStandardStacks)
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
e.WriteBool(c.IsSpecialFeatureAllowed(i + 1))
|
||||||
|
}
|
||||||
|
for i := 0; i < 24; i++ {
|
||||||
|
e.WriteBool(c.IsPurposeAllowed(i + 1))
|
||||||
|
}
|
||||||
|
for i := 0; i < 24; i++ {
|
||||||
|
e.WriteBool(c.IsPurposeLIAllowed(i + 1))
|
||||||
|
}
|
||||||
|
e.WriteBool(c.PurposeOneTreatment)
|
||||||
|
e.WriteIsoCode(c.PublisherCC)
|
||||||
|
|
||||||
|
e.WriteInt(c.MaxVendorId, 16)
|
||||||
|
e.WriteBool(c.IsRangeEncoding)
|
||||||
|
if c.IsRangeEncoding {
|
||||||
|
e.WriteInt(len(c.RangeEntries), 12)
|
||||||
|
e.WriteRangeEntries(c.RangeEntries)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < c.MaxVendorId; i++ {
|
||||||
|
e.WriteBool(c.IsVendorAllowed(i + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(c.MaxVendorIdLI, 16)
|
||||||
|
e.WriteBool(c.IsRangeEncodingLI)
|
||||||
|
if c.IsRangeEncodingLI {
|
||||||
|
e.WriteInt(len(c.RangeEntriesLI), 12)
|
||||||
|
e.WriteRangeEntries(c.RangeEntriesLI)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < c.MaxVendorIdLI; i++ {
|
||||||
|
e.WriteBool(c.IsVendorLIAllowed(i + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(len(c.PubRestrictions), 12)
|
||||||
|
e.WritePubRestrictions(c.PubRestrictions)
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(e.bytes)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DisclosedVendors struct {
|
||||||
|
SegmentType int
|
||||||
|
MaxVendorId int
|
||||||
|
IsRangeEncoding bool
|
||||||
|
DisclosedVendors map[int]bool
|
||||||
|
NumEntries int
|
||||||
|
RangeEntries []*RangeEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DisclosedVendors) IsVendorDisclosed(id int) bool {
|
||||||
|
if d.IsRangeEncoding {
|
||||||
|
for _, entry := range d.RangeEntries {
|
||||||
|
if entry.StartVendorID <= id && id <= entry.EndVendorID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.DisclosedVendors[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DisclosedVendors) Encode() string {
|
||||||
|
bitSize := 20
|
||||||
|
|
||||||
|
if d.IsRangeEncoding {
|
||||||
|
bitSize += 12
|
||||||
|
entriesSize := len(d.RangeEntries)
|
||||||
|
for _, entry := range d.RangeEntries {
|
||||||
|
if entry.EndVendorID > entry.StartVendorID {
|
||||||
|
entriesSize += 16 * 2
|
||||||
|
} else {
|
||||||
|
entriesSize += 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitSize += entriesSize
|
||||||
|
} else {
|
||||||
|
bitSize += d.MaxVendorId
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = NewTCEncoder(make([]byte, bitSize/8))
|
||||||
|
if bitSize%8 != 0 {
|
||||||
|
e = NewTCEncoder(make([]byte, bitSize/8+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(d.SegmentType, 3)
|
||||||
|
e.WriteInt(d.MaxVendorId, 16)
|
||||||
|
e.WriteBool(d.IsRangeEncoding)
|
||||||
|
if d.IsRangeEncoding {
|
||||||
|
e.WriteInt(len(d.RangeEntries), 12)
|
||||||
|
e.WriteRangeEntries(d.RangeEntries)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < d.MaxVendorId; i++ {
|
||||||
|
e.WriteBool(d.IsVendorDisclosed(i + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(e.bytes)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
type PublisherTC struct {
|
||||||
|
SegmentType int
|
||||||
|
PubPurposesConsent map[int]bool
|
||||||
|
PubPurposesLITransparency map[int]bool
|
||||||
|
NumCustomPurposes int
|
||||||
|
CustomPurposesConsent map[int]bool
|
||||||
|
CustomPurposesLITransparency map[int]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublisherTC) IsPurposeAllowed(id int) bool {
|
||||||
|
return p.PubPurposesConsent[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublisherTC) IsPurposeLIAllowed(id int) bool {
|
||||||
|
return p.PubPurposesLITransparency[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublisherTC) IsCustomPurposeAllowed(id int) bool {
|
||||||
|
return p.CustomPurposesConsent[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublisherTC) IsCustomPurposeLIAllowed(id int) bool {
|
||||||
|
return p.CustomPurposesLITransparency[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PublisherTC) Encode() string {
|
||||||
|
bitSize := 57 + (p.NumCustomPurposes * 2)
|
||||||
|
|
||||||
|
var e = NewTCEncoder(make([]byte, bitSize/8))
|
||||||
|
if bitSize%8 != 0 {
|
||||||
|
e = NewTCEncoder(make([]byte, bitSize/8+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.WriteInt(p.SegmentType, 3)
|
||||||
|
for i := 0; i < 24; i++ {
|
||||||
|
e.WriteBool(p.IsPurposeAllowed(i + 1))
|
||||||
|
}
|
||||||
|
for i := 0; i < 24; i++ {
|
||||||
|
e.WriteBool(p.IsPurposeLIAllowed(i + 1))
|
||||||
|
}
|
||||||
|
e.WriteInt(p.NumCustomPurposes, 6)
|
||||||
|
for i := 0; i < p.NumCustomPurposes; i++ {
|
||||||
|
e.WriteBool(p.IsCustomPurposeAllowed(i + 1))
|
||||||
|
}
|
||||||
|
for i := 0; i < p.NumCustomPurposes; i++ {
|
||||||
|
e.WriteBool(p.IsCustomPurposeLIAllowed(i + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(e.bytes)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package iabtcf
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type TCData struct {
|
||||||
|
CoreString *CoreString
|
||||||
|
DisclosedVendors *DisclosedVendors
|
||||||
|
AllowedVendors *AllowedVendors
|
||||||
|
PublisherTC *PublisherTC
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCData) IsPurposeAllowed(id int) bool {
|
||||||
|
return t.CoreString.IsPurposeAllowed(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCData) IsPurposeLIAllowed(id int) bool {
|
||||||
|
return t.CoreString.IsPurposeLIAllowed(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCData) IsVendorAllowed(id int) bool {
|
||||||
|
return t.CoreString.IsVendorAllowed(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCData) IsVendorLIAllowed(id int) bool {
|
||||||
|
return t.CoreString.IsVendorLIAllowed(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TCData) ToTCString() string {
|
||||||
|
var segments []string
|
||||||
|
|
||||||
|
if t.CoreString != nil {
|
||||||
|
segments = append(segments, t.CoreString.Encode())
|
||||||
|
}
|
||||||
|
if t.DisclosedVendors != nil {
|
||||||
|
segments = append(segments, t.DisclosedVendors.Encode())
|
||||||
|
}
|
||||||
|
if t.AllowedVendors != nil {
|
||||||
|
segments = append(segments, t.AllowedVendors.Encode())
|
||||||
|
}
|
||||||
|
if t.PublisherTC != nil {
|
||||||
|
segments = append(segments, t.PublisherTC.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(segments, ".")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user