@@ -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,6 +227,40 @@ 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 );
@@ -231,16 +271,25 @@ private void handleSaltSimulateToken(RoutingContext rc) {
231271 .map (TargetDate ::new )
232272 .orElse (TargetDate .now ().plusDays (1 ));
233273
234- // Step 1. Run /v2/token/generate for 10,000 emails
274+ // Step 1. Run /v2/token/generate for all emails
235275 List <String > emails = new ArrayList <>();
276+ Map <String , Boolean > preRotationEmailToSaltMap = new HashMap <>();
277+ RotatingSaltProvider .SaltSnapshot snapshot = saltProvider .getSnapshots ().getLast ();
236278 for (int j = 0 ; j < IDENTITY_COUNT ; j ++) {
237279 String email = randomEmail ();
238280 emails .add (email );
281+
282+ SaltEntry salt = getSalt (email , snapshot );
283+ boolean isV4 = salt .currentKeySalt () != null && salt .currentKeySalt ().key () != null && salt .currentKeySalt ().salt () != null ;
284+ preRotationEmailToSaltMap .put (email , isV4 );
239285 }
240286
241287 Map <String , String > emailToRefreshTokenMap = new HashMap <>();
242288 Map <String , String > emailToRefreshResponseKeyMap = new HashMap <>();
243- for (String email : emails ) {
289+ for (int i = 0 ; i < emails .size (); i ++) {
290+ LOGGER .info ("Step 2 - Token Generate {}/{}" , i + 1 , emails .size ());
291+ String email = emails .get (i );
292+
244293 JsonNode tokens = v2TokenGenerate (email );
245294 String refreshToken = tokens .at ("/body/refresh_token" ).asText ();
246295 String refreshResponseKey = tokens .at ("/body/refresh_response_key" ).asText ();
@@ -251,22 +300,25 @@ private void handleSaltSimulateToken(RoutingContext rc) {
251300
252301 // Step 2. Rotate salts
253302 saltRotation .setEnableV4RawUid (true );
254- RotatingSaltProvider . SaltSnapshot snapshot = null ;
303+ snapshot = null ;
255304 for (int i = 0 ; i < iterations ; i ++) {
256305 snapshot = rotateSalts (rc , fraction , targetDate , i );
257306 }
258307
259- // Step 3. Count how many emails are v2 vs v4 salts
260- Map <String , Boolean > emailToV4TokenMap = new HashMap <>();
308+ Map <String , Boolean > postRotationEmailToSaltMap = new HashMap <>();
261309 for (String email : emails ) {
262310 SaltEntry salt = getSalt (email , snapshot );
263311 boolean isV4 = salt .currentKeySalt () != null && salt .currentKeySalt ().key () != null && salt .currentKeySalt ().salt () != null ;
264- emailToV4TokenMap .put (email , isV4 );
312+ postRotationEmailToSaltMap .put (email , isV4 );
265313 }
266314
267315 // Step 4. Run /v2/token/refresh for all emails
316+ v2LoadSalts ();
268317 Map <String , Boolean > emailToRefreshSuccessMap = new HashMap <>();
269- for (String email : emails ) {
318+ for (int i = 0 ; i < emails .size (); i ++) {
319+ LOGGER .info ("Step 4 - Token Refresh {}/{}" , i + 1 , emails .size ());
320+ String email = emails .get (i );
321+
270322 try {
271323 JsonNode response = v2TokenRefresh (emailToRefreshTokenMap .get (email ), emailToRefreshResponseKeyMap .get (email ));
272324 emailToRefreshSuccessMap .put (email , response != null );
@@ -277,11 +329,14 @@ private void handleSaltSimulateToken(RoutingContext rc) {
277329 }
278330
279331 LOGGER .info (
280- "UID token simulation: success_count={}, failure_count={}, v4_count={}, v2_count={}" ,
332+ "UID token simulation: success_count={}, failure_count={}, " +
333+ "v2_to_v4_count={}, v4_to_v4_count={}, v4_to_v2_count={}, v2_to_v2_count={}" ,
281334 emailToRefreshSuccessMap .values ().stream ().filter (x -> x ).count (),
282335 emailToRefreshSuccessMap .values ().stream ().filter (x -> !x ).count (),
283- emailToV4TokenMap .values ().stream ().filter (x -> x ).count (),
284- emailToV4TokenMap .values ().stream ().filter (x -> !x ).count ());
336+ emails .stream ().filter (email -> !preRotationEmailToSaltMap .get (email ) && postRotationEmailToSaltMap .get (email )).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 ());
285340
286341 rc .response ()
287342 .putHeader (HttpHeaders .CONTENT_TYPE , "application/json" )
@@ -296,9 +351,9 @@ private void handleSaltSimulate(RoutingContext rc) {
296351 try {
297352 final double fraction = RequestUtil .getDouble (rc , "fraction" ).orElse (0.002740 );
298353
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 ();
354+ final int preMigrationIterations = RequestUtil .getDouble (rc , "pre_migration_iterations " ).orElse (0.0 ).intValue ();
355+ final int migrationV4Iterations = RequestUtil .getDouble (rc , "migration_v4_iterations " ).orElse (0.0 ).intValue ();
356+ final int migrationV2Iterations = RequestUtil .getDouble (rc , "migration_v2_iterations " ).orElse (0.0 ).intValue ();
302357
303358 TargetDate targetDate =
304359 RequestUtil .getDate (rc , "target_date" , DateTimeFormatter .ISO_LOCAL_DATE )
@@ -331,8 +386,8 @@ private void handleSaltSimulate(RoutingContext rc) {
331386 }
332387
333388 saltRotation .setEnableV4RawUid (false );
334- for (int i = 0 ; i < migrationV2V3Iterations ; i ++) {
335- LOGGER .info ("Step 3 - Migration V2/V3 Iteration {}/{}" , i + 1 , migrationV2V3Iterations );
389+ for (int i = 0 ; i < migrationV2Iterations ; i ++) {
390+ LOGGER .info ("Step 3 - Migration V2 Iteration {}/{}" , i + 1 , migrationV2Iterations );
336391 simulationIteration (rc , fraction , targetDate , preMigrationIterations + migrationV4Iterations + i , false , emails , emailToUidMapping );
337392 targetDate = targetDate .plusDays (1 );
338393 }
@@ -723,6 +778,10 @@ private String toBase64String(byte[] b) {
723778 return Base64 .getEncoder ().encodeToString (b );
724779 }
725780
781+ private void v2LoadSalts () throws Exception {
782+ HTTP_CLIENT .post (String .format ("%s/v2/salts/load" , OPERATOR_URL ), "" , OPERATOR_HEADERS );
783+ }
784+
726785 private JsonNode v3IdentityMap (List <String > emails ) throws Exception {
727786 StringBuilder reqBody = new StringBuilder ("{ \" email\" : [" );
728787 for (String email : emails ) {
0 commit comments