├── .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 | ![GitHub release](https://img.shields.io/github/release/InteractiveAdvertisingBureau/Consent-String-SDK-Java.svg) 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 | --------------------------------------------------------------------------------