@@ -77,7 +77,7 @@ public class SaltService implements IService {
7777 private static final String OPERATOR_URL = "http://proxy:80" ;
7878 private static final Map <String , String > OPERATOR_HEADERS = Map .of ("Authorization" , String .format ("Bearer %s" , CLIENT_API_KEY ));
7979
80- private static final int IDENTITY_COUNT = 10_000 ;
80+ private static final int IDENTITY_COUNT = 1_000 ;
8181 private static final int TIMESTAMP_LENGTH = 8 ;
8282 private static final int IV_LENGTH = 12 ;
8383
@@ -110,13 +110,19 @@ public void setupRoutes(Router router) {
110110 }
111111 }, new AuditParams (List .of ("fraction" , "target_date" ), Collections .emptyList ()), Role .SUPER_USER , Role .SECRET_ROTATION ));
112112
113- router .post ("/api/salt/simulate/token" ).blockingHandler (auth .handle ((ctx ) -> {
113+ router .post ("/api/salt/fastForward" ).blockingHandler (auth .handle (ctx -> {
114+ synchronized (writeLock ) {
115+ this .handleSaltFastForward (ctx );
116+ }
117+ }, new AuditParams (List .of ("fraction" ), Collections .emptyList ()), Role .MAINTAINER , Role .SECRET_ROTATION ));
118+
119+ router .post ("/api/salt/simulateToken" ).blockingHandler (auth .handle (ctx -> {
114120 synchronized (writeLock ) {
115121 this .handleSaltSimulateToken (ctx );
116122 }
117- }, new AuditParams (List .of ("target_date" ), Collections .emptyList ()), Role .MAINTAINER , Role .SECRET_ROTATION ));
123+ }, new AuditParams (List .of ("fraction" , " target_date" ), Collections .emptyList ()), Role .MAINTAINER , Role .SECRET_ROTATION ));
118124
119- router .post ("/api/salt/simulate" ).blockingHandler (auth .handle (( ctx ) -> {
125+ router .post ("/api/salt/simulate" ).blockingHandler (auth .handle (ctx -> {
120126 synchronized (writeLock ) {
121127 this .handleSaltSimulate (ctx );
122128 }
@@ -221,26 +227,70 @@ private JsonObject toJson(RotatingSaltProvider.SaltSnapshot snapshot) {
221227 return jo ;
222228 }
223229
230+ private void handleSaltFastForward (RoutingContext rc ) {
231+ try {
232+ final double fraction = RequestUtil .getDouble (rc , "fraction" ).orElse (0.002740 );
233+ final int iterations = RequestUtil .getDouble (rc , "fast_forward_iterations" ).orElse (0.0 ).intValue ();
234+ final boolean enableV4 = RequestUtil .getBoolean (rc , "enable_v4" , false ).orElse (false );
235+
236+ TargetDate targetDate =
237+ RequestUtil .getDate (rc , "target_date" , DateTimeFormatter .ISO_LOCAL_DATE )
238+ .map (TargetDate ::new )
239+ .orElse (TargetDate .now ().plusDays (1 ));
240+
241+ saltProvider .loadContent ();
242+ storageManager .archiveSaltLocations ();
243+
244+ var snapshot = saltProvider .getSnapshots ().getLast ();
245+
246+ saltRotation .setEnableV4RawUid (enableV4 );
247+ var result = saltRotation .rotateSaltsFastForward (snapshot , SALT_ROTATION_AGE_THRESHOLDS , fraction , targetDate , iterations );
248+ if (!result .hasSnapshot ()) {
249+ ResponseUtil .error (rc , 200 , result .getReason ());
250+ return ;
251+ }
252+
253+ storageManager .upload (result .getSnapshot ());
254+
255+ rc .response ()
256+ .putHeader (HttpHeaders .CONTENT_TYPE , "application/json" )
257+ .end (toJson (result .getSnapshot ()).encode ());
258+ } catch (Exception e ) {
259+ LOGGER .error (e .getMessage (), e );
260+ rc .fail (500 , e );
261+ }
262+ }
263+
224264 private void handleSaltSimulateToken (RoutingContext rc ) {
225265 try {
226266 final double fraction = RequestUtil .getDouble (rc , "fraction" ).orElse (0.002740 );
227267 final int iterations = RequestUtil .getDouble (rc , "iterations" ).orElse (0.0 ).intValue ();
268+ final boolean enableV4 = RequestUtil .getBoolean (rc , "enable_v4" , false ).orElse (false );
228269
229270 TargetDate targetDate =
230271 RequestUtil .getDate (rc , "target_date" , DateTimeFormatter .ISO_LOCAL_DATE )
231272 .map (TargetDate ::new )
232273 .orElse (TargetDate .now ().plusDays (1 ));
233274
234- // Step 1. Run /v2/token/generate for 10,000 emails
275+ // Step 1. Run /v2/token/generate for all emails
235276 List <String > emails = new ArrayList <>();
277+ Map <String , Boolean > preRotationEmailToSaltMap = new HashMap <>();
278+ RotatingSaltProvider .SaltSnapshot snapshot = saltProvider .getSnapshots ().getLast ();
236279 for (int j = 0 ; j < IDENTITY_COUNT ; j ++) {
237280 String email = randomEmail ();
238281 emails .add (email );
282+
283+ SaltEntry salt = getSalt (email , snapshot );
284+ boolean isV4 = salt .currentKeySalt () != null && salt .currentKeySalt ().key () != null && salt .currentKeySalt ().salt () != null ;
285+ preRotationEmailToSaltMap .put (email , isV4 );
239286 }
240287
241288 Map <String , String > emailToRefreshTokenMap = new HashMap <>();
242289 Map <String , String > emailToRefreshResponseKeyMap = new HashMap <>();
243- for (String email : emails ) {
290+ for (int i = 0 ; i < emails .size (); i ++) {
291+ LOGGER .info ("Step 1 - Token Generate {}/{}" , i + 1 , emails .size ());
292+ String email = emails .get (i );
293+
244294 JsonNode tokens = v2TokenGenerate (email );
245295 String refreshToken = tokens .at ("/body/refresh_token" ).asText ();
246296 String refreshResponseKey = tokens .at ("/body/refresh_response_key" ).asText ();
@@ -250,23 +300,26 @@ private void handleSaltSimulateToken(RoutingContext rc) {
250300 }
251301
252302 // Step 2. Rotate salts
253- saltRotation .setEnableV4RawUid (true );
254- RotatingSaltProvider .SaltSnapshot snapshot = null ;
303+ saltRotation .setEnableV4RawUid (enableV4 );
255304 for (int i = 0 ; i < iterations ; i ++) {
256- snapshot = rotateSalts (rc , fraction , targetDate , i );
305+ LOGGER .info ("Step 2 - Rotate Salts {}/{}" , i + 1 , iterations );
306+ snapshot = rotateSalts (rc , fraction , targetDate .plusDays (i ), i );
257307 }
258308
259- // Step 3. Count how many emails are v2 vs v4 salts
260- Map <String , Boolean > emailToV4TokenMap = new HashMap <>();
309+ Map <String , Boolean > postRotationEmailToSaltMap = new HashMap <>();
261310 for (String email : emails ) {
262311 SaltEntry salt = getSalt (email , snapshot );
263312 boolean isV4 = salt .currentKeySalt () != null && salt .currentKeySalt ().key () != null && salt .currentKeySalt ().salt () != null ;
264- emailToV4TokenMap .put (email , isV4 );
313+ postRotationEmailToSaltMap .put (email , isV4 );
265314 }
266315
267316 // Step 4. Run /v2/token/refresh for all emails
317+ v2LoadSalts ();
268318 Map <String , Boolean > emailToRefreshSuccessMap = new HashMap <>();
269- for (String email : emails ) {
319+ for (int i = 0 ; i < emails .size (); i ++) {
320+ LOGGER .info ("Step 3 - Token Refresh {}/{}" , i + 1 , emails .size ());
321+ String email = emails .get (i );
322+
270323 try {
271324 JsonNode response = v2TokenRefresh (emailToRefreshTokenMap .get (email ), emailToRefreshResponseKeyMap .get (email ));
272325 emailToRefreshSuccessMap .put (email , response != null );
@@ -277,11 +330,14 @@ private void handleSaltSimulateToken(RoutingContext rc) {
277330 }
278331
279332 LOGGER .info (
280- "UID token simulation: success_count={}, failure_count={}, v4_count={}, v2_count={}" ,
333+ "UID token simulation: success_count={}, failure_count={}, " +
334+ "v2_to_v4_count={}, v4_to_v4_count={}, v4_to_v2_count={}, v2_to_v2_count={}" ,
281335 emailToRefreshSuccessMap .values ().stream ().filter (x -> x ).count (),
282336 emailToRefreshSuccessMap .values ().stream ().filter (x -> !x ).count (),
283- emailToV4TokenMap .values ().stream ().filter (x -> x ).count (),
284- emailToV4TokenMap .values ().stream ().filter (x -> !x ).count ());
337+ emails .stream ().filter (email -> !preRotationEmailToSaltMap .get (email ) && postRotationEmailToSaltMap .get (email )).count (),
338+ emails .stream ().filter (email -> preRotationEmailToSaltMap .get (email ) && postRotationEmailToSaltMap .get (email )).count (),
339+ emails .stream ().filter (email -> preRotationEmailToSaltMap .get (email ) && !postRotationEmailToSaltMap .get (email )).count (),
340+ emails .stream ().filter (email -> !preRotationEmailToSaltMap .get (email ) && !postRotationEmailToSaltMap .get (email )).count ());
285341
286342 rc .response ()
287343 .putHeader (HttpHeaders .CONTENT_TYPE , "application/json" )
@@ -296,9 +352,9 @@ private void handleSaltSimulate(RoutingContext rc) {
296352 try {
297353 final double fraction = RequestUtil .getDouble (rc , "fraction" ).orElse (0.002740 );
298354
299- final int preMigrationIterations = RequestUtil .getDouble (rc , "preMigrationIterations " ).orElse (0.0 ).intValue ();
300- final int migrationV4Iterations = RequestUtil .getDouble (rc , "migrationV4Iterations " ).orElse (0.0 ).intValue ();
301- final int migrationV2V3Iterations = RequestUtil .getDouble (rc , "migrationV2V3Iterations " ).orElse (0.0 ).intValue ();
355+ final int preMigrationIterations = RequestUtil .getDouble (rc , "pre_migration_iterations " ).orElse (0.0 ).intValue ();
356+ final int migrationV4Iterations = RequestUtil .getDouble (rc , "migration_v4_iterations " ).orElse (0.0 ).intValue ();
357+ final int migrationV2Iterations = RequestUtil .getDouble (rc , "migration_v2_iterations " ).orElse (0.0 ).intValue ();
302358
303359 TargetDate targetDate =
304360 RequestUtil .getDate (rc , "target_date" , DateTimeFormatter .ISO_LOCAL_DATE )
@@ -331,8 +387,8 @@ private void handleSaltSimulate(RoutingContext rc) {
331387 }
332388
333389 saltRotation .setEnableV4RawUid (false );
334- for (int i = 0 ; i < migrationV2V3Iterations ; i ++) {
335- LOGGER .info ("Step 3 - Migration V2/V3 Iteration {}/{}" , i + 1 , migrationV2V3Iterations );
390+ for (int i = 0 ; i < migrationV2Iterations ; i ++) {
391+ LOGGER .info ("Step 3 - Migration V2 Iteration {}/{}" , i + 1 , migrationV2Iterations );
336392 simulationIteration (rc , fraction , targetDate , preMigrationIterations + migrationV4Iterations + i , false , emails , emailToUidMapping );
337393 targetDate = targetDate .plusDays (1 );
338394 }
@@ -723,6 +779,10 @@ private String toBase64String(byte[] b) {
723779 return Base64 .getEncoder ().encodeToString (b );
724780 }
725781
782+ private void v2LoadSalts () throws Exception {
783+ HTTP_CLIENT .post (String .format ("%s/v2/salts/load" , OPERATOR_URL ), "" , OPERATOR_HEADERS );
784+ }
785+
726786 private JsonNode v3IdentityMap (List <String > emails ) throws Exception {
727787 StringBuilder reqBody = new StringBuilder ("{ \" email\" : [" );
728788 for (String email : emails ) {
0 commit comments