Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
8b84427
lazy decoding
Aug 30, 2023
89c5567
java 8 compatible gppmodel tests
Aug 31, 2023
1c4ae8a
rename missed multistate usp* methods to us*
Sep 3, 2023
2b159f4
lazier decoding
Sep 5, 2023
4a516b9
lazier decoding
Sep 6, 2023
6eccd6d
tests for null and empty string constructor arguments
Sep 6, 2023
1137652
validation
Sep 11, 2023
5b536dc
fix typo
Sep 11, 2023
6c705b3
remove redundant validate call
Sep 11, 2023
909cad2
default validate
Sep 11, 2023
47874d2
remove empty validate method from header core segment
Sep 11, 2023
97fd395
fix usct validator
Sep 11, 2023
db78746
3.1.1
Sep 14, 2023
9824305
3.1.2-SNAPSHOT
Sep 14, 2023
8106a9e
Merge remote-tracking branch 'upstream/master'
Sep 23, 2023
0e67b3c
tcfca publisher restrictions and disclosed vendors
Oct 3, 2023
51d289a
merge validation and ca-pub-restrictions
Oct 7, 2023
8af6c08
deprecate multi-state usp* methods
Oct 7, 2023
4b871d9
merge multistate-usp-to-us
Oct 7, 2023
cf5560a
merge multistate-usp-to-us
Oct 7, 2023
aa2bfc2
substring error handling
Oct 7, 2023
615ab24
merge ca-pub-restrictions
Oct 7, 2023
080eb5d
remove deprecated usp methods
Oct 7, 2023
a3f0f1c
remove deprecated usp methods
Oct 7, 2023
46323b7
remove deprecated usp methods
Oct 7, 2023
cdffeab
Merge branch 'multistate-usp-to-us' into lazy-decoding
Oct 7, 2023
0c2bc92
Merge branch 'lazy-decoding' into validation
Oct 7, 2023
c976640
Merge branch 'validation' into develop
Oct 7, 2023
9ff3562
Merge remote-tracking branch 'upstream/master' into lazy-decoding
Oct 7, 2023
eda06e6
Merge remote-tracking branch 'upstream/master' into validation
Oct 7, 2023
38bd153
cleanup validators
Oct 8, 2023
55411c1
merge validation
Oct 8, 2023
811d172
Better decoding exception messaging
Oct 8, 2023
157cacb
remove unused classes
Oct 8, 2023
ecbd815
Update README
Oct 8, 2023
667e1ba
Merge branch 'ca-pub-restrictions' into develop
Oct 8, 2023
75d9427
add support for the old headerless tcfeuv2 strings
Oct 9, 2023
af2c0b7
encodeSection fix
Oct 9, 2023
4f182f8
encodeSection lazy fix
Oct 9, 2023
24c53ca
Merge branch 'lazy-decoding' into validation
Oct 9, 2023
3b5badb
Merge branch 'validation' into develop
Oct 9, 2023
cedac14
tcfeu pub restrictions fix
Dec 13, 2023
a7f851a
merge pub restrictions
Dec 13, 2023
9b477a4
pub restrictions getters
Dec 13, 2023
d92bce0
merge pub restrictions
Dec 13, 2023
c7dbab5
tcfeu pub restirctions fix
Dec 13, 2023
b94ded6
merge pub restrictions
Dec 13, 2023
41df5bc
cleanup
Dec 13, 2023
1a5cdab
Merge branch 'ca-pub-restrictions' into develop
Dec 13, 2023
58556f5
pub restrictions fix
Dec 13, 2023
c60776d
merge pub restrictions
Dec 13, 2023
7ca0288
optimize bitstring padding
Dec 16, 2023
14a7edf
Merge branch 'lazy-decoding' into develop
Dec 16, 2023
254b844
Validating first 2 characters of string to avoid OOM
Jul 22, 2024
3cfd3df
Fail on empty or mismatched sections
Jul 22, 2024
94cce75
minor cleanup
Jul 22, 2024
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,15 @@ CmpList cmpList = loader.cmpList(cmpListContent);
|tcfcav1|5|PurposesImpliedConsent|Boolean list of size 24|
|tcfcav1|5|VendorExpressConsent|Integer list of variable size|
|tcfcav1|5|VendorImpliedConsent|Integer list of variable size|
|tcfcav1|5|PubRestrictions|RangeEntry list of variable size|
|tcfcav1|5|PubPurposesSegmentType|3 bit int. Value is 3|
|tcfcav1|5|PubPurposesExpressConsent|Boolean list of size 24|
|tcfcav1|5|PubPurposesImpliedConsent|Boolean list of size 24|
|tcfcav1|5|NumCustomPurposes|6 bit int|
|tcfcav1|5|CustomPurposesExpressConsent|Boolean list where size is set by the NumCustomPurposes field|
|tcfcav1|5|CustomPurposesImpliedConsent|Boolean list where size is set by the NumCustomPurposes field|
|tcfcav1|5|DisclosedVendorsSegmentType|3 bit int. Value is 1|
|tcfcav1|5|DisclosedVendors|Integer list of variable size|
|uspv1|6|Version|6 bit int. Value is 1|
|uspv1|6|Notice|2 bit int|
|uspv1|6|OptOutSale|2 bit int|
Expand Down
215 changes: 163 additions & 52 deletions iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.iab.gpp.encoder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.iab.gpp.encoder.error.DecodingException;
import com.iab.gpp.encoder.error.EncodingException;
import com.iab.gpp.encoder.error.InvalidFieldException;
import com.iab.gpp.encoder.field.HeaderV1Field;
import com.iab.gpp.encoder.section.EncodableSection;
import com.iab.gpp.encoder.section.HeaderV1;
import com.iab.gpp.encoder.section.Sections;
Expand All @@ -18,27 +20,36 @@
import com.iab.gpp.encoder.section.UsCtV1;
import com.iab.gpp.encoder.section.UsNatV1;
import com.iab.gpp.encoder.section.UsUtV1;
import com.iab.gpp.encoder.section.UspV1;
import com.iab.gpp.encoder.section.UsVaV1;
import com.iab.gpp.encoder.section.UspV1;

public class GppModel {
private Map<String, EncodableSection> sections = new HashMap<>();

private String encodedString;

private boolean dirty = false;
private boolean decoded = true;

public GppModel() {

}

public GppModel(String encodedString) throws DecodingException {
if (encodedString != null && encodedString.length() > 0) {
this.decode(encodedString);
}
public GppModel(String encodedString) {
decode(encodedString);
}

public void setFieldValue(int sectionId, String fieldName, Object value) throws InvalidFieldException {
public void setFieldValue(int sectionId, String fieldName, Object value) {
setFieldValue(Sections.SECTION_ID_NAME_MAP.get(sectionId), fieldName, value);
}

public void setFieldValue(String sectionName, String fieldName, Object value) throws InvalidFieldException {
public void setFieldValue(String sectionName, String fieldName, Object value) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

EncodableSection section = null;
if (!this.sections.containsKey(sectionName)) {
if (sectionName.equals(TcfCaV1.NAME)) {
Expand Down Expand Up @@ -75,6 +86,7 @@ public void setFieldValue(String sectionName, String fieldName, Object value) th

if (section != null) {
section.setFieldValue(fieldName, value);
this.dirty = true;
} else {
throw new InvalidFieldException(sectionName + "." + fieldName + " not found");
}
Expand All @@ -85,6 +97,12 @@ public Object getFieldValue(int sectionId, String fieldName) {
}

public Object getFieldValue(String sectionName, String fieldName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

if (this.sections.containsKey(sectionName)) {
return this.sections.get(sectionName).getFieldValue(fieldName);
} else {
Expand All @@ -97,6 +115,12 @@ public boolean hasField(int sectionId, String fieldName) {
}

public boolean hasField(String sectionName, String fieldName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

if (this.sections.containsKey(sectionName)) {
return this.sections.get(sectionName).hasField(fieldName);
} else {
Expand All @@ -109,10 +133,22 @@ public boolean hasSection(int sectionId) {
}

public boolean hasSection(String sectionName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

return this.sections.containsKey(sectionName);
}

public HeaderV1 getHeader() {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

HeaderV1 header = new HeaderV1();
try {
header.setFieldValue("SectionIds", this.getSectionIds());
Expand All @@ -127,6 +163,12 @@ public EncodableSection getSection(int sectionId) {
}

public EncodableSection getSection(String sectionName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

if (this.sections.containsKey(sectionName)) {
return this.sections.get(sectionName);
} else {
Expand All @@ -139,13 +181,23 @@ public void deleteSection(int sectionId) {
}

public void deleteSection(String sectionName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

if (this.sections.containsKey(sectionName)) {
this.sections.remove(sectionName);
this.dirty = true;
}
}

public void clear() {
this.sections.clear();
this.encodedString = null;
this.dirty = false;
this.decoded = true;
}

public TcfCaV1 getTcfCaV1Section() {
Expand Down Expand Up @@ -185,6 +237,12 @@ public UsCtV1 getUsCtV1Section() {
}

public List<Integer> getSectionIds() {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

List<Integer> sectionIds = new ArrayList<>();
for (int i = 0; i < Sections.SECTION_ORDER.size(); i++) {
String sectionName = Sections.SECTION_ORDER.get(i);
Expand All @@ -196,21 +254,21 @@ public List<Integer> getSectionIds() {
return sectionIds;
}

public String encode() throws EncodingException {
protected String encodeModel(Map<String, EncodableSection> sections) {
List<String> encodedSections = new ArrayList<>();
List<Integer> sectionIds = new ArrayList<>();
for (int i = 0; i < Sections.SECTION_ORDER.size(); i++) {
String sectionName = Sections.SECTION_ORDER.get(i);
if (this.sections.containsKey(sectionName)) {
EncodableSection section = this.sections.get(sectionName);
if (sections.containsKey(sectionName)) {
EncodableSection section = sections.get(sectionName);
encodedSections.add(section.encode());
sectionIds.add(section.getId());
}
}

HeaderV1 header = new HeaderV1();
try {
header.setFieldValue("SectionIds", this.getSectionIds());
header.setFieldValue("SectionIds", getSectionIds());
} catch (InvalidFieldException e) {
throw new EncodingException(e);
}
Expand All @@ -220,64 +278,99 @@ public String encode() throws EncodingException {
return encodedString;
}

public void decode(String str) throws DecodingException {
this.sections.clear();
protected Map<String, EncodableSection> decodeModel(String str) {
if(str == null || str.isEmpty() || str.startsWith("DB")) {
Map<String, EncodableSection> sections = new HashMap<>();

if(str != null && !str.isEmpty()) {
String[] encodedSections = str.split("~");
HeaderV1 header = new HeaderV1(encodedSections[0]);
sections.put(HeaderV1.NAME, header);

@SuppressWarnings("unchecked")
List<Integer> sectionIds = (List<Integer>) header.getFieldValue("SectionIds");

if(sectionIds.size() != encodedSections.length-1) {
throw new DecodingException("Unable to decode '" + str + "'. The number of sections does not match the number of sections defined in the header.");
}

for (int i = 0; i < sectionIds.size(); i++) {
String encodedSection = encodedSections[i + 1];
if(encodedSection.trim().isEmpty()) {
throw new DecodingException("Unable to decode '" + str + "'. Section " + (i+1) + " is blank.");
}
if (sectionIds.get(i).equals(TcfEuV2.ID)) {
TcfEuV2 section = new TcfEuV2(encodedSection);
sections.put(TcfEuV2.NAME, section);
} else if (sectionIds.get(i).equals(TcfCaV1.ID)) {
TcfCaV1 section = new TcfCaV1(encodedSection);
sections.put(TcfCaV1.NAME, section);
} else if (sectionIds.get(i).equals(UspV1.ID)) {
UspV1 section = new UspV1(encodedSection);
sections.put(UspV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCaV1.ID)) {
UsCaV1 section = new UsCaV1(encodedSection);
sections.put(UsCaV1.NAME, section);
} else if (sectionIds.get(i).equals(UsNatV1.ID)) {
UsNatV1 section = new UsNatV1(encodedSection);
sections.put(UsNatV1.NAME, section);
} else if (sectionIds.get(i).equals(UsVaV1.ID)) {
UsVaV1 section = new UsVaV1(encodedSection);
sections.put(UsVaV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCoV1.ID)) {
UsCoV1 section = new UsCoV1(encodedSection);
sections.put(UsCoV1.NAME, section);
} else if (sectionIds.get(i).equals(UsUtV1.ID)) {
UsUtV1 section = new UsUtV1(encodedSection);
sections.put(UsUtV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCtV1.ID)) {
UsCtV1 section = new UsCtV1(encodedSection);
sections.put(UsCtV1.NAME, section);
}
}
}

return sections;
} else if(str.startsWith("C")) {
// old tcfeu only string
Map<String, EncodableSection> sections = new HashMap<>();

String[] encodedSections = str.split("~");
HeaderV1 header = new HeaderV1(encodedSections[0]);
this.sections.put(HeaderV1.NAME, header);
TcfEuV2 section = new TcfEuV2(str);
sections.put(TcfEuV2.NAME, section);

@SuppressWarnings("unchecked")
List<Integer> sectionIds = (List<Integer>) header.getFieldValue("SectionIds");
for (int i = 0; i < sectionIds.size(); i++) {
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(TcfCaV1.ID)) {
TcfCaV1 section = new TcfCaV1(encodedSections[i + 1]);
this.sections.put(TcfCaV1.NAME, section);
} else if (sectionIds.get(i).equals(UspV1.ID)) {
UspV1 section = new UspV1(encodedSections[i + 1]);
this.sections.put(UspV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCaV1.ID)) {
UsCaV1 section = new UsCaV1(encodedSections[i + 1]);
this.sections.put(UsCaV1.NAME, section);
} else if (sectionIds.get(i).equals(UsNatV1.ID)) {
UsNatV1 section = new UsNatV1(encodedSections[i + 1]);
this.sections.put(UsNatV1.NAME, section);
} else if (sectionIds.get(i).equals(UsVaV1.ID)) {
UsVaV1 section = new UsVaV1(encodedSections[i + 1]);
this.sections.put(UsVaV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCoV1.ID)) {
UsCoV1 section = new UsCoV1(encodedSections[i + 1]);
this.sections.put(UsCoV1.NAME, section);
} else if (sectionIds.get(i).equals(UsUtV1.ID)) {
UsUtV1 section = new UsUtV1(encodedSections[i + 1]);
this.sections.put(UsUtV1.NAME, section);
} else if (sectionIds.get(i).equals(UsCtV1.ID)) {
UsCtV1 section = new UsCtV1(encodedSections[i + 1]);
this.sections.put(UsCtV1.NAME, section);
}
HeaderV1 header = new HeaderV1();
header.setFieldValue(HeaderV1Field.SECTION_IDS, Arrays.asList(2));
sections.put(HeaderV1.NAME, section);

return sections;
} else {
throw new DecodingException("Unable to decode '" + str + "'");
}
}

public String encodeSection(int sectionId) throws EncodingException {
public String encodeSection(int sectionId) {
return encodeSection(Sections.SECTION_ID_NAME_MAP.get(sectionId));
}

public String encodeSection(String sectionName) throws EncodingException {
public String encodeSection(String sectionName) {
if (!this.decoded) {
this.sections = this.decodeModel(this.encodedString);
this.dirty = false;
this.decoded = true;
}

if (this.sections.containsKey(sectionName)) {
return this.sections.get(sectionName).encode();
} else {
return null;
}
}

public void decodeSection(int sectionId, String encodedString) throws DecodingException {
public void decodeSection(int sectionId, String encodedString) {
decodeSection(Sections.SECTION_ID_NAME_MAP.get(sectionId), encodedString);
}

public void decodeSection(String sectionName, String encodedString) throws DecodingException {
public void decodeSection(String sectionName, String encodedString) {
EncodableSection section = null;
if (!this.sections.containsKey(sectionName)) {
if (sectionName.equals(TcfEuV2.NAME)) {
Expand Down Expand Up @@ -316,4 +409,22 @@ public void decodeSection(String sectionName, String encodedString) throws Decod
section.decode(encodedString);
}
}
}

public String encode() {
if (this.encodedString == null || this.encodedString.isEmpty() || this.dirty) {
this.encodedString = encodeModel(this.sections);
this.dirty = false;
this.decoded = true;
}

return this.encodedString;
}

public void decode(String encodedString) {
this.encodedString = encodedString;
this.dirty = false;
this.decoded = false;
}


}
Loading