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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.iab.gpp.encoder.datatype;

import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.OptimizedFibonacciRangeEncoder;
import com.iab.gpp.encoder.field.FieldKey;
import com.iab.gpp.encoder.segment.EncodableSegment;
import java.util.List;

/**
* Encodes an {@code N-ArrayOfRanges(X,Y)} field where each record's ids are encoded as an {@code
* OptimizedRange} (Fibonacci coded), per the GPP Consent String Specification. Counterpart to
* {@link EncodableArrayOfFixedIntegerRanges}, which encodes ids using fixed-integer ranges for
* downward compatibility (e.g. TCF EU).
*/
public final class EncodableArrayOfOptimizedFibonacciRanges<E extends Enum<E> & FieldKey>
extends AbstractDirtyableBitStringDataType<E, DirtyableList<RangeEntry>> {

private final int keyBitStringLength;
private final int typeBitStringLength;

public EncodableArrayOfOptimizedFibonacciRanges(
String name, int keyBitStringLength, int typeBitStringLength) {
super(name, null);
this.keyBitStringLength = keyBitStringLength;
this.typeBitStringLength = typeBitStringLength;
}

@Override
public String toString() {
return name + "=N-ArrayOfRanges(" + keyBitStringLength + "," + typeBitStringLength + ")";
}

@Override
protected DirtyableList<RangeEntry> initialize() {
return new DirtyableList<>();
}

@Override
protected boolean isPresent(DirtyableList<RangeEntry> value) {
return !value.isEmpty();
}

@Override
protected void encode(
BitString sb, DirtyableList<RangeEntry> entries, EncodableSegment<E> segment) {
sb.writeInt(entries.size(), 12);
for (RangeEntry entry : entries) {
sb.writeInt(entry.getKey(), keyBitStringLength);
sb.writeInt(entry.getType(), typeBitStringLength);
OptimizedFibonacciRangeEncoder.encode(sb, entry.getIds());
}
}

@Override
protected DirtyableList<RangeEntry> decode(BitString reader, EncodableSegment<E> segment) {
int size = reader.readInt(12);
DirtyableList<RangeEntry> value = initialize();
for (int i = 0; i < size; i++) {
int key = reader.readInt(keyBitStringLength);
int type = reader.readInt(typeBitStringLength);
IntegerSet ids = OptimizedFibonacciRangeEncoder.decode(reader);
RangeEntry entry = new RangeEntry(key, type, ids);
value.add(entry);
}
return value;
}

@SuppressWarnings("unchecked")
@Override
protected DirtyableList<RangeEntry> processValue(
DirtyableList<RangeEntry> oldValue, Object newValue) {
oldValue.clear();
oldValue.addAll((List<RangeEntry>) newValue);
return oldValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.iab.gpp.encoder.datatype;

import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.OptimizedFibonacciRangeEncoder;
import com.iab.gpp.encoder.field.FieldKey;
import com.iab.gpp.encoder.segment.EncodableSegment;
import java.util.Collection;

public final class EncodableOptimizedFibonacciRange<E extends Enum<E> & FieldKey>
extends AbstractDirtyableBitStringDataType<E, IntegerSet> {

public EncodableOptimizedFibonacciRange(String name) {
super(name, null);
}

@Override
protected IntegerSet initialize() {
return new IntegerSet();
}

@Override
protected boolean isPresent(IntegerSet value) {
return !value.isEmpty();
}

@Override
protected void encode(BitString builder, IntegerSet value, EncodableSegment<E> segment) {
OptimizedFibonacciRangeEncoder.encode(builder, value);
}

@Override
protected IntegerSet decode(BitString reader, EncodableSegment<E> segment) {
return OptimizedFibonacciRangeEncoder.decode(reader);
}

@SuppressWarnings("unchecked")
@Override
protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
oldValue.clear();
oldValue.addAll((Collection<Integer>) newValue);
return oldValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.iab.gpp.encoder.datatype.encoder;

import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.IntegerSet;
import com.iab.gpp.encoder.error.DecodingException;
import com.iab.gpp.encoder.error.EncodingException;

/**
* Encodes an {@code OptimizedRange} that uses Fibonacci coding for the range representation, per
* the GPP Consent String Specification. Counterpart to {@link OptimizedFixedRangeEncoder}, which
* uses fixed-integer ranges for downward compatibility.
*/
public class OptimizedFibonacciRangeEncoder {

public static void encode(BitString builder, IntegerSet value) throws EncodingException {
// TODO: encoding the range before choosing the shortest is inefficient. There is probably a way
// to identify in advance which will be shorter based on the array length and values
BitString rangeBitString = new BitString();
int max = FibonacciIntegerRangeEncoder.encode(rangeBitString, value);
int rangeLength = rangeBitString.length();
int bitFieldLength = max;

if (rangeLength <= bitFieldLength) {
builder.writeInt(max, 16);
builder.writeBoolean(true);
builder.write(rangeBitString);
} else {
builder.writeInt(max, 16);
builder.writeBoolean(false);
for (int i = 0; i < max; i++) {
builder.writeBoolean(value.containsInt(i + 1));
}
}
}

public static IntegerSet decode(BitString reader) throws DecodingException {
int size = reader.readInt(16);
if (reader.readBoolean()) {
return FibonacciIntegerRangeEncoder.decode(reader);
} else {
return reader.readIntegerSet(size);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.iab.gpp.encoder.field;

import com.iab.gpp.encoder.datatype.DataType;
import com.iab.gpp.encoder.datatype.EncodableArrayOfFixedIntegerRanges;
import com.iab.gpp.encoder.datatype.EncodableArrayOfOptimizedFibonacciRanges;
import com.iab.gpp.encoder.datatype.EncodableBoolean;
import com.iab.gpp.encoder.datatype.EncodableDatetime;
import com.iab.gpp.encoder.datatype.EncodableFixedBitfield;
import com.iab.gpp.encoder.datatype.EncodableFixedInteger;
import com.iab.gpp.encoder.datatype.EncodableFixedString;
import com.iab.gpp.encoder.datatype.EncodableFlexibleBitfield;
import com.iab.gpp.encoder.datatype.EncodableOptimizedFixedRange;
import com.iab.gpp.encoder.datatype.EncodableOptimizedFibonacciRange;
import com.iab.gpp.encoder.section.TcfCaV1;

public enum TcfCaV1Field implements FieldKey {
Expand All @@ -25,9 +25,9 @@ public enum TcfCaV1Field implements FieldKey {
SPECIAL_FEATURE_EXPRESS_CONSENT(new EncodableFixedBitfield<>("SpecialFeatureExpressConsent", 12)),
PURPOSES_EXPRESS_CONSENT(new EncodableFixedBitfield<>("PurposesExpressConsent", 24)),
PURPOSES_IMPLIED_CONSENT(new EncodableFixedBitfield<>("PurposesImpliedConsent", 24)),
VENDOR_EXPRESS_CONSENT(new EncodableOptimizedFixedRange<>("VendorExpressConsent")),
VENDOR_IMPLIED_CONSENT(new EncodableOptimizedFixedRange<>("VendorImpliedConsent")),
PUB_RESTRICTIONS(new EncodableArrayOfFixedIntegerRanges<>("PubRestrictions", 6, 2)),
VENDOR_EXPRESS_CONSENT(new EncodableOptimizedFibonacciRange<>("VendorExpressConsent")),
VENDOR_IMPLIED_CONSENT(new EncodableOptimizedFibonacciRange<>("VendorImpliedConsent")),
PUB_RESTRICTIONS(new EncodableArrayOfOptimizedFibonacciRanges<>("PubRestrictions", 6, 2)),

PUB_PURPOSES_SEGMENT_TYPE(new EncodableFixedInteger<>("PubPurposesSegmentType", 3, 3)),
PUB_PURPOSES_EXPRESS_CONSENT(new EncodableFixedBitfield<>("PubPurposesExpressConsent", 24)),
Expand All @@ -41,7 +41,7 @@ public enum TcfCaV1Field implements FieldKey {
"CustomPurposesImpliedConsent", TcfCaV1Field.NUM_CUSTOM_PURPOSES)),

DISCLOSED_VENDORS_SEGMENT_TYPE(new EncodableFixedInteger<>("DisclosedVendorsSegmentType", 3, 1)),
DISCLOSED_VENDORS(new EncodableOptimizedFixedRange<>("DisclosedVendors"));
DISCLOSED_VENDORS(new EncodableOptimizedFibonacciRange<>("DisclosedVendors"));

private final DataType<TcfCaV1Field, ?> type;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ public void testEncodeUspV1AndTcfEuV2AndTcfCaV1() {

String gppString = gppModel.encode();
Assertions.assertEquals(
"DBACOeA~CPSG_8APSG_8ANwAAAENAwCAAAAAAAAAAAAAAAAAAAAA.IAAA~BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBgABABAAABAB4AACACAAA.fHHHA4444ao~1YNN",
"DBACOeA~CPSG_8APSG_8ANwAAAENAwCAAAAAAAAAAAAAAAAAAAAA.IAAA~BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBhADVqxGAD0AILVgAA.fHHHA4444ao~1YNN",
gppString);

Assertions.assertEquals(4, gppString.split("~").length);
Expand Down Expand Up @@ -616,7 +616,7 @@ public void testDecodeUspv1AndTcfEuV2() {
@Test
public void testDecodeUspv1AndTcfEuV2AndTcfCaV1() {
String gppString =
"DBACOeA~CPSG_8APSG_8ANwAAAENAwCAAAAAAAAAAAAAAAAAAAAA.QAAA.IAAA~BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBgABABAAABAB4AACACAAA.fHHHA4444ao~1YNN";
"DBACOeA~CPSG_8APSG_8ANwAAAENAwCAAAAAAAAAAAAAAAAAAAAA.QAAA.IAAA~BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBhADVqxGAD0AILVgAA.fHHHA4444ao~1YNN";
GppModel gppModel = new GppModel(gppString);

Assertions.assertEquals(Set.of(2, 5, 6), gppModel.getSectionIds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void testEncode2() {
ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")).toInstant());

Assertions.assertEquals(
"BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBgABABAAABAB4AACACAAA.fHHHA4444ao", tcfCaV1.encode());
"BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBhADVqxGAD0AILVgAA.fHHHA4444ao", tcfCaV1.encode());
}

@Test
Expand Down Expand Up @@ -103,8 +103,7 @@ public void testEncode4() throws EncodingException, InvalidFieldException {
TcfCaV1Field.LAST_UPDATED,
ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")).toInstant());
Assertions.assertEquals(
"BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAACCgBwABAAOAAoADgAJA.YAAAAAAAAAA",
tcfCaV1.encode());
"BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAACCgAS7o.YAAAAAAAAAA", tcfCaV1.encode());
}

@Test
Expand Down Expand Up @@ -143,7 +142,7 @@ public void testDecode1() {
@Test
public void testDecode2() {
TcfCaV1 tcfCaV1 =
new TcfCaV1("BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBgABABAAABAB4AACACAAA.fHHHA4444ao");
new TcfCaV1("BPSG_8APSG_8AAyACAENGdCgf_gfgAfgfgBhADVqxGAD0AILVgAA.fHHHA4444ao");

Assertions.assertEquals(50, tcfCaV1.getCmpId());
Assertions.assertEquals(2, tcfCaV1.getCmpVersion());
Expand Down Expand Up @@ -186,8 +185,7 @@ public void testDecode3() throws DecodingException {

@Test
public void testDecode4() throws DecodingException {
TcfCaV1 tcfCaV1 =
new TcfCaV1("BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAACCgBwABAAOAAoADgAJA.YAAAAAAAAAA");
TcfCaV1 tcfCaV1 = new TcfCaV1("BPSG_8APSG_8AAAAAAENAACAAAAAAAAAAAAAAAAACCgAS7o.YAAAAAAAAAA");

List<RangeEntry> pubRestictions = tcfCaV1.getPubRestrictions();
Assertions.assertEquals(1, pubRestictions.size());
Expand Down
Loading