├── .gitattributes
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── src
├── main
│ └── java
│ │ └── com
│ │ └── iab
│ │ └── gdpr
│ │ ├── exception
│ │ ├── VendorConsentException.java
│ │ ├── VendorConsentCreateException.java
│ │ └── VendorConsentParseException.java
│ │ ├── consent
│ │ ├── VendorConsentEncoder.java
│ │ ├── range
│ │ │ ├── RangeEntry.java
│ │ │ ├── SingleRangeEntry.java
│ │ │ └── StartEndRangeEntry.java
│ │ ├── VendorConsentDecoder.java
│ │ ├── VendorConsent.java
│ │ └── implementation
│ │ │ └── v1
│ │ │ ├── ByteBufferBackedVendorConsent.java
│ │ │ └── VendorConsentBuilder.java
│ │ ├── GdprConstants.java
│ │ ├── Purpose.java
│ │ └── Bits.java
└── test
│ └── java
│ └── com
│ └── iab
│ └── gdpr
│ ├── util
│ └── Utils.java
│ └── consent
│ ├── VendorConsentEncoderTest.java
│ ├── VendorConsentDecoderTest.java
│ └── implementation
│ └── v1
│ ├── VendorConsentBuilderTest.java
│ └── ByteBufferBackedVendorConsentTest.java
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── gradlew.bat
├── gradlew
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.bat !text
3 | *.cmd !text
4 | .gitignore !text
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/InteractiveAdvertisingBureau/Consent-String-SDK-Java/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/*
3 | /bin/
4 | /build/
5 | *.swp
6 | gradle-app.setting
7 | !gradle-wrapper.jar
8 | ### IntelliJ IDEA ###
9 | .idea
10 | *.iws
11 | *.iml
12 | *.ipr
13 | /out/
14 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'consent-string-sdk-java'
2 |
3 | // Maven deployment properties. Set actual values in $HOME/.gradle/settings.gradle
4 |
5 | mavenUsername = ''
6 | mavenPassword = ''
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/exception/VendorConsentException.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.exception;
2 |
3 | /**
4 | * Generic vendor consent exception
5 | */
6 | public class VendorConsentException extends RuntimeException {
7 |
8 | public VendorConsentException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/exception/VendorConsentCreateException.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.exception;
2 |
3 | /**
4 | * Exception for the case where consent string cannot be created
5 | */
6 | public class VendorConsentCreateException extends VendorConsentException {
7 |
8 | public VendorConsentCreateException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/exception/VendorConsentParseException.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.exception;
2 |
3 | /**
4 | * Exception for the case where consent string cannot be parsed
5 | */
6 | public class VendorConsentParseException extends VendorConsentException {
7 |
8 | public VendorConsentParseException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - oraclejdk8
5 | - oraclejdk9
6 | - openjdk8
7 |
8 | sudo: false
9 |
10 | before_cache:
11 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
12 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
13 |
14 | cache:
15 | directories:
16 | - $HOME/.gradle/caches/
17 | - $HOME/.gradle/wrapper/
18 |
19 | script:
20 | - ./gradlew test
21 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/VendorConsentEncoder.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent;
2 |
3 | import java.util.Base64;
4 |
5 | /**
6 | * Encode {@link VendorConsent} to Base64 string
7 | */
8 | public class VendorConsentEncoder {
9 |
10 | // As per the GDPR framework guidelines padding should be omitted
11 | private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding();
12 |
13 | /**
14 | * Encode vendor consent to Base64 string
15 | * @param vendorConsent vendor consent
16 | * @return Base64 encoded string
17 | */
18 | public static String toBase64String(VendorConsent vendorConsent) {
19 | return ENCODER.encodeToString(vendorConsent.toByteArray());
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/com/iab/gdpr/util/Utils.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.util;
2 |
3 | import com.iab.gdpr.Bits;
4 |
5 | /**
6 | * Testing utility functions
7 | */
8 | public class Utils {
9 |
10 | /**
11 | * Create bit buffer from string representation
12 | * @param binaryString binary string
13 | * @return bit buffer
14 | */
15 | public static Bits fromBinaryString(String binaryString) {
16 | final int length = binaryString.length();
17 | final boolean bitsFit = (length % 8) == 0;
18 | final Bits bits = new Bits(new byte[length / 8 + (bitsFit ? 0 : 1)]);
19 |
20 | for (int i = 0; i < length; i++)
21 | if (binaryString.charAt(i) == '1')
22 | bits.setBit(i);
23 | else
24 | bits.unsetBit(i);
25 |
26 | return bits;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/range/RangeEntry.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.range;
2 |
3 | import com.iab.gdpr.Bits;
4 |
5 | /**
6 | * Range entry of the vendor consent range section. Range entry is a single or range of VendorIds
7 | * whose consent value is the opposite of DefaultConsent.
8 | */
9 | public interface RangeEntry {
10 |
11 | /**
12 | * @return Size of range entry in bits
13 | */
14 | int size();
15 |
16 | /**
17 | * Append this range entry to the bit buffer
18 | * @param buffer bit buffer
19 | * @param currentOffset current offset in the buffer
20 | * @return new offset
21 | */
22 | int appendTo(Bits buffer, int currentOffset);
23 |
24 | /**
25 | * Check if range entry is valid for the specified max vendor id
26 | * @param maxVendorId max vendor id
27 | * @return true if range entry is valid, false otherwise
28 | */
29 | boolean valid(int maxVendorId);
30 | }
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [3.0.2] - 02-08-2019
5 |
6 | ### Changed
7 | - Added VendorConsent.getAllowedVendorIds(). Thanks to **ssick**
8 |
9 |
10 | ## [3.0.1] - 12-21-2018
11 |
12 | ### Changed
13 | - Removed VendorConsent class that was deprecated since version 2.0.1
14 | - Better handle index out of bounds conditions with corrupt consent strings
15 |
16 |
17 | ## [2.0.2] - 06-19-2018
18 |
19 | ### Changed
20 | - Add a new API fromByteArray() to avoid the base64 decoding. Thanks to **hydrogen2**
21 |
22 |
23 | ## [2.0.1] - 06-13-2018
24 |
25 | ### Changed
26 | - Optimize isPurposeAllowed() to just check the bit instead of creating unnecessary Set. Thanks to **hydrogen2**
27 |
28 |
29 | ## [2.0.0] - 06-07-2018
30 |
31 | ### Changed
32 | - Refactored code to separate vendor consent string decoding, encoding and representation concerns
33 | - Deprecated VendorConsent class
34 | - Setup Maven publishing
35 | - Updated documentation
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/range/SingleRangeEntry.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.range;
2 |
3 | import com.iab.gdpr.Bits;
4 |
5 | import static com.iab.gdpr.GdprConstants.VENDOR_ID_SIZE;
6 |
7 | /**
8 | * {@link RangeEntry} with single vendor ID
9 | */
10 | public class SingleRangeEntry implements RangeEntry {
11 | private final int singeVendorId;
12 |
13 | public SingleRangeEntry(int singeVendorId) {
14 | this.singeVendorId = singeVendorId;
15 | }
16 |
17 | @Override
18 | public int size() {
19 | // One bit for SingleOrRange flag, VENDOR_ID_SIZE for single vendor ID
20 | return 1+VENDOR_ID_SIZE;
21 | }
22 |
23 | @Override
24 | public int appendTo(Bits buffer, int currentOffset) {
25 | int newOffset = currentOffset;
26 | buffer.unsetBit(newOffset++); // 0=Single
27 | buffer.setInt(newOffset, VENDOR_ID_SIZE, singeVendorId);
28 | newOffset += VENDOR_ID_SIZE;
29 | return newOffset;
30 | }
31 |
32 | @Override
33 | public boolean valid(int maxVendorId) {
34 | return singeVendorId > 0 && singeVendorId <= maxVendorId;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/range/StartEndRangeEntry.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.range;
2 |
3 | import com.iab.gdpr.Bits;
4 |
5 | import static com.iab.gdpr.GdprConstants.VENDOR_ID_SIZE;
6 |
7 | /**
8 | * {@link RangeEntry} with start and end vendor IDs
9 | */
10 | public class StartEndRangeEntry implements RangeEntry{
11 | private final int startVendorId;
12 | private final int endVendorId;
13 |
14 | public StartEndRangeEntry(int startVendorId, int endVendorId) {
15 | this.startVendorId = startVendorId;
16 | this.endVendorId = endVendorId;
17 | }
18 |
19 | @Override
20 | public int size() {
21 | // One bit for SingleOrRange flag, 2 * VENDOR_ID_SIZE for 2 vendor IDs
22 | return 1+VENDOR_ID_SIZE * 2;
23 | }
24 |
25 | @Override
26 | public int appendTo(Bits buffer, int currentOffset) {
27 | int newOffset = currentOffset;
28 | buffer.setBit(newOffset++); // 1=Range
29 | buffer.setInt(newOffset, VENDOR_ID_SIZE, startVendorId);
30 | newOffset += VENDOR_ID_SIZE;
31 | buffer.setInt(newOffset, VENDOR_ID_SIZE, endVendorId);
32 | newOffset += VENDOR_ID_SIZE;
33 | return newOffset;
34 | }
35 |
36 | @Override
37 | public boolean valid(int maxVendorId) {
38 | return startVendorId > 0 && endVendorId > 0 && startVendorId < endVendorId && endVendorId <= maxVendorId;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/GdprConstants.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr;
2 |
3 | /**
4 | * Various constants related to positions and sizes of GDPR consent string bits
5 | */
6 | public class GdprConstants {
7 | public static final int VENDOR_ENCODING_RANGE = 1;
8 | public static final int VERSION_BIT_OFFSET = 0;
9 | public static final int VERSION_BIT_SIZE = 6;
10 | public static final int CREATED_BIT_OFFSET = 6;
11 | public static final int CREATED_BIT_SIZE = 36;
12 | public static final int UPDATED_BIT_OFFSET = 42;
13 | public static final int UPDATED_BIT_SIZE = 36;
14 | public static final int CMP_ID_OFFSET = 78;
15 | public static final int CMP_ID_SIZE = 12;
16 | public static final int CMP_VERSION_OFFSET = 90;
17 | public static final int CMP_VERSION_SIZE = 12;
18 | public static final int CONSENT_SCREEN_SIZE_OFFSET = 102;
19 | public static final int CONSENT_SCREEN_SIZE = 6;
20 | public static final int CONSENT_LANGUAGE_OFFSET = 108;
21 | public static final int CONSENT_LANGUAGE_SIZE = 12;
22 | public static final int VENDOR_LIST_VERSION_OFFSET = 120;
23 | public static final int VENDOR_LIST_VERSION_SIZE = 12;
24 | public static final int PURPOSES_OFFSET = 132;
25 | public static final int PURPOSES_SIZE = 24;
26 | public static final int MAX_VENDOR_ID_OFFSET = 156;
27 | public static final int MAX_VENDOR_ID_SIZE = 16;
28 | public static final int ENCODING_TYPE_OFFSET = 172;
29 | public static final int ENCODING_TYPE_SIZE = 1;
30 | public static final int VENDOR_BITFIELD_OFFSET = 173;
31 | public static final int DEFAULT_CONSENT_OFFSET = 173;
32 | public static final int NUM_ENTRIES_OFFSET = 174;
33 | public static final int NUM_ENTRIES_SIZE = 12;
34 | public static final int RANGE_ENTRY_OFFSET = 186;
35 | public static final int VENDOR_ID_SIZE = 16;
36 | }
37 |
--------------------------------------------------------------------------------
/src/test/java/com/iab/gdpr/consent/VendorConsentEncoderTest.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent;
2 |
3 | import com.iab.gdpr.consent.implementation.v1.ByteBufferBackedVendorConsent;
4 | import com.iab.gdpr.util.Utils;
5 | import org.junit.Test;
6 |
7 | import static org.hamcrest.Matchers.is;
8 | import static org.hamcrest.Matchers.notNullValue;
9 | import static org.junit.Assert.*;
10 |
11 | public class VendorConsentEncoderTest {
12 |
13 | @Test
14 | public void testEncode() {
15 | // Given: vendor consent binary string
16 | final String binaryString = "000011" + // Version
17 | "001110001110110011010000101000000000" + // Created
18 | "001110001110110011010000101000000000" + // Updated
19 | "000000001111" + // CMP ID
20 | "000000000101" + // CMP version
21 | "010010" + // Content screen ID
22 | "000100001101" + // Language code
23 | "000010010110" + // Vendor list version
24 | "111110000000001000000001" + // Allowed purposes bitmap
25 | "0000000000100000" + // Max vendor ID
26 | "0" + // Bit field encoding
27 | "10000000000000000000000010000100" // Vendor bits in bit field
28 | ;
29 |
30 | // And: ByteBufferBackedVendorConsent constructed from binary string
31 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
32 |
33 | // When: encode is called
34 | final String base64String = VendorConsentEncoder.toBase64String(vendorConsent);
35 |
36 |
37 | // Then: encoded string is returned
38 | assertThat(vendorConsent.getAllowedPurposesBits(),is(notNullValue()));
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/VendorConsentDecoder.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent;
2 |
3 | import com.iab.gdpr.Bits;
4 | import com.iab.gdpr.consent.implementation.v1.ByteBufferBackedVendorConsent;
5 |
6 | import java.util.Base64;
7 |
8 | import static com.iab.gdpr.GdprConstants.VERSION_BIT_OFFSET;
9 | import static com.iab.gdpr.GdprConstants.VERSION_BIT_SIZE;
10 |
11 | /**
12 | * {@link VendorConsent} decoder from Base64 string. Right now only version 1 is know, but eventually
13 | * this can be extended to support new versions
14 | */
15 | public class VendorConsentDecoder {
16 |
17 | private static final Base64.Decoder BASE64_DECODER = Base64.getUrlDecoder();
18 |
19 | public static VendorConsent fromBase64String(String consentString) {
20 | if (isNullOrEmpty(consentString))
21 | throw new IllegalArgumentException("Null or empty consent string passed as an argument");
22 |
23 | return fromByteArray(BASE64_DECODER.decode(consentString));
24 | }
25 |
26 | public static VendorConsent fromByteArray(byte[] bytes) {
27 | if (bytes == null || bytes.length == 0)
28 | throw new IllegalArgumentException("Null or empty consent bytes passed as an argument");
29 |
30 | final Bits bits = new Bits(bytes);
31 | final int version = getVersion(bits);
32 | switch (version) {
33 | case 1:
34 | return new ByteBufferBackedVendorConsent(bits);
35 | default:
36 | throw new IllegalStateException("Unsupported version: " + version);
37 | }
38 | }
39 |
40 | /**
41 | * Get the version field from bitmap
42 | * @param bits bitmap
43 | * @return a version number
44 | */
45 | private static int getVersion(Bits bits) {
46 | return bits.getInt(VERSION_BIT_OFFSET, VERSION_BIT_SIZE);
47 | }
48 |
49 | /**
50 | * Utility method to check whether string is empty or null
51 | * @param string value to check
52 | * @return a boolean value of the check
53 | */
54 | private static boolean isNullOrEmpty(String string) {
55 | return string == null || string.isEmpty();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/Purpose.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr;
2 |
3 | /**
4 | *
5 | * Enumeration of currently defined purposes by IAB
6 | *
7 | * @see https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework
8 | *
9 | */
10 | public enum Purpose {
11 |
12 | STORAGE_AND_ACCESS(1), // The storage of information, or access to information that is already stored, on user device such as accessing advertising identifiers
13 | // and/or other device identifiers, and/or using cookies or similar technologies.
14 |
15 | PERSONALIZATION(2), // The collection and processing of information about user of a site to subsequently personalize advertising for them in other contexts,
16 | // i.e. on other sites or apps, over time. Typically, the content of the site or app is used to make inferences about user interests, which inform future selections.
17 |
18 | AD_SELECTION(3), // The collection of information and combination with previously collected information, to select and deliver advertisements and to measure the delivery
19 | // and effectiveness of such advertisements. This includes using previously collected information about user interests to select ads, processing data about
20 | // what advertisements were shown, how often they were shown, when and where they were shown, and whether they took any action related to the advertisement,
21 | // including for example clicking an ad or making a purchase.
22 |
23 | CONTENT_DELIVERY(4), // The collection of information, and combination with previously collected information, to select and deliver content and to measure the delivery and
24 | // effectiveness of such content. This includes using previously collected information about user interests to select content, processing data about
25 | // what content was shown, how often or how long it was shown, when and where it was shown, and whether they took any action related to the content,
26 | // including for example clicking on content.
27 |
28 | MEASUREMENT(5), // The collection of information about user use of content, and combination with previously collected information, used to measure, understand,
29 | // and report on user usage of content.
30 |
31 | UNDEFINED(-1), // Purpose ID that is currently not defined
32 | ;
33 |
34 | private final int id;
35 |
36 | Purpose(int id) {
37 | this.id = id;
38 | }
39 |
40 | public int getId() {
41 | return id;
42 | }
43 |
44 | /**
45 | * Map numeric purpose ID to Enum
46 | * @param purposeId purpose ID
47 | * @return Enum value of purpose
48 | */
49 | public static Purpose valueOf(int purposeId) {
50 | switch(purposeId) {
51 | case 1: return STORAGE_AND_ACCESS;
52 | case 2: return PERSONALIZATION;
53 | case 3: return AD_SELECTION;
54 | case 4: return CONTENT_DELIVERY;
55 | case 5: return MEASUREMENT;
56 | default: return UNDEFINED;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/iab/gdpr/consent/VendorConsentDecoderTest.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent;
2 |
3 | import com.iab.gdpr.Bits;
4 | import com.iab.gdpr.consent.implementation.v1.ByteBufferBackedVendorConsent;
5 | import org.junit.Test;
6 |
7 | import java.util.Base64;
8 |
9 | import static com.iab.gdpr.GdprConstants.VERSION_BIT_OFFSET;
10 | import static com.iab.gdpr.GdprConstants.VERSION_BIT_SIZE;
11 | import static org.hamcrest.Matchers.is;
12 | import static org.junit.Assert.assertThat;
13 |
14 | public class VendorConsentDecoderTest {
15 |
16 | @Test(expected = IllegalArgumentException.class)
17 | public void testNullConsentString() {
18 | // Given: null consent string
19 | String consentString = null;
20 |
21 | // When: decoder is called
22 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
23 |
24 | // Then IllegalArgumentException exception is thrown
25 | }
26 |
27 | @Test(expected = IllegalArgumentException.class)
28 | public void testNullConsentBytes() {
29 | // Given: null consent string
30 | byte[] consentBytes = null;
31 |
32 | // When: decoder is called
33 | final VendorConsent vendorConsent = VendorConsentDecoder.fromByteArray(consentBytes);
34 |
35 | // Then IllegalArgumentException exception is thrown
36 | }
37 |
38 | @Test(expected = IllegalArgumentException.class)
39 | public void testEmptyConsentString() {
40 | // Given: empty consent string
41 | String consentString = "";
42 |
43 | // When: decoder is called
44 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
45 |
46 | // Then IllegalArgumentException exception is thrown
47 |
48 | }
49 |
50 | @Test(expected = IllegalArgumentException.class)
51 | public void testEmptyConsentBytes() {
52 | // Given: empty consent string
53 | byte[] consentBytes = new byte[0];
54 |
55 | // When: decoder is called
56 | final VendorConsent vendorConsent = VendorConsentDecoder.fromByteArray(consentBytes);
57 |
58 | // Then IllegalArgumentException exception is thrown
59 |
60 | }
61 |
62 | @Test(expected = IllegalStateException.class)
63 | public void testUnknownVersion() {
64 | // Given: unknown version number in consent string
65 | final Bits bits = new Bits(new byte[100]);
66 | bits.setInt(VERSION_BIT_OFFSET, VERSION_BIT_SIZE, 10);
67 |
68 | // When: decoder is called
69 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(Base64.getUrlEncoder().withoutPadding().encodeToString(bits.toByteArray()));
70 |
71 | // Then IllegalStateException exception is thrown
72 | }
73 |
74 | @Test
75 | public void testVersion1() {
76 | // Given: version 1 consent string
77 | final String consentString = "BOOlLqOOOlLqTABABAENAk-AAAAXx7_______9______9uz_Gv_r_f__3nW8_39P3g_7_O3_7m_-zzV48_lrQV1yPAUCgA";
78 |
79 | // When: decoder is called
80 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
81 |
82 | // Then: v1 ByteBufferVendorConsent is returned
83 | assertThat(vendorConsent.getClass(),is(ByteBufferBackedVendorConsent.class));
84 |
85 | }
86 |
87 | }
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/VendorConsent.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent;
2 |
3 | import com.iab.gdpr.Purpose;
4 |
5 | import java.time.Instant;
6 | import java.util.Set;
7 |
8 | /**
9 | * Representation of the values in the vendor consent string.
10 | *
11 | * Combination of {@link VendorConsent#isPurposeAllowed(int)} and {@link VendorConsent#isVendorAllowed(int)} methods
12 | * fully describes user's consent for particular action by a particular vendor
13 | *
14 | */
15 | public interface VendorConsent {
16 |
17 | /**
18 | *
19 | * @return the version of consent string format
20 | */
21 | int getVersion();
22 |
23 | /**
24 | * @return the {@link Instant} at which the consent string was created
25 | */
26 | Instant getConsentRecordCreated();
27 |
28 | /**
29 | *
30 | * @return the {@link Instant} at which consent string was last updated
31 | */
32 | Instant getConsentRecordLastUpdated();
33 |
34 | /**
35 | *
36 | * @return the Consent Manager Provider ID that last updated the consent string
37 | */
38 | int getCmpId();
39 |
40 | /**
41 | *
42 | * @return the Consent Manager Provider version
43 | */
44 | int getCmpVersion();
45 |
46 | /**
47 | *
48 | * @return the screen number in the CMP where consent was given
49 | */
50 | int getConsentScreen();
51 |
52 | /**
53 | *
54 | * @return the two-letter ISO639-1 language code that CMP asked for consent in
55 | */
56 | String getConsentLanguage();
57 |
58 | /**
59 | *
60 | * @return version of vendor list used in most recent consent string update.
61 | */
62 | int getVendorListVersion();
63 |
64 | /**
65 | *
66 | * @return the set of purpose id's which are permitted according to this consent string
67 | */
68 | Set getAllowedPurposeIds();
69 |
70 | /**
71 | *
72 | * @return the set of allowed purposes which are permitted according to this consent string
73 | */
74 | Set getAllowedPurposes();
75 |
76 | /**
77 | *
78 | * @return an integer equivalent of allowed purpose id bits according to this consent string
79 | */
80 | int getAllowedPurposesBits();
81 |
82 | /**
83 | *
84 | * @return the set of allowed vendor id's which are permitted according to this consent string
85 | */
86 | Set getAllowedVendorIds();
87 |
88 | /**
89 | *
90 | * @return the maximum VendorId for which consent values are given.
91 | */
92 | int getMaxVendorId();
93 |
94 | /**
95 | * Check whether purpose with specified ID is allowed
96 | * @param purposeId purpose ID
97 | * @return true if purpose is allowed in this consent, false otherwise
98 | */
99 | boolean isPurposeAllowed(int purposeId);
100 |
101 | /**
102 | * Check whether specified purpose is allowed
103 | * @param purpose purpose to check
104 | * @return true if purpose is allowed in this consent, false otherwise
105 | */
106 | boolean isPurposeAllowed(Purpose purpose);
107 |
108 | /**
109 | * Check whether vendor with specified ID is allowd
110 | * @param vendorId vendor ID
111 | * @return a boolean describing if a user has consented to a particular vendor
112 | */
113 | boolean isVendorAllowed(int vendorId);
114 |
115 | /**
116 | *
117 | * @return the value of this consent as byte array
118 | */
119 | byte[] toByteArray();
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Transparency and Consent Framework: Consent-String-SDK-Java
4 |
5 | Encode and decode web-safe base64 consent information with the IAB EU's GDPR Transparency and Consent Framework.
6 |
7 | This library is a Java reference implementation for dealing with consent strings in the IAB EU's GDPR Transparency and Consent Framework.
8 | It should be used by anyone who receives or sends consent information like vendors that receive consent data from a partner, or consent management platforms that need to encode/decode the global cookie.
9 |
10 | The IAB specification for the consent string format is available on the [IAB Github](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md) (section "Vendor Consent Cookie Format").
11 |
12 | **This library supports the version v1.1 of the specification. It can encode and decode consent strings with version bit 1. If you're looking for TCF version 2, please go to https://github.com/InteractiveAdvertisingBureau/iabtcf-java**
13 |
14 | ## IAB Europe Transparency and Consent Framework
15 |
16 | In November 2017, IAB Europe and a cross-section of the publishing and advertising industry, announced a new Transparency & Consent Framework to help publishers, advertisers and technology companies comply with key elements of GDPR. The Framework will give the publishing and advertising industries a common language with which to communicate consumer consent for the delivery of relevant online advertising and content.
17 |
18 | Framework Technical specifications available at: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework
19 |
20 |
21 | # Consent String SDK (Java)
22 | - [Installation](#installation)
23 | - [Usage](#usage)
24 | - [Building](#building)
25 | - [Contributing](#contributing)
26 | - [Versioning](#versioning)
27 |
28 |
29 | ## Installation
30 |
31 | Add dependency to you pom.xml
32 |
33 | ```
34 |
35 | com.conversantmedia.gdpr
36 | consent-string-sdk-java
37 | 3.0.2
38 |
39 | ```
40 |
41 | ## Usage
42 |
43 | ### Decoding consent string
44 |
45 | ```
46 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
47 |
48 | if (vendorConsent.isVendorAllowed(vendorId) && vendorConsent.isPurposeAllowed(STORAGE_AND_ACCESS) {
49 | ...
50 | } else {
51 | ...
52 | }
53 |
54 | ```
55 |
56 | ### Creating vendor consent
57 | ```
58 | final VendorConsent vendorConsent = new VendorConsentBuilder()
59 | .withConsentRecordCreatedOn(now)
60 | .withConsentRecordLastUpdatedOn(now)
61 | .withCmpID(cmpId)
62 | .withCmpVersion(cmpVersion)
63 | .withConsentScreenID(consentScreenID)
64 | .withConsentLanguage(consentLanguage)
65 | .withVendorListVersion(vendorListVersion)
66 | .withAllowedPurposes(allowedPurposes)
67 | .withMaxVendorId(maxVendorId)
68 | .withVendorEncodingType(vendorEncodingType)
69 | .withDefaultConsent(false)
70 | .withRangeEntries(rangeEntries)
71 | .build();
72 | ```
73 |
74 | ### Encoding vendor consent to string
75 | ```
76 | final String base64String = VendorConsentEncoder.toBase64String(vendorConsent);
77 | ```
78 |
79 | ## Building
80 |
81 | Use Gradle command to build the project
82 | ```
83 | git clone https://github.com/InteractiveAdvertisingBureau/Consent-String-SDK-Java.git
84 | cd Consent-String-SDK-Java
85 | ./gradlew build
86 | ```
87 |
88 | ## Contributing
89 |
90 | ### Branching
91 | We use following branching setup
92 | 1. **master** branch is the current branch where active development is taking place and is associated with the latest release
93 | 1. Previous releases are under branches release/x, where x is the major release version, for example release/1. Those branches are used for bug bixes in previous releases
94 | 1. Each release version is associated with a git tag, for example tag v2.0.0
95 |
96 | ### Pull request procedures
97 | 1. Make sure there is a unit test for each added feature. If pull request is for bug fix, create a unit test that would trigger a bug
98 | 1. Make sure **all** tests pass
99 | 1. Update this document if usage is changing
100 |
101 |
102 | ## Versioning
103 |
104 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/tags).
105 |
106 | ## About
107 |
108 | #### About IAB Tech Lab
109 |
110 | The IAB Technology Laboratory (Tech Lab) is a non-profit research and development consortium that produces and provides standards, software, and services to drive growth of an effective and sustainable global digital media ecosystem. Comprised of digital publishers and ad technology firms, as well as marketers, agencies, and other companies with interests in the interactive marketing arena, IAB Tech Lab aims to enable brand and media growth via a transparent, safe, effective supply chain, simpler and more consistent measurement, and better advertising experiences for consumers, with a focus on mobile and ?TV?/digital video channel enablement. The IAB Tech Lab portfolio includes the DigiTrust real-time standardized identity service designed to improve the digital experience for consumers, publishers, advertisers, and third-party platforms. Board members include AppNexus, ExtremeReach, Google, GroupM, Hearst Digital Media, Integral Ad Science, Index Exchange, LinkedIn, MediaMath, Microsoft, Moat, Pandora, PubMatic, Quantcast, Telaria, The Trade Desk, and Yahoo! Japan. Established in 2014, the IAB Tech Lab is headquartered in New York City with an office in San Francisco and representation in Seattle and London.
111 |
112 | Learn more about IAB Tech Lab here: [https://www.iabtechlab.com/](https://www.iabtechlab.com/)
113 |
114 | #### About IAB Europe
115 |
116 | IAB Europe is the voice of digital business and the leading European-level industry association for the interactive advertising ecosystem. Its mission is to promote the development of this innovative sector by shaping the regulatory environment, investing in research and education, and developing and facilitating the uptake of business standards.
117 |
118 | Learn more about IAB Europe here: [https://www.iabeurope.eu/](https://www.iabeurope.eu/)
119 |
120 |
121 | #### Contributors and Technical Governance
122 |
123 | GDPR Technical Working Group members provide contributions to this repository. Participants in the GDPR Technical Working group must be members of IAB Tech Lab. Technical Governance for the project is provided by the IAB Tech Lab GDPR Commit Group.
124 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/Bits.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr;
2 |
3 | import java.time.Instant;
4 |
5 | import com.iab.gdpr.exception.VendorConsentCreateException;
6 | import com.iab.gdpr.exception.VendorConsentException;
7 | import com.iab.gdpr.exception.VendorConsentParseException;
8 |
9 | /*
10 | * since java.util.BitSet is inappropiate to use here--as it reversed the bit order of the consent string
11 | * implement bitwise operations here
12 | */
13 | public class Bits {
14 | // big endian
15 | private static final byte[] bytePows = { -128, 64, 32, 16, 8, 4, 2, 1 };
16 | private final byte[] bytes;
17 |
18 | public Bits(byte[] b) {
19 | this.bytes = b;
20 | }
21 |
22 | /**
23 | *
24 | * @param index:
25 | * the nth number bit to get from the bit string
26 | * @return boolean bit, true if the bit is switched to 1, false otherwise
27 | */
28 | public boolean getBit(int index) {
29 | int byteIndex = index / 8;
30 | if (byteIndex > bytes.length - 1)
31 | throw new VendorConsentParseException("Expected consent string to contain at least " + (byteIndex + 1) + " bytes, but found only " + bytes.length + " bytes");
32 | int bitExact = index % 8;
33 | byte b = bytes[byteIndex];
34 | return (b & bytePows[bitExact]) != 0;
35 | }
36 |
37 | /**
38 | *
39 | * @param index:
40 | * set the nth number bit from the bit string
41 | */
42 | public void setBit(int index) {
43 | int byteIndex = index / 8;
44 | int shift = (byteIndex + 1) * 8 - index - 1;
45 | bytes[byteIndex] |= 1 << shift;
46 | }
47 |
48 | /**
49 | *
50 | * @param index:
51 | * unset the nth number bit from the bit string
52 | */
53 | public void unsetBit(int index) {
54 | int byteIndex = index / 8;
55 | int shift = (byteIndex + 1) * 8 - index - 1;
56 | bytes[byteIndex] &= ~(1 << shift);
57 | }
58 |
59 | /**
60 | * interprets n number of bits as a big endiant int
61 | *
62 | * @param startInclusive:
63 | * the nth to begin interpreting from
64 | * @param size:
65 | * the number of bits to interpret
66 | * @return integer value
67 | * @throws VendorConsentException
68 | * when the bits cannot fit in an int sized field
69 | */
70 | public int getInt(int startInclusive, int size) throws VendorConsentException {
71 | if (size > Integer.SIZE) {
72 | throw new VendorConsentParseException("can't fit bit range in int " + size);
73 | }
74 | int val = 0;
75 | int sigMask = 1;
76 | int sigIndex = size - 1;
77 |
78 | for (int i = 0; i < size; i++) {
79 | if (getBit(startInclusive + i)) {
80 | val += (sigMask << sigIndex);
81 | }
82 | sigIndex--;
83 | }
84 | return val;
85 | }
86 |
87 | /**
88 | * Writes an integer value into a bit array of given size
89 | *
90 | * @param startInclusive:
91 | * the nth to begin writing to
92 | * @param size:
93 | * the number of bits available to write
94 | * @param to:
95 | * the integer to write out
96 | * @throws VendorConsentException
97 | * when the bits cannot fit into the provided size
98 | */
99 | public void setInt(int startInclusive, int size, int to) throws VendorConsentException {
100 | if (size > Integer.SIZE || to > maxOfSize(size) || to < 0) {
101 | throw new VendorConsentCreateException("can't fit integer into bit range of size" + size);
102 | }
103 |
104 | setNumber(startInclusive, size, to);
105 | }
106 |
107 | /**
108 | * interprets n bits as a big endian long
109 | *
110 | * @param startInclusive:
111 | * the nth to begin interpreting from
112 | * @param size:the
113 | * number of bits to interpret
114 | * @return the long value create by interpretation of provided bits
115 | * @throws VendorConsentException
116 | * when the bits cannot fit in an int sized field
117 | */
118 | private long getLong(int startInclusive, int size) throws VendorConsentException {
119 | if (size > Long.SIZE) {
120 | throw new VendorConsentParseException("can't fit bit range in long: " + size);
121 | }
122 | long val = 0;
123 | long sigMask = 1;
124 | int sigIndex = size - 1;
125 |
126 | for (int i = 0; i < size; i++) {
127 | if (getBit(startInclusive + i)) {
128 | val += (sigMask << sigIndex);
129 | }
130 | sigIndex--;
131 | }
132 | return val;
133 | }
134 |
135 | /**
136 | * Writes a long value into a bit array of given size
137 | *
138 | * @param startInclusive:
139 | * the nth to begin writing to
140 | * @param size:
141 | * the number of bits available to write
142 | * @param to:
143 | * the long number to write out
144 | * @throws VendorConsentException
145 | * when the bits cannot fit into the provided size
146 | */
147 | private void setLong(int startInclusive, int size, long to) throws VendorConsentException {
148 | if (size > Long.SIZE || to > maxOfSize(size) || to < 0) {
149 | throw new VendorConsentCreateException("can't fit long into bit range of size " + size);
150 | }
151 |
152 | setNumber(startInclusive, size, to);
153 | }
154 |
155 | /**
156 | * returns an {@link Instant} derived from interpreting the given interval on the bit string as long representing
157 | * the number of demiseconds from the unix epoch
158 | *
159 | * @param startInclusive:
160 | * the bit from which to begin interpreting
161 | * @param size:
162 | * the number of bits to interpret
163 | * @return instant value
164 | * @throws VendorConsentException
165 | * when the number of bits requested cannot fit in a long
166 | */
167 | public Instant getInstantFromEpochDeciseconds(int startInclusive, int size) throws VendorConsentException {
168 | long epochDemi = getLong(startInclusive, size);
169 | return Instant.ofEpochMilli(epochDemi * 100);
170 | }
171 |
172 | public void setInstantToEpochDeciseconds(int startInclusive, int size, Instant instant)
173 | throws VendorConsentException {
174 | setLong(startInclusive, size, instant.toEpochMilli() / 100);
175 | }
176 |
177 | /**
178 | * This method interprets the given interval in the bit string as a series of six bit characters, where 0=A and 26=Z
179 | *
180 | * @param startInclusive:
181 | * the nth bit in the bitstring from which to start the interpretation
182 | * @param size:
183 | * the number of bits to include in the string
184 | * @return the string given by the above interpretation
185 | * @throws VendorConsentException
186 | * when the requested interval is not a multiple of six
187 | */
188 | public String getSixBitString(int startInclusive, int size) throws VendorConsentException {
189 | if (size % 6 != 0) {
190 | throw new VendorConsentParseException("string bit length must be multiple of six: " + size);
191 | }
192 | int charNum = size / 6;
193 | StringBuilder val = new StringBuilder();
194 | for (int i = 0; i < charNum; i++) {
195 | int charCode = getInt(startInclusive + (i * 6), 6) + 65;
196 | val.append((char) charCode);
197 | }
198 | return val.toString().toUpperCase();
199 | }
200 |
201 | /**
202 | * This method interprets characters, as 0=A and 26=Z and writes to the given interval in the bit string as a series
203 | * of six bits
204 | *
205 | * @param startInclusive:
206 | * the nth bit in the bitstring from which to start writing
207 | * @param size:
208 | * the size of the bitstring
209 | * @param to:
210 | * the string given by the above interpretation
211 | * @throws VendorConsentException
212 | * when the requested interval is not a multiple of six
213 | */
214 | public void setSixBitString(int startInclusive, int size, String to) throws VendorConsentException {
215 | if (size % 6 != 0 || size / 6 != to.length()) {
216 | throw new VendorConsentCreateException(
217 | "bit array size must be multiple of six and equal to 6 times the size of string");
218 | }
219 | char[] values = to.toCharArray();
220 | for (int i = 0; i < values.length; i++) {
221 | int charCode = values[i] - 65;
222 | setInt(startInclusive + (i * 6), 6, charCode);
223 | }
224 | }
225 |
226 | public byte[] toByteArray() {
227 | return bytes;
228 | }
229 |
230 | private void setNumber(int startInclusive, int size, long to) {
231 | for (int i = size - 1; i >= 0; i--) {
232 | int index = startInclusive + i;
233 | int byteIndex = index / 8;
234 | int shift = (byteIndex + 1) * 8 - index - 1;
235 | bytes[byteIndex] |= (to % 2) << shift;
236 | to /= 2;
237 | }
238 | }
239 |
240 | private long maxOfSize(int size) {
241 | long max = 0;
242 | for (int i = 0; i < size; i++) {
243 | max += Math.pow(2, i);
244 | }
245 | return max;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/test/java/com/iab/gdpr/consent/implementation/v1/VendorConsentBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.implementation.v1;
2 |
3 | import com.iab.gdpr.Purpose;
4 | import com.iab.gdpr.consent.range.RangeEntry;
5 | import com.iab.gdpr.consent.range.SingleRangeEntry;
6 | import com.iab.gdpr.consent.range.StartEndRangeEntry;
7 | import com.iab.gdpr.consent.VendorConsent;
8 | import com.iab.gdpr.exception.VendorConsentCreateException;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 |
12 | import java.time.Instant;
13 | import java.time.LocalDateTime;
14 | import java.time.ZoneOffset;
15 | import java.util.Arrays;
16 | import java.util.HashSet;
17 | import java.util.List;
18 | import java.util.Set;
19 |
20 | import static com.iab.gdpr.Purpose.*;
21 | import static org.hamcrest.Matchers.is;
22 | import static org.junit.Assert.assertFalse;
23 | import static org.junit.Assert.assertThat;
24 | import static org.junit.Assert.assertTrue;
25 |
26 | public class VendorConsentBuilderTest {
27 |
28 | private Instant now;
29 |
30 | @Before
31 | public void setUp() {
32 | now = LocalDateTime.now().withNano(0).toInstant(ZoneOffset.UTC);
33 | }
34 |
35 | @Test(expected = IllegalArgumentException.class)
36 | public void testInvalidPurpose() {
37 | // Given: invalid purpose ID in the set
38 | final Set allowedPurposes = new HashSet<>(Arrays.asList(1, 2, 3, 99));
39 |
40 | // When: passing set to the builder
41 | new VendorConsentBuilder().withAllowedPurposeIds(allowedPurposes);
42 |
43 | // Then: exception is thrown
44 | }
45 |
46 | @Test(expected = IllegalArgumentException.class)
47 | public void testInvalidVendorEncodingType() {
48 | // Given: invalid vendor encoding type
49 | int vendorEncodingType = 3;
50 |
51 | // When: passing vendor type to builder
52 | new VendorConsentBuilder().withVendorEncodingType(vendorEncodingType);
53 |
54 | // Then: exception is thrown
55 | }
56 |
57 | @Test(expected = VendorConsentCreateException.class)
58 | public void testInvalidVendorListVersion() {
59 | // Given: invalid vendor list version - 50
60 | int vendorListVersion = -50;
61 |
62 | // When: trying to build using invalid value
63 | final VendorConsent vendorConsent = new VendorConsentBuilder()
64 | .withConsentRecordCreatedOn(now)
65 | .withConsentRecordLastUpdatedOn(now)
66 | .withConsentLanguage("EN")
67 | .withVendorListVersion(vendorListVersion)
68 | .build();
69 |
70 | // Then: exception is thrown
71 | }
72 |
73 | @Test(expected = VendorConsentCreateException.class)
74 | public void testInvalidMaxVendorId() {
75 | // Given: invalid max vendor ID = -1
76 | int maxVendorId = -1;
77 |
78 | // When: trying to build using invalid value
79 | final VendorConsent vendorConsent = new VendorConsentBuilder()
80 | .withConsentRecordCreatedOn(now)
81 | .withConsentRecordLastUpdatedOn(now)
82 | .withConsentLanguage("EN")
83 | .withVendorListVersion(10)
84 | .withMaxVendorId(maxVendorId)
85 | .build();
86 |
87 | // Then: exception is thrown
88 | }
89 |
90 | @Test(expected = VendorConsentCreateException.class)
91 | public void testInvalidRangeEntry() {
92 | // Given: max vendor ID of 300;
93 | int maxVendorId = 300;
94 |
95 | // And: list of range entries with values that are > max vendor ID
96 | final List rangeEntries = Arrays.asList(
97 | new SingleRangeEntry(1),
98 | new StartEndRangeEntry(100,400)
99 | );
100 |
101 | // When: trying to build using invalid value
102 | final VendorConsent vendorConsent = new VendorConsentBuilder()
103 | .withConsentRecordCreatedOn(now)
104 | .withConsentRecordLastUpdatedOn(now)
105 | .withConsentLanguage("EN")
106 | .withVendorListVersion(10)
107 | .withMaxVendorId(maxVendorId)
108 | .withVendorEncodingType(1)
109 | .withRangeEntries(rangeEntries)
110 | .build();
111 |
112 | // Then: exception is thrown
113 | }
114 |
115 | @Test
116 | public void testValidBidFieldEncoding() {
117 | // Given: set of consent string parameters
118 | final Set allowedPurposes = new HashSet<>(Arrays.asList(1, 2, 3, 24));
119 | final int cmpId = 1;
120 | final int cmpVersion = 1;
121 | final int consentScreenID = 1;
122 | final String consentLanguage = "EN";
123 | final int vendorListVersion = 10;
124 | final int maxVendorId = 180;
125 | final int vendorEncodingType = 0; // Bit field
126 | final Set allowedVendors = new HashSet<>(Arrays.asList(1, 10, 180));
127 |
128 | // When: vendor consent is build
129 | final VendorConsent vendorConsent = new VendorConsentBuilder()
130 | .withConsentRecordCreatedOn(now)
131 | .withConsentRecordLastUpdatedOn(now)
132 | .withCmpID(cmpId)
133 | .withCmpVersion(cmpVersion)
134 | .withConsentScreenID(consentScreenID)
135 | .withConsentLanguage(consentLanguage)
136 | .withVendorListVersion(vendorListVersion)
137 | .withAllowedPurposeIds(allowedPurposes)
138 | .withMaxVendorId(maxVendorId)
139 | .withVendorEncodingType(vendorEncodingType)
140 | .withBitField(allowedVendors)
141 | .build();
142 |
143 |
144 | // Then: values in vendor consent match parameters
145 | assertThat(vendorConsent.getVersion(),is(1));
146 | assertThat(vendorConsent.getConsentRecordCreated(),is(now));
147 | assertThat(vendorConsent.getConsentRecordLastUpdated(),is(now));
148 | assertThat(vendorConsent.getCmpId(),is(cmpId));
149 | assertThat(vendorConsent.getCmpVersion(),is(cmpVersion));
150 | assertThat(vendorConsent.getConsentScreen(),is(consentScreenID));
151 | assertThat(vendorConsent.getVendorListVersion(),is(vendorListVersion));
152 | assertThat(vendorConsent.getAllowedPurposeIds(),is(allowedPurposes));
153 | assertThat(vendorConsent.getMaxVendorId(),is(maxVendorId));
154 |
155 | assertTrue(vendorConsent.isPurposeAllowed(1));
156 | assertTrue(vendorConsent.isPurposeAllowed(2));
157 | assertTrue(vendorConsent.isPurposeAllowed(3));
158 | assertTrue(vendorConsent.isPurposeAllowed(24));
159 |
160 | assertTrue(vendorConsent.isVendorAllowed(1));
161 | assertTrue(vendorConsent.isVendorAllowed(10));
162 | assertTrue(vendorConsent.isVendorAllowed(180));
163 |
164 | assertFalse(vendorConsent.isPurposeAllowed(4));
165 | assertFalse(vendorConsent.isPurposeAllowed(5));
166 |
167 | assertFalse(vendorConsent.isVendorAllowed(5));
168 | assertFalse(vendorConsent.isVendorAllowed(11));
169 | assertFalse(vendorConsent.isVendorAllowed(179));
170 | }
171 |
172 | @Test
173 | public void testValidRangedEncoding() {
174 | // Given: set of consent string parameters
175 | final Set allowedPurposes = new HashSet<>(Arrays.asList(STORAGE_AND_ACCESS, PERSONALIZATION, AD_SELECTION, CONTENT_DELIVERY, MEASUREMENT));
176 | final int cmpId = 10;
177 | final int cmpVersion = 3;
178 | final int consentScreenID = 4;
179 | final String consentLanguage = "FR";
180 | final int vendorListVersion = 231;
181 | final int maxVendorId = 400;
182 | final int vendorEncodingType = 1;
183 | final List rangeEntries = Arrays.asList(
184 | new SingleRangeEntry(10),
185 | new StartEndRangeEntry(100,200),
186 | new SingleRangeEntry(350)
187 | );
188 |
189 | // When: vendor consent is build
190 | final VendorConsent vendorConsent = new VendorConsentBuilder()
191 | .withConsentRecordCreatedOn(now)
192 | .withConsentRecordLastUpdatedOn(now)
193 | .withCmpID(cmpId)
194 | .withCmpVersion(cmpVersion)
195 | .withConsentScreenID(consentScreenID)
196 | .withConsentLanguage(consentLanguage)
197 | .withVendorListVersion(vendorListVersion)
198 | .withAllowedPurposes(allowedPurposes)
199 | .withMaxVendorId(maxVendorId)
200 | .withVendorEncodingType(vendorEncodingType)
201 | .withDefaultConsent(false)
202 | .withRangeEntries(rangeEntries)
203 | .build();
204 |
205 |
206 | // Then: values in vendor consent match parameters
207 | assertThat(vendorConsent.getVersion(),is(1));
208 | assertThat(vendorConsent.getConsentRecordCreated(),is(now));
209 | assertThat(vendorConsent.getConsentRecordLastUpdated(),is(now));
210 | assertThat(vendorConsent.getCmpId(),is(cmpId));
211 | assertThat(vendorConsent.getCmpVersion(),is(cmpVersion));
212 | assertThat(vendorConsent.getConsentScreen(),is(consentScreenID));
213 | assertThat(vendorConsent.getVendorListVersion(),is(vendorListVersion));
214 | assertThat(vendorConsent.getAllowedPurposes(),is(allowedPurposes));
215 | assertThat(vendorConsent.getMaxVendorId(),is(maxVendorId));
216 |
217 | assertTrue(vendorConsent.isPurposeAllowed(STORAGE_AND_ACCESS));
218 | assertTrue(vendorConsent.isPurposeAllowed(PERSONALIZATION));
219 | assertTrue(vendorConsent.isPurposeAllowed(AD_SELECTION));
220 | assertTrue(vendorConsent.isPurposeAllowed(CONTENT_DELIVERY));
221 | assertTrue(vendorConsent.isPurposeAllowed(MEASUREMENT));
222 |
223 | assertTrue(vendorConsent.isVendorAllowed(10));
224 | assertTrue(vendorConsent.isVendorAllowed(150));
225 | assertTrue(vendorConsent.isVendorAllowed(350));
226 |
227 | assertFalse(vendorConsent.isVendorAllowed(50));
228 | assertFalse(vendorConsent.isVendorAllowed(240));
229 | }
230 | }
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/implementation/v1/ByteBufferBackedVendorConsent.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.implementation.v1;
2 |
3 |
4 | import com.iab.gdpr.Bits;
5 | import com.iab.gdpr.Purpose;
6 | import com.iab.gdpr.consent.VendorConsent;
7 | import com.iab.gdpr.exception.VendorConsentParseException;
8 |
9 | import java.time.Instant;
10 | import java.util.Arrays;
11 | import java.util.HashSet;
12 | import java.util.Set;
13 | import java.util.stream.Collectors;
14 | import java.util.stream.IntStream;
15 |
16 | import static com.iab.gdpr.GdprConstants.*;
17 |
18 | /**
19 | * Implementation of {@link VendorConsent}. This implementation uses byte buffer (wrapped with {@link Bits})
20 | * as a storage of consent string values and parses individual fields on demand.
21 | *
22 | * This should work well in environment where vendor consent string is decoded, couple of isPurposeAllowed()/isVendorAllowed()
23 | * calls are made and then value of the consent is discarded.
24 | *
25 | * In the environment where decoded consent string is kept for longer time with numerous isPurposeAllowed()/isVendorAllowed()
26 | * calls a different implementation may be needed that would cache results of those calls.
27 | *
28 | */
29 | public class ByteBufferBackedVendorConsent implements VendorConsent {
30 | private final Bits bits;
31 |
32 | public ByteBufferBackedVendorConsent(Bits bits) {
33 | this.bits = bits;
34 | }
35 |
36 | @Override
37 | public int getVersion() {
38 | return bits.getInt(VERSION_BIT_OFFSET, VERSION_BIT_SIZE);
39 | }
40 |
41 | @Override
42 | public Instant getConsentRecordCreated() {
43 | return bits.getInstantFromEpochDeciseconds(CREATED_BIT_OFFSET, CREATED_BIT_SIZE);
44 | }
45 |
46 | @Override
47 | public Instant getConsentRecordLastUpdated() {
48 | return bits.getInstantFromEpochDeciseconds(UPDATED_BIT_OFFSET, UPDATED_BIT_SIZE);
49 | }
50 |
51 | @Override
52 | public int getCmpId() {
53 | return bits.getInt(CMP_ID_OFFSET, CMP_ID_SIZE);
54 | }
55 |
56 | @Override
57 | public int getCmpVersion() {
58 | return bits.getInt(CMP_VERSION_OFFSET, CMP_VERSION_SIZE);
59 | }
60 |
61 | @Override
62 | public int getConsentScreen() {
63 | return bits.getInt(CONSENT_SCREEN_SIZE_OFFSET, CONSENT_SCREEN_SIZE);
64 | }
65 |
66 | @Override
67 | public String getConsentLanguage() {
68 | return bits.getSixBitString(CONSENT_LANGUAGE_OFFSET, CONSENT_LANGUAGE_SIZE);
69 | }
70 |
71 | @Override
72 | public int getVendorListVersion() {
73 | return bits.getInt(VENDOR_LIST_VERSION_OFFSET, VENDOR_LIST_VERSION_SIZE);
74 | }
75 |
76 | @Override
77 | public Set getAllowedPurposeIds() {
78 | final Set allowedPurposes = new HashSet<>();
79 | for (int i = PURPOSES_OFFSET; i < PURPOSES_OFFSET + PURPOSES_SIZE; i++) {
80 | if (bits.getBit(i)) {
81 | allowedPurposes.add(i - PURPOSES_OFFSET + 1);
82 | }
83 | }
84 | return allowedPurposes;
85 | }
86 |
87 | @Override
88 | public Set getAllowedPurposes() {
89 | return getAllowedPurposeIds().stream().map(Purpose::valueOf).collect(Collectors.toSet());
90 | }
91 |
92 | @Override
93 | public int getAllowedPurposesBits() {
94 | return bits.getInt(PURPOSES_OFFSET, PURPOSES_SIZE);
95 | }
96 |
97 | @Override
98 | public Set getAllowedVendorIds() {
99 | final Set allowedVendorIds = new HashSet<>();
100 | final int maxVendorId = getMaxVendorId();
101 | if (encodingType() == VENDOR_ENCODING_RANGE) {
102 | final Set vendorIds = new HashSet<>();
103 | final boolean isDefaultConsent = bits.getBit(DEFAULT_CONSENT_OFFSET);
104 | final int numEntries = bits.getInt(NUM_ENTRIES_OFFSET, NUM_ENTRIES_SIZE);
105 | int currentOffset = RANGE_ENTRY_OFFSET;
106 | for (int i = 0; i < numEntries; i++) {
107 | final boolean isRange = bits.getBit(currentOffset);
108 | currentOffset++;
109 | if(isRange) {
110 | int startVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
111 | currentOffset += VENDOR_ID_SIZE;
112 | int endVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
113 | currentOffset += VENDOR_ID_SIZE;
114 | validate(startVendorId, endVendorId, maxVendorId);
115 | IntStream.rangeClosed(startVendorId, endVendorId).forEach(vendorIds::add);
116 | } else {
117 | int singleVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
118 | currentOffset += VENDOR_ID_SIZE;
119 | validate(singleVendorId, maxVendorId);
120 | vendorIds.add(singleVendorId);
121 | }
122 | }
123 | if (isDefaultConsent) {
124 | IntStream.rangeClosed(1, getMaxVendorId())
125 | .filter(id -> !vendorIds.contains(id))
126 | .forEach(allowedVendorIds::add);
127 | } else {
128 | allowedVendorIds.addAll(vendorIds);
129 | }
130 | } else {
131 | for (int i = VENDOR_BITFIELD_OFFSET; i < VENDOR_BITFIELD_OFFSET + maxVendorId; i++) {
132 | if(bits.getBit(i)) {
133 | allowedVendorIds.add(i - VENDOR_BITFIELD_OFFSET + 1);
134 | }
135 | }
136 | }
137 | return allowedVendorIds;
138 | }
139 |
140 | @Override
141 | public int getMaxVendorId() {
142 | return bits.getInt(MAX_VENDOR_ID_OFFSET, MAX_VENDOR_ID_SIZE);
143 | }
144 |
145 | @Override
146 | public boolean isPurposeAllowed(int purposeId) {
147 | if (purposeId < 1 || purposeId > PURPOSES_SIZE) return false;
148 | return bits.getBit(PURPOSES_OFFSET + purposeId - 1);
149 | }
150 |
151 | @Override
152 | public boolean isPurposeAllowed(Purpose purpose) {
153 | return isPurposeAllowed(purpose.getId());
154 | }
155 |
156 | @Override
157 | public boolean isVendorAllowed(int vendorId) {
158 | final int maxVendorId = getMaxVendorId();
159 | if (vendorId < 1 || vendorId > maxVendorId) return false;
160 |
161 | if (encodingType() == VENDOR_ENCODING_RANGE) {
162 | final boolean defaultConsent = bits.getBit(DEFAULT_CONSENT_OFFSET);
163 | final boolean present = isVendorPresentInRange(vendorId);
164 | return present != defaultConsent;
165 | } else {
166 | return bits.getBit(VENDOR_BITFIELD_OFFSET + vendorId - 1);
167 | }
168 | }
169 |
170 | @Override
171 | public byte[] toByteArray() {
172 | return bits.toByteArray();
173 | }
174 |
175 | /**
176 | *
177 | * @return the encoding type - 0=BitField 1=Range
178 | */
179 | private int encodingType() {
180 | return bits.getInt(ENCODING_TYPE_OFFSET, ENCODING_TYPE_SIZE);
181 | }
182 |
183 | /**
184 | * Check whether specified vendor ID is present in the range section of the bits. This assumes that
185 | * encoding type was already checked and is VENDOR_ENCODING_RANGE
186 | * @param vendorId vendor ID to check
187 | * @return boolean value of vendor ID presence
188 | */
189 | private boolean isVendorPresentInRange(int vendorId) {
190 | final int numEntries = bits.getInt(NUM_ENTRIES_OFFSET, NUM_ENTRIES_SIZE);
191 | final int maxVendorId = getMaxVendorId();
192 | int currentOffset = RANGE_ENTRY_OFFSET;
193 | for (int i = 0; i < numEntries; i++) {
194 | boolean range = bits.getBit(currentOffset);
195 | currentOffset++;
196 | if (range) {
197 | int startVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
198 | currentOffset += VENDOR_ID_SIZE;
199 | int endVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
200 | currentOffset += VENDOR_ID_SIZE;
201 | validate(startVendorId, endVendorId, maxVendorId);
202 | if (vendorId >= startVendorId && vendorId <= endVendorId) return true;
203 |
204 | } else {
205 | int singleVendorId = bits.getInt(currentOffset, VENDOR_ID_SIZE);
206 | currentOffset += VENDOR_ID_SIZE;
207 | validate(singleVendorId, maxVendorId);
208 | if (singleVendorId == vendorId) return true;
209 | }
210 | }
211 | return false;
212 | }
213 |
214 | private static void validate(int startVendorId, int endVendorId, int maxVendorId) throws VendorConsentParseException {
215 | if (startVendorId > endVendorId || endVendorId > maxVendorId) {
216 | throw new VendorConsentParseException(
217 | "Start VendorId must not be greater than End VendorId and "
218 | + "End VendorId must not be greater than Max Vendor Id");
219 | }
220 | }
221 |
222 | private static void validate(int singleVendorId, int maxVendorId) throws VendorConsentParseException{
223 | if (singleVendorId > maxVendorId) {
224 | throw new VendorConsentParseException(
225 | "VendorId in the range entries must not be greater than Max VendorId");
226 | }
227 | }
228 |
229 | @Override
230 | public boolean equals(Object o) {
231 | if (this == o) return true;
232 | if (o == null || getClass() != o.getClass()) return false;
233 | ByteBufferBackedVendorConsent that = (ByteBufferBackedVendorConsent) o;
234 | return Arrays.equals(bits.toByteArray(), that.bits.toByteArray());
235 | }
236 |
237 | @Override
238 | public int hashCode() {
239 | return Arrays.hashCode(bits.toByteArray());
240 | }
241 |
242 | @Override
243 | public String toString() {
244 | return "ByteBufferVendorConsent{" +
245 | "Version=" + getVersion() +
246 | ",Created=" + getConsentRecordCreated() +
247 | ",LastUpdated=" + getConsentRecordLastUpdated() +
248 | ",CmpId=" + getCmpId() +
249 | ",CmpVersion=" + getCmpVersion() +
250 | ",ConsentScreen=" + getConsentScreen() +
251 | ",ConsentLanguage=" + getConsentLanguage() +
252 | ",VendorListVersion=" + getVendorListVersion() +
253 | ",PurposesAllowed=" + getAllowedPurposeIds() +
254 | ",MaxVendorId=" + getMaxVendorId() +
255 | ",EncodingType=" + encodingType() +
256 | "}";
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/src/main/java/com/iab/gdpr/consent/implementation/v1/VendorConsentBuilder.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.implementation.v1;
2 |
3 | import com.iab.gdpr.Bits;
4 | import com.iab.gdpr.GdprConstants;
5 | import com.iab.gdpr.Purpose;
6 | import com.iab.gdpr.consent.range.RangeEntry;
7 | import com.iab.gdpr.consent.VendorConsent;
8 | import com.iab.gdpr.exception.VendorConsentCreateException;
9 |
10 | import java.time.Instant;
11 | import java.util.*;
12 | import java.util.stream.Collectors;
13 |
14 | import static com.iab.gdpr.GdprConstants.*;
15 |
16 | /**
17 | * Builder for version 1 of vendor consent
18 | */
19 | public class VendorConsentBuilder {
20 |
21 | private static final int VERSION = 1;
22 |
23 | private Instant consentRecordCreated;
24 | private Instant consentRecordLastUpdated;
25 | private int cmpID;
26 | private int cmpVersion;
27 | private int consentScreenID;
28 | private String consentLanguage;
29 | private int vendorListVersion;
30 | private int maxVendorId;
31 | private int vendorEncodingType;
32 | private Set allowedPurposes = new HashSet<>(PURPOSES_SIZE);
33 | private Set vendorsBitField; // used when bit field encoding is used
34 | private List rangeEntries; // used when range entry encoding is used
35 | private boolean defaultConsent;
36 |
37 | /**
38 | * With creation date
39 | * @param consentRecordCreated Epoch deciseconds when record was created
40 | * @return builder
41 | */
42 | public VendorConsentBuilder withConsentRecordCreatedOn(Instant consentRecordCreated) {
43 | this.consentRecordCreated = consentRecordCreated;
44 | return this;
45 | }
46 |
47 | /**
48 | * With update date
49 | * @param consentRecordLastUpdated Epoch deciseconds when consent string was last updated
50 | * @return builder
51 | */
52 | public VendorConsentBuilder withConsentRecordLastUpdatedOn(Instant consentRecordLastUpdated) {
53 | this.consentRecordLastUpdated = consentRecordLastUpdated;
54 | return this;
55 | }
56 |
57 | /**
58 | * With CMP id
59 | * @param cmpID Consent Manager Provider ID that last updated the consent string
60 | * @return builder
61 | */
62 | public VendorConsentBuilder withCmpID(int cmpID) {
63 | this.cmpID = cmpID;
64 | return this;
65 | }
66 |
67 | /**
68 | * With CMP version
69 | * @param cmpVersion Consent Manager Provider version
70 | * @return builder
71 | */
72 | public VendorConsentBuilder withCmpVersion(int cmpVersion) {
73 | this.cmpVersion = cmpVersion;
74 | return this;
75 | }
76 |
77 | /**
78 | * With consent screen ID
79 | * @param consentScreenID Screen number in the CMP where consent was given
80 | * @return builder
81 | */
82 | public VendorConsentBuilder withConsentScreenID(int consentScreenID) {
83 | this.consentScreenID = consentScreenID;
84 | return this;
85 | }
86 |
87 | /**
88 | * With consent language
89 | * @param consentLanguage Two-letter ISO639-1 language code that CMP asked for consent in
90 | * @return builder
91 | */
92 | public VendorConsentBuilder withConsentLanguage(String consentLanguage) {
93 | this.consentLanguage = consentLanguage;
94 | return this;
95 | }
96 |
97 | /**
98 | * With vendor list version
99 | * @param vendorListVersion Version of vendor list used in most recent consent string update
100 | * @return builder
101 | */
102 | public VendorConsentBuilder withVendorListVersion(int vendorListVersion) {
103 | this.vendorListVersion = vendorListVersion;
104 | return this;
105 | }
106 |
107 | /**
108 | * With allowed purpose IDs
109 | * @param allowedPurposeIds set of allowed purposes
110 | * @return builder
111 | */
112 | public VendorConsentBuilder withAllowedPurposeIds(Set allowedPurposeIds) {
113 | // Validate
114 | Objects.requireNonNull(allowedPurposeIds, "Argument allowedPurposeIds is null");
115 | final boolean invalidPurposeIdFound = allowedPurposeIds.stream().anyMatch(purposeId -> purposeId < 0 || purposeId > PURPOSES_SIZE);
116 | if (invalidPurposeIdFound) throw new IllegalArgumentException("Invalid purpose ID found");
117 |
118 | this.allowedPurposes = allowedPurposeIds;
119 | return this;
120 | }
121 |
122 | /**
123 | * With allowed purposes
124 | * @param allowedPurposes set of allowed purposes
125 | * @return builder
126 | */
127 | public VendorConsentBuilder withAllowedPurposes(Set allowedPurposes) {
128 | // Validate
129 | Objects.requireNonNull(allowedPurposes, "Argument allowedPurposes is null");
130 |
131 | this.allowedPurposes = allowedPurposes.stream().map(Purpose::getId).collect(Collectors.toSet());
132 | return this;
133 | }
134 |
135 | /**
136 | * With max vendor ID
137 | * @param maxVendorId The maximum VendorId for which consent values are given.
138 | * @return builder
139 | */
140 | public VendorConsentBuilder withMaxVendorId(int maxVendorId) {
141 | this.maxVendorId = maxVendorId;
142 | return this;
143 | }
144 |
145 | /**
146 | * With vendor encoding type
147 | * @param vendorEncodingType 0=BitField 1=Range
148 | * @return builder
149 | */
150 | public VendorConsentBuilder withVendorEncodingType(int vendorEncodingType) {
151 | if (vendorEncodingType < 0 || vendorEncodingType > 1)
152 | throw new IllegalArgumentException("Illegal value for argument vendorEncodingType:" + vendorEncodingType);
153 |
154 | this.vendorEncodingType = vendorEncodingType;
155 | return this;
156 | }
157 |
158 | /**
159 | * With bit field entries
160 | * @param bitFieldEntries set of VendorIds for which the vendors have consent
161 | * @return builder
162 | */
163 | public VendorConsentBuilder withBitField(Set bitFieldEntries) {
164 | this.vendorsBitField = bitFieldEntries;
165 | return this;
166 | }
167 |
168 | /**
169 | * With range entries
170 | * @param rangeEntries List of VendorIds or a range of VendorIds for which the vendors have consent
171 | * @return builder
172 | */
173 | public VendorConsentBuilder withRangeEntries(List rangeEntries) {
174 | this.rangeEntries = rangeEntries;
175 | return this;
176 | }
177 |
178 | /**
179 | * With default consent
180 | * @param defaultConsent Default consent for VendorIds not covered by a RangeEntry. 0=No Consent 1=Consent
181 | * @return builder
182 | */
183 | public VendorConsentBuilder withDefaultConsent(boolean defaultConsent) {
184 | this.defaultConsent = defaultConsent;
185 | return this;
186 | }
187 |
188 | /**
189 | * Validate supplied values and build {@link VendorConsent} object
190 | * @return vendor consent object
191 | */
192 | public VendorConsent build() {
193 |
194 | Objects.requireNonNull(consentRecordCreated, "consentRecordCreated must be set");
195 | Objects.requireNonNull(consentRecordLastUpdated, "consentRecordLastUpdated must be set");
196 | Objects.requireNonNull(consentLanguage, "consentLanguage must be set");
197 |
198 | if (vendorListVersion <=0 )
199 | throw new VendorConsentCreateException("Invalid value for vendorListVersion:" + vendorListVersion);
200 |
201 | if (maxVendorId <=0 )
202 | throw new VendorConsentCreateException("Invalid value for maxVendorId:" + maxVendorId);
203 |
204 | // For range encoding, check if each range entry is valid
205 | if (vendorEncodingType == VENDOR_ENCODING_RANGE) {
206 | Objects.requireNonNull(rangeEntries, "Range entries must be set");
207 | final boolean invalidRangeEntriesFound = rangeEntries.stream().anyMatch(rangeEntry -> !rangeEntry.valid(maxVendorId));
208 | if (invalidRangeEntriesFound) throw new VendorConsentCreateException("Invalid range entries found");
209 | }
210 |
211 | // Calculate size of bit buffer in bits
212 | final int bitBufferSizeInBits;
213 | if (vendorEncodingType == VENDOR_ENCODING_RANGE) {
214 | final int rangeEntrySectionSize = rangeEntries.stream().mapToInt(RangeEntry::size).sum();
215 | bitBufferSizeInBits = RANGE_ENTRY_OFFSET + rangeEntrySectionSize;
216 | } else {
217 | bitBufferSizeInBits = VENDOR_BITFIELD_OFFSET + this.maxVendorId;
218 | }
219 |
220 | // Create new bit buffer
221 | final boolean bitsFit = (bitBufferSizeInBits % 8) == 0;
222 | final Bits bits = new Bits(new byte[bitBufferSizeInBits / 8 + (bitsFit ? 0 : 1)]);
223 |
224 | // Set fields in bit buffer
225 | bits.setInt(VERSION_BIT_OFFSET, VERSION_BIT_SIZE, VERSION);
226 | bits.setInstantToEpochDeciseconds(CREATED_BIT_OFFSET, CREATED_BIT_SIZE, consentRecordCreated);
227 | bits.setInstantToEpochDeciseconds(UPDATED_BIT_OFFSET, UPDATED_BIT_SIZE, consentRecordLastUpdated);
228 | bits.setInt(CMP_ID_OFFSET, CMP_ID_SIZE, this.cmpID);
229 | bits.setInt(CMP_VERSION_OFFSET, CMP_VERSION_SIZE, cmpVersion);
230 | bits.setInt(CONSENT_SCREEN_SIZE_OFFSET, CONSENT_SCREEN_SIZE, consentScreenID);
231 | bits.setSixBitString(CONSENT_LANGUAGE_OFFSET, CONSENT_LANGUAGE_SIZE, consentLanguage);
232 | bits.setInt(VENDOR_LIST_VERSION_OFFSET, VENDOR_LIST_VERSION_SIZE, vendorListVersion);
233 |
234 | // Set purposes bits
235 | for (int i = 0; i < PURPOSES_SIZE; i++) {
236 | if (allowedPurposes.contains(i+1))
237 | bits.setBit(PURPOSES_OFFSET + i);
238 | else
239 | bits.unsetBit(PURPOSES_OFFSET + i);
240 | }
241 |
242 | bits.setInt(MAX_VENDOR_ID_OFFSET, MAX_VENDOR_ID_SIZE, maxVendorId);
243 | bits.setInt(ENCODING_TYPE_OFFSET, ENCODING_TYPE_SIZE, vendorEncodingType);
244 |
245 | // Set the bit field or range sections
246 | if (vendorEncodingType == VENDOR_ENCODING_RANGE) {
247 | // Range encoding
248 | if (defaultConsent) {
249 | bits.setBit(DEFAULT_CONSENT_OFFSET);
250 | } else {
251 | bits.unsetBit(DEFAULT_CONSENT_OFFSET);
252 | }
253 | bits.setInt(NUM_ENTRIES_OFFSET, NUM_ENTRIES_SIZE, rangeEntries.size());
254 |
255 | int currentOffset = GdprConstants.RANGE_ENTRY_OFFSET;
256 |
257 | for (RangeEntry rangeEntry : rangeEntries) {
258 | currentOffset = rangeEntry.appendTo(bits, currentOffset);
259 | }
260 |
261 | } else {
262 | // Bit field encoding
263 | for (int i = 0; i < maxVendorId; i++) {
264 | if (vendorsBitField.contains(i+1))
265 | bits.setBit(VENDOR_BITFIELD_OFFSET+i);
266 | else
267 | bits.unsetBit(VENDOR_BITFIELD_OFFSET + i);
268 | }
269 | }
270 |
271 | return new ByteBufferBackedVendorConsent(bits);
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/test/java/com/iab/gdpr/consent/implementation/v1/ByteBufferBackedVendorConsentTest.java:
--------------------------------------------------------------------------------
1 | package com.iab.gdpr.consent.implementation.v1;
2 |
3 | import com.iab.gdpr.consent.VendorConsent;
4 | import com.iab.gdpr.consent.VendorConsentDecoder;
5 | import com.iab.gdpr.exception.VendorConsentParseException;
6 | import com.iab.gdpr.util.Utils;
7 | import org.junit.Test;
8 |
9 | import java.time.Instant;
10 | import java.time.LocalDateTime;
11 | import java.time.ZoneOffset;
12 | import java.time.format.DateTimeFormatter;
13 | import java.util.Arrays;
14 | import java.util.HashSet;
15 | import java.util.Set;
16 | import java.util.stream.Collectors;
17 | import java.util.stream.IntStream;
18 |
19 | import static com.iab.gdpr.Purpose.*;
20 | import static org.hamcrest.Matchers.is;
21 | import static org.junit.Assert.*;
22 |
23 | public class ByteBufferBackedVendorConsentTest {
24 |
25 | @Test
26 | public void testVersion() {
27 | // Given: version field set to 3
28 | final String binaryString = "000011" + "000000000000";
29 |
30 | // When: object is constructed
31 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
32 |
33 | // Then: correct version is returned
34 | assertThat(vendorConsent.getVersion(),is(3));
35 | }
36 |
37 | @Test
38 | public void testgetConsentRecordCreated() {
39 | // Given: created date of Monday, June 4, 2018 12:00:00 AM, epoch = 1528070400
40 | final String binaryString = "000011" + // Version
41 | "001110001110110011010000101000000000" + // Created
42 | "0000";
43 |
44 | // When: object is constructed
45 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
46 |
47 | // Then: correct created timestamp is returned
48 | assertThat(vendorConsent.getConsentRecordCreated(),is(LocalDateTime.of(2018,6,4,0,0,0).toInstant(ZoneOffset.UTC)));
49 | }
50 |
51 | @Test
52 | public void testgetConsentRecordLastUpdated() {
53 | // Given: updated date of Monday, June 4, 2018 12:00:00 AM, epoch = 1528070400
54 | final String binaryString = "000011" + // Version
55 | "001110001110110011010000101000000000" + // Created
56 | "001110001110110011010000101000000000" + // Updated
57 | "0000";
58 |
59 | // When: object is constructed
60 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
61 |
62 | // Then: correct updated timestamp is returned
63 | assertThat(vendorConsent.getConsentRecordLastUpdated(),is(LocalDateTime.of(2018,6,4,0,0,0).toInstant(ZoneOffset.UTC)));
64 | }
65 |
66 | @Test
67 | public void testgetCmpId() {
68 | // Given: CMP ID of 15
69 | final String binaryString = "000011" + // Version
70 | "001110001110110011010000101000000000" + // Created
71 | "001110001110110011010000101000000000" + // Updated
72 | "000000001111" + // CMP ID
73 | "0000";
74 |
75 | // When: object is constructed
76 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
77 |
78 | // Then: correct cmp ID is returned
79 | assertThat(vendorConsent.getCmpId(),is(15));
80 | }
81 |
82 | @Test
83 | public void testgetCmpVersion() {
84 | // Given: CMP version of 5
85 | final String binaryString = "000011" + // Version
86 | "001110001110110011010000101000000000" + // Created
87 | "001110001110110011010000101000000000" + // Updated
88 | "000000001111" + // CMP ID
89 | "000000000101" + // CMP version
90 | "0000";
91 |
92 | // When: object is constructed
93 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
94 |
95 | // Then: correct cmp version is returned
96 | assertThat(vendorConsent.getCmpVersion(),is(5));
97 | }
98 |
99 | @Test
100 | public void testgetConsentScreen() {
101 | // Given: content screen ID of 18
102 | final String binaryString = "000011" + // Version
103 | "001110001110110011010000101000000000" + // Created
104 | "001110001110110011010000101000000000" + // Updated
105 | "000000001111" + // CMP ID
106 | "000000000101" + // CMP version
107 | "010010" + // Content screen ID
108 | "0000";
109 |
110 | // When: object is constructed
111 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
112 |
113 | // Then: correct content screen ID is returned
114 | assertThat(vendorConsent.getConsentScreen(),is(18));
115 | }
116 |
117 | @Test
118 | public void testgetConsentLanguage() {
119 | // Given: language code of EN
120 | final String binaryString = "000011" + // Version
121 | "001110001110110011010000101000000000" + // Created
122 | "001110001110110011010000101000000000" + // Updated
123 | "000000001111" + // CMP ID
124 | "000000000101" + // CMP version
125 | "010010" + // Content screen ID
126 | "000100001101" + // Language code
127 | "0000";
128 |
129 | // When: object is constructed
130 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
131 |
132 | // Then: correct language code is returned
133 | assertThat(vendorConsent.getConsentLanguage(),is("EN"));
134 | }
135 |
136 | @Test
137 | public void testgetVendorListVersion() {
138 | // Given: vendor list version of 150
139 | final String binaryString = "000011" + // Version
140 | "001110001110110011010000101000000000" + // Created
141 | "001110001110110011010000101000000000" + // Updated
142 | "000000001111" + // CMP ID
143 | "000000000101" + // CMP version
144 | "010010" + // Content screen ID
145 | "000100001101" + // Language code
146 | "000010010110" + // vendor list version
147 | "0000";
148 |
149 | // When: object is constructed
150 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
151 |
152 | // Then: correct vendor list version is returned
153 | assertThat(vendorConsent.getVendorListVersion(),is(150));
154 | }
155 |
156 | @Test
157 | public void testgetAllowedPurposes() {
158 | // Given: allowed purposes of 1,2,3,4,5,15,24
159 | final String binaryString = "000011" + // Version
160 | "001110001110110011010000101000000000" + // Created
161 | "001110001110110011010000101000000000" + // Updated
162 | "000000001111" + // CMP ID
163 | "000000000101" + // CMP version
164 | "010010" + // Content screen ID
165 | "000100001101" + // Language code
166 | "000010010110" + // Vendor list version
167 | "111110000000001000000001" + // Allowed purposes bitmap
168 | "0000";
169 |
170 | // When: object is constructed
171 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
172 |
173 | // Then: correct allowed versions are returned
174 | assertThat(vendorConsent.getAllowedPurposeIds(),is(new HashSet<>(Arrays.asList(1,2,3,4,5,15,24))));
175 | assertThat(vendorConsent.getAllowedPurposes(),is(new HashSet<>(Arrays.asList(STORAGE_AND_ACCESS,PERSONALIZATION,AD_SELECTION,CONTENT_DELIVERY,MEASUREMENT,UNDEFINED))));
176 | assertThat(vendorConsent.getAllowedPurposesBits(),is(16253441));
177 | assertTrue(vendorConsent.isPurposeAllowed(1));
178 | assertTrue(vendorConsent.isPurposeAllowed(STORAGE_AND_ACCESS));
179 | assertTrue(vendorConsent.isPurposeAllowed(2));
180 | assertTrue(vendorConsent.isPurposeAllowed(PERSONALIZATION));
181 | assertTrue(vendorConsent.isPurposeAllowed(3));
182 | assertTrue(vendorConsent.isPurposeAllowed(AD_SELECTION));
183 | assertTrue(vendorConsent.isPurposeAllowed(4));
184 | assertTrue(vendorConsent.isPurposeAllowed(CONTENT_DELIVERY));
185 | assertTrue(vendorConsent.isPurposeAllowed(5));
186 | assertTrue(vendorConsent.isPurposeAllowed(MEASUREMENT));
187 | assertTrue(vendorConsent.isPurposeAllowed(15));
188 | assertTrue(vendorConsent.isPurposeAllowed(24));
189 | }
190 |
191 | @Test
192 | public void testgetMaxVendorId() {
193 | // Given: max vendor ID of 382
194 | final String binaryString = "000011" + // Version
195 | "001110001110110011010000101000000000" + // Created
196 | "001110001110110011010000101000000000" + // Updated
197 | "000000001111" + // CMP ID
198 | "000000000101" + // CMP version
199 | "010010" + // Content screen ID
200 | "000100001101" + // Language code
201 | "000010010110" + // Vendor list version
202 | "111110000000001000000001" + // Allowed purposes bitmap
203 | "0000000101111110" + // Max vendor ID
204 | "0000";
205 |
206 | // When: object is constructed
207 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
208 |
209 | // Then: correct max vendor ID is returned
210 | assertThat(vendorConsent.getMaxVendorId(),is(382));
211 | }
212 |
213 | @Test
214 | public void testBitFieldEncoding() {
215 | // Given: vendors 1,25 and 30 in bit field, with max vendor ID of 32
216 | final String binaryString = "000011" + // Version
217 | "001110001110110011010000101000000000" + // Created
218 | "001110001110110011010000101000000000" + // Updated
219 | "000000001111" + // CMP ID
220 | "000000000101" + // CMP version
221 | "010010" + // Content screen ID
222 | "000100001101" + // Language code
223 | "000010010110" + // Vendor list version
224 | "111110000000001000000001" + // Allowed purposes bitmap
225 | "0000000000100000" + // Max vendor ID
226 | "0" + // Bit field encoding
227 | "10000000000000000000000010000100" // Vendor bits in bit field
228 | ;
229 |
230 | // When: object is constructed
231 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
232 |
233 | // Then: correct vendor IDs are allowed
234 | assertTrue(vendorConsent.isVendorAllowed(1));
235 | assertTrue(vendorConsent.isVendorAllowed(25));
236 | assertTrue(vendorConsent.isVendorAllowed(30));
237 | assertThat(vendorConsent.getAllowedVendorIds(), is(new HashSet<>(Arrays.asList(1, 25, 30))));
238 |
239 | assertFalse(vendorConsent.isVendorAllowed(2));
240 | assertFalse(vendorConsent.isVendorAllowed(3));
241 | assertFalse(vendorConsent.isVendorAllowed(31));
242 | assertFalse(vendorConsent.isVendorAllowed(32));
243 |
244 | // Vendors outside range [1, MaxVendorId] are not allowed
245 | assertFalse(vendorConsent.isVendorAllowed(-99));
246 | assertFalse(vendorConsent.isVendorAllowed(-1));
247 | assertFalse(vendorConsent.isVendorAllowed(0));
248 | assertFalse(vendorConsent.isVendorAllowed(33));
249 | assertFalse(vendorConsent.isVendorAllowed(34));
250 | assertFalse(vendorConsent.isVendorAllowed(99));
251 | }
252 |
253 | @Test
254 | public void testRangeEncodingDefaultFalse() {
255 | // Given: vendors 1-25 and 30 with consent, with max vendor ID of 32
256 | final String binaryString = "000011" + // Version
257 | "001110001110110011010000101000000000" + // Created
258 | "001110001110110011010000101000000000" + // Updated
259 | "000000001111" + // CMP ID
260 | "000000000101" + // CMP version
261 | "010010" + // Content screen ID
262 | "000100001101" + // Language code
263 | "000010010110" + // Vendor list version
264 | "111110000000001000000001" + // Allowed purposes bitmap
265 | "0000000000100000" + // Max vendor ID
266 | "1" + // Range encoding
267 | "0" + // Default 0=No Consent
268 | "000000000010" + // Number of entries = 2
269 | "1" + // First entry range = 1
270 | "0000000000000001" + // First entry from = 1
271 | "0000000000011001" + // First entry to = 25
272 | "0" + // Second entry single = 0
273 | "0000000000011110" // Second entry value = 30
274 | ;
275 |
276 | // When: object is constructed
277 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
278 |
279 | // Then: correct vendor IDs are allowed
280 | assertTrue(vendorConsent.isVendorAllowed(1));
281 | assertTrue(vendorConsent.isVendorAllowed(10));
282 | assertTrue(vendorConsent.isVendorAllowed(25));
283 | assertTrue(vendorConsent.isVendorAllowed(30));
284 |
285 | Set expectedVendorIds = IntStream
286 | .concat(IntStream.rangeClosed(1, 25), IntStream.of(30))
287 | .boxed()
288 | .collect(Collectors.toSet());
289 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
290 |
291 | assertFalse(vendorConsent.isVendorAllowed(26));
292 | assertFalse(vendorConsent.isVendorAllowed(28));
293 | assertFalse(vendorConsent.isVendorAllowed(31));
294 | assertFalse(vendorConsent.isVendorAllowed(32));
295 |
296 | // Vendors outside range [1, MaxVendorId] are not allowed
297 | assertFalse(vendorConsent.isVendorAllowed(-99));
298 | assertFalse(vendorConsent.isVendorAllowed(-1));
299 | assertFalse(vendorConsent.isVendorAllowed(0));
300 | assertFalse(vendorConsent.isVendorAllowed(33));
301 | assertFalse(vendorConsent.isVendorAllowed(34));
302 | assertFalse(vendorConsent.isVendorAllowed(99));
303 | }
304 |
305 | @Test
306 | public void testRangeEncodingDefaultTrue() {
307 | // Given: vendors 1 and 25-30 without consent, with max vendor ID of 32
308 | final String binaryString = "000011" + // Version
309 | "001110001110110011010000101000000000" + // Created
310 | "001110001110110011010000101000000000" + // Updated
311 | "000000001111" + // CMP ID
312 | "000000000101" + // CMP version
313 | "010010" + // Content screen ID
314 | "000100001101" + // Language code
315 | "000010010110" + // Vendor list version
316 | "111110000000001000000001" + // Allowed purposes bitmap
317 | "0000000000100000" + // Max vendor ID
318 | "1" + // Range encoding
319 | "1" + // Default 1=Consent
320 | "000000000010" + // Number of entries = 2
321 | "0" + // First entry single = 0
322 | "0000000000000001" + // First entry value = 1
323 | "1" + // Second entry range = 1
324 | "0000000000011001" + // Second entry from = 25
325 | "0000000000011110" // Second entry to = 30
326 | ;
327 |
328 | // When: object is constructed
329 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
330 |
331 | // Then: correct vendor IDs are allowed
332 | assertFalse(vendorConsent.isVendorAllowed(1));
333 | assertFalse(vendorConsent.isVendorAllowed(25));
334 | assertFalse(vendorConsent.isVendorAllowed(27));
335 | assertFalse(vendorConsent.isVendorAllowed(30));
336 |
337 | assertTrue(vendorConsent.isVendorAllowed(2));
338 | assertTrue(vendorConsent.isVendorAllowed(15));
339 | assertTrue(vendorConsent.isVendorAllowed(31));
340 | assertTrue(vendorConsent.isVendorAllowed(32));
341 |
342 | Set expectedVendorIds = IntStream
343 | .concat(IntStream.rangeClosed(2, 24), IntStream.rangeClosed(31, 32))
344 | .boxed()
345 | .collect(Collectors.toSet());
346 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
347 |
348 | // Vendors outside range [1, MaxVendorId] are not allowed
349 | assertFalse(vendorConsent.isVendorAllowed(-99));
350 | assertFalse(vendorConsent.isVendorAllowed(-1));
351 | assertFalse(vendorConsent.isVendorAllowed(0));
352 | assertFalse(vendorConsent.isVendorAllowed(33));
353 | assertFalse(vendorConsent.isVendorAllowed(34));
354 | assertFalse(vendorConsent.isVendorAllowed(99));
355 | }
356 |
357 | @Test(expected = VendorConsentParseException.class)
358 | public void testInvalidVendorId1() {
359 | // Given: invalid vendor ID in range
360 | final String binaryString = "000011" + // Version
361 | "001110001110110011010000101000000000" + // Created
362 | "001110001110110011010000101000000000" + // Updated
363 | "000000001111" + // CMP ID
364 | "000000000101" + // CMP version
365 | "010010" + // Content screen ID
366 | "000100001101" + // Language code
367 | "000010010110" + // Vendor list version
368 | "111110000000001000000001" + // Allowed purposes bitmap
369 | "0000000000100000" + // Max vendor ID
370 | "1" + // Range encoding
371 | "1" + // Default 1=Consent
372 | "000000000010" + // Number of entries = 2
373 | "0" + // First entry single = 0
374 | "0000000000000001" + // First entry value = 1
375 | "1" + // Second entry range = 1
376 | "0000000000101000" + // Second entry from = 40 - INVALID
377 | "0000000000011110" // Second entry to = 30
378 | ;
379 |
380 | // When: object is constructed
381 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
382 |
383 | // And: vendor check is performed
384 | assertTrue(vendorConsent.isVendorAllowed(32));
385 |
386 | // Then: exception is raised
387 | }
388 |
389 | @Test(expected = VendorConsentParseException.class)
390 | public void testInvalidVendorId2() {
391 | // Given: invalid vendor ID in range
392 | final String binaryString = "000011" + // Version
393 | "001110001110110011010000101000000000" + // Created
394 | "001110001110110011010000101000000000" + // Updated
395 | "000000001111" + // CMP ID
396 | "000000000101" + // CMP version
397 | "010010" + // Content screen ID
398 | "000100001101" + // Language code
399 | "000010010110" + // Vendor list version
400 | "111110000000001000000001" + // Allowed purposes bitmap
401 | "0000000000100000" + // Max vendor ID
402 | "1" + // Range encoding
403 | "1" + // Default 1=Consent
404 | "000000000010" + // Number of entries = 2
405 | "0" + // First entry single = 0
406 | "0000000000101000" + // First entry value = 40 - INVALID
407 | "1" + // Second entry range = 1
408 | "0000000000011001" + // Second entry from = 25
409 | "0000000000011110" // Second entry to = 30
410 | ;
411 |
412 | // When: object is constructed
413 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
414 |
415 | // And: vendor check is performed
416 | assertTrue(vendorConsent.isVendorAllowed(32));
417 |
418 | // Then: exception is raised
419 | }
420 |
421 | @Test(expected = VendorConsentParseException.class)
422 | public void testInvalidVendorId3() {
423 | // Given: invalid vendor ID in range
424 | final String binaryString = "000011" + // Version
425 | "001110001110110011010000101000000000" + // Created
426 | "001110001110110011010000101000000000" + // Updated
427 | "000000001111" + // CMP ID
428 | "000000000101" + // CMP version
429 | "010010" + // Content screen ID
430 | "000100001101" + // Language code
431 | "000010010110" + // Vendor list version
432 | "111110000000001000000001" + // Allowed purposes bitmap
433 | "0000000000100000" + // Max vendor ID
434 | "1" + // Range encoding
435 | "1" + // Default 1=Consent
436 | "000000000010" + // Number of entries = 2
437 | "0" + // First entry single = 0
438 | "0000000000101000" + // First entry value = 40 - INVALID
439 | "1" + // Second entry range = 1
440 | "0000000000011001" + // Second entry from = 25
441 | "0000000000011110" // Second entry to = 30
442 | ;
443 |
444 | // When: object is constructed
445 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
446 |
447 | // And: allowed vendor IDs are obtained
448 | vendorConsent.getAllowedVendorIds();
449 |
450 | // Then: exception is raised
451 | }
452 |
453 | @Test(expected = VendorConsentParseException.class)
454 | public void testInvalidVendorId4() {
455 | // Given: invalid vendor ID in range
456 | final String binaryString = "000011" + // Version
457 | "001110001110110011010000101000000000" + // Created
458 | "001110001110110011010000101000000000" + // Updated
459 | "000000001111" + // CMP ID
460 | "000000000101" + // CMP version
461 | "010010" + // Content screen ID
462 | "000100001101" + // Language code
463 | "000010010110" + // Vendor list version
464 | "111110000000001000000001" + // Allowed purposes bitmap
465 | "0000000000100000" + // Max vendor ID
466 | "1" + // Range encoding
467 | "1" + // Default 1=Consent
468 | "000000000010" + // Number of entries = 2
469 | "0" + // First entry single = 0
470 | "0000000000101000" + // First entry value = 40 - INVALID
471 | "1" + // Second entry range = 1
472 | "0000000000011001" + // Second entry from = 25
473 | "0000000000011110" // Second entry to = 30
474 | ;
475 |
476 | // When: object is constructed
477 | ByteBufferBackedVendorConsent vendorConsent = new ByteBufferBackedVendorConsent(Utils.fromBinaryString(binaryString));
478 |
479 | // And: allowed vendor IDs are obtained
480 | vendorConsent.getAllowedVendorIds();
481 |
482 | // Then: exception is raised
483 | }
484 | /*
485 | Below are tests for encoded strings from previous version of the code
486 | */
487 |
488 | @Test
489 | public void testRealString1() {
490 | // Given: known vendor consent string
491 | final String consentString = "BOOlLqOOOlLqTABABAENAk-AAAAXx7_______9______9uz_Gv_r_f__3nW8_39P3g_7_O3_7m_-zzV48_lrQV1yPAUCgA";
492 |
493 | // When: vendor consent is constructed
494 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
495 |
496 | // Then: values match expectation
497 | assertEquals(380, vendorConsent.getMaxVendorId());
498 | assertTrue(vendorConsent.isVendorAllowed(380));
499 | assertFalse(vendorConsent.isVendorAllowed(379));
500 |
501 | Set expectedVendorIds = new HashSet<>(Arrays.asList(1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
502 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
503 | 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
504 | 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 100,
505 | 101, 102, 104, 105, 108, 109, 110, 111, 112, 113, 114, 115, 119, 120, 122, 124, 125, 126, 127, 128, 129,
506 | 130, 131, 132, 133, 134, 136, 138, 139, 140, 141, 142, 143, 144, 145, 147, 148, 149, 150, 151, 152, 153,
507 | 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 167, 168, 169, 170, 173, 174, 175, 177, 179,
508 | 180, 182, 183, 184, 185, 188, 189, 190, 191, 192, 193, 194, 195, 197, 198, 199, 200, 201, 202, 203, 205,
509 | 208, 209, 210, 211, 212, 213, 215, 216, 217, 218, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, 235,
510 | 236, 237, 238, 239, 240, 241, 244, 245, 246, 248, 249, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260,
511 | 261, 262, 264, 265, 266, 269, 270, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285, 288,
512 | 289, 290, 291, 294, 295, 297, 299, 301, 302, 303, 304, 308, 309, 310, 311, 314, 315, 316, 317, 318, 319,
513 | 320, 323, 325, 326, 328, 330, 331, 333, 339, 341, 343, 344, 345, 347, 349, 350, 351, 354, 358, 359, 360,
514 | 361, 369, 371, 378, 380));
515 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
516 | }
517 |
518 | @Test
519 | public void testRealString2() {
520 | // Given: known vendor consent string
521 | final String consentString = "BN5lERiOMYEdiAOAWeFRAAYAAaAAptQ";
522 |
523 | // When: vendor consent is constructed
524 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
525 |
526 | // Then: values match expectation
527 | assertThat(vendorConsent.getCmpId(), is(14));
528 | assertThat(vendorConsent.getCmpVersion(), is(22));
529 | assertThat(vendorConsent.getConsentLanguage(), is("FR"));
530 | assertThat(vendorConsent.getConsentRecordCreated(), is(Instant.ofEpochMilli(14924661858L * 100)));
531 | assertThat(vendorConsent.getConsentRecordLastUpdated(), is(Instant.ofEpochMilli(15240021858L * 100)));
532 | assertThat(vendorConsent.getAllowedPurposeIds().size(), is(5));
533 | assertThat(vendorConsent.getAllowedPurposesBits(), is(6291482));
534 |
535 | assertTrue(vendorConsent.isPurposeAllowed(2));
536 | assertFalse(vendorConsent.isPurposeAllowed(1));
537 | assertTrue(vendorConsent.isPurposeAllowed(21));
538 | assertTrue(vendorConsent.isVendorAllowed(1));
539 | assertTrue(vendorConsent.isVendorAllowed(5));
540 | assertTrue(vendorConsent.isVendorAllowed(7));
541 | assertTrue(vendorConsent.isVendorAllowed(9));
542 | assertFalse(vendorConsent.isVendorAllowed(0));
543 | assertFalse(vendorConsent.isVendorAllowed(10));
544 |
545 | Set expectedVendorIds = new HashSet<>(Arrays.asList(1, 2, 4, 5, 7, 9));
546 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
547 | }
548 |
549 | @Test
550 | public void testRealString3() {
551 | // Given: known vendor consent string
552 | final String consentString = "BN5lERiOMYEdiAKAWXEND1HoSBE6CAFAApAMgBkIDIgM0AgOJxAnQA";
553 |
554 | // When: vendor consent is constructed
555 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
556 |
557 | // Then: values match expectation
558 | assertThat(vendorConsent.getCmpId(), is(10));
559 | assertThat(vendorConsent.getCmpVersion(), is(22));
560 | assertThat(vendorConsent.getConsentLanguage(), is("EN"));
561 | assertThat(vendorConsent.getConsentRecordCreated(), is(Instant.ofEpochMilli(14924661858L * 100)));
562 | assertThat(vendorConsent.getConsentRecordLastUpdated(), is(Instant.ofEpochMilli(15240021858L * 100)));
563 | assertThat(vendorConsent.getAllowedPurposeIds().size(), is(8));
564 | assertThat(vendorConsent.getAllowedPurposesBits(), is(2000001));
565 |
566 | assertTrue(vendorConsent.isPurposeAllowed(4));
567 | assertFalse(vendorConsent.isPurposeAllowed(1));
568 | assertTrue(vendorConsent.isPurposeAllowed(24));
569 | assertFalse(vendorConsent.isPurposeAllowed(25));
570 | assertFalse(vendorConsent.isPurposeAllowed(0));
571 | assertFalse(vendorConsent.isVendorAllowed(1));
572 | assertFalse(vendorConsent.isVendorAllowed(3));
573 | assertTrue(vendorConsent.isVendorAllowed(225));
574 | assertTrue(vendorConsent.isVendorAllowed(5000));
575 | assertTrue(vendorConsent.isVendorAllowed(515));
576 | assertFalse(vendorConsent.isVendorAllowed(0));
577 | assertFalse(vendorConsent.isVendorAllowed(411));
578 | assertFalse(vendorConsent.isVendorAllowed(3244));
579 |
580 | Set expectedVendorIds = IntStream.concat(IntStream.of(20), IntStream.rangeClosed(200, 410))
581 | .boxed()
582 | .collect(Collectors.toSet());
583 | expectedVendorIds.add(515);
584 | expectedVendorIds.addAll(IntStream.rangeClosed(5000, 5024).boxed().collect(Collectors.toSet()));
585 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
586 | }
587 |
588 | @Test
589 | public void testRealString4() {
590 | // Given: known vendor consent string
591 | final String consentString = "BOOMzbgOOQww_AtABAFRAb-AAAsvOA3gACAAkABgArgBaAF0AMAA1gBuAH8AQQBSgCoAL8AYQBigDIAM0AaABpgDYAOYAdgA8AB6gD4AQoAiABFQCMAI6ASABIgCTAEqAJeATIBQQCiAKSAU4BVQCtAK-AWYBaQC2ALcAXMAvAC-gGAAYcAxQDGAGQAMsAZsA0ADTAGqANcAbMA4ADjAHKAOiAdQB1gDtgHgAeMA9AD2AHzAP4BAACBAEEAIbAREBEgCKQEXARhZeYA";
592 |
593 | // When: vendor consent is constructed
594 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
595 |
596 | // Then: values match expectation
597 | assertThat(vendorConsent.getCmpId(), is(45));
598 | assertThat(vendorConsent.getCmpVersion(), is(1));
599 | assertThat(vendorConsent.getConsentLanguage(), is("FR"));
600 | assertThat(vendorConsent.getConsentRecordCreated(), is(Instant.ofEpochMilli(15270622944L * 100)));
601 | assertThat(vendorConsent.getConsentRecordLastUpdated(), is(Instant.ofEpochMilli(15271660607L * 100)));
602 | assertThat(vendorConsent.getAllowedPurposeIds().size(), is(5));
603 |
604 | assertTrue(vendorConsent.isPurposeAllowed(1));
605 | assertTrue(vendorConsent.isPurposeAllowed(2));
606 | assertTrue(vendorConsent.isPurposeAllowed(3));
607 | assertTrue(vendorConsent.isPurposeAllowed(4));
608 | assertTrue(vendorConsent.isPurposeAllowed(5));
609 | assertFalse(vendorConsent.isPurposeAllowed(6));
610 | assertFalse(vendorConsent.isPurposeAllowed(25));
611 | assertFalse(vendorConsent.isPurposeAllowed(0));
612 | assertTrue(vendorConsent.isVendorAllowed(1));
613 | assertFalse(vendorConsent.isVendorAllowed(5));
614 | assertTrue(vendorConsent.isVendorAllowed(45));
615 | assertFalse(vendorConsent.isVendorAllowed(47));
616 | assertFalse(vendorConsent.isVendorAllowed(146));
617 | assertTrue(vendorConsent.isVendorAllowed(147));
618 |
619 | Set expectedVendorIds = new HashSet<>(Arrays.asList(1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
620 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
621 | 45, 46, 48, 49, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
622 | 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 100, 101, 102, 104,
623 | 105, 108, 109, 110, 111, 112, 113, 114, 115, 118, 120, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132,
624 | 133, 136, 138, 140, 141, 142, 144, 145, 147, 149, 151, 153, 154, 155, 156, 157, 158, 159, 160, 162, 163,
625 | 164, 167, 168, 169, 170, 173, 174, 175, 179, 180, 182, 183, 185, 188, 189, 190, 192, 193, 194, 195, 197,
626 | 198, 200, 203, 205, 208, 209, 210, 211, 213, 215, 217, 224, 225, 226, 227, 229, 232, 234, 235, 237, 240,
627 | 241, 244, 245, 246, 249, 254, 255, 256, 258, 260, 269, 273, 274, 276, 279, 280, 45811));
628 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
629 | }
630 |
631 | @Test
632 | public void testRealString5() {
633 | // Given: known vendor consent string
634 | final String consentString = "BONZt-1ONZt-1AHABBENAO-AAAAHCAEAASABmADYAOAAeA";
635 |
636 | // When: vendor vendorConsent is constructed
637 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
638 |
639 | // Then: values match expectation
640 | assertTrue(vendorConsent.isPurposeAllowed(1));
641 | assertTrue(vendorConsent.isPurposeAllowed(3));
642 | assertTrue(vendorConsent.isVendorAllowed(28));
643 | assertFalse(vendorConsent.isVendorAllowed(1));
644 | assertFalse(vendorConsent.isVendorAllowed(3));
645 | assertTrue(vendorConsent.isVendorAllowed(27));
646 |
647 | Set expectedVendorIds = new HashSet<>(Arrays.asList(9, 25, 27, 28, 30));
648 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
649 | }
650 |
651 | @Test
652 | public void testRealString6() {
653 | // Given: known vendor consent string
654 | final String consentString = "BOOj_adOOj_adABABADEAb-AAAA-iATAAUAA2ADAAMgAgABIAC0AGQANAAcAA-ACKAEwAKIAaABFACQAHIAP0B9A";
655 |
656 | // When: vendor vendorConsent is constructed
657 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(consentString);
658 |
659 | // Then: values match expectation
660 | assertThat(vendorConsent.getVersion(),is(1));
661 | final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
662 | assertThat(vendorConsent.getConsentRecordCreated(),is(LocalDateTime.parse("2018-05-30 08:48:54.100",formatter).toInstant(ZoneOffset.UTC)));
663 | assertThat(vendorConsent.getConsentRecordLastUpdated(),is(LocalDateTime.parse("2018-05-30 08:48:54.100",formatter).toInstant(ZoneOffset.UTC)));
664 | assertThat(vendorConsent.getCmpId(),is(1));
665 | assertThat(vendorConsent.getCmpVersion(),is(1));
666 | assertThat(vendorConsent.getConsentScreen(),is(0));
667 | assertThat(vendorConsent.getConsentLanguage(),is("DE"));
668 | assertThat(vendorConsent.getAllowedPurposeIds(),is(new HashSet<>(Arrays.asList(1,2,3,4,5))));
669 | assertThat(vendorConsent.getMaxVendorId(),is(1000));
670 | assertTrue(vendorConsent.isVendorAllowed(10));
671 | assertTrue(vendorConsent.isVendorAllowed(13));
672 | assertTrue(vendorConsent.isVendorAllowed(24));
673 | assertTrue(vendorConsent.isVendorAllowed(25));
674 | assertTrue(vendorConsent.isVendorAllowed(32));
675 | assertTrue(vendorConsent.isVendorAllowed(36));
676 | assertTrue(vendorConsent.isVendorAllowed(45));
677 | assertTrue(vendorConsent.isVendorAllowed(50));
678 | assertTrue(vendorConsent.isVendorAllowed(52));
679 | assertTrue(vendorConsent.isVendorAllowed(56));
680 | assertTrue(vendorConsent.isVendorAllowed(62));
681 | assertTrue(vendorConsent.isVendorAllowed(69));
682 | assertTrue(vendorConsent.isVendorAllowed(76));
683 | assertTrue(vendorConsent.isVendorAllowed(81));
684 | assertTrue(vendorConsent.isVendorAllowed(104));
685 | assertTrue(vendorConsent.isVendorAllowed(138));
686 | assertTrue(vendorConsent.isVendorAllowed(144));
687 | assertTrue(vendorConsent.isVendorAllowed(228));
688 | assertTrue(vendorConsent.isVendorAllowed(253));
689 | assertTrue(vendorConsent.isVendorAllowed(1000));
690 |
691 | Set expectedVendorIds = new HashSet<>(
692 | Arrays.asList(10, 13, 24, 25, 32, 36, 45, 50, 52, 56, 62, 69, 76, 81, 104, 138, 144, 228, 253, 1000));
693 | assertThat(vendorConsent.getAllowedVendorIds(), is(expectedVendorIds));
694 | }
695 |
696 | @Test(expected = VendorConsentParseException.class)
697 | public void testCorruptString() {
698 | final String corruptConsentString = "BOUy_skOUy_skABABBENA8-AAAAbN7"; // Cut short at 22 bytes
699 |
700 | // When: decoder is called
701 | final VendorConsent vendorConsent = VendorConsentDecoder.fromBase64String(corruptConsentString);
702 |
703 | vendorConsent.isVendorAllowed(10);
704 | fail("VendorConsentParseException expected");
705 | }
706 |
707 | }
708 |
--------------------------------------------------------------------------------