diff --git a/pom.xml b/pom.xml index 08f9f9e9..dd6664c6 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.12.2 5.11.2 - 10.9.4 + 11.0.0 0.5.10 ${project.version} diff --git a/src/main/java/com/uid2/admin/AdminConst.java b/src/main/java/com/uid2/admin/AdminConst.java index b8ccd338..4bcb97ad 100644 --- a/src/main/java/com/uid2/admin/AdminConst.java +++ b/src/main/java/com/uid2/admin/AdminConst.java @@ -8,4 +8,5 @@ private AdminConst() { public static final String ROLE_OKTA_GROUP_MAP_MAINTAINER = "role_okta_group_map_maintainer"; public static final String ROLE_OKTA_GROUP_MAP_PRIVILEGED = "role_okta_group_map_privileged"; public static final String ROLE_OKTA_GROUP_MAP_SUPER_USER = "role_okta_group_map_super_user"; + public static final String ENABLE_V4_RAW_UID = "enable_v4_raw_uid"; } diff --git a/src/main/java/com/uid2/admin/Main.java b/src/main/java/com/uid2/admin/Main.java index 41dd33a4..91dd9e90 100644 --- a/src/main/java/com/uid2/admin/Main.java +++ b/src/main/java/com/uid2/admin/Main.java @@ -233,7 +233,7 @@ public void run() { WriteLock writeLock = new WriteLock(); KeyHasher keyHasher = new KeyHasher(); IKeypairGenerator keypairGenerator = new SecureKeypairGenerator(); - SaltRotation saltRotation = new SaltRotation(keyGenerator); + SaltRotation saltRotation = new SaltRotation(keyGenerator, config); EncryptionKeyService encryptionKeyService = new EncryptionKeyService( config, auth, writeLock, encryptionKeyStoreWriter, keysetKeyStoreWriter, keyProvider, keysetKeysProvider, adminKeysetProvider, adminKeysetStoreWriter, keyGenerator, clock); KeysetManager keysetManager = new KeysetManager( diff --git a/src/main/java/com/uid2/admin/salt/KeyIdGenerator.java b/src/main/java/com/uid2/admin/salt/KeyIdGenerator.java new file mode 100644 index 00000000..d072fc99 --- /dev/null +++ b/src/main/java/com/uid2/admin/salt/KeyIdGenerator.java @@ -0,0 +1,56 @@ +package com.uid2.admin.salt; + +import com.uid2.shared.model.SaltEntry; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Assumptions: + * - The latest assigned key ids are from the latest updated buckets + * - Key ids from these buckets will always be monotonically increasing (apart from wraparound) as they have not rotated again after last assignment + * + * Intended outcomes of KeyIdGenerator: + * - Key ids are always monotonically increasing, starting from 0 + * - When the last allocated key id reaches 16777215, the next key id will wrap around to 0 + * - Continuing to increment from the highest key id will result in monotonic incrementation of key ids for all newly rotated buckets + **/ +public class KeyIdGenerator { + private static final int MAX_KEY_ID = 16777215; // 3 bytes + private final AtomicInteger nextActiveKeyId; + + public KeyIdGenerator(SaltEntry[] buckets) { + this.nextActiveKeyId = new AtomicInteger(getNextActiveKeyId(buckets)); + } + + private static int getNextActiveKeyId(SaltEntry[] buckets) { + long lastUpdatedTimestampWithKey = Arrays.stream(buckets).filter(s -> s.currentKeySalt() != null).mapToLong(SaltEntry::lastUpdated).max().orElse(0); + if (lastUpdatedTimestampWithKey == 0) return 0; + + int[] lastActiveKeyIdsSorted = Arrays.stream(buckets) + .filter(s -> s.lastUpdated() == lastUpdatedTimestampWithKey && s.currentKeySalt() != null) + .mapToInt(s -> s.currentKeySalt().id()) + .sorted() + .toArray(); + + int highestId = lastActiveKeyIdsSorted[lastActiveKeyIdsSorted.length - 1]; + + int nextKeyId = highestId + 1; + if (nextKeyId <= MAX_KEY_ID) return nextKeyId; + + // Wrapped case - find the last consecutive ID from 0 + for (int i = 0; i < lastActiveKeyIdsSorted.length - 1; i++) { + if (lastActiveKeyIdsSorted[i + 1] - lastActiveKeyIdsSorted[i] > 1) { + return lastActiveKeyIdsSorted[i] + 1; + } + } + + return 0; + } + + public int getNextKeyId() { + return nextActiveKeyId.getAndUpdate(id -> + id + 1 > MAX_KEY_ID ? 0 : id + 1 + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/uid2/admin/salt/SaltRotation.java b/src/main/java/com/uid2/admin/salt/SaltRotation.java index 983c7311..693b24eb 100644 --- a/src/main/java/com/uid2/admin/salt/SaltRotation.java +++ b/src/main/java/com/uid2/admin/salt/SaltRotation.java @@ -1,10 +1,12 @@ package com.uid2.admin.salt; +import com.uid2.admin.AdminConst; import com.uid2.shared.model.SaltEntry; import com.uid2.shared.secret.IKeyGenerator; import com.uid2.shared.store.salt.ISaltProvider.ISaltSnapshot; import com.uid2.shared.store.salt.RotatingSaltProvider.SaltSnapshot; +import io.vertx.core.json.JsonObject; import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,12 +19,15 @@ public class SaltRotation { private static final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis(); private static final double MAX_SALT_PERCENTAGE = 0.8; + private final boolean ENABLE_V4_RAW_UID; private final IKeyGenerator keyGenerator; + private static final Logger LOGGER = LoggerFactory.getLogger(SaltRotation.class); - public SaltRotation(IKeyGenerator keyGenerator) { + public SaltRotation(IKeyGenerator keyGenerator, JsonObject config) { this.keyGenerator = keyGenerator; + this.ENABLE_V4_RAW_UID = config.getBoolean(AdminConst.ENABLE_V4_RAW_UID, false); } public Result rotateSalts( @@ -57,6 +62,7 @@ public Result rotateSalts( logSaltAges("refreshable-salts", targetDate, refreshableSalts); logSaltAges("rotated-salts", targetDate, saltsToRotate); logSaltAges("total-salts", targetDate, Arrays.asList(postRotationSalts)); + logBucketFormatCount(targetDate, postRotationSalts); var nextSnapshot = new SaltSnapshot( nextEffective, @@ -99,45 +105,85 @@ private boolean isRefreshable(TargetDate targetDate, SaltEntry salt) { } private SaltEntry[] rotateSalts(SaltEntry[] oldSalts, List saltsToRotate, TargetDate targetDate) throws Exception { + var keyIdGenerator = new KeyIdGenerator(oldSalts); var saltIdsToRotate = saltsToRotate.stream().map(SaltEntry::id).collect(Collectors.toSet()); var updatedSalts = new SaltEntry[oldSalts.length]; for (int i = 0; i < oldSalts.length; i++) { var shouldRotate = saltIdsToRotate.contains(oldSalts[i].id()); - updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate); + updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate, keyIdGenerator); } return updatedSalts; } - private SaltEntry updateSalt(SaltEntry oldSalt, TargetDate targetDate, boolean shouldRotate) throws Exception { - var currentSalt = shouldRotate ? this.keyGenerator.generateRandomKeyString(32) : oldSalt.currentSalt(); - var lastUpdated = shouldRotate ? targetDate.asEpochMs() : oldSalt.lastUpdated(); - var refreshFrom = calculateRefreshFrom(oldSalt, targetDate); - var previousSalt = calculatePreviousSalt(oldSalt, shouldRotate, targetDate); + private SaltEntry updateSalt(SaltEntry oldBucket, TargetDate targetDate, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception { + var lastUpdated = shouldRotate ? targetDate.asEpochMs() : oldBucket.lastUpdated(); + var refreshFrom = calculateRefreshFrom(oldBucket, targetDate); + var currentSalt = calculateCurrentSalt(oldBucket, shouldRotate); + var previousSalt = calculatePreviousSalt(oldBucket, shouldRotate, targetDate); + var currentKeySalt = calculateCurrentKeySalt(oldBucket, shouldRotate, keyIdGenerator); + var previousKeySalt = calculatePreviousKeySalt(oldBucket,shouldRotate, targetDate); return new SaltEntry( - oldSalt.id(), - oldSalt.hashedId(), + oldBucket.id(), + oldBucket.hashedId(), lastUpdated, currentSalt, refreshFrom, previousSalt, - null, - null + currentKeySalt, + previousKeySalt ); } - private long calculateRefreshFrom(SaltEntry salt, TargetDate targetDate) { - long multiplier = targetDate.saltAgeInDays(salt) / 30 + 1; - return Instant.ofEpochMilli(salt.lastUpdated()).truncatedTo(ChronoUnit.DAYS).toEpochMilli() + (multiplier * THIRTY_DAYS_IN_MS); + private long calculateRefreshFrom(SaltEntry bucket, TargetDate targetDate) { + long multiplier = targetDate.saltAgeInDays(bucket) / 30 + 1; + return Instant.ofEpochMilli(bucket.lastUpdated()).truncatedTo(ChronoUnit.DAYS).toEpochMilli() + (multiplier * THIRTY_DAYS_IN_MS); + } + + private String calculateCurrentSalt(SaltEntry bucket, boolean shouldRotate) throws Exception { + if (shouldRotate) { + if (ENABLE_V4_RAW_UID) { + return null; + } + else { + return this.keyGenerator.generateRandomKeyString(32); + } + } + return bucket.currentSalt(); } - private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, TargetDate targetDate) { + private String calculatePreviousSalt(SaltEntry bucket, boolean shouldRotate, TargetDate targetDate) { if (shouldRotate) { - return salt.currentSalt(); + return bucket.currentSalt(); } - if (targetDate.saltAgeInDays(salt) < 90) { - return salt.previousSalt(); + if (targetDate.saltAgeInDays(bucket) < 90) { + return bucket.previousSalt(); + } + return null; + } + + private SaltEntry.KeyMaterial calculateCurrentKeySalt(SaltEntry bucket, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception { + if (shouldRotate) { + if (ENABLE_V4_RAW_UID) { + return new SaltEntry.KeyMaterial( + keyIdGenerator.getNextKeyId(), + this.keyGenerator.generateRandomKeyString(32), + this.keyGenerator.generateRandomKeyString(32) + ); + } else { + return null; + } + } + return bucket.currentKeySalt(); + } + + private SaltEntry.KeyMaterial calculatePreviousKeySalt(SaltEntry bucket, boolean shouldRotate, TargetDate targetDate) { + if (shouldRotate) { + return bucket.currentKeySalt(); + } + if (targetDate.saltAgeInDays(bucket) < 90) { + return bucket.previousKeySalt(); } return null; } @@ -207,6 +253,24 @@ private void logSaltAges(String saltCountType, TargetDate targetDate, Collection } } + + /** Logging to monitor migration of buckets from salts (old format - v2/v3) to encryption keys (new format - v4) **/ + private void logBucketFormatCount(TargetDate targetDate, SaltEntry[] postRotationBuckets) { + int totalKeys = 0, totalSalts = 0, totalPreviousKeys = 0, totalPreviousSalts = 0; + + for (SaltEntry bucket : postRotationBuckets) { + if (bucket.currentKeySalt() != null) totalKeys++; + if (bucket.currentSalt() != null) totalSalts++; + if (bucket.previousKeySalt() != null) totalPreviousKeys++; + if (bucket.previousSalt() != null) totalPreviousSalts++; + } + + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-current-key-buckets", totalKeys); + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-current-salt-buckets", totalSalts); + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-previous-key-buckets", totalPreviousKeys); + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-previous-salt-buckets", totalPreviousSalts); + } + @Getter public static final class Result { private final SaltSnapshot snapshot; // can be null if new snapshot is not needed diff --git a/src/main/java/com/uid2/admin/store/writer/SaltSerializer.java b/src/main/java/com/uid2/admin/store/writer/SaltSerializer.java index 5aefb506..e753dd99 100644 --- a/src/main/java/com/uid2/admin/store/writer/SaltSerializer.java +++ b/src/main/java/com/uid2/admin/store/writer/SaltSerializer.java @@ -22,7 +22,7 @@ private static void addLine(SaltEntry entry, StringBuilder stringBuilder) { .append(",") .append(lastUpdated % 1000 == 0 ? lastUpdated + 1 : lastUpdated) .append(",") - .append(entry.currentSalt()); + .append(serializeNullable(entry.currentSalt())); stringBuilder.append(","); stringBuilder.append(serializeNullable(entry.refreshFrom())); @@ -30,8 +30,8 @@ private static void addLine(SaltEntry entry, StringBuilder stringBuilder) { stringBuilder.append(","); stringBuilder.append(serializeNullable(entry.previousSalt())); - appendKey(stringBuilder, entry.currentKey()); - appendKey(stringBuilder, entry.previousKey()); + appendKey(stringBuilder, entry.currentKeySalt()); + appendKey(stringBuilder, entry.previousKeySalt()); stringBuilder.append("\n"); } diff --git a/src/main/java/com/uid2/admin/store/writer/SaltStoreWriter.java b/src/main/java/com/uid2/admin/store/writer/SaltStoreWriter.java index f1bc34d8..c9b8ccc7 100644 --- a/src/main/java/com/uid2/admin/store/writer/SaltStoreWriter.java +++ b/src/main/java/com/uid2/admin/store/writer/SaltStoreWriter.java @@ -4,7 +4,6 @@ import com.uid2.admin.store.version.VersionGenerator; import com.uid2.shared.cloud.CloudStorageException; import com.uid2.shared.cloud.TaggableCloudStorage; -import com.uid2.shared.model.SaltEntry; import com.uid2.shared.store.CloudPath; import com.uid2.shared.store.salt.RotatingSaltProvider; import io.vertx.core.json.JsonArray; diff --git a/src/main/resources/localstack/s3/core/salts/metadata.json b/src/main/resources/localstack/s3/core/salts/metadata.json index b20251e8..59b6950b 100644 --- a/src/main/resources/localstack/s3/core/salts/metadata.json +++ b/src/main/resources/localstack/s3/core/salts/metadata.json @@ -11,10 +11,10 @@ "location" : "salts/salts.txt.1670796729291", "size" : 2 },{ - "effective" : 1745907348982, - "expires" : 1766720293000, - "location" : "salts/salts.txt.1745907348982", - "size" : 2 + "effective" : 1755648000000, + "expires" : 1756252800000, + "location" : "salts/salts.txt.1755648000000", + "size" : 1001 } ] } \ No newline at end of file diff --git a/src/main/resources/localstack/s3/core/salts/salts.txt.1755648000000 b/src/main/resources/localstack/s3/core/salts/salts.txt.1755648000000 new file mode 100644 index 00000000..7131cba9 --- /dev/null +++ b/src/main/resources/localstack/s3/core/salts/salts.txt.1755648000000 @@ -0,0 +1,1001 @@ +1000009,1753142400001,salt,1755734400000,prev_salt,,,,,, +1031010,1745446352107,salt,1755734400000,,,,,,, +1062031,1745446352107,salt,1755734400000,,,,,,, +1093279,1740262327470,salt,1755734400000,,,,,,, +1124331,1747958400001,salt,1755734400000,prev_salt,,,,,, +1156119,1750550400001,salt,1755734400000,prev_salt,,,,,, +1186433,1732486496031,salt,1755734400000,,,,,,, +1217165,1729894369359,salt,1755734400000,,,,,,, +1247308,1753142400001,salt,1755734400000,prev_salt,,,,,, +1277860,1747958400001,salt,1755734400000,prev_salt,,,,,, +1310079,1729894369359,salt,1755734400000,,,,,,, +1342189,1747958400001,salt,1755734400000,prev_salt,,,,,, +1373221,1747958400001,salt,1755734400000,prev_salt,,,,,, +1405031,1740262327470,salt,1755734400000,,,,,,, +1436581,1745446352107,salt,1755734400000,,,,,,, +1469349,1737670329961,salt,1755734400000,,,,,,, +1500393,1735078341236,salt,1755734400000,,,,,,, +1532203,1747958400001,salt,1755734400000,prev_salt,,,,,, +1562380,1740262327470,salt,1755734400000,,,,,,, +1594264,1737670329961,salt,1755734400000,,,,,,, +1627008,1745446352107,salt,1755734400000,,,,,,, +1658850,1747958400001,salt,1755734400000,prev_salt,,,,,, +1689573,1722118330445,salt,1755734400000,,,,,,, +1721531,1745446352107,salt,1755734400000,,,,,,, +1752213,1747958400001,salt,1755734400000,prev_salt,,,,,, +1784315,1732486496031,salt,1755734400000,,,,,,, +1815728,1745446352107,salt,1755734400000,,,,,,, +1845685,1737670329961,salt,1755734400000,,,,,,, +1877345,1737670329961,salt,1755734400000,,,,,,, +1908345,1745446352107,salt,1755734400000,,,,,,, +1938735,1724710364338,salt,1755734400000,,,,,,, +1970338,1753142400001,salt,1755734400000,prev_salt,,,,,, +2000052,1745446352107,salt,1755734400000,,,,,,, +2031136,1735078341236,salt,1755734400000,,,,,,, +1014342,1732572727782,salt,1755820800000,,,,,,, +1044315,1742940726705,salt,1755820800000,,,,,,, +1075200,1742940726705,salt,1755820800000,,,,,,, +1104919,1727388756593,salt,1755820800000,,,,,,, +1136266,1735164729690,salt,1755820800000,,,,,,, +1166880,1745532777048,salt,1755820800000,,,,,,, +1197247,1748044800001,salt,1755820800000,prev_salt,,,,,, +1227178,1732572727782,salt,1755820800000,,,,,,, +1259166,1753228800001,salt,1755820800000,prev_salt,,,,,, +1291127,1735164729690,salt,1755820800000,,,,,,, +1321494,1737756755192,salt,1755820800000,,,,,,, +1352328,1750636800001,salt,1755820800000,prev_salt,,,,,, +1385236,1729980751957,salt,1755820800000,,,,,,, +1417268,1724796800903,salt,1755820800000,,,,,,, +1447092,1742940726705,salt,1755820800000,,,,,,, +1478902,1735164729690,salt,1755820800000,,,,,,, +1509074,1748044800001,salt,1755820800000,prev_salt,,,,,, +1539745,1732572727782,salt,1755820800000,,,,,,, +1571717,1742940726705,salt,1755820800000,,,,,,, +1602312,1742940726705,salt,1755820800000,,,,,,, +1634436,1729980751957,salt,1755820800000,,,,,,, +1666022,1748044800001,salt,1755820800000,prev_salt,,,,,, +1697275,1750636800001,salt,1755820800000,prev_salt,,,,,, +1729893,1732572727782,salt,1755820800000,,,,,,, +1760678,1742940726705,salt,1755820800000,,,,,,, +1791927,1727388756593,salt,1755820800000,,,,,,, +1822937,1727388756593,salt,1755820800000,,,,,,, +1853417,1742940726705,salt,1755820800000,,,,,,, +1883885,1729980751957,salt,1755820800000,,,,,,, +1916248,1750636800001,salt,1755820800000,prev_salt,,,,,, +1946286,1748044800001,salt,1755820800000,prev_salt,,,,,, +1975847,1753228800001,salt,1755820800000,prev_salt,,,,,, +2006185,1727388756593,salt,1755820800000,,,,,,, +2036771,1748044800001,salt,1755820800000,prev_salt,,,,,, +1019838,1745619165396,salt,1755907200000,,,,,,, +1050299,1727475131133,salt,1755907200000,,,,,,, +1078552,1724883154616,salt,1755907200000,,,,,,, +1108945,1735251129535,salt,1755907200000,,,,,,, +1140378,1750723200001,salt,1755907200000,prev_salt,,,,,, +1171300,1732659149214,salt,1755907200000,,,,,,, +1202791,1737843127448,salt,1755907200000,,,,,,, +1232548,1743027129450,salt,1755907200000,,,,,,, +1264388,1748131200001,salt,1755907200000,prev_salt,,,,,, +1295594,1737843127448,salt,1755907200000,,,,,,, +1326108,1748131200001,salt,1755907200000,prev_salt,,,,,, +1356927,1735251129535,salt,1755907200000,,,,,,, +1387104,1740435138167,salt,1755907200000,,,,,,, +1419219,1745619165396,salt,1755907200000,,,,,,, +1449393,1750723200001,salt,1755907200000,prev_salt,,,,,, +1480024,1743027129450,salt,1755907200000,,,,,,, +1509715,1737843127448,salt,1755907200000,,,,,,, +1541231,1735251129535,salt,1755907200000,,,,,,, +1570156,1745619165396,salt,1755907200000,,,,,,, +1601188,1724883154616,salt,1755907200000,,,,,,, +1632564,1737843127448,salt,1755907200000,,,,,,, +1662835,1730067137348,salt,1755907200000,,,,,,, +1692631,1748131200001,salt,1755907200000,prev_salt,,,,,, +1723382,1743027129450,salt,1755907200000,,,,,,, +1753461,1737843127448,salt,1755907200000,,,,,,, +1782624,1740435138167,salt,1755907200000,,,,,,, +1815138,1727475131133,salt,1755907200000,,,,,,, +1846436,1724883154616,salt,1755907200000,,,,,,, +1877078,1724883154616,salt,1755907200000,,,,,,, +1907710,1743027129450,salt,1755907200000,,,,,,, +1938663,1743027129450,salt,1755907200000,,,,,,, +1971495,1737843127448,salt,1755907200000,,,,,,, +2003123,1732659149214,salt,1755907200000,,,,,,, +2034621,1722291139605,salt,1755907200000,,,,,,, +1017017,1748217600001,salt,1755993600000,prev_salt,,,,,, +1047661,1735337541640,salt,1755993600000,,,,,,, +1077962,1735337541640,salt,1755993600000,,,,,,, +1107377,1750809600001,salt,1755993600000,prev_salt,,,,,, +1136665,1727561565057,salt,1755993600000,,,,,,, +1167988,1745705627824,salt,1755993600000,,,,,,, +1196690,1748217600001,salt,1755993600000,prev_salt,,,,,, +1227770,1743113590803,salt,1755993600000,,,,,,, +1256757,1730153535605,salt,1755993600000,,,,,,, +1288396,1750809600001,salt,1755993600000,prev_salt,,,,,, +1318289,1745705627824,salt,1755993600000,,,,,,, +1349494,1722377572674,salt,1755993600000,,,,,,, +1380118,1753401600001,salt,1755993600000,prev_salt,,,,,, +1411339,1743113590803,salt,1755993600000,,,,,,, +1441638,1743113590803,salt,1755993600000,,,,,,, +1472848,1735337541640,salt,1755993600000,,,,,,, +1502371,1740521676392,salt,1755993600000,,,,,,, +1532922,1750809600001,salt,1755993600000,prev_salt,,,,,, +1562590,1743113590803,salt,1755993600000,,,,,,, +1591305,1745705627824,salt,1755993600000,,,,,,, +1621529,1730153535605,salt,1755993600000,,,,,,, +1650427,1743113590803,salt,1755993600000,,,,,,, +1680456,1724969537042,salt,1755993600000,,,,,,, +1710949,1743113590803,salt,1755993600000,,,,,,, +1741947,1753401600001,salt,1755993600000,prev_salt,,,,,, +1772138,1750809600001,salt,1755993600000,prev_salt,,,,,, +1801693,1740521676392,salt,1755993600000,,,,,,, +1833969,1740521676392,salt,1755993600000,,,,,,, +1864372,1724969537042,salt,1755993600000,,,,,,, +1895297,1750809600001,salt,1755993600000,prev_salt,,,,,, +1926316,1724969537042,salt,1755993600000,,,,,,, +1956376,1745705627824,salt,1755993600000,,,,,,, +1987622,1753401600001,salt,1755993600000,prev_salt,,,,,, +2019076,1730153535605,salt,1755993600000,,,,,,, +1001849,1748304000001,salt,1756080000000,prev_salt,,,,,, +1032697,1722463929018,salt,1756080000000,,,,,,, +1063524,1735423927776,salt,1756080000000,,,,,,, +1093979,1745791959015,salt,1756080000000,,,,,,, +1122959,1730239938681,salt,1756080000000,,,,,,, +1152095,1745791959015,salt,1756080000000,,,,,,, +1181908,1748304000001,salt,1756080000000,prev_salt,,,,,, +1210941,1725055938469,salt,1756080000000,,,,,,, +1240463,1740607927469,salt,1756080000000,,,,,,, +1269318,1722463929018,salt,1756080000000,,,,,,, +1299495,1738016033973,salt,1756080000000,,,,,,, +1328575,1727647966566,salt,1756080000000,,,,,,, +1357190,1743199927581,salt,1756080000000,,,,,,, +1387863,1735423927776,salt,1756080000000,,,,,,, +1417600,1732831936095,salt,1756080000000,,,,,,, +1448832,1722463929018,salt,1756080000000,,,,,,, +1478595,1740607927469,salt,1756080000000,,,,,,, +1508888,1753488000001,salt,1756080000000,prev_salt,,,,,, +1539607,1748304000001,salt,1756080000000,prev_salt,,,,,, +1569380,1743199927581,salt,1756080000000,,,,,,, +1598917,1722463929018,salt,1756080000000,,,,,,, +1629116,1735423927776,salt,1756080000000,,,,,,, +1657350,1730239938681,salt,1756080000000,,,,,,, +1687205,1725055938469,salt,1756080000000,,,,,,, +1717999,1740607927469,salt,1756080000000,,,,,,, +1749225,1748304000001,salt,1756080000000,prev_salt,,,,,, +1778616,1745791959015,salt,1756080000000,,,,,,, +1808248,1735423927776,salt,1756080000000,,,,,,, +1838588,1743199927581,salt,1756080000000,,,,,,, +1867951,1743199927581,salt,1756080000000,,,,,,, +1897779,1753488000001,salt,1756080000000,prev_salt,,,,,, +1928642,1748304000001,salt,1756080000000,prev_salt,,,,,, +1959342,1743199927581,salt,1756080000000,,,,,,, +1988782,1732831936095,salt,1756080000000,,,,,,, +2017409,1748304000001,salt,1756080000000,prev_salt,,,,,, +2047499,1732831936095,salt,1756080000000,,,,,,, +1028896,1725142359660,salt,1756166400000,,,,,,, +1057662,1725142359660,salt,1756166400000,,,,,,, +1088503,1745878389020,salt,1756166400000,,,,,,, +1119316,1743286328716,salt,1756166400000,,,,,,, +1150463,1743286328716,salt,1756166400000,,,,,,, +1182160,1750982400001,salt,1756166400000,prev_salt,,,,,, +1211745,1722550384198,salt,1756166400000,,,,,,, +1240507,1748390400001,salt,1756166400000,prev_salt,,,,,, +1271659,1725142359660,salt,1756166400000,,,,,,, +1300258,1722550384198,salt,1756166400000,,,,,,, +1330331,1725142359660,salt,1756166400000,,,,,,, +1360582,1743286328716,salt,1756166400000,,,,,,, +1390687,1735510346011,salt,1756166400000,,,,,,, +1420325,1745878389020,salt,1756166400000,,,,,,, +1451269,1732918343327,salt,1756166400000,,,,,,, +1480533,1732918343327,salt,1756166400000,,,,,,, +1509460,1750982400001,salt,1756166400000,prev_salt,,,,,, +1538564,1738102327052,salt,1756166400000,,,,,,, +1567725,1753574400001,salt,1756166400000,prev_salt,,,,,, +1597572,1745878389020,salt,1756166400000,,,,,,, +1625595,1722550384198,salt,1756166400000,,,,,,, +1656641,1750982400001,salt,1756166400000,prev_salt,,,,,, +1685417,1745878389020,salt,1756166400000,,,,,,, +1715101,1753574400001,salt,1756166400000,prev_salt,,,,,, +1744291,1727734328854,salt,1756166400000,,,,,,, +1775009,1753574400001,salt,1756166400000,prev_salt,,,,,, +1805925,1753574400001,salt,1756166400000,prev_salt,,,,,, +1836747,1743286328716,salt,1756166400000,,,,,,, +1864674,1732918343327,salt,1756166400000,,,,,,, +1894755,1735510346011,salt,1756166400000,,,,,,, +1924153,1745878389020,salt,1756166400000,,,,,,, +1955081,1727734328854,salt,1756166400000,,,,,,, +1985091,1725142359660,salt,1756166400000,,,,,,, +2014557,1727734328854,salt,1756166400000,,,,,,, +2044520,1750982400001,salt,1756166400000,prev_salt,,,,,, +1025268,1751068800001,salt,1756252800000,prev_salt,,,,,, +1054666,1743372726987,salt,1756252800000,,,,,,, +1084319,1740780731234,salt,1756252800000,,,,,,, +1114103,1745964726390,salt,1756252800000,,,,,,, +1142113,1745964726390,salt,1756252800000,,,,,,, +1172179,1740780731234,salt,1756252800000,,,,,,, +1202405,1727820749670,salt,1756252800000,,,,,,, +1231377,1730412786526,salt,1756252800000,,,,,,, +1260429,1745964726390,salt,1756252800000,,,,,,, +1289779,1740780731234,salt,1756252800000,,,,,,, +1319870,1725228729472,salt,1756252800000,,,,,,, +1348521,1733004745531,salt,1756252800000,,,,,,, +1378004,1745964726390,salt,1756252800000,,,,,,, +1408014,1740780731234,salt,1756252800000,,,,,,, +1437267,1733004745531,salt,1756252800000,,,,,,, +1468354,1743372726987,salt,1756252800000,,,,,,, +1496509,1753660800001,salt,1756252800000,prev_salt,,,,,, +1526241,1751068800001,salt,1756252800000,prev_salt,,,,,, +1555933,1748476800001,salt,1756252800000,prev_salt,,,,,, +1585584,1722636742629,salt,1756252800000,,,,,,, +1616836,1743372726987,salt,1756252800000,,,,,,, +1645003,1722636742629,salt,1756252800000,,,,,,, +1674472,1735596740213,salt,1756252800000,,,,,,, +1704967,1748476800001,salt,1756252800000,prev_salt,,,,,, +1733006,1738188728150,salt,1756252800000,,,,,,, +1761849,1738188728150,salt,1756252800000,,,,,,, +1791699,1751068800001,salt,1756252800000,prev_salt,,,,,, +1820071,1725228729472,salt,1756252800000,,,,,,, +1849090,1748476800001,salt,1756252800000,prev_salt,,,,,, +1877201,1722636742629,salt,1756252800000,,,,,,, +1906216,1725228729472,salt,1756252800000,,,,,,, +1936008,1733004745531,salt,1756252800000,,,,,,, +1965431,1720044735782,salt,1756252800000,,,,,,, +1995273,1733004745531,salt,1756252800000,,,,,,, +2024836,1735596740213,salt,1756252800000,,,,,,, +1006287,1753747200001,salt,1756339200000,prev_salt,,,,,, +1038574,1740867126642,salt,1756339200000,,,,,,, +1072019,1740867126642,salt,1756339200000,,,,,,, +1103410,1753747200001,salt,1756339200000,prev_salt,,,,,, +1134825,1753747200001,salt,1756339200000,prev_salt,,,,,, +1166682,1735683141707,salt,1756339200000,,,,,,, +1197902,1746051181524,salt,1756339200000,,,,,,, +1230165,1727907141720,salt,1756339200000,,,,,,, +1261853,1733091157815,salt,1756339200000,,,,,,, +1295066,1733091157815,salt,1756339200000,,,,,,, +1326429,1743459127831,salt,1756339200000,,,,,,, +1357219,1748563200001,salt,1756339200000,prev_salt,,,,,, +1389797,1740867126642,salt,1756339200000,,,,,,, +1422620,1730499228767,salt,1756339200000,,,,,,, +1453909,1733091157815,salt,1756339200000,,,,,,, +1486316,1740867126642,salt,1756339200000,,,,,,, +1519195,1743459127831,salt,1756339200000,,,,,,, +1550178,1730499228767,salt,1756339200000,,,,,,, +1580101,1746051181524,salt,1756339200000,,,,,,, +1612373,1751155200001,salt,1756339200000,prev_salt,,,,,, +1643620,1740867126642,salt,1756339200000,,,,,,, +1675742,1725315138098,salt,1756339200000,,,,,,, +1708131,1746051181524,salt,1756339200000,,,,,,, +1740039,1746051181524,salt,1756339200000,,,,,,, +1771672,1733091157815,salt,1756339200000,,,,,,, +1803903,1730499228767,salt,1756339200000,,,,,,, +1835086,1740867126642,salt,1756339200000,,,,,,, +1866353,1743459127831,salt,1756339200000,,,,,,, +1899148,1725315138098,salt,1756339200000,,,,,,, +1931970,1746051181524,salt,1756339200000,,,,,,, +1964554,1753747200001,salt,1756339200000,prev_salt,,,,,, +1995783,1730499228767,salt,1756339200000,,,,,,, +2026903,1735683141707,salt,1756339200000,,,,,,, +1009907,1751241600001,salt,1756425600000,prev_salt,,,,,, +1039979,1735769540406,salt,1756425600000,,,,,,, +1072116,1743545527584,salt,1756425600000,,,,,,, +1102088,1730585581706,salt,1756425600000,,,,,,, +1133122,1740953528586,salt,1756425600000,,,,,,, +1164243,1748649600001,salt,1756425600000,prev_salt,,,,,, +1196467,1727993552169,salt,1756425600000,,,,,,, +1230199,1746137540814,salt,1756425600000,,,,,,, +1263372,1748649600001,salt,1756425600000,prev_salt,,,,,, +1295455,1751241600001,salt,1756425600000,prev_salt,,,,,, +1327039,1727993552169,salt,1756425600000,,,,,,, +1358579,1725401528670,salt,1756425600000,,,,,,, +1390796,1743545527584,salt,1756425600000,,,,,,, +1421370,1733177609984,salt,1756425600000,,,,,,, +1453361,1743545527584,salt,1756425600000,,,,,,, +1486560,1743545527584,salt,1756425600000,,,,,,, +1519175,1751241600001,salt,1756425600000,prev_salt,,,,,, +1551647,1725401528670,salt,1756425600000,,,,,,, +1584539,1743545527584,salt,1756425600000,,,,,,, +1615988,1738361528184,salt,1756425600000,,,,,,, +1648162,1725401528670,salt,1756425600000,,,,,,, +1679727,1725401528670,salt,1756425600000,,,,,,, +1712235,1727993552169,salt,1756425600000,,,,,,, +1744792,1748649600001,salt,1756425600000,prev_salt,,,,,, +1775700,1748649600001,salt,1756425600000,prev_salt,,,,,, +1808036,1735769540406,salt,1756425600000,,,,,,, +1838965,1753833600001,salt,1756425600000,prev_salt,,,,,, +1871258,1727993552169,salt,1756425600000,,,,,,, +1901232,1753833600001,salt,1756425600000,prev_salt,,,,,, +1933740,1738361528184,salt,1756425600000,,,,,,, +1965649,1753833600001,salt,1756425600000,prev_salt,,,,,, +1996168,1738361528184,salt,1756425600000,,,,,,, +2029823,1748649600001,salt,1756425600000,prev_salt,,,,,, +1012660,1746223942119,salt,1756512000000,,,,,,, +1045246,1743631928737,salt,1756512000000,,,,,,, +1077528,1751328000001,salt,1756512000000,prev_salt,,,,,, +1108794,1730671978382,salt,1756512000000,,,,,,, +1139633,1751328000001,salt,1756512000000,prev_salt,,,,,, +1171193,1748736000001,salt,1756512000000,prev_salt,,,,,, +1203700,1751328000001,salt,1756512000000,prev_salt,,,,,, +1235502,1728079928546,salt,1756512000000,,,,,,, +1267874,1730671978382,salt,1756512000000,,,,,,, +1298995,1735855966506,salt,1756512000000,,,,,,, +1328732,1735855966506,salt,1756512000000,,,,,,, +1359532,1748736000001,salt,1756512000000,prev_salt,,,,,, +1391606,1738447926535,salt,1756512000000,,,,,,, +1423466,1735855966506,salt,1756512000000,,,,,,, +1455967,1743631928737,salt,1756512000000,,,,,,, +1487686,1730671978382,salt,1756512000000,,,,,,, +1518253,1738447926535,salt,1756512000000,,,,,,, +1551654,1725487951930,salt,1756512000000,,,,,,, +1583661,1728079928546,salt,1756512000000,,,,,,, +1616118,1753920000001,salt,1756512000000,prev_salt,,,,,, +1646680,1730671978382,salt,1756512000000,,,,,,, +1678758,1733263928976,salt,1756512000000,,,,,,, +1712105,1728079928546,salt,1756512000000,,,,,,, +1743270,1748736000001,salt,1756512000000,prev_salt,,,,,, +1774763,1741039982634,salt,1756512000000,,,,,,, +1807321,1746223942119,salt,1756512000000,,,,,,, +1837888,1746223942119,salt,1756512000000,,,,,,, +1871604,1728079928546,salt,1756512000000,,,,,,, +1903995,1728079928546,salt,1756512000000,,,,,,, +1935849,1751328000001,salt,1756512000000,prev_salt,,,,,, +1968634,1751328000001,salt,1756512000000,prev_salt,,,,,, +2000984,1741039982634,salt,1756512000000,,,,,,, +2033065,1751328000001,salt,1756512000000,prev_salt,,,,,, +1014851,1741126434346,salt,1756598400000,,,,,,, +1046475,1733350366708,salt,1756598400000,,,,,,, +1079192,1751414400001,salt,1756598400000,prev_salt,,,,,, +1110734,1728166351967,salt,1756598400000,,,,,,, +1143811,1735942338132,salt,1756598400000,,,,,,, +1173213,1751414400001,salt,1756598400000,prev_salt,,,,,, +1204237,1728166351967,salt,1756598400000,,,,,,, +1235745,1728166351967,salt,1756598400000,,,,,,, +1267703,1754006400001,salt,1756598400000,prev_salt,,,,,, +1300681,1735942338132,salt,1756598400000,,,,,,, +1334300,1746310365231,salt,1756598400000,,,,,,, +1364869,1741126434346,salt,1756598400000,,,,,,, +1395868,1751414400001,salt,1756598400000,prev_salt,,,,,, +1428381,1746310365231,salt,1756598400000,,,,,,, +1458941,1728166351967,salt,1756598400000,,,,,,, +1490897,1748822400001,salt,1756598400000,prev_salt,,,,,, +1522983,1735942338132,salt,1756598400000,,,,,,, +1554300,1733350366708,salt,1756598400000,,,,,,, +1587725,1725574328739,salt,1756598400000,,,,,,, +1620287,1735942338132,salt,1756598400000,,,,,,, +1652633,1746310365231,salt,1756598400000,,,,,,, +1684483,1730758338672,salt,1756598400000,,,,,,, +1717030,1738534328052,salt,1756598400000,,,,,,, +1748538,1741126434346,salt,1756598400000,,,,,,, +1780531,1733350366708,salt,1756598400000,,,,,,, +1812767,1733350366708,salt,1756598400000,,,,,,, +1845152,1738534328052,salt,1756598400000,,,,,,, +1876831,1754006400001,salt,1756598400000,prev_salt,,,,,, +1908285,1728166351967,salt,1756598400000,,,,,,, +1939748,1748822400001,salt,1756598400000,prev_salt,,,,,, +1971440,1741126434346,salt,1756598400000,,,,,,, +2003201,1738534328052,salt,1756598400000,,,,,,, +2035285,1754006400001,salt,1756598400000,prev_salt,,,,,, +1019126,1736028729008,salt,1756684800000,,,,,,, +1052643,1738620726611,salt,1756684800000,,,,,,, +1085667,1741212810916,salt,1756684800000,,,,,,, +1116702,1728252733719,salt,1756684800000,,,,,,, +1148291,1748908800001,salt,1756684800000,prev_salt,,,,,, +1179163,1736028729008,salt,1756684800000,,,,,,, +1210967,1738620726611,salt,1756684800000,,,,,,, +1243321,1743804726397,salt,1756684800000,,,,,,, +1273711,1748908800001,salt,1756684800000,prev_salt,,,,,, +1305748,1738620726611,salt,1756684800000,,,,,,, +1336349,1743804726397,salt,1756684800000,,,,,,, +1367440,1746396752413,salt,1756684800000,,,,,,, +1398411,1746396752413,salt,1756684800000,,,,,,, +1431813,1736028729008,salt,1756684800000,,,,,,, +1464251,1751500800001,salt,1756684800000,prev_salt,,,,,, +1495455,1746396752413,salt,1756684800000,,,,,,, +1526901,1741212810916,salt,1756684800000,,,,,,, +1559969,1736028729008,salt,1756684800000,,,,,,, +1591684,1748908800001,salt,1756684800000,prev_salt,,,,,, +1622928,1754092800001,salt,1756684800000,prev_salt,,,,,, +1655656,1733436743475,salt,1756684800000,,,,,,, +1688061,1754092800001,salt,1756684800000,prev_salt,,,,,, +1720454,1730844749988,salt,1756684800000,,,,,,, +1753832,1743804726397,salt,1756684800000,,,,,,, +1786046,1725660738910,salt,1756684800000,,,,,,, +1815884,1751500800001,salt,1756684800000,prev_salt,,,,,, +1848183,1751500800001,salt,1756684800000,prev_salt,,,,,, +1881177,1730844749988,salt,1756684800000,,,,,,, +1912263,1754092800001,salt,1756684800000,prev_salt,,,,,, +1944262,1743804726397,salt,1756684800000,,,,,,, +1975116,1730844749988,salt,1756684800000,,,,,,, +2007196,1728252733719,salt,1756684800000,,,,,,, +2037737,1743804726397,salt,1756684800000,,,,,,, +1021649,1738707127784,salt,1756771200000,,,,,,, +1052354,1728339137667,salt,1756771200000,,,,,,, +1083916,1730931200144,salt,1756771200000,,,,,,, +1116651,1730931200144,salt,1756771200000,,,,,,, +1149792,1736115128134,salt,1756771200000,,,,,,, +1182146,1728339137667,salt,1756771200000,,,,,,, +1214349,1738707127784,salt,1756771200000,,,,,,, +1246982,1738707127784,salt,1756771200000,,,,,,, +1277696,1728339137667,salt,1756771200000,,,,,,, +1308923,1730931200144,salt,1756771200000,,,,,,, +1341622,1725747131193,salt,1756771200000,,,,,,, +1373209,1748995200001,salt,1756771200000,prev_salt,,,,,, +1405793,1730931200144,salt,1756771200000,,,,,,, +1438696,1730931200144,salt,1756771200000,,,,,,, +1469688,1725747131193,salt,1756771200000,,,,,,, +1500426,1748995200001,salt,1756771200000,prev_salt,,,,,, +1533649,1743891181656,salt,1756771200000,,,,,,, +1565188,1751587200001,salt,1756771200000,prev_salt,,,,,, +1597186,1748995200001,salt,1756771200000,prev_salt,,,,,, +1627687,1743891181656,salt,1756771200000,,,,,,, +1658950,1748995200001,salt,1756771200000,prev_salt,,,,,, +1689478,1748995200001,salt,1756771200000,prev_salt,,,,,, +1722114,1725747131193,salt,1756771200000,,,,,,, +1755437,1733523151995,salt,1756771200000,,,,,,, +1785706,1728339137667,salt,1756771200000,,,,,,, +1818108,1730931200144,salt,1756771200000,,,,,,, +1850074,1741299128324,salt,1756771200000,,,,,,, +1882649,1730931200144,salt,1756771200000,,,,,,, +1913832,1748995200001,salt,1756771200000,prev_salt,,,,,, +1946579,1728339137667,salt,1756771200000,,,,,,, +1978478,1754179200001,salt,1756771200000,prev_salt,,,,,, +2008935,1730931200144,salt,1756771200000,,,,,,, +2040261,1738707127784,salt,1756771200000,,,,,,, +1024610,1738793548325,salt,1756857600000,,,,,,, +1056956,1754265600001,salt,1756857600000,prev_salt,,,,,, +1089766,1731017530961,salt,1756857600000,,,,,,, +1122603,1728425550950,salt,1756857600000,,,,,,, +1155169,1754265600001,salt,1756857600000,prev_salt,,,,,, +1187451,1731017530961,salt,1756857600000,,,,,,, +1219390,1749081600001,salt,1756857600000,prev_salt,,,,,, +1251143,1728425550950,salt,1756857600000,,,,,,, +1281707,1741385530127,salt,1756857600000,,,,,,, +1313221,1743977525973,salt,1756857600000,,,,,,, +1344012,1728425550950,salt,1756857600000,,,,,,, +1375578,1736201586214,salt,1756857600000,,,,,,, +1407732,1751673600001,salt,1756857600000,prev_salt,,,,,, +1437911,1749081600001,salt,1756857600000,prev_salt,,,,,, +1470674,1754265600001,salt,1756857600000,prev_salt,,,,,, +1502743,1746569538833,salt,1756857600000,,,,,,, +1534881,1725833537439,salt,1756857600000,,,,,,, +1566763,1754265600001,salt,1756857600000,prev_salt,,,,,, +1597949,1751673600001,salt,1756857600000,prev_salt,,,,,, +1628639,1749081600001,salt,1756857600000,prev_salt,,,,,, +1660484,1754265600001,salt,1756857600000,prev_salt,,,,,, +1692956,1731017530961,salt,1756857600000,,,,,,, +1725167,1733609547054,salt,1756857600000,,,,,,, +1757752,1725833537439,salt,1756857600000,,,,,,, +1789329,1728425550950,salt,1756857600000,,,,,,, +1822287,1731017530961,salt,1756857600000,,,,,,, +1853822,1738793548325,salt,1756857600000,,,,,,, +1885097,1741385530127,salt,1756857600000,,,,,,, +1916704,1754265600001,salt,1756857600000,prev_salt,,,,,, +1947547,1725833537439,salt,1756857600000,,,,,,, +1978818,1733609547054,salt,1756857600000,,,,,,, +2010577,1746569538833,salt,1756857600000,,,,,,, +2043331,1738793548325,salt,1756857600000,,,,,,, +1026111,1751760000001,salt,1756944000000,prev_salt,,,,,, +1059182,1749168000001,salt,1756944000000,prev_salt,,,,,, +1089989,1728511940607,salt,1756944000000,,,,,,, +1120985,1749168000001,salt,1756944000000,prev_salt,,,,,, +1154789,1736287926283,salt,1756944000000,,,,,,, +1186468,1744064028453,salt,1756944000000,,,,,,, +1218006,1754352000001,salt,1756944000000,prev_salt,,,,,, +1251590,1744064028453,salt,1756944000000,,,,,,, +1282228,1751760000001,salt,1756944000000,prev_salt,,,,,, +1314082,1754352000001,salt,1756944000000,prev_salt,,,,,, +1346703,1733695946587,salt,1756944000000,,,,,,, +1378816,1725919928572,salt,1756944000000,,,,,,, +1409170,1744064028453,salt,1756944000000,,,,,,, +1442498,1749168000001,salt,1756944000000,prev_salt,,,,,, +1473578,1738879927589,salt,1756944000000,,,,,,, +1506091,1733695946587,salt,1756944000000,,,,,,, +1538340,1749168000001,salt,1756944000000,prev_salt,,,,,, +1568250,1744064028453,salt,1756944000000,,,,,,, +1600996,1749168000001,salt,1756944000000,prev_salt,,,,,, +1632026,1736287926283,salt,1756944000000,,,,,,, +1664425,1738879927589,salt,1756944000000,,,,,,, +1695871,1754352000001,salt,1756944000000,prev_salt,,,,,, +1727639,1746655929510,salt,1756944000000,,,,,,, +1759754,1749168000001,salt,1756944000000,prev_salt,,,,,, +1791547,1733695946587,salt,1756944000000,,,,,,, +1822653,1746655929510,salt,1756944000000,,,,,,, +1854798,1731103950340,salt,1756944000000,,,,,,, +1886031,1754352000001,salt,1756944000000,prev_salt,,,,,, +1917753,1744064028453,salt,1756944000000,,,,,,, +1949880,1744064028453,salt,1756944000000,,,,,,, +1980850,1754352000001,salt,1756944000000,prev_salt,,,,,, +2012703,1725919928572,salt,1756944000000,,,,,,, +2046248,1754352000001,salt,1756944000000,prev_salt,,,,,, +1029878,1726006336822,salt,1757030400000,,,,,,, +1060905,1738966330431,salt,1757030400000,,,,,,, +1092483,1728598375924,salt,1757030400000,,,,,,, +1124265,1751846400001,salt,1757030400000,prev_salt,,,,,, +1156490,1741558328198,salt,1757030400000,,,,,,, +1187089,1741558328198,salt,1757030400000,,,,,,, +1218920,1728598375924,salt,1757030400000,,,,,,, +1248857,1754438400001,salt,1757030400000,prev_salt,,,,,, +1281073,1746742356122,salt,1757030400000,,,,,,, +1310844,1728598375924,salt,1757030400000,,,,,,, +1343048,1741558328198,salt,1757030400000,,,,,,, +1373116,1726006336822,salt,1757030400000,,,,,,, +1405060,1741558328198,salt,1757030400000,,,,,,, +1435346,1726006336822,salt,1757030400000,,,,,,, +1466837,1746742356122,salt,1757030400000,,,,,,, +1500256,1754438400001,salt,1757030400000,prev_salt,,,,,, +1531370,1744150347936,salt,1757030400000,,,,,,, +1564920,1728598375924,salt,1757030400000,,,,,,, +1596722,1726006336822,salt,1757030400000,,,,,,, +1628904,1749254400001,salt,1757030400000,prev_salt,,,,,, +1660138,1736374327563,salt,1757030400000,,,,,,, +1693078,1741558328198,salt,1757030400000,,,,,,, +1724162,1754438400001,salt,1757030400000,prev_salt,,,,,, +1757123,1726006336822,salt,1757030400000,,,,,,, +1788836,1726006336822,salt,1757030400000,,,,,,, +1821682,1746742356122,salt,1757030400000,,,,,,, +1854032,1733782336150,salt,1757030400000,,,,,,, +1887001,1731190371389,salt,1757030400000,,,,,,, +1918423,1733782336150,salt,1757030400000,,,,,,, +1951812,1738966330431,salt,1757030400000,,,,,,, +1985753,1728598375924,salt,1757030400000,,,,,,, +2016355,1751846400001,salt,1757030400000,prev_salt,,,,,, +1000575,1751932800001,salt,1757116800000,prev_salt,,,,,, +1031635,1754524800001,salt,1757116800000,prev_salt,,,,,, +1064289,1749340800001,salt,1757116800000,prev_salt,,,,,, +1095604,1739052727203,salt,1757116800000,,,,,,, +1128844,1751932800001,salt,1757116800000,prev_salt,,,,,, +1159388,1728684728245,salt,1757116800000,,,,,,, +1191252,1726092728741,salt,1757116800000,,,,,,, +1224417,1739052727203,salt,1757116800000,,,,,,, +1256455,1739052727203,salt,1757116800000,,,,,,, +1287299,1731276767997,salt,1757116800000,,,,,,, +1318079,1746828738316,salt,1757116800000,,,,,,, +1350979,1741644726827,salt,1757116800000,,,,,,, +1383117,1754524800001,salt,1757116800000,prev_salt,,,,,, +1414764,1749340800001,salt,1757116800000,prev_salt,,,,,, +1445359,1733868748490,salt,1757116800000,,,,,,, +1478836,1749340800001,salt,1757116800000,prev_salt,,,,,, +1509749,1726092728741,salt,1757116800000,,,,,,, +1541952,1741644726827,salt,1757116800000,,,,,,, +1575363,1733868748490,salt,1757116800000,,,,,,, +1607951,1739052727203,salt,1757116800000,,,,,,, +1640586,1749340800001,salt,1757116800000,prev_salt,,,,,, +1671444,1736460726143,salt,1757116800000,,,,,,, +1703112,1726092728741,salt,1757116800000,,,,,,, +1735262,1749340800001,salt,1757116800000,prev_salt,,,,,, +1766508,1726092728741,salt,1757116800000,,,,,,, +1798843,1741644726827,salt,1757116800000,,,,,,, +1830840,1741644726827,salt,1757116800000,,,,,,, +1861863,1736460726143,salt,1757116800000,,,,,,, +1893925,1726092728741,salt,1757116800000,,,,,,, +1924001,1736460726143,salt,1757116800000,,,,,,, +1956308,1741644726827,salt,1757116800000,,,,,,, +1988635,1754524800001,salt,1757116800000,prev_salt,,,,,, +2020920,1741644726827,salt,1757116800000,,,,,,, +1003407,1754611200001,salt,1757203200000,prev_salt,,,,,, +1034498,1736547128493,salt,1757203200000,,,,,,, +1065416,1739139129284,salt,1757203200000,,,,,,, +1097113,1749427200001,salt,1757203200000,prev_salt,,,,,, +1131229,1744323128573,salt,1757203200000,,,,,,, +1162253,1733955127668,salt,1757203200000,,,,,,, +1194520,1733955127668,salt,1757203200000,,,,,,, +1225061,1741731139100,salt,1757203200000,,,,,,, +1255854,1736547128493,salt,1757203200000,,,,,,, +1288091,1733955127668,salt,1757203200000,,,,,,, +1321164,1744323128573,salt,1757203200000,,,,,,, +1352393,1731363218667,salt,1757203200000,,,,,,, +1384224,1733955127668,salt,1757203200000,,,,,,, +1416339,1754611200001,salt,1757203200000,prev_salt,,,,,, +1446894,1754611200001,salt,1757203200000,prev_salt,,,,,, +1478930,1754611200001,salt,1757203200000,prev_salt,,,,,, +1511034,1752019200001,salt,1757203200000,prev_salt,,,,,, +1542154,1744323128573,salt,1757203200000,,,,,,, +1574098,1749427200001,salt,1757203200000,prev_salt,,,,,, +1608416,1752019200001,salt,1757203200000,prev_salt,,,,,, +1640297,1744323128573,salt,1757203200000,,,,,,, +1673448,1741731139100,salt,1757203200000,,,,,,, +1705466,1736547128493,salt,1757203200000,,,,,,, +1738719,1739139129284,salt,1757203200000,,,,,,, +1769579,1731363218667,salt,1757203200000,,,,,,, +1800901,1741731139100,salt,1757203200000,,,,,,, +1833850,1736547128493,salt,1757203200000,,,,,,, +1865452,1754611200001,salt,1757203200000,prev_salt,,,,,, +1898116,1726179137201,salt,1757203200000,,,,,,, +1928564,1728771200067,salt,1757203200000,,,,,,, +1959454,1746915131408,salt,1757203200000,,,,,,, +1990781,1741731139100,salt,1757203200000,,,,,,, +2021791,1733955127668,salt,1757203200000,,,,,,, +1005810,1736633537649,salt,1757289600000,,,,,,, +1037770,1744409543713,salt,1757289600000,,,,,,, +1070200,1726265543987,salt,1757289600000,,,,,,, +1102945,1739225527026,salt,1757289600000,,,,,,, +1135424,1739225527026,salt,1757289600000,,,,,,, +1168981,1752105600001,salt,1757289600000,prev_salt,,,,,, +1199508,1747001530333,salt,1757289600000,,,,,,, +1232086,1728857529219,salt,1757289600000,,,,,,, +1264497,1734041538618,salt,1757289600000,,,,,,, +1296318,1754697600001,salt,1757289600000,prev_salt,,,,,, +1328171,1731449528743,salt,1757289600000,,,,,,, +1360870,1726265543987,salt,1757289600000,,,,,,, +1392783,1754697600001,salt,1757289600000,prev_salt,,,,,, +1424076,1728857529219,salt,1757289600000,,,,,,, +1454381,1739225527026,salt,1757289600000,,,,,,, +1485263,1747001530333,salt,1757289600000,,,,,,, +1517140,1741817526890,salt,1757289600000,,,,,,, +1549241,1752105600001,salt,1757289600000,prev_salt,,,,,, +1581522,1739225527026,salt,1757289600000,,,,,,, +1613330,1741817526890,salt,1757289600000,,,,,,, +1645257,1747001530333,salt,1757289600000,,,,,,, +1675722,1752105600001,salt,1757289600000,prev_salt,,,,,, +1707304,1728857529219,salt,1757289600000,,,,,,, +1738526,1728857529219,salt,1757289600000,,,,,,, +1770514,1752105600001,salt,1757289600000,prev_salt,,,,,, +1802492,1728857529219,salt,1757289600000,,,,,,, +1834510,1726265543987,salt,1757289600000,,,,,,, +1867668,1734041538618,salt,1757289600000,,,,,,, +1899152,1744409543713,salt,1757289600000,,,,,,, +1930298,1726265543987,salt,1757289600000,,,,,,, +1961460,1741817526890,salt,1757289600000,,,,,,, +1992646,1741817526890,salt,1757289600000,,,,,,, +2025033,1744409543713,salt,1757289600000,,,,,,, +1008036,1726351942967,salt,1757376000000,,,,,,, +1038827,1749600000001,salt,1757376000000,prev_salt,,,,,, +1067685,1741903928829,salt,1757376000000,,,,,,, +1100865,1731535936552,salt,1757376000000,,,,,,, +1134008,1726351942967,salt,1757376000000,,,,,,, +1166200,1747087958250,salt,1757376000000,,,,,,, +1198117,1747087958250,salt,1757376000000,,,,,,, +1229677,1734127929741,salt,1757376000000,,,,,,, +1261379,1739311928639,salt,1757376000000,,,,,,, +1295439,1754784000001,salt,1757376000000,prev_salt,,,,,, +1326775,1726351942967,salt,1757376000000,,,,,,, +1359989,1734127929741,salt,1757376000000,,,,,,, +1392243,1747087958250,salt,1757376000000,,,,,,, +1425733,1726351942967,salt,1757376000000,,,,,,, +1458640,1739311928639,salt,1757376000000,,,,,,, +1490220,1752192000001,salt,1757376000000,prev_salt,,,,,, +1522751,1728943936609,salt,1757376000000,,,,,,, +1554115,1744495995707,salt,1757376000000,,,,,,, +1586755,1731535936552,salt,1757376000000,,,,,,, +1617504,1726351942967,salt,1757376000000,,,,,,, +1650169,1747087958250,salt,1757376000000,,,,,,, +1680642,1736719938108,salt,1757376000000,,,,,,, +1710643,1734127929741,salt,1757376000000,,,,,,, +1742626,1747087958250,salt,1757376000000,,,,,,, +1774495,1752192000001,salt,1757376000000,prev_salt,,,,,, +1805475,1744495995707,salt,1757376000000,,,,,,, +1837268,1754784000001,salt,1757376000000,prev_salt,,,,,, +1869200,1754784000001,salt,1757376000000,prev_salt,,,,,, +1899334,1736719938108,salt,1757376000000,,,,,,, +1931611,1752192000001,salt,1757376000000,prev_salt,,,,,, +1962946,1747087958250,salt,1757376000000,,,,,,, +1995375,1747087958250,salt,1757376000000,,,,,,, +2026839,1739311928639,salt,1757376000000,,,,,,, +1010490,1734214337076,salt,1757462400000,,,,,,, +1042010,1731622330383,salt,1757462400000,,,,,,, +1075603,1754870400001,salt,1757462400000,prev_salt,,,,,, +1105482,1752278400001,salt,1757462400000,prev_salt,,,,,, +1138191,1736806341740,salt,1757462400000,,,,,,, +1171449,1734214337076,salt,1757462400000,,,,,,, +1204079,1729030383120,salt,1757462400000,,,,,,, +1235943,1726438335691,salt,1757462400000,,,,,,, +1268496,1744582376487,salt,1757462400000,,,,,,, +1301165,1739398338199,salt,1757462400000,,,,,,, +1331225,1741990326885,salt,1757462400000,,,,,,, +1363602,1754870400001,salt,1757462400000,prev_salt,,,,,, +1393761,1734214337076,salt,1757462400000,,,,,,, +1423891,1726438335691,salt,1757462400000,,,,,,, +1457552,1726438335691,salt,1757462400000,,,,,,, +1489789,1744582376487,salt,1757462400000,,,,,,, +1520977,1749686400001,salt,1757462400000,prev_salt,,,,,, +1553762,1749686400001,salt,1757462400000,prev_salt,,,,,, +1586129,1726438335691,salt,1757462400000,,,,,,, +1616164,1731622330383,salt,1757462400000,,,,,,, +1646201,1754870400001,salt,1757462400000,prev_salt,,,,,, +1679178,1726438335691,salt,1757462400000,,,,,,, +1712077,1747094400001,salt,1757462400000,,,,,,, +1744707,1752278400001,salt,1757462400000,prev_salt,,,,,, +1776330,1744582376487,salt,1757462400000,,,,,,, +1808521,1749686400001,salt,1757462400000,prev_salt,,,,,, +1839691,1744582376487,salt,1757462400000,,,,,,, +1872484,1739398338199,salt,1757462400000,,,,,,, +1903772,1739398338199,salt,1757462400000,,,,,,, +1935074,1739398338199,salt,1757462400000,,,,,,, +1967372,1749686400001,salt,1757462400000,prev_salt,,,,,, +1997532,1744582376487,salt,1757462400000,,,,,,, +2029984,1734214337076,salt,1757462400000,,,,,,, +1012284,1726524780233,salt,1757548800000,,,,,,, +1045049,1734300728979,salt,1757548800000,,,,,,, +1077159,1739484739016,salt,1757548800000,,,,,,, +1109983,1736892726021,salt,1757548800000,,,,,,, +1142770,1729116766943,salt,1757548800000,,,,,,, +1173671,1749772800001,salt,1757548800000,prev_salt,,,,,, +1205669,1754956800001,salt,1757548800000,prev_salt,,,,,, +1238744,1754956800001,salt,1757548800000,prev_salt,,,,,, +1269604,1752364800001,salt,1757548800000,prev_salt,,,,,, +1302473,1736892726021,salt,1757548800000,,,,,,, +1335535,1747180800001,salt,1757548800000,,,,,,, +1368648,1752364800001,salt,1757548800000,prev_salt,,,,,, +1400981,1749772800001,salt,1757548800000,prev_salt,,,,,, +1431908,1729116766943,salt,1757548800000,,,,,,, +1463649,1729116766943,salt,1757548800000,,,,,,, +1493151,1747180800001,salt,1757548800000,,,,,,, +1522864,1734300728979,salt,1757548800000,,,,,,, +1555247,1729116766943,salt,1757548800000,,,,,,, +1586450,1739484739016,salt,1757548800000,,,,,,, +1618636,1754956800001,salt,1757548800000,prev_salt,,,,,, +1648800,1729116766943,salt,1757548800000,,,,,,, +1680561,1729116766943,salt,1757548800000,,,,,,, +1710433,1742076728408,salt,1757548800000,,,,,,, +1743077,1754956800001,salt,1757548800000,prev_salt,,,,,, +1775487,1742076728408,salt,1757548800000,,,,,,, +1807083,1726524780233,salt,1757548800000,,,,,,, +1838338,1744668760655,salt,1757548800000,,,,,,, +1868677,1754956800001,salt,1757548800000,prev_salt,,,,,, +1902338,1742076728408,salt,1757548800000,,,,,,, +1936057,1754956800001,salt,1757548800000,prev_salt,,,,,, +1967077,1739484739016,salt,1757548800000,,,,,,, +1998857,1744668760655,salt,1757548800000,,,,,,, +2030178,1752364800001,salt,1757548800000,prev_salt,,,,,, +1012603,1731795143943,salt,1757635200000,,,,,,, +1044434,1729203205559,salt,1757635200000,,,,,,, +1075735,1747267200001,salt,1757635200000,,,,,,, +1106196,1747267200001,salt,1757635200000,,,,,,, +1137926,1726611169153,salt,1757635200000,,,,,,, +1168017,1752451200001,salt,1757635200000,prev_salt,,,,,, +1201680,1744755195402,salt,1757635200000,,,,,,, +1233820,1734387137012,salt,1757635200000,,,,,,, +1265156,1729203205559,salt,1757635200000,,,,,,, +1296437,1747267200001,salt,1757635200000,,,,,,, +1329090,1726611169153,salt,1757635200000,,,,,,, +1359877,1752451200001,salt,1757635200000,prev_salt,,,,,, +1391135,1742163155906,salt,1757635200000,,,,,,, +1422717,1731795143943,salt,1757635200000,,,,,,, +1454467,1739571137670,salt,1757635200000,,,,,,, +1485484,1742163155906,salt,1757635200000,,,,,,, +1518138,1739571137670,salt,1757635200000,,,,,,, +1548728,1742163155906,salt,1757635200000,,,,,,, +1579411,1747267200001,salt,1757635200000,,,,,,, +1611081,1752451200001,salt,1757635200000,prev_salt,,,,,, +1643282,1734387137012,salt,1757635200000,,,,,,, +1677062,1739571137670,salt,1757635200000,,,,,,, +1710787,1755043200001,salt,1757635200000,prev_salt,,,,,, +1741135,1755043200001,salt,1757635200000,prev_salt,,,,,, +1772761,1731795143943,salt,1757635200000,,,,,,, +1804333,1729203205559,salt,1757635200000,,,,,,, +1836049,1752451200001,salt,1757635200000,prev_salt,,,,,, +1869376,1736979141679,salt,1757635200000,,,,,,, +1901603,1747267200001,salt,1757635200000,,,,,,, +1935001,1744755195402,salt,1757635200000,,,,,,, +1966139,1744755195402,salt,1757635200000,,,,,,, +1999888,1726611169153,salt,1757635200000,,,,,,, +2031189,1729203205559,salt,1757635200000,,,,,,, +1014696,1742249526180,salt,1757721600000,,,,,,, +1046576,1734473552771,salt,1757721600000,,,,,,, +1079814,1747353600001,salt,1757721600000,,,,,,, +1110848,1744841539615,salt,1757721600000,,,,,,, +1141599,1749945600001,salt,1757721600000,prev_salt,,,,,, +1173161,1752537600001,salt,1757721600000,prev_salt,,,,,, +1205794,1731881529114,salt,1757721600000,,,,,,, +1239559,1752537600001,salt,1757721600000,prev_salt,,,,,, +1271439,1729289529459,salt,1757721600000,,,,,,, +1303462,1739657538987,salt,1757721600000,,,,,,, +1334635,1747353600001,salt,1757721600000,,,,,,, +1365951,1742249526180,salt,1757721600000,,,,,,, +1395847,1739657538987,salt,1757721600000,,,,,,, +1426647,1747353600001,salt,1757721600000,,,,,,, +1458113,1726697555407,salt,1757721600000,,,,,,, +1491883,1752537600001,salt,1757721600000,prev_salt,,,,,, +1524659,1744841539615,salt,1757721600000,,,,,,, +1557251,1744841539615,salt,1757721600000,,,,,,, +1588680,1742249526180,salt,1757721600000,,,,,,, +1619728,1729289529459,salt,1757721600000,,,,,,, +1652337,1744841539615,salt,1757721600000,,,,,,, +1683405,1755129600001,salt,1757721600000,prev_salt,,,,,, +1714231,1744841539615,salt,1757721600000,,,,,,, +1745696,1726697555407,salt,1757721600000,,,,,,, +1777555,1752537600001,salt,1757721600000,prev_salt,,,,,, +1809361,1737065538032,salt,1757721600000,,,,,,, +1840589,1726697555407,salt,1757721600000,,,,,,, +1871522,1739657538987,salt,1757721600000,,,,,,, +1904893,1734473552771,salt,1757721600000,,,,,,, +1936921,1731881529114,salt,1757721600000,,,,,,, +1968537,1755129600001,salt,1757721600000,prev_salt,,,,,, +2000130,1742249526180,salt,1757721600000,,,,,,, +2031049,1742249526180,salt,1757721600000,,,,,,, +1014178,1737151929639,salt,1757808000000,,,,,,, +1046041,1744927927810,salt,1757808000000,,,,,,, +1076088,1737151929639,salt,1757808000000,,,,,,, +1108434,1734559975322,salt,1757808000000,,,,,,, +1139514,1737151929639,salt,1757808000000,,,,,,, +1172035,1752624000001,salt,1757808000000,prev_salt,,,,,, +1204349,1747440000001,salt,1757808000000,,,,,,, +1237008,1752624000001,salt,1757808000000,prev_salt,,,,,, +1269279,1729376002593,salt,1757808000000,,,,,,, +1300247,1729376002593,salt,1757808000000,,,,,,, +1332186,1734559975322,salt,1757808000000,,,,,,, +1364197,1739743940223,salt,1757808000000,,,,,,, +1396543,1726783969269,salt,1757808000000,,,,,,, +1427790,1742335928211,salt,1757808000000,,,,,,, +1459338,1734559975322,salt,1757808000000,,,,,,, +1490527,1750032000001,salt,1757808000000,prev_salt,,,,,, +1522510,1755216000001,salt,1757808000000,prev_salt,,,,,, +1553565,1750032000001,salt,1757808000000,prev_salt,,,,,, +1584597,1755216000001,salt,1757808000000,prev_salt,,,,,, +1618088,1750032000001,salt,1757808000000,prev_salt,,,,,, +1651589,1729376002593,salt,1757808000000,,,,,,, +1682596,1726783969269,salt,1757808000000,,,,,,, +1712728,1752624000001,salt,1757808000000,prev_salt,,,,,, +1744587,1755216000001,salt,1757808000000,prev_salt,,,,,, +1777229,1755216000001,salt,1757808000000,prev_salt,,,,,, +1810484,1744927927810,salt,1757808000000,,,,,,, +1841190,1729376002593,salt,1757808000000,,,,,,, +1873265,1747440000001,salt,1757808000000,,,,,,, +1904891,1726783969269,salt,1757808000000,,,,,,, +1936462,1731967951756,salt,1757808000000,,,,,,, +1968591,1726783969269,salt,1757808000000,,,,,,, +1999462,1739743940223,salt,1757808000000,,,,,,, +2031185,1744927927810,salt,1757808000000,,,,,,, +1013494,1750118400001,salt,1757894400000,prev_salt,,,,,, +1044945,1729462328764,salt,1757894400000,,,,,,, +1077964,1750118400001,salt,1757894400000,prev_salt,,,,,, +1109626,1734646422354,salt,1757894400000,,,,,,, +1141913,1745014355572,salt,1757894400000,,,,,,, +1173378,1734646422354,salt,1757894400000,,,,,,, +1203612,1755302400001,salt,1757894400000,prev_salt,,,,,, +1236332,1734646422354,salt,1757894400000,,,,,,, +1267315,1726870384866,salt,1757894400000,,,,,,, +1299118,1745014355572,salt,1757894400000,,,,,,, +1331779,1726870384866,salt,1757894400000,,,,,,, +1364409,1755302400001,salt,1757894400000,prev_salt,,,,,, +1395340,1737238351779,salt,1757894400000,,,,,,, +1426836,1737238351779,salt,1757894400000,,,,,,, +1457582,1737238351779,salt,1757894400000,,,,,,, +1490124,1729462328764,salt,1757894400000,,,,,,, +1521736,1729462328764,salt,1757894400000,,,,,,, +1554827,1726870384866,salt,1757894400000,,,,,,, +1587885,1732054342453,salt,1757894400000,,,,,,, +1618815,1732054342453,salt,1757894400000,,,,,,, +1649541,1732054342453,salt,1757894400000,,,,,,, +1680127,1729462328764,salt,1757894400000,,,,,,, +1709385,1742422326936,salt,1757894400000,,,,,,, +1742285,1742422326936,salt,1757894400000,,,,,,, +1774078,1755302400001,salt,1757894400000,prev_salt,,,,,, +1805206,1732054342453,salt,1757894400000,,,,,,, +1836913,1734646422354,salt,1757894400000,,,,,,, +1868035,1747526400001,salt,1757894400000,,,,,,, +1901757,1739830377658,salt,1757894400000,,,,,,, +1934055,1742422326936,salt,1757894400000,,,,,,, +1965446,1745014355572,salt,1757894400000,,,,,,, +1997309,1737238351779,salt,1757894400000,,,,,,, +2028949,1726870384866,salt,1757894400000,,,,,,, +1011703,1747612800001,salt,1757980800000,,,,,,, +1044444,1737324727117,salt,1757980800000,,,,,,, +1075765,1742508757985,salt,1757980800000,,,,,,, +1108399,1734732753616,salt,1757980800000,,,,,,, +1138140,1742508757985,salt,1757980800000,,,,,,, +1171475,1742508757985,salt,1757980800000,,,,,,, +1204017,1732140740042,salt,1757980800000,,,,,,, +1234938,1737324727117,salt,1757980800000,,,,,,, +1266518,1734732753616,salt,1757980800000,,,,,,, +1298883,1745100738060,salt,1757980800000,,,,,,, +1330838,1734732753616,salt,1757980800000,,,,,,, +1362688,1726956728683,salt,1757980800000,,,,,,, +1393724,1755388800001,salt,1757980800000,prev_salt,,,,,, +1425247,1739916755515,salt,1757980800000,,,,,,, +1455982,1752796800001,salt,1757980800000,prev_salt,,,,,, +1488005,1726956728683,salt,1757980800000,,,,,,, +1519832,1726956728683,salt,1757980800000,,,,,,, +1550597,1742508757985,salt,1757980800000,,,,,,, +1582550,1732140740042,salt,1757980800000,,,,,,, +1613537,1752796800001,salt,1757980800000,prev_salt,,,,,, +1646704,1745100738060,salt,1757980800000,,,,,,, +1677000,1734732753616,salt,1757980800000,,,,,,, +1708159,1745100738060,salt,1757980800000,,,,,,, +1739163,1732140740042,salt,1757980800000,,,,,,, +1770199,1747612800001,salt,1757980800000,,,,,,, +1800715,1742508757985,salt,1757980800000,,,,,,, +1833389,1752796800001,salt,1757980800000,prev_salt,,,,,, +1865978,1726956728683,salt,1757980800000,,,,,,, +1897050,1732140740042,salt,1757980800000,,,,,,, +1928296,1737324727117,salt,1757980800000,,,,,,, +1959755,1755388800001,salt,1757980800000,prev_salt,,,,,, +1991464,1729548737311,salt,1757980800000,,,,,,, +2025156,1742508757985,salt,1757980800000,,,,,,, +1008465,1734819141831,salt,1758067200000,,,,,,, +1038807,1747699200001,salt,1758067200000,,,,,,, +1069321,1737411127893,salt,1758067200000,,,,,,, +1103061,1737411127893,salt,1758067200000,,,,,,, +1134753,1752883200001,salt,1758067200000,prev_salt,,,,,, +1165989,1752883200001,salt,1758067200000,prev_salt,,,,,, +1197564,1752883200001,salt,1758067200000,prev_salt,,,,,, +1228574,1742595127178,salt,1758067200000,,,,,,, +1262752,1745187127963,salt,1758067200000,,,,,,, +1294897,1742595127178,salt,1758067200000,,,,,,, +1327082,1745187127963,salt,1758067200000,,,,,,, +1360748,1734819141831,salt,1758067200000,,,,,,, +1392007,1727043135780,salt,1758067200000,,,,,,, +1425431,1734819141831,salt,1758067200000,,,,,,, +1456722,1742595127178,salt,1758067200000,,,,,,, +1488353,1745187127963,salt,1758067200000,,,,,,, +1521381,1745187127963,salt,1758067200000,,,,,,, +1553180,1755475200001,salt,1758067200000,prev_salt,,,,,, +1582407,1734819141831,salt,1758067200000,,,,,,, +1613439,1737411127893,salt,1758067200000,,,,,,, +1646734,1752883200001,salt,1758067200000,prev_salt,,,,,, +1679913,1745187127963,salt,1758067200000,,,,,,, +1712530,1745187127963,salt,1758067200000,,,,,,, +1744167,1729635141717,salt,1758067200000,,,,,,, +1775834,1727043135780,salt,1758067200000,,,,,,, +1805829,1750291200001,salt,1758067200000,prev_salt,,,,,, +1835979,1740003173041,salt,1758067200000,,,,,,, +1867874,1742595127178,salt,1758067200000,,,,,,, +1896742,1747699200001,salt,1758067200000,,,,,,, +1928364,1742595127178,salt,1758067200000,,,,,,, +1957845,1745187127963,salt,1758067200000,,,,,,, +1990016,1734819141831,salt,1758067200000,,,,,,, +2021724,1737411127893,salt,1758067200000,,,,,,, +1003165,1755561600001,salt,1758153600000,prev_salt,,,,,, +1035787,1752969600001,salt,1758153600000,prev_salt,,,,,, +1066873,1752969600001,salt,1758153600000,prev_salt,,,,,, +1098640,1752969600001,salt,1758153600000,prev_salt,,,,,, +1129571,1742681539791,salt,1758153600000,,,,,,, +1160295,1729721529279,salt,1758153600000,,,,,,, +1191077,1745273527555,salt,1758153600000,,,,,,, +1222890,1740089527213,salt,1758153600000,,,,,,, +1252717,1729721529279,salt,1758153600000,,,,,,, +1283542,1729721529279,salt,1758153600000,,,,,,, +1313848,1732313550454,salt,1758153600000,,,,,,, +1344928,1747785600001,salt,1758153600000,,,,,,, +1375656,1732313550454,salt,1758153600000,,,,,,, +1407390,1732313550454,salt,1758153600000,,,,,,, +1439182,1729721529279,salt,1758153600000,,,,,,, +1470669,1740089527213,salt,1758153600000,,,,,,, +1501376,1727129556079,salt,1758153600000,,,,,,, +1534434,1752969600001,salt,1758153600000,prev_salt,,,,,, +1565724,1732313550454,salt,1758153600000,,,,,,, +1597757,1727129556079,salt,1758153600000,,,,,,, +1629783,1740089527213,salt,1758153600000,,,,,,, +1662599,1732313550454,salt,1758153600000,,,,,,, +1694100,1734905529862,salt,1758153600000,,,,,,, +1724489,1742681539791,salt,1758153600000,,,,,,, +1754880,1729721529279,salt,1758153600000,,,,,,, +1787214,1737497611772,salt,1758153600000,,,,,,, +1819239,1755561600001,salt,1758153600000,prev_salt,,,,,, +1851103,1745273527555,salt,1758153600000,,,,,,, +1882645,1734905529862,salt,1758153600000,,,,,,, +1913237,1742681539791,salt,1758153600000,,,,,,, +1946015,1747785600001,salt,1758153600000,,,,,,, +1977140,1747785600001,salt,1758153600000,,,,,,, +2010453,1740089527213,salt,1758153600000,,,,,,, +2043032,1740089527213,salt,1758153600000,,,,,,, +1026214,1737583938639,salt,1758240000000,,,,,,, +1056787,1755648000001,salt,1758240000000,prev_salt,,,,,, +1089132,1727215928452,salt,1758240000000,,,,,,, +1119990,1732399928457,salt,1758240000000,,,,,,, +1149807,1734991967099,salt,1758240000000,,,,,,, +1181185,1750464000001,salt,1758240000000,prev_salt,,,,,, +1212918,1753056000001,salt,1758240000000,prev_salt,,,,,, +1244092,1745359927758,salt,1758240000000,,,,,,, +1275248,1740175928920,salt,1758240000000,,,,,,, +1307332,1732399928457,salt,1758240000000,,,,,,, +1339315,1727215928452,salt,1758240000000,,,,,,, +1370889,1734991967099,salt,1758240000000,,,,,,, +1401419,1753056000001,salt,1758240000000,prev_salt,,,,,, +1431823,1742767926789,salt,1758240000000,,,,,,, +1464947,1745359927758,salt,1758240000000,,,,,,, +1496372,1737583938639,salt,1758240000000,,,,,,, +1528030,1740175928920,salt,1758240000000,,,,,,, +1559406,1737583938639,salt,1758240000000,,,,,,, +1589283,1737583938639,salt,1758240000000,,,,,,, +1621961,1737583938639,salt,1758240000000,,,,,,, +1651878,1729807938265,salt,1758240000000,,,,,,, +1685571,1747872000001,salt,1758240000000,,,,,,, +1716881,1737583938639,salt,1758240000000,,,,,,, +1747093,1750464000001,salt,1758240000000,prev_salt,,,,,, +1779103,1742767926789,salt,1758240000000,,,,,,, +1809348,1750464000001,salt,1758240000000,prev_salt,,,,,, +1839970,1747872000001,salt,1758240000000,,,,,,, +1872844,1755648000001,salt,1758240000000,prev_salt,,,,,, +1903580,1740175928920,salt,1758240000000,,,,,,, +1935667,1729807938265,salt,1758240000000,,,,,,, +1967509,1755648000001,salt,1758240000000,prev_salt,,,,,, +1999213,1755648000001,salt,1758240000000,prev_salt,,,,,, +2030945,1750464000001,salt,1758240000000,prev_salt,,,,,, \ No newline at end of file diff --git a/src/test/java/com/uid2/admin/salt/SaltRotationTest.java b/src/test/java/com/uid2/admin/salt/SaltRotationTest.java index 94865312..56344bab 100644 --- a/src/test/java/com/uid2/admin/salt/SaltRotationTest.java +++ b/src/test/java/com/uid2/admin/salt/SaltRotationTest.java @@ -2,21 +2,26 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import com.uid2.admin.AdminConst; import com.uid2.admin.salt.helper.SaltBuilder; import com.uid2.admin.salt.helper.SaltSnapshotBuilder; import com.uid2.shared.model.SaltEntry; import com.uid2.shared.secret.IKeyGenerator; +import io.vertx.core.json.JsonObject; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.time.*; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.uid2.admin.salt.helper.TargetDateUtil.*; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -43,7 +48,7 @@ void setup() { appender.start(); ((Logger) LoggerFactory.getLogger(SaltRotation.class)).addAppender(appender); - saltRotation = new SaltRotation(keyGenerator); + saltRotation = new SaltRotation(keyGenerator, new JsonObject()); } @AfterEach @@ -215,7 +220,7 @@ void testRotateSaltsRotateSaltsInsufficientOutdatedSalts() throws Exception { }) void testRefreshFromCalculation(int lastRotationDaysAgo, int lastRotationMsOffset, int refreshFromDaysFromRotation) throws Exception { var lastRotation = daysEarlier(lastRotationDaysAgo); - SaltBuilder saltBuilder = SaltBuilder.start().lastUpdated(lastRotation.asInstant().plusMillis(lastRotationMsOffset)).refreshFrom(targetDate()); + SaltBuilder saltBuilder = SaltBuilder.start().lastUpdated(lastRotation.asInstant().plusMillis(lastRotationMsOffset)).refreshFrom(targetDate()).currentSalt(); var lastSnapshot = SaltSnapshotBuilder.start() .entries(saltBuilder, saltBuilder, saltBuilder, saltBuilder) .build(); @@ -292,8 +297,8 @@ void testRotateSaltsRemovePreviousSaltsOver90DaysOld() throws Exception { var validForRotation = daysEarlier(120); var lastSnapshot = SaltSnapshotBuilder.start() .entries( - SaltBuilder.start().lastUpdated(exactly90Days).refreshFrom(targetDate()).previousSalt("90DaysOld"), - SaltBuilder.start().lastUpdated(over90Days).refreshFrom(targetDate()).previousSalt("over90DaysOld") + SaltBuilder.start().lastUpdated(exactly90Days).refreshFrom(targetDate()).currentSalt().previousSalt("90DaysOld"), + SaltBuilder.start().lastUpdated(over90Days).refreshFrom(targetDate()).currentSalt().previousSalt("over90DaysOld") ) .entries(1, validForRotation, targetDate()) .build(); @@ -308,7 +313,7 @@ void testRotateSaltsRemovePreviousSaltsOver90DaysOld() throws Exception { @Test void testRotateSaltsRotateWhenRefreshFromIsTargetDate() throws Exception { - saltRotation = new SaltRotation(keyGenerator); + saltRotation = new SaltRotation(keyGenerator, new JsonObject()); final Duration[] minAges = { Duration.ofDays(90), @@ -323,9 +328,9 @@ void testRotateSaltsRotateWhenRefreshFromIsTargetDate() throws Exception { var lastSnapshot = SaltSnapshotBuilder.start() .entries( - SaltBuilder.start().lastUpdated(validForRotation1).refreshFrom(refreshNow), - SaltBuilder.start().lastUpdated(notValidForRotation).refreshFrom(refreshNow), - SaltBuilder.start().lastUpdated(validForRotation2).refreshFrom(refreshLater) + SaltBuilder.start().lastUpdated(validForRotation1).refreshFrom(refreshNow).currentSalt(), + SaltBuilder.start().lastUpdated(notValidForRotation).refreshFrom(refreshNow).currentSalt(), + SaltBuilder.start().lastUpdated(validForRotation2).refreshFrom(refreshLater).currentSalt() ) .build(); @@ -346,18 +351,18 @@ void testRotateSaltsRotateWhenRefreshFromIsTargetDate() throws Exception { @Test void testLogFewSaltAgesOnRotation() throws Exception { - saltRotation = new SaltRotation(keyGenerator); + saltRotation = new SaltRotation(keyGenerator, new JsonObject()); // 7 salts total, 5 refreshable, 3 will rotate (6 * 0.4 rounded up), up to 2 will rotate per age (3 * 0.8) var lastSnapshot = SaltSnapshotBuilder.start() .entries( - SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()), // Refreshable, old enough - SaltBuilder.start().lastUpdated(daysEarlier(5)).refreshFrom(targetDate()), // Refreshable, too new - SaltBuilder.start().lastUpdated(daysEarlier(33)).refreshFrom(targetDate()), // Refreshable, old enough - SaltBuilder.start().lastUpdated(daysEarlier(50)).refreshFrom(daysLater(1)), // Not refreshable, old enough - SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()), // Refreshable, old enough - SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()), // Refreshable, old enough - SaltBuilder.start().lastUpdated(daysEarlier(10)).refreshFrom(daysLater(10)) // Not refreshable, too new + SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()).currentSalt(), // Refreshable, old enough + SaltBuilder.start().lastUpdated(daysEarlier(5)).refreshFrom(targetDate()).currentSalt(), // Refreshable, too new + SaltBuilder.start().lastUpdated(daysEarlier(33)).refreshFrom(targetDate()).currentSalt(), // Refreshable, old enough + SaltBuilder.start().lastUpdated(daysEarlier(50)).refreshFrom(daysLater(1)).currentSalt(), // Not refreshable, old enough + SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()).currentSalt(), // Refreshable, old enough + SaltBuilder.start().lastUpdated(daysEarlier(65)).refreshFrom(targetDate()).currentSalt(), // Refreshable, old enough + SaltBuilder.start().lastUpdated(daysEarlier(10)).refreshFrom(daysLater(10)).currentSalt() // Not refreshable, too new ) .build(); @@ -383,13 +388,13 @@ void testLogFewSaltAgesOnRotation() throws Exception { var minAges = new Duration[]{Duration.ofDays(30), Duration.ofDays(60)}; saltRotation.rotateSalts(lastSnapshot, minAges, 0.4, targetDate()); - var actual = appender.list.stream().map(Object::toString).collect(Collectors.toSet()); + var actual = appender.list.stream().map(Object::toString).filter(s -> s.contains("salt_count_type") || s.contains("Salt rotation complete")).collect(Collectors.toSet()); assertThat(actual).isEqualTo(expected); } @Test void testLogManySaltAgesOnRotation() throws Exception { - saltRotation = new SaltRotation(keyGenerator); + saltRotation = new SaltRotation(keyGenerator, new JsonObject()); // 50 salts total, 16 refreshable, 10 will rotate (18 * 0.2 rounded up), up to 8 will rotate per age (10 * 0.8) var lastSnapshot = SaltSnapshotBuilder.start() @@ -423,7 +428,7 @@ void testLogManySaltAgesOnRotation() throws Exception { var minAges = new Duration[]{Duration.ofDays(30), Duration.ofDays(60)}; saltRotation.rotateSalts(lastSnapshot, minAges, 0.2, targetDate()); - var actual = appender.list.stream().map(Object::toString).collect(Collectors.toSet()); + var actual = appender.list.stream().map(Object::toString).filter(s -> s.contains("salt_count_type") || s.contains("Salt rotation complete")).collect(Collectors.toSet()); assertThat(actual).isEqualTo(expected); } @@ -439,20 +444,20 @@ private int countEntriesWithLastUpdated(SaltEntry[] entries, Instant lastUpdated void testRotateSaltsZeroDoesntRotateSaltsButUpdatesRefreshFrom() throws Exception { var lastSnapshot = SaltSnapshotBuilder.start() .entries( - SaltBuilder.start().lastUpdated(targetDate().minusDays(75)).refreshFrom(targetDate().minusDays(45)).id(1), - SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(targetDate()).id(2), - SaltBuilder.start().lastUpdated(targetDate().minusDays(30)).refreshFrom(targetDate()).id(3), - SaltBuilder.start().lastUpdated(targetDate().minusDays(20)).refreshFrom(targetDate().plusDays(10)).id(4) + SaltBuilder.start().lastUpdated(targetDate().minusDays(75)).refreshFrom(targetDate().minusDays(45)).id(1).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(targetDate()).id(2).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(30)).refreshFrom(targetDate()).id(3).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(20)).refreshFrom(targetDate().plusDays(10)).id(4).currentSalt() ) .effective(daysEarlier(1)) .expires(daysLater(6)) .build(); var expected = List.of( - SaltBuilder.start().lastUpdated(targetDate().minusDays(75)).refreshFrom(targetDate().plusDays(15)).id(1).build(), - SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(targetDate().plusDays(30)).id(2).build(), - SaltBuilder.start().lastUpdated(targetDate().minusDays(30)).refreshFrom(targetDate().plusDays(30)).id(3).build(), - SaltBuilder.start().lastUpdated(targetDate().minusDays(20)).refreshFrom(targetDate().plusDays(10)).id(4).build() + SaltBuilder.start().lastUpdated(targetDate().minusDays(75)).refreshFrom(targetDate().plusDays(15)).id(1).currentSalt().build(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(targetDate().plusDays(30)).id(2).currentSalt().build(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(30)).refreshFrom(targetDate().plusDays(30)).id(3).currentSalt().build(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(20)).refreshFrom(targetDate().plusDays(10)).id(4).currentSalt().build() ).toArray(); var result = saltRotation.rotateSaltsZero(lastSnapshot, targetDate(), targetDate().asInstant()); @@ -473,7 +478,7 @@ void testRotateSaltsZeroWorksWhenThereIsFutureSaltFile() throws Exception { // In regular salt rotations if there is a salt var lastSnapshot = SaltSnapshotBuilder.start() - .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(75))) + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(75)).currentSalt()) .effective(daysLater(10)) .build(); @@ -483,4 +488,206 @@ void testRotateSaltsZeroWorksWhenThereIsFutureSaltFile() throws Exception { assertThat(result.getSnapshot().getEffective()).isEqualTo(targetDate().asInstant()); assertThat(result.getSnapshot().getExpires()).isEqualTo(daysLater(7).asInstant()); } + + @Test + void testRotateFromSaltToKey() throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, true)); + when(keyGenerator.generateRandomKeyString(anyInt())).thenReturn("random-key-string"); + + final Duration[] minAges = { + Duration.ofDays(30), + }; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt("salt1"), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt("salt2"), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentSalt("salt3")) + .build(); + + var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + + assertThat(result.hasSnapshot()).isTrue(); + + var buckets = result.getSnapshot().getAllRotatingSalts(); + + assertThat(buckets[0].currentSalt()).isNull(); + assertThat(buckets[0].previousSalt()).isEqualTo("salt1"); + assertThat(buckets[0].currentKeySalt().id()).isEqualTo(0); + assertThat(buckets[0].currentKeySalt().key()).isNotNull(); + assertThat(buckets[0].currentKeySalt().salt()).isNotNull(); + assertThat(buckets[0].previousKeySalt()).isNull(); + + assertThat(buckets[1].currentSalt()).isNull(); + assertThat(buckets[1].previousSalt()).isEqualTo("salt2"); + assertThat(buckets[1].currentKeySalt().id()).isEqualTo(1); + assertThat(buckets[1].currentKeySalt().key()).isNotNull(); + assertThat(buckets[1].currentKeySalt().salt()).isNotNull(); + assertThat(buckets[1].previousKeySalt()).isNull(); + + assertThat(buckets[2].currentSalt()).isEqualTo("salt3"); + assertThat(buckets[2].previousSalt()).isNull(); + assertThat(buckets[2].currentKeySalt()).isNull(); + assertThat(buckets[2].previousKeySalt()).isNull(); + } + + private static Stream getKeyIdArguments() { + return Stream.of( + Arguments.of(new int[]{0, 1, 2, 3}, new int[]{4, 5, 6}), + Arguments.of(new int[]{16777211, 16777212, 16777213, 16777214}, new int[]{16777215, 0, 1}), + Arguments.of(new int[]{16777212, 16777213, 16777214, 16777215}, new int[]{0, 1, 2}), + Arguments.of(new int[]{16777214, 16777215, 0, 1}, new int[]{2, 3, 4}), + Arguments.of(new int[]{16777210, 8, 9, 10}, new int[]{11, 12, 13}) + ); + } + + @ParameterizedTest + @MethodSource("getKeyIdArguments") + void testKeyRotationKeyIdWrap(int[] existingIds, int[] nextIds) throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, true)); + + final Duration[] minAges = {Duration.ofDays(30), Duration.ofDays(60), Duration.ofDays(90)}; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries( + SaltBuilder.start().lastUpdated(targetDate().minusDays(90)).refreshFrom(willNotRefresh).currentKeySalt(existingIds[0]), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(existingIds[1]), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(existingIds[2]), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(existingIds[3]), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt()) + .build(); + + var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + var buckets = result.getSnapshot().getAllRotatingSalts(); + + assertEquals(existingIds[0], buckets[0].currentKeySalt().id()); + assertEquals(existingIds[1], buckets[1].currentKeySalt().id()); + assertEquals(existingIds[2], buckets[2].currentKeySalt().id()); + assertEquals(existingIds[3], buckets[3].currentKeySalt().id()); + assertEquals(nextIds[0], buckets[4].currentKeySalt().id()); + assertEquals(nextIds[1], buckets[5].currentKeySalt().id()); + assertEquals(nextIds[2], buckets[6].currentKeySalt().id()); + } + + @Test + void testKeyRotationPopulatePreviousKey() throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, true)); + + final Duration[] minAges = { + Duration.ofDays(30), + }; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentKeySalt(0, "keyKey", "keySalt"), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(1), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(2)) + .build(); + + var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + var buckets = result.getSnapshot().getAllRotatingSalts(); + + assertEquals(3, buckets[0].currentKeySalt().id()); + assertEquals(1, buckets[1].currentKeySalt().id()); + assertEquals(2, buckets[2].currentKeySalt().id()); + verify(keyGenerator, times(2)).generateRandomKeyString(anyInt()); + + assertEquals(0, buckets[0].previousKeySalt().id()); + assertEquals("keyKey", buckets[0].previousKeySalt().key()); + assertEquals("keySalt", buckets[0].previousKeySalt().salt()); + + assertNull(buckets[1].previousKeySalt()); + assertNull(buckets[2].previousKeySalt()); + } + + @ParameterizedTest + @CsvSource({ + "89, true", + "90, false", + "91, false" + }) + void testKeyRotationPreviousKeyVisibleFor90Days(int lastUpdatedDaysAgo, boolean hasPreviousKey) throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, true)); + + final Duration[] minAges = { + Duration.ofDays(30), + }; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(lastUpdatedDaysAgo)).refreshFrom(willNotRefresh).currentKeySalt(2).previousKeySalt(0), + SaltBuilder.start().lastUpdated(targetDate().minusDays(lastUpdatedDaysAgo)).refreshFrom(willRefresh).currentKeySalt(1)) // for sake of getting snapshot + .build(); + + var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + var buckets = result.getSnapshot().getAllRotatingSalts(); + + assertEquals(hasPreviousKey, buckets[0].previousKeySalt() != null); + } + + @Test + void testKeyRotationKeyToSaltRotation() throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, false)); + when(keyGenerator.generateRandomKeyString(anyInt())).thenReturn("random-key-string"); + + final Duration[] minAges = { + Duration.ofDays(30), + }; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentKeySalt(0, "keyKey1", "keySalt1"), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentSalt()) + .build(); + + var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + var buckets = result.getSnapshot().getAllRotatingSalts(); + + assertThat(buckets[0].currentSalt()).isNotNull(); + assertThat(buckets[0].previousSalt()).isNull(); + assertThat(buckets[0].currentKeySalt()).isNull(); + assertThat(buckets[0].previousKeySalt()).isEqualTo(new SaltEntry.KeyMaterial(0, "keyKey1", "keySalt1")); + } + + @ParameterizedTest + @CsvSource({ + "true, 3, 1", + "false, 1, 3" + }) + void testKeyRotationLogBucketFormat(boolean v4Enabled, int expectedTotalKeyBuckets, int expectedTotalSaltBuckets) throws Exception { + saltRotation = new SaltRotation(keyGenerator, JsonObject.of(AdminConst.ENABLE_V4_RAW_UID, v4Enabled)); + when(keyGenerator.generateRandomKeyString(anyInt())).thenReturn("random-key-string"); + + final Duration[] minAges = { + Duration.ofDays(30) + }; + + var willRefresh = targetDate(); + var willNotRefresh = targetDate().plusDays(30); + var lastSnapshot = SaltSnapshotBuilder.start() + .entries(SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentSalt(), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willRefresh).currentKeySalt(1), + SaltBuilder.start().lastUpdated(targetDate().minusDays(60)).refreshFrom(willNotRefresh).currentKeySalt(2)) + .build(); + + saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate()); + + var expected = Set.of( + String.format("[INFO] UID bucket format: target_date=2025-01-01 bucket_format=total-current-key-buckets bucket_count=%d", expectedTotalKeyBuckets), + String.format("[INFO] UID bucket format: target_date=2025-01-01 bucket_format=total-current-salt-buckets bucket_count=%d", expectedTotalSaltBuckets), + String.format("[INFO] UID bucket format: target_date=2025-01-01 bucket_format=total-previous-key-buckets bucket_count=%d", 1), + String.format("[INFO] UID bucket format: target_date=2025-01-01 bucket_format=total-previous-salt-buckets bucket_count=%d", 1) + ); + var actual = appender.list.stream().map(Object::toString).filter(s -> s.contains("UID bucket format")).collect(Collectors.toSet()); + assertThat(actual).isEqualTo(expected); + } } diff --git a/src/test/java/com/uid2/admin/salt/helper/SaltBuilder.java b/src/test/java/com/uid2/admin/salt/helper/SaltBuilder.java index a4775d69..400c37e5 100644 --- a/src/test/java/com/uid2/admin/salt/helper/SaltBuilder.java +++ b/src/test/java/com/uid2/admin/salt/helper/SaltBuilder.java @@ -14,6 +14,8 @@ public class SaltBuilder { private Instant refreshFrom = Instant.now(); private String currentSalt = null; private String previousSalt = null; + private SaltEntry.KeyMaterial currentKeySalt = null; + private SaltEntry.KeyMaterial previousKeySalt = null; private SaltBuilder() { } @@ -47,6 +49,11 @@ public SaltBuilder refreshFrom(Instant refreshFrom) { return this; } + public SaltBuilder currentSalt() { + this.currentSalt = "salt " + id; + return this; + } + public SaltBuilder currentSalt(String currentSalt) { this.currentSalt = currentSalt; return this; @@ -57,16 +64,34 @@ public SaltBuilder previousSalt(String previousSalt) { return this; } + public SaltBuilder currentKeySalt(int keyId) { + return this.currentKeySalt(keyId, "currentKeyKey" + id, "currentKeySalt" + id); + } + + public SaltBuilder previousKeySalt(int keyId) { + return this.previousKeySalt(keyId, "previousKeyKey" + id, "previousKeySalt" + id); + } + + public SaltBuilder currentKeySalt(int keyId, String key, String salt) { + this.currentKeySalt = new SaltEntry.KeyMaterial(keyId, key, salt); + return this; + } + + public SaltBuilder previousKeySalt(int keyId, String key, String salt) { + this.previousKeySalt = new SaltEntry.KeyMaterial(keyId, key, salt); + return this; + } + public SaltEntry build() { return new SaltEntry( id, Integer.toString(id), lastUpdated.toEpochMilli(), - currentSalt == null ? "salt " + id : currentSalt, + currentSalt, refreshFrom.toEpochMilli(), previousSalt, - null, - null + currentKeySalt, + previousKeySalt ); } } diff --git a/src/test/java/com/uid2/admin/salt/helper/SaltSnapshotBuilder.java b/src/test/java/com/uid2/admin/salt/helper/SaltSnapshotBuilder.java index 0cbb9ed8..b184c817 100644 --- a/src/test/java/com/uid2/admin/salt/helper/SaltSnapshotBuilder.java +++ b/src/test/java/com/uid2/admin/salt/helper/SaltSnapshotBuilder.java @@ -27,14 +27,14 @@ public static SaltSnapshotBuilder start() { public SaltSnapshotBuilder entries(int count, TargetDate lastUpdated) { for (int i = 0; i < count; ++i) { - entries.add(SaltBuilder.start().lastUpdated(lastUpdated).refreshFrom(lastUpdated.plusDays(30)).build()); + entries.add(SaltBuilder.start().lastUpdated(lastUpdated).refreshFrom(lastUpdated.plusDays(30)).currentSalt().build()); } return this; } public SaltSnapshotBuilder entries(int count, TargetDate lastUpdated, TargetDate refreshFrom) { for (int i = 0; i < count; ++i) { - entries.add(SaltBuilder.start().lastUpdated(lastUpdated).refreshFrom(refreshFrom).build()); + entries.add(SaltBuilder.start().lastUpdated(lastUpdated).refreshFrom(refreshFrom).currentSalt().build()); } return this; } diff --git a/src/test/java/com/uid2/admin/store/writer/EncryptedSaltStoreWriterTest.java b/src/test/java/com/uid2/admin/store/writer/EncryptedSaltStoreWriterTest.java index 5eb5db22..7c55c63d 100644 --- a/src/test/java/com/uid2/admin/store/writer/EncryptedSaltStoreWriterTest.java +++ b/src/test/java/com/uid2/admin/store/writer/EncryptedSaltStoreWriterTest.java @@ -241,12 +241,12 @@ private void assertWrittenFileEquals(String fileLocation, RotatingSaltProvider.S () -> assertEquals(entry.currentSalt(), fields[2]), () -> assertEquals(entry.refreshFrom(), parseLong(fields[3])), () -> assertEquals(entry.previousSalt(), fields[4]), - () -> assertEquals(entry.currentKey().id(), parseLong(fields[5])), - () -> assertEquals(entry.currentKey().key(), fields[6]), - () -> assertEquals(entry.currentKey().salt(), fields[7]), - () -> assertEquals(entry.previousKey().id(), parseLong(fields[8])), - () -> assertEquals(entry.previousKey().key(), fields[9]), - () -> assertEquals(entry.previousKey().salt(), fields[10]) + () -> assertEquals(entry.currentKeySalt().id(), parseLong(fields[5])), + () -> assertEquals(entry.currentKeySalt().key(), fields[6]), + () -> assertEquals(entry.currentKeySalt().salt(), fields[7]), + () -> assertEquals(entry.previousKeySalt().id(), parseLong(fields[8])), + () -> assertEquals(entry.previousKeySalt().key(), fields[9]), + () -> assertEquals(entry.previousKeySalt().salt(), fields[10]) ); } } diff --git a/src/test/java/com/uid2/admin/store/writer/SaltSerializerTest.java b/src/test/java/com/uid2/admin/store/writer/SaltSerializerTest.java index 58e29148..28214bb0 100644 --- a/src/test/java/com/uid2/admin/store/writer/SaltSerializerTest.java +++ b/src/test/java/com/uid2/admin/store/writer/SaltSerializerTest.java @@ -118,4 +118,26 @@ void toCsv_toleratesNullsInKeys() { assertThat(actual).isEqualTo(expected); } + + @Test + void toCsv_mixedNullsInKeysAndSalts() { + var expected = """ +1,100,,1000,previousSalt,0,currentKeyKey,currentKeySalt,,, +2,200,,2000,,0,currentKeyKey,currentKeySalt,1,previousKeyKey,previousKeySalt +3,300,salt,3000,,,,,1,previousKeyKey,previousKeySalt +4,400,,4000,,0,currentKeyKey,currentKeySalt,,, +"""; + var currentKey = new KeyMaterial(0, "currentKeyKey", "currentKeySalt"); + var previousKey = new KeyMaterial(1, "previousKeyKey", "previousKeySalt"); + + var salts = new SaltEntry[]{ + new SaltEntry(1, "hashedId1", 100, null, 1000L, "previousSalt", currentKey, null), + new SaltEntry(2, "hashedId2", 200, null, 2000L, null, currentKey, previousKey), + new SaltEntry(3, "hashedId3", 300, "salt", 3000L, null, null, previousKey), + new SaltEntry(4, "hashedId4", 400, null, 4000L, null, currentKey, null), + }; + var actual = SaltSerializer.toCsv(salts); + + assertThat(actual).isEqualTo(expected); + } } \ No newline at end of file