mirror of
https://github.com/cubixle/iabtcfv2.git
synced 2026-04-24 21:34:43 +01:00
Encode and decode TCString
This commit is contained in:
63
bits.go
Normal file
63
bits.go
Normal file
@@ -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
|
||||
}
|
||||
203
decode.go
Normal file
203
decode.go
Normal file
@@ -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
decode_test.go
Normal file
164
decode_test.go
Normal file
@@ -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
encode_test.go
Normal file
335
encode_test.go
Normal file
@@ -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
encoder.go
Normal file
105
encoder.go
Normal file
@@ -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
|
||||
}
|
||||
65
segment_allowed_vendors.go
Normal file
65
segment_allowed_vendors.go
Normal file
@@ -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)
|
||||
}
|
||||
190
segment_core_string.go
Normal file
190
segment_core_string.go
Normal file
@@ -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)
|
||||
}
|
||||
65
segment_disclosed_vendors.go
Normal file
65
segment_disclosed_vendors.go
Normal file
@@ -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)
|
||||
}
|
||||
54
segment_publisher_tc.go
Normal file
54
segment_publisher_tc.go
Normal file
@@ -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)
|
||||
}
|
||||
45
tcdata.go
Normal file
45
tcdata.go
Normal file
@@ -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