diff --git a/src/main/java/org/prebid/server/cookie/UidsCookieService.java b/src/main/java/org/prebid/server/cookie/UidsCookieService.java index 12f1a7d631e..8bfe414884c 100644 --- a/src/main/java/org/prebid/server/cookie/UidsCookieService.java +++ b/src/main/java/org/prebid/server/cookie/UidsCookieService.java @@ -42,12 +42,6 @@ public class UidsCookieService { private static final int MIN_NUMBER_OF_UID_COOKIES = 1; private static final int MAX_NUMBER_OF_UID_COOKIES = 30; - // {"tempUIDs":{},"optout":false} - private static final int TEMP_UIDS_BASE64_BYTES = "eyJ0ZW1wVUlEcyI6e30sIm9wdG91dCI6ZmFsc2V9".length(); - // "":{"uid":"","expires":"1970-01-01T00:00:00.000000000Z"}, - private static final int UID_BASE64_BYTES = ("IiI6eyJ1aWQiOiIiLCJleHBpcmVzI" - + "joiMTk3MC0wMS0wMVQwMDowMDowMC4wMDAwMDAwMDBaIn0s").length(); - private final String optOutCookieName; private final String optOutCookieValue; private final String hostCookieFamily; @@ -287,22 +281,19 @@ public List splitUidsIntoCookies(UidsCookie uidsCookie) { final Iterator cookieFamilies = cookieFamilyNamesByDescPriorityAndExpiration(uidsCookie); final List splitCookies = new ArrayList<>(); - final int staticCookieDataBytes = makeCookie(COOKIE_NAME, StringUtils.EMPTY, ttlSeconds).encode().length(); - + final int cookieSchemaSize = UidsCookieSize.schemaSize(makeCookie(COOKIE_NAME, StringUtils.EMPTY, ttlSeconds)); String nextCookieFamily = null; + for (int i = 0; i < numberOfUidCookies; i++) { + final int digits = i < 10 ? Integer.signum(i) : 2; + final UidsCookieSize uidsCookieSize = new UidsCookieSize(cookieSchemaSize + digits, maxCookieSizeBytes); - for (int uidsIndex = 0; uidsIndex < numberOfUidCookies; uidsIndex++) { - int actualCookieSize = staticCookieDataBytes + TEMP_UIDS_BASE64_BYTES; final Map tempUids = new HashMap<>(); - while (nextCookieFamily != null || cookieFamilies.hasNext()) { nextCookieFamily = nextCookieFamily == null ? cookieFamilies.next() : nextCookieFamily; - final UidWithExpiry uidWithExpiry = uids.get(nextCookieFamily); - actualCookieSize += UID_BASE64_BYTES - + calculateCookieSize(uidsIndex, nextCookieFamily, uidWithExpiry.getUid()); - if (maxCookieSizeBytes > 0 && actualCookieSize > maxCookieSizeBytes) { + uidsCookieSize.addUid(nextCookieFamily, uidWithExpiry.getUid()); + if (!uidsCookieSize.isValid()) { break; } @@ -310,7 +301,7 @@ public List splitUidsIntoCookies(UidsCookie uidsCookie) { nextCookieFamily = null; } - final String uidsName = uidsIndex == 0 ? COOKIE_NAME : COOKIE_NAME_FORMAT.formatted(uidsIndex + 1); + final String uidsName = i == 0 ? COOKIE_NAME : COOKIE_NAME_FORMAT.formatted(i + 1); if (tempUids.isEmpty()) { splitCookies.add(expiredCookie(uidsName)); @@ -330,14 +321,6 @@ public List splitUidsIntoCookies(UidsCookie uidsCookie) { return splitCookies; } - private static int calculateCookieSize(int uidsIndex, String cookieFamily, String uid) { - final int approximateBase64CookieFamilySize = (int) Math.ceil(cookieFamily.length() * 1.33); - final int approximateBase64UidSize = (int) Math.ceil(uid.length() * 1.33); - final int uidsIndexSize = uidsIndex == 0 ? 0 : 2; - - return uidsIndexSize + approximateBase64CookieFamilySize + approximateBase64UidSize; - } - private Iterator cookieFamilyNamesByDescPriorityAndExpiration(UidsCookie uidsCookie) { return uidsCookie.getCookieUids().getUids().entrySet().stream() .sorted(this::compareCookieFamilyNames) diff --git a/src/main/java/org/prebid/server/cookie/UidsCookieSize.java b/src/main/java/org/prebid/server/cookie/UidsCookieSize.java new file mode 100644 index 00000000000..c98d74297b2 --- /dev/null +++ b/src/main/java/org/prebid/server/cookie/UidsCookieSize.java @@ -0,0 +1,73 @@ +package org.prebid.server.cookie; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.vertx.core.http.Cookie; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.json.ObjectMapperProvider; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class UidsCookieSize { + + // {"tempUIDs":{},"optout":false} + private static final int TEMP_UIDS_BASE64_BYTES = "eyJ0ZW1wVUlEcyI6e30sIm9wdG91dCI6ZmFsc2V9".length(); + private static final int UID_TEMPLATE_BYTES; + + static { + try { + UID_TEMPLATE_BYTES = "\"\":{\"uid\":\"\",\"expires\":\"%s\"}," + .formatted(ObjectMapperProvider.mapper().writeValueAsString( + ZonedDateTime.ofInstant(Instant.ofEpochSecond(0, 1), ZoneId.of("UTC")))) + .length(); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private final int cookieSchemaSize; + private final int maxSize; + private int encodedUidsSize; + + public UidsCookieSize(int cookieSchemaSize, int maxSize) { + this.cookieSchemaSize = cookieSchemaSize; + this.maxSize = maxSize; + + encodedUidsSize = 0; + } + + public static int schemaSize(Cookie cookieSchema) { + return cookieSchema.setValue(StringUtils.EMPTY).encode().length(); + } + + public boolean isValid() { + return maxSize <= 0 || totalSize() <= maxSize; + } + + public int totalSize() { + return cookieSchemaSize + + TEMP_UIDS_BASE64_BYTES + + Base64Size.base64Size(encodedUidsSize); + } + + public void addUid(String cookieFamily, String uid) { + final int uidSize = UID_TEMPLATE_BYTES + cookieFamily.length() + uid.length(); + encodedUidsSize = Base64Size.encodeSize(Base64Size.decodeSize(encodedUidsSize) + uidSize); + } + + private static class Base64Size { + + public static int encodeSize(int size) { + return size / 3 * 4 + size % 3; + } + + public static int decodeSize(int encodedSize) { + return encodedSize / 4 * 3 + encodedSize % 4; + } + + private static int base64Size(int encodedSize) { + return (encodedSize & -4) + 4 * Integer.signum(encodedSize % 4); + } + } +}