Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tcfcav2 #1

Merged
merged 1 commit into from
Sep 27, 2022
Merged
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
tcfcav2
  • Loading branch information
chad committed Sep 27, 2022
commit 0615f4da7327f63d969f4fca3d016ea1508b00d9
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
target
/.idea/
/iabtcf-core.iml
*~
.checkstyle
.classpath
.project
.settings
iabtcf-decoder/iabtcf-decoder.iml
iabtcf-encoder/iabtcf-encoder.iml
iabtcf-extras/iabtcf-extras.iml
iabtcf-extras-jackson/iabtcf-extras-jackson.iml
18 changes: 16 additions & 2 deletions iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import com.iab.gpp.encoder.error.EncodingException;
import com.iab.gpp.encoder.section.EncodableSection;
import com.iab.gpp.encoder.section.HeaderV1;
import com.iab.gpp.encoder.section.TcfCaV2;
import com.iab.gpp.encoder.section.TcfEuV2;
import com.iab.gpp.encoder.section.UspV1;

public class GppModel {
private Map<String, EncodableSection> sections = new HashMap<>();
private String[] sectionOrder = new String[] {TcfEuV2.NAME, UspV1.NAME};
private String[] sectionOrder = new String[] {TcfEuV2.NAME, TcfCaV2.NAME, UspV1.NAME};

public GppModel() {

Expand All @@ -29,7 +30,10 @@ public GppModel(String encodedString) throws DecodingException {
public void setFieldValue(String sectionName, String fieldName, Object value) {
EncodableSection section = null;
if (!this.sections.containsKey(sectionName)) {
if (sectionName.equals(TcfEuV2.NAME)) {
if (sectionName.equals(TcfCaV2.NAME)) {
section = new TcfCaV2();
this.sections.put(TcfCaV2.NAME, section);
} else if (sectionName.equals(TcfEuV2.NAME)) {
section = new TcfEuV2();
this.sections.put(TcfEuV2.NAME, section);
} else if (sectionName.equals(UspV1.NAME)) {
Expand Down Expand Up @@ -81,6 +85,10 @@ public EncodableSection getSection(String sectionName) {
}
}

public TcfCaV2 getTcfCaV2Section() {
return (TcfCaV2)getSection(TcfCaV2.NAME);
}

public TcfEuV2 getTcfEuV2Section() {
return (TcfEuV2)getSection(TcfEuV2.NAME);
}
Expand Down Expand Up @@ -134,6 +142,9 @@ public void decode(String str) throws DecodingException {
if (sectionIds.get(i).equals(TcfEuV2.ID)) {
TcfEuV2 section = new TcfEuV2(encodedSections[i + 1]);
this.sections.put(TcfEuV2.NAME, section);
} else if (sectionIds.get(i).equals(TcfCaV2.ID)) {
TcfCaV2 section = new TcfCaV2(encodedSections[i + 1]);
this.sections.put(TcfCaV2.NAME, section);
} else if (sectionIds.get(i).equals(UspV1.ID)) {
UspV1 section = new UspV1(encodedSections[i + 1]);
this.sections.put(UspV1.NAME, section);
Expand All @@ -155,6 +166,9 @@ public void decodeSection(String sectionName, String encodedString) throws Decod
if (sectionName.equals(TcfEuV2.NAME)) {
section = new TcfEuV2();
this.sections.put(TcfEuV2.NAME, section);
} else if (sectionName.equals(TcfCaV2.NAME)) {
section = new TcfCaV2();
this.sections.put(TcfCaV2.NAME, section);
} else if (sectionName.equals(UspV1.NAME)) {
section = new UspV1();
this.sections.put(UspV1.NAME, section);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ public class DatetimeEncoder {
private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);

public static String encode(ZonedDateTime value) {
return FixedLongEncoder.encode(value.toInstant().toEpochMilli() / 100, 36);
if(value != null) {
return FixedLongEncoder.encode(value.toInstant().toEpochMilli() / 100, 36);
} else {
return FixedLongEncoder.encode(0, 36);
}
}

public static ZonedDateTime decode(String bitString) throws DecodingException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.iab.gpp.encoder.field;

public class TcfCaV2Field {

public static String VERSION = "Version";
public static String CREATED = "Created";
public static String LAST_UPDATED = "LastUpdated";
public static String CMP_ID = "CmpId";
public static String CMP_VERSION = "CmpVersion";
public static String CONSENT_SCREEN = "ConsentScreen";
public static String CONSENT_LANGUAGE = "ConsentLanguage";
public static String VENDOR_LIST_VERSION = "VendorListVersion";
public static String TCF_POLICY_VERSION = "TcfPolicyVersion";
public static String USE_NON_STANDARD_STACKS = "UseNonStandardStacks";
public static String SPECIAL_FEATURE_EXPRESS_CONSENT = "SpecialFeatureExpressConsent";
public static String PURPOSES_EXPRESS_CONSENT = "PurposesExpressConsent";
public static String PURPOSES_IMPLIED_CONSENT = "PurposesImpliedConsent";
public static String VENDOR_EXPRESS_CONSENT = "VendorExpressConsent";
public static String VENDOR_IMPLIED_CONSENT = "VendorImpliedConsent";
public static String SEGMENT_TYPE = "SegmentType";
public static String PUB_PURPOSES_EXPRESS_CONSENT = "PubPurposesExpressConsent";
public static String PUB_PURPOSES_IMPLIED_CONSENT = "PubPurposesImpliedConsent";
public static String NUM_CUSTOM_PURPOSES = "NumCustomPurposes";
public static String CUSTOM_PURPOSES_EXPRESS_CONSENT = "CustomPurposesExpressConsent";
public static String CUSTOM_PURPOSES_IMPLIED_CONSENT = "CustomPurposesImpliedConsent";

}
284 changes: 284 additions & 0 deletions iabgpp-encoder/src/main/java/com/iab/gpp/encoder/section/TcfCaV2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package com.iab.gpp.encoder.section;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
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.EncodableFixedIntegerRange;
import com.iab.gpp.encoder.datatype.EncodableFixedString;
import com.iab.gpp.encoder.datatype.EncodableFlexibleBitfield;
import com.iab.gpp.encoder.datatype.encoder.Base64UrlEncoder;
import com.iab.gpp.encoder.error.DecodingException;
import com.iab.gpp.encoder.error.EncodingException;
import com.iab.gpp.encoder.field.TcfCaV2Field;

public class TcfCaV2 extends AbstractEncodableSegmentedBitStringSection {
public static int ID = 5;
public static int VERSION = 2;
public static String NAME = "tcfcav2";

public TcfCaV2() {
initFields();
}

public TcfCaV2(String encodedString) throws DecodingException {
initFields();

if (encodedString != null && encodedString.length() > 0) {
this.decode(encodedString);
}
}

private void initFields() {
fields = new HashMap<>();

// core section
fields.put(TcfCaV2Field.VERSION, new EncodableFixedInteger(6, TcfCaV2.VERSION));
fields.put(TcfCaV2Field.CREATED, new EncodableDatetime());
fields.put(TcfCaV2Field.LAST_UPDATED, new EncodableDatetime());
fields.put(TcfCaV2Field.CMP_ID, new EncodableFixedInteger(12, 0));
fields.put(TcfCaV2Field.CMP_VERSION, new EncodableFixedInteger(12, 0));
fields.put(TcfCaV2Field.CONSENT_SCREEN, new EncodableFixedInteger(6, 0));
fields.put(TcfCaV2Field.CONSENT_LANGUAGE, new EncodableFixedString(2, "EN"));
fields.put(TcfCaV2Field.VENDOR_LIST_VERSION, new EncodableFixedInteger(12, 0));
fields.put(TcfCaV2Field.TCF_POLICY_VERSION, new EncodableFixedInteger(6, 2));
fields.put(TcfCaV2Field.USE_NON_STANDARD_STACKS, new EncodableBoolean(false));
fields.put(TcfCaV2Field.SPECIAL_FEATURE_EXPRESS_CONSENT, new EncodableFixedBitfield(12, new ArrayList<>()));
fields.put(TcfCaV2Field.PURPOSES_EXPRESS_CONSENT, new EncodableFixedBitfield(24, new ArrayList<>()));
fields.put(TcfCaV2Field.PURPOSES_IMPLIED_CONSENT, new EncodableFixedBitfield(24, new ArrayList<>()));
fields.put(TcfCaV2Field.VENDOR_EXPRESS_CONSENT, new EncodableFixedIntegerRange(new ArrayList<>()));
fields.put(TcfCaV2Field.VENDOR_IMPLIED_CONSENT, new EncodableFixedIntegerRange(new ArrayList<>()));

// publisher purposes segment
fields.put(TcfCaV2Field.SEGMENT_TYPE, new EncodableFixedInteger(3, 3));
fields.put(TcfCaV2Field.PUB_PURPOSES_EXPRESS_CONSENT, new EncodableFixedBitfield(24, new ArrayList<>()));
fields.put(TcfCaV2Field.PUB_PURPOSES_IMPLIED_CONSENT, new EncodableFixedBitfield(24, new ArrayList<>()));

EncodableFixedInteger numCustomPurposes = new EncodableFixedInteger(6, 0);
fields.put(TcfCaV2Field.NUM_CUSTOM_PURPOSES, numCustomPurposes);

IntSupplier getLengthSupplier = new IntSupplier() {

@Override
public int getAsInt() {
return numCustomPurposes.getValue();
}

};

fields.put(
TcfCaV2Field.CUSTOM_PURPOSES_EXPRESS_CONSENT,
new EncodableFlexibleBitfield(getLengthSupplier, new ArrayList<>())
);

fields.put(
TcfCaV2Field.CUSTOM_PURPOSES_IMPLIED_CONSENT,
new EncodableFlexibleBitfield(getLengthSupplier, new ArrayList<>())
);

//@formatter:off
String[] coreSegment = new String[] {
TcfCaV2Field.VERSION,
TcfCaV2Field.CREATED,
TcfCaV2Field.LAST_UPDATED,
TcfCaV2Field.CMP_ID,
TcfCaV2Field.CMP_VERSION,
TcfCaV2Field.CONSENT_SCREEN,
TcfCaV2Field.CONSENT_LANGUAGE,
TcfCaV2Field.VENDOR_LIST_VERSION,
TcfCaV2Field.TCF_POLICY_VERSION,
TcfCaV2Field.USE_NON_STANDARD_STACKS,
TcfCaV2Field.SPECIAL_FEATURE_EXPRESS_CONSENT,
TcfCaV2Field.PURPOSES_EXPRESS_CONSENT,
TcfCaV2Field.PURPOSES_IMPLIED_CONSENT,
TcfCaV2Field.PUB_PURPOSES_IMPLIED_CONSENT,
TcfCaV2Field.VENDOR_EXPRESS_CONSENT,
TcfCaV2Field.VENDOR_IMPLIED_CONSENT
};

String[] publisherPurposesSegment = new String[] {
TcfCaV2Field.SEGMENT_TYPE,
TcfCaV2Field.PUB_PURPOSES_EXPRESS_CONSENT,
TcfCaV2Field.PUB_PURPOSES_IMPLIED_CONSENT,
TcfCaV2Field.NUM_CUSTOM_PURPOSES,
TcfCaV2Field.CUSTOM_PURPOSES_EXPRESS_CONSENT,
TcfCaV2Field.CUSTOM_PURPOSES_IMPLIED_CONSENT,
};

segments = new String[][] {
coreSegment,
publisherPurposesSegment
};
//@formatter:on
}

@Override
public String encode() throws EncodingException {
List<String> segmentBitStrings = this.encodeSegmentsToBitStrings();
List<String> encodedSegments = new ArrayList<>();
if(segmentBitStrings.size() >= 1) {
encodedSegments.add(Base64UrlEncoder.encode(segmentBitStrings.get(0)));

if(segmentBitStrings.size() >= 2) {
encodedSegments.add(Base64UrlEncoder.encode(segmentBitStrings.get(1)));
}
}

return encodedSegments.stream().collect(Collectors.joining("."));
}

@Override
public void decode(String encodedSection) throws DecodingException {
String[] encodedSegments = encodedSection.split("\\.");
String[] segmentBitStrings = new String[4];
for (int i = 0; i < encodedSegments.length; i++) {
/**
* first char will contain 6 bits, we only need the first 3. In version 1
* and 2 of the TC string there is no segment type for the CORE string.
* Instead the first 6 bits are reserved for the encoding version, but
* because we're only on a maximum of encoding version 2 the first 3 bits
* in the core segment will evaluate to 0.
*/
String segmentBitString = Base64UrlEncoder.decode(encodedSegments[i]);
switch (segmentBitString.substring(0, 3)) {
// unfortunately, the segment ordering doesn't match the segment ids
case "000": {
segmentBitStrings[0] = segmentBitString;
break;
}
case "011": {
segmentBitStrings[1] = segmentBitString;
break;
}
default: {
throw new DecodingException("Unable to decode segment '" + encodedSegments[i] + "'");
}
}
}
this.decodeSegmentsFromBitStrings(Arrays.asList(segmentBitStrings));
}

@Override
public void setFieldValue(String fieldName, Object value) {
super.setFieldValue(fieldName, value);

if (!fieldName.equals(TcfCaV2Field.CREATED) && !fieldName.equals(TcfCaV2Field.LAST_UPDATED)) {
ZonedDateTime utcDateTime = ZonedDateTime.now(ZoneId.of("UTC"));

super.setFieldValue(TcfCaV2Field.CREATED, utcDateTime);
super.setFieldValue(TcfCaV2Field.LAST_UPDATED, utcDateTime);
}
}

@Override
public int getId() {
return TcfCaV2.ID;
}

@Override
public String getName() {
return TcfCaV2.NAME;
}

public Integer getVersion() {
return (Integer)this.fields.get(TcfCaV2Field.VERSION).getValue();
}

public ZonedDateTime getCreated() {
return (ZonedDateTime)this.fields.get(TcfCaV2Field.CREATED).getValue();
}

public ZonedDateTime getLastUpdated() {
return (ZonedDateTime)this.fields.get(TcfCaV2Field.LAST_UPDATED).getValue();
}

public Integer getCmpId() {
return (Integer)this.fields.get(TcfCaV2Field.CMP_ID).getValue();
}

public Integer getCmpVersion() {
return (Integer)this.fields.get(TcfCaV2Field.CMP_VERSION).getValue();
}

public Integer getConsentScreen() {
return (Integer)this.fields.get(TcfCaV2Field.CONSENT_SCREEN).getValue();
}

public String getConsentLanguage() {
return (String)this.fields.get(TcfCaV2Field.CONSENT_LANGUAGE).getValue();
}

public Integer getVendorListVersion() {
return (Integer)this.fields.get(TcfCaV2Field.VENDOR_LIST_VERSION).getValue();
}

public Integer getPolicyVersion() {
return (Integer)this.fields.get(TcfCaV2Field.TCF_POLICY_VERSION).getValue();
}

public Boolean getUseNonStandardStacks() {
return (Boolean)this.fields.get(TcfCaV2Field.USE_NON_STANDARD_STACKS).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getSpecialFeatureExpressConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.SPECIAL_FEATURE_EXPRESS_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getPurposesExpressConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.PURPOSES_EXPRESS_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getPurposesImpliedConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.PURPOSES_IMPLIED_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getVendorExpressConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.VENDOR_EXPRESS_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getVendorImpliedConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.VENDOR_IMPLIED_CONSENT).getValue();
}

public Integer getSegmentType() {
return (Integer)this.fields.get(TcfCaV2Field.SEGMENT_TYPE).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getPubPurposesExpressConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.PUB_PURPOSES_EXPRESS_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getPubPurposesImpliedConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.PUB_PURPOSES_IMPLIED_CONSENT).getValue();
}

public Integer getNumCustomPurposes() {
return (Integer)this.fields.get(TcfCaV2Field.NUM_CUSTOM_PURPOSES).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getCustomPurposesExpressConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.CUSTOM_PURPOSES_EXPRESS_CONSENT).getValue();
}

@SuppressWarnings("unchecked")
public List<Integer> getCustomPurposesImpliedConsent() {
return (List<Integer>)this.fields.get(TcfCaV2Field.CUSTOM_PURPOSES_IMPLIED_CONSENT).getValue();
}

}
Loading