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