Entirely discard the old message cache machinery.
This commit is contained in:
		
							parent
							
								
									6061d0603a
								
							
						
					
					
						commit
						18ecd748dd
					
				| 
						 | 
				
			
			@ -68,9 +68,7 @@ directory:
 | 
			
		|||
    reconciliationChunkIntervalMs: # CDS reconciliation chunk interval, in milliseconds
 | 
			
		||||
 | 
			
		||||
messageCache: # Redis server configuration for message store cache
 | 
			
		||||
  redis:
 | 
			
		||||
    url:
 | 
			
		||||
    replicaUrls:
 | 
			
		||||
  persistDelayMinutes:
 | 
			
		||||
 | 
			
		||||
  cluster:
 | 
			
		||||
    urls:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -150,7 +150,6 @@ import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
 | 
			
		|||
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
 | 
			
		||||
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
 | 
			
		||||
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
 | 
			
		||||
import org.whispersystems.textsecuregcm.workers.ScourMessageCacheCommand;
 | 
			
		||||
import org.whispersystems.textsecuregcm.workers.VacuumCommand;
 | 
			
		||||
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
 | 
			
		||||
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +182,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
			
		|||
    bootstrap.addCommand(new DeleteUserCommand());
 | 
			
		||||
    bootstrap.addCommand(new CertificateCommand());
 | 
			
		||||
    bootstrap.addCommand(new ZkParamsCommand());
 | 
			
		||||
    bootstrap.addCommand(new ScourMessageCacheCommand());
 | 
			
		||||
 | 
			
		||||
    bootstrap.addBundle(new NameableMigrationsBundle<WhisperServerConfiguration>("accountdb", "accountsdb.xml") {
 | 
			
		||||
      @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,11 +7,6 @@ import javax.validation.constraints.NotNull;
 | 
			
		|||
 | 
			
		||||
public class MessageCacheConfiguration {
 | 
			
		||||
 | 
			
		||||
  @JsonProperty
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Valid
 | 
			
		||||
  private RedisConfiguration redis;
 | 
			
		||||
 | 
			
		||||
  @JsonProperty
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Valid
 | 
			
		||||
| 
						 | 
				
			
			@ -20,10 +15,6 @@ public class MessageCacheConfiguration {
 | 
			
		|||
  @JsonProperty
 | 
			
		||||
  private int persistDelayMinutes = 10;
 | 
			
		||||
 | 
			
		||||
  public RedisConfiguration getRedisConfiguration() {
 | 
			
		||||
    return redis;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public RedisClusterConfiguration getRedisClusterConfiguration() {
 | 
			
		||||
    return cluster;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,270 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.storage;
 | 
			
		||||
 | 
			
		||||
import com.codahale.metrics.MetricRegistry;
 | 
			
		||||
import com.codahale.metrics.SharedMetricRegistries;
 | 
			
		||||
import com.codahale.metrics.Timer;
 | 
			
		||||
import com.google.protobuf.InvalidProtocolBufferException;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.LuaScript;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
 | 
			
		||||
import org.whispersystems.textsecuregcm.util.Constants;
 | 
			
		||||
import org.whispersystems.textsecuregcm.util.Pair;
 | 
			
		||||
import redis.clients.jedis.Jedis;
 | 
			
		||||
import redis.clients.util.SafeEncoder;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.LinkedList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
			
		||||
 | 
			
		||||
public class MessagesCache implements UserMessagesCache {
 | 
			
		||||
 | 
			
		||||
  private static final Logger         logger            = LoggerFactory.getLogger(MessagesCache.class);
 | 
			
		||||
 | 
			
		||||
  private static final MetricRegistry metricRegistry    = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
 | 
			
		||||
  private static final Timer          insertTimer       = metricRegistry.timer(name(MessagesCache.class, "insert"      ));
 | 
			
		||||
  private static final Timer          removeByIdTimer   = metricRegistry.timer(name(MessagesCache.class, "removeById"  ));
 | 
			
		||||
  private static final Timer          removeByNameTimer = metricRegistry.timer(name(MessagesCache.class, "removeByName"));
 | 
			
		||||
  private static final Timer          removeByGuidTimer = metricRegistry.timer(name(MessagesCache.class, "removeByGuid"));
 | 
			
		||||
  private static final Timer          getTimer          = metricRegistry.timer(name(MessagesCache.class, "get"         ));
 | 
			
		||||
  private static final Timer          clearAccountTimer = metricRegistry.timer(name(MessagesCache.class, "clearAccount"));
 | 
			
		||||
  private static final Timer          clearDeviceTimer  = metricRegistry.timer(name(MessagesCache.class, "clearDevice" ));
 | 
			
		||||
 | 
			
		||||
  private final ReplicatedJedisPool jedisPool;
 | 
			
		||||
 | 
			
		||||
  private final InsertOperation insertOperation;
 | 
			
		||||
  private final RemoveOperation removeOperation;
 | 
			
		||||
  private final GetOperation    getOperation;
 | 
			
		||||
 | 
			
		||||
  public MessagesCache(ReplicatedJedisPool jedisPool) throws IOException {
 | 
			
		||||
    this.jedisPool        = jedisPool;
 | 
			
		||||
 | 
			
		||||
    this.insertOperation  = new InsertOperation(jedisPool);
 | 
			
		||||
    this.removeOperation  = new RemoveOperation(jedisPool);
 | 
			
		||||
    this.getOperation     = new GetOperation(jedisPool);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public long insert(UUID guid, String destination, final UUID destinationUuid, long destinationDevice, Envelope message) {
 | 
			
		||||
    final Envelope messageWithGuid = message.toBuilder().setServerGuid(guid.toString()).build();
 | 
			
		||||
 | 
			
		||||
    Timer.Context timer = insertTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      return insertOperation.insert(guid, destination, destinationDevice, System.currentTimeMillis(), messageWithGuid);
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Optional<OutgoingMessageEntity> remove(String destination, final UUID destinationUuid, long destinationDevice, long id) {
 | 
			
		||||
    OutgoingMessageEntity removedMessageEntity = null;
 | 
			
		||||
 | 
			
		||||
    try (Jedis         jedis   = jedisPool.getWriteResource();
 | 
			
		||||
         Timer.Context ignored = removeByIdTimer.time())
 | 
			
		||||
    {
 | 
			
		||||
      byte[] serialized = removeOperation.remove(jedis, destination, destinationDevice, id);
 | 
			
		||||
 | 
			
		||||
      if (serialized != null) {
 | 
			
		||||
        removedMessageEntity = UserMessagesCache.constructEntityFromEnvelope(id, Envelope.parseFrom(serialized));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
      logger.warn("Failed to parse envelope", e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Optional.ofNullable(removedMessageEntity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Optional<OutgoingMessageEntity> remove(String destination, final UUID destinationUuid, long destinationDevice, String sender, long timestamp) {
 | 
			
		||||
    OutgoingMessageEntity removedMessageEntity = null;
 | 
			
		||||
    Timer.Context timer = removeByNameTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      byte[] serialized = removeOperation.remove(destination, destinationDevice, sender, timestamp);
 | 
			
		||||
 | 
			
		||||
      if (serialized != null) {
 | 
			
		||||
        removedMessageEntity = UserMessagesCache.constructEntityFromEnvelope(0, Envelope.parseFrom(serialized));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
      logger.warn("Failed to parse envelope", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Optional.ofNullable(removedMessageEntity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Optional<OutgoingMessageEntity> remove(String destination, final UUID destinationUuid, long destinationDevice, UUID guid) {
 | 
			
		||||
    OutgoingMessageEntity removedMessageEntity = null;
 | 
			
		||||
    Timer.Context timer = removeByGuidTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      byte[] serialized = removeOperation.remove(destination, destinationDevice, guid);
 | 
			
		||||
 | 
			
		||||
      if (serialized != null) {
 | 
			
		||||
        removedMessageEntity = UserMessagesCache.constructEntityFromEnvelope(0, Envelope.parseFrom(serialized));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
      logger.warn("Failed to parse envelope", e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Optional.ofNullable(removedMessageEntity);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public List<OutgoingMessageEntity> get(String destination, final UUID destinationUuid, long destinationDevice, int limit) {
 | 
			
		||||
    Timer.Context timer = getTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      List<OutgoingMessageEntity> results = new LinkedList<>();
 | 
			
		||||
      Key                         key     = new Key(destination, destinationDevice);
 | 
			
		||||
      List<Pair<byte[], Double>>  items   = getOperation.getItems(key.getUserMessageQueue(), key.getUserMessageQueuePersistInProgress(), limit);
 | 
			
		||||
 | 
			
		||||
      for (Pair<byte[], Double> item : items) {
 | 
			
		||||
        try {
 | 
			
		||||
          long     id      = item.second().longValue();
 | 
			
		||||
          Envelope message = Envelope.parseFrom(item.first());
 | 
			
		||||
          results.add(UserMessagesCache.constructEntityFromEnvelope(id, message));
 | 
			
		||||
        } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
          logger.warn("Failed to parse envelope", e);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return results;
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clear(String destination, final UUID destinationUuid) {
 | 
			
		||||
    Timer.Context timer = clearAccountTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      for (int i = 1; i < 255; i++) {
 | 
			
		||||
        clear(destination, destinationUuid, i);
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void clear(String destination, final UUID destinationUuid, long deviceId) {
 | 
			
		||||
    Timer.Context timer = clearDeviceTimer.time();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      removeOperation.clear(destination, deviceId);
 | 
			
		||||
    } finally {
 | 
			
		||||
      timer.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class InsertOperation {
 | 
			
		||||
    private final LuaScript insert;
 | 
			
		||||
 | 
			
		||||
    InsertOperation(ReplicatedJedisPool jedisPool) throws IOException {
 | 
			
		||||
      this.insert = LuaScript.fromResource(jedisPool, "lua/insert_item.lua");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long insert(UUID guid, String destination, long destinationDevice, long timestamp, Envelope message) {
 | 
			
		||||
      Key    key    = new Key(destination, destinationDevice);
 | 
			
		||||
      String sender = message.hasSource() ? (message.getSource() + "::" + message.getTimestamp()) : "nil";
 | 
			
		||||
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex());
 | 
			
		||||
      List<byte[]> args = Arrays.asList(message.toByteArray(), String.valueOf(timestamp).getBytes(), sender.getBytes(), guid.toString().getBytes());
 | 
			
		||||
 | 
			
		||||
      return (long)insert.execute(keys, args);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class RemoveOperation {
 | 
			
		||||
 | 
			
		||||
    private final LuaScript removeById;
 | 
			
		||||
    private final LuaScript removeBySender;
 | 
			
		||||
    private final LuaScript removeByGuid;
 | 
			
		||||
    private final LuaScript removeQueue;
 | 
			
		||||
 | 
			
		||||
    RemoveOperation(ReplicatedJedisPool jedisPool) throws IOException {
 | 
			
		||||
      this.removeById     = LuaScript.fromResource(jedisPool, "lua/remove_item_by_id.lua"    );
 | 
			
		||||
      this.removeBySender = LuaScript.fromResource(jedisPool, "lua/remove_item_by_sender.lua");
 | 
			
		||||
      this.removeByGuid   = LuaScript.fromResource(jedisPool, "lua/remove_item_by_guid.lua"  );
 | 
			
		||||
      this.removeQueue    = LuaScript.fromResource(jedisPool, "lua/remove_queue.lua"         );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] remove(Jedis jedis, String destination, long destinationDevice, long id) {
 | 
			
		||||
      Key key = new Key(destination, destinationDevice);
 | 
			
		||||
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex());
 | 
			
		||||
      List<byte[]> args = Collections.singletonList(String.valueOf(id).getBytes());
 | 
			
		||||
 | 
			
		||||
      return (byte[])this.removeById.execute(jedis, keys, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] remove(String destination, long destinationDevice, String sender, long timestamp) {
 | 
			
		||||
      Key    key       = new Key(destination, destinationDevice);
 | 
			
		||||
      String senderKey = sender + "::" + timestamp;
 | 
			
		||||
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex());
 | 
			
		||||
      List<byte[]> args = Collections.singletonList(senderKey.getBytes());
 | 
			
		||||
 | 
			
		||||
      return (byte[])this.removeBySender.execute(keys, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] remove(String destination, long destinationDevice, UUID guid) {
 | 
			
		||||
      Key key = new Key(destination, destinationDevice);
 | 
			
		||||
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex());
 | 
			
		||||
      List<byte[]> args = Collections.singletonList(guid.toString().getBytes());
 | 
			
		||||
 | 
			
		||||
      return (byte[])this.removeByGuid.execute(keys, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void clear(String destination, long deviceId) {
 | 
			
		||||
      Key key = new Key(destination, deviceId);
 | 
			
		||||
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(key.getUserMessageQueue(), key.getUserMessageQueueMetadata(), Key.getUserMessageQueueIndex());
 | 
			
		||||
      List<byte[]> args = new LinkedList<>();
 | 
			
		||||
 | 
			
		||||
      this.removeQueue.execute(keys, args);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class GetOperation {
 | 
			
		||||
 | 
			
		||||
    private final LuaScript getItems;
 | 
			
		||||
 | 
			
		||||
    GetOperation(ReplicatedJedisPool jedisPool) throws IOException {
 | 
			
		||||
      this.getItems  = LuaScript.fromResource(jedisPool, "lua/get_items.lua");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    List<Pair<byte[], Double>> getItems(byte[] queue, byte[] lock, int limit) {
 | 
			
		||||
      List<byte[]> keys = Arrays.asList(queue, lock);
 | 
			
		||||
      List<byte[]> args = Collections.singletonList(String.valueOf(limit).getBytes());
 | 
			
		||||
 | 
			
		||||
      Iterator<byte[]>           results = ((List<byte[]>) getItems.execute(keys, args)).iterator();
 | 
			
		||||
      List<Pair<byte[], Double>> items   = new LinkedList<>();
 | 
			
		||||
 | 
			
		||||
      while (results.hasNext()) {
 | 
			
		||||
        items.add(new Pair<>(results.next(), Double.valueOf(SafeEncoder.encode(results.next()))));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return items;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ import java.util.concurrent.ExecutorService;
 | 
			
		|||
 | 
			
		||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
			
		||||
 | 
			
		||||
public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String, String> implements UserMessagesCache, Managed {
 | 
			
		||||
public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String, String> implements Managed {
 | 
			
		||||
 | 
			
		||||
    private final FaultTolerantRedisCluster                     redisCluster;
 | 
			
		||||
    private final FaultTolerantPubSubConnection<String, String> pubSubConnection;
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +123,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public long insert(final UUID guid, final String destination, final UUID destinationUuid, final long destinationDevice, final MessageProtos.Envelope message) {
 | 
			
		||||
        final MessageProtos.Envelope messageWithGuid = message.toBuilder().setServerGuid(guid.toString()).build();
 | 
			
		||||
        final String                 sender          = message.hasSource() ? (message.getSource() + "::" + message.getTimestamp()) : "nil";
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +137,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
                                                   guid.toString().getBytes(StandardCharsets.UTF_8))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Optional<OutgoingMessageEntity> remove(final String destination, final UUID destinationUuid, final long destinationDevice, final long id) {
 | 
			
		||||
        try {
 | 
			
		||||
            final byte[] serialized = (byte[])Metrics.timer(REMOVE_TIMER_NAME, REMOVE_METHOD_TAG, REMOVE_METHOD_ID).record(() ->
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +147,7 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
            if (serialized != null) {
 | 
			
		||||
                return Optional.of(UserMessagesCache.constructEntityFromEnvelope(id, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
                return Optional.of(constructEntityFromEnvelope(id, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final InvalidProtocolBufferException e) {
 | 
			
		||||
            logger.warn("Failed to parse envelope", e);
 | 
			
		||||
| 
						 | 
				
			
			@ -158,7 +156,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        return Optional.empty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Optional<OutgoingMessageEntity> remove(final String destination, final UUID destinationUuid, final long destinationDevice, final String sender, final long timestamp) {
 | 
			
		||||
        try {
 | 
			
		||||
            final byte[] serialized = (byte[])Metrics.timer(REMOVE_TIMER_NAME, REMOVE_METHOD_TAG, REMOVE_METHOD_SENDER).record(() ->
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +165,7 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
                                                       List.of((sender + "::" + timestamp).getBytes(StandardCharsets.UTF_8))));
 | 
			
		||||
 | 
			
		||||
            if (serialized != null) {
 | 
			
		||||
                return Optional.of(UserMessagesCache.constructEntityFromEnvelope(0, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
                return Optional.of(constructEntityFromEnvelope(0, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final InvalidProtocolBufferException e) {
 | 
			
		||||
            logger.warn("Failed to parse envelope", e);
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +174,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        return Optional.empty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Optional<OutgoingMessageEntity> remove(final String destination, final UUID destinationUuid, final long destinationDevice, final UUID messageGuid) {
 | 
			
		||||
        try {
 | 
			
		||||
            final byte[] serialized = (byte[])Metrics.timer(REMOVE_TIMER_NAME, REMOVE_METHOD_TAG, REMOVE_METHOD_UUID).record(() ->
 | 
			
		||||
| 
						 | 
				
			
			@ -187,7 +183,7 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
                                                     List.of(messageGuid.toString().getBytes(StandardCharsets.UTF_8))));
 | 
			
		||||
 | 
			
		||||
            if (serialized != null) {
 | 
			
		||||
                return Optional.of(UserMessagesCache.constructEntityFromEnvelope(0, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
                return Optional.of(constructEntityFromEnvelope(0, MessageProtos.Envelope.parseFrom(serialized)));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (final InvalidProtocolBufferException e) {
 | 
			
		||||
            logger.warn("Failed to parse envelope", e);
 | 
			
		||||
| 
						 | 
				
			
			@ -196,7 +192,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        return Optional.empty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    public List<OutgoingMessageEntity> get(final String destination, final UUID destinationUuid, final long destinationDevice, final int limit) {
 | 
			
		||||
        return getMessagesTimer.record(() -> {
 | 
			
		||||
| 
						 | 
				
			
			@ -214,7 +209,7 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
                        final MessageProtos.Envelope message = MessageProtos.Envelope.parseFrom(queueItems.get(i));
 | 
			
		||||
                        final long id = Long.parseLong(new String(queueItems.get(i + 1), StandardCharsets.UTF_8));
 | 
			
		||||
 | 
			
		||||
                        messageEntities.add(UserMessagesCache.constructEntityFromEnvelope(id, message));
 | 
			
		||||
                        messageEntities.add(constructEntityFromEnvelope(id, message));
 | 
			
		||||
                    } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
                        logger.warn("Failed to parse envelope", e);
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +252,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear(final String destination, final UUID destinationUuid) {
 | 
			
		||||
        // TODO Remove null check in a fully UUID-based world
 | 
			
		||||
        if (destinationUuid != null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -267,7 +261,6 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear(final String destination, final UUID destinationUuid, final long deviceId) {
 | 
			
		||||
        clearQueueTimer.record(() ->
 | 
			
		||||
                removeQueueScript.executeBinary(List.of(getMessageQueueKey(destinationUuid, deviceId),
 | 
			
		||||
| 
						 | 
				
			
			@ -354,6 +347,21 @@ public class RedisClusterMessagesCache extends RedisClusterPubSubAdapter<String,
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    static OutgoingMessageEntity constructEntityFromEnvelope(long id, MessageProtos.Envelope envelope) {
 | 
			
		||||
        return new OutgoingMessageEntity(id, true,
 | 
			
		||||
                envelope.hasServerGuid() ? UUID.fromString(envelope.getServerGuid()) : null,
 | 
			
		||||
                envelope.getType().getNumber(),
 | 
			
		||||
                envelope.getRelay(),
 | 
			
		||||
                envelope.getTimestamp(),
 | 
			
		||||
                envelope.getSource(),
 | 
			
		||||
                envelope.hasSourceUuid() ? UUID.fromString(envelope.getSourceUuid()) : null,
 | 
			
		||||
                envelope.getSourceDevice(),
 | 
			
		||||
                envelope.hasLegacyMessage() ? envelope.getLegacyMessage().toByteArray() : null,
 | 
			
		||||
                envelope.hasContent() ? envelope.getContent().toByteArray() : null,
 | 
			
		||||
                envelope.hasServerTimestamp() ? envelope.getServerTimestamp() : 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    static String getQueueName(final UUID accountUuid, final long deviceId) {
 | 
			
		||||
        return accountUuid + "::" + deviceId;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.storage;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public interface UserMessagesCache {
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    static OutgoingMessageEntity constructEntityFromEnvelope(long id, MessageProtos.Envelope envelope) {
 | 
			
		||||
      return new OutgoingMessageEntity(id, true,
 | 
			
		||||
                                       envelope.hasServerGuid() ? UUID.fromString(envelope.getServerGuid()) : null,
 | 
			
		||||
                                       envelope.getType().getNumber(),
 | 
			
		||||
                                       envelope.getRelay(),
 | 
			
		||||
                                       envelope.getTimestamp(),
 | 
			
		||||
                                       envelope.getSource(),
 | 
			
		||||
                                       envelope.hasSourceUuid() ? UUID.fromString(envelope.getSourceUuid()) : null,
 | 
			
		||||
                                       envelope.getSourceDevice(),
 | 
			
		||||
                                       envelope.hasLegacyMessage() ? envelope.getLegacyMessage().toByteArray() : null,
 | 
			
		||||
                                       envelope.hasContent() ? envelope.getContent().toByteArray() : null,
 | 
			
		||||
                                       envelope.hasServerTimestamp() ? envelope.getServerTimestamp() : 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    long insert(UUID guid, String destination, UUID destinationUuid, long destinationDevice, MessageProtos.Envelope message);
 | 
			
		||||
 | 
			
		||||
    Optional<OutgoingMessageEntity> remove(String destination, UUID destinationUuid, long destinationDevice, long id);
 | 
			
		||||
 | 
			
		||||
    Optional<OutgoingMessageEntity> remove(String destination, UUID destinationUuid, long destinationDevice, String sender, long timestamp);
 | 
			
		||||
 | 
			
		||||
    Optional<OutgoingMessageEntity> remove(String destination, UUID destinationUuid, long destinationDevice, UUID guid);
 | 
			
		||||
 | 
			
		||||
    List<OutgoingMessageEntity> get(String destination, UUID destinationUuid, long destinationDevice, int limit);
 | 
			
		||||
 | 
			
		||||
    void clear(String destination, UUID destinationUuid);
 | 
			
		||||
 | 
			
		||||
    void clear(String destination, UUID destinationUuid, long deviceId);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,116 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.workers;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.protobuf.InvalidProtocolBufferException;
 | 
			
		||||
import io.dropwizard.cli.ConfiguredCommand;
 | 
			
		||||
import io.dropwizard.setup.Bootstrap;
 | 
			
		||||
import io.lettuce.core.ScanArgs;
 | 
			
		||||
import io.lettuce.core.ScanIterator;
 | 
			
		||||
import io.lettuce.core.ScoredValue;
 | 
			
		||||
import net.sourceforge.argparse4j.inf.Namespace;
 | 
			
		||||
import org.jdbi.v3.core.Jdbi;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
 | 
			
		||||
import org.whispersystems.textsecuregcm.configuration.DatabaseConfiguration;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
 | 
			
		||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
 | 
			
		||||
import org.whispersystems.textsecuregcm.storage.Messages;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class ScourMessageCacheCommand extends ConfiguredCommand<WhisperServerConfiguration> {
 | 
			
		||||
 | 
			
		||||
    private FaultTolerantRedisClient redisClient;
 | 
			
		||||
    private Messages                 messageDatabase;
 | 
			
		||||
 | 
			
		||||
    private static final int MESSAGE_PAGE_SIZE = 100;
 | 
			
		||||
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(ScourMessageCacheCommand.class);
 | 
			
		||||
 | 
			
		||||
    public ScourMessageCacheCommand() {
 | 
			
		||||
        super("scourmessagecache", "Persist and remove all message queues from the old message cache");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("ConstantConditions")
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void run(final Bootstrap<WhisperServerConfiguration> bootstrap, final Namespace namespace, final WhisperServerConfiguration config) {
 | 
			
		||||
        final DatabaseConfiguration messageDbConfig = config.getMessageStoreConfiguration();
 | 
			
		||||
        final Jdbi messageJdbi                      = Jdbi.create(messageDbConfig.getUrl(), messageDbConfig.getUser(), messageDbConfig.getPassword());
 | 
			
		||||
        final FaultTolerantDatabase messageDatabase = new FaultTolerantDatabase("message_database", messageJdbi, config.getMessageStoreConfiguration().getCircuitBreakerConfiguration());
 | 
			
		||||
 | 
			
		||||
        this.setMessageDatabase(new Messages(messageDatabase));
 | 
			
		||||
        this.setRedisClient(new FaultTolerantRedisClient("scourMessageCacheClient", config.getMessageCacheConfiguration().getRedisConfiguration()));
 | 
			
		||||
 | 
			
		||||
        scourMessageCache();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    void setRedisClient(final FaultTolerantRedisClient redisClient) {
 | 
			
		||||
        this.redisClient = redisClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    void setMessageDatabase(final Messages messageDatabase) {
 | 
			
		||||
        this.messageDatabase = messageDatabase;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    void scourMessageCache() {
 | 
			
		||||
        redisClient.useClient(connection -> ScanIterator.scan(connection.sync(), ScanArgs.Builder.matches("user_queue::*"))
 | 
			
		||||
                                                        .stream()
 | 
			
		||||
                                                        .forEach(this::persistQueue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    void persistQueue(final String queueKey) {
 | 
			
		||||
        final String accountNumber;
 | 
			
		||||
        {
 | 
			
		||||
            final int startOfAccountNumber = queueKey.indexOf("::");
 | 
			
		||||
            accountNumber = queueKey.substring(startOfAccountNumber + 2, queueKey.indexOf("::", startOfAccountNumber + 1));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final long          deviceId      = Long.parseLong(queueKey.substring(queueKey.lastIndexOf("::") + 2));
 | 
			
		||||
        final byte[]        queueKeyBytes = queueKey.getBytes(StandardCharsets.UTF_8);
 | 
			
		||||
 | 
			
		||||
        int                       messageCount  = 0;
 | 
			
		||||
        List<ScoredValue<byte[]>> messages;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            final int start = messageCount;
 | 
			
		||||
 | 
			
		||||
            messages = redisClient.withBinaryClient(connection -> connection.sync().zrangeWithScores(queueKeyBytes, start, start + MESSAGE_PAGE_SIZE));
 | 
			
		||||
 | 
			
		||||
            for (final ScoredValue<byte[]> scoredValue : messages) {
 | 
			
		||||
                persistMessage(accountNumber, deviceId, scoredValue.getValue());
 | 
			
		||||
                messageCount++;
 | 
			
		||||
            }
 | 
			
		||||
        } while (!messages.isEmpty());
 | 
			
		||||
 | 
			
		||||
        redisClient.useClient(connection -> {
 | 
			
		||||
            final String accountNumberAndDeviceId = accountNumber + "::" + deviceId;
 | 
			
		||||
 | 
			
		||||
            connection.async().del("user_queue::" + accountNumberAndDeviceId,
 | 
			
		||||
                                  "user_queue_metadata::" + accountNumberAndDeviceId,
 | 
			
		||||
                                  "user_queue_persisting::" + accountNumberAndDeviceId);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        log.info("Persisted a queue with {} messages", messageCount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void persistMessage(final String accountNumber, final long deviceId, final byte[] message) {
 | 
			
		||||
        try {
 | 
			
		||||
            MessageProtos.Envelope envelope = MessageProtos.Envelope.parseFrom(message);
 | 
			
		||||
            UUID                   guid     = envelope.hasServerGuid() ? UUID.fromString(envelope.getServerGuid()) : null;
 | 
			
		||||
 | 
			
		||||
            envelope = envelope.toBuilder().clearServerGuid().build();
 | 
			
		||||
 | 
			
		||||
            messageDatabase.store(guid, envelope, accountNumber, deviceId);
 | 
			
		||||
        } catch (InvalidProtocolBufferException e) {
 | 
			
		||||
            log.error("Error parsing envelope", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,158 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.storage;
 | 
			
		||||
 | 
			
		||||
import com.google.protobuf.ByteString;
 | 
			
		||||
import junitparams.JUnitParamsRunner;
 | 
			
		||||
import junitparams.Parameters;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
 | 
			
		||||
@RunWith(JUnitParamsRunner.class)
 | 
			
		||||
public abstract class AbstractMessagesCacheTest extends AbstractRedisClusterTest {
 | 
			
		||||
 | 
			
		||||
    private static final String DESTINATION_ACCOUNT   = "+18005551234";
 | 
			
		||||
    private static final UUID   DESTINATION_UUID      = UUID.randomUUID();
 | 
			
		||||
    private static final int    DESTINATION_DEVICE_ID = 7;
 | 
			
		||||
 | 
			
		||||
    private final Random random          = new Random();
 | 
			
		||||
    private       long   serialTimestamp = 0;
 | 
			
		||||
 | 
			
		||||
    protected abstract UserMessagesCache getMessagesCache();
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testInsert(final boolean sealedSender) {
 | 
			
		||||
        final UUID messageGuid = UUID.randomUUID();
 | 
			
		||||
        assertTrue(getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, generateRandomMessage(messageGuid, sealedSender)) > 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testRemoveById(final boolean sealedSender) {
 | 
			
		||||
        final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
        final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
        final long                            messageId           = getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageId);
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(UserMessagesCache.constructEntityFromEnvelope(messageId, message), maybeRemovedMessage.get());
 | 
			
		||||
        assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testRemoveBySender() {
 | 
			
		||||
        final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
        final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, false);
 | 
			
		||||
 | 
			
		||||
        getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp());
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(UserMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
 | 
			
		||||
        assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testRemoveByUUID(final boolean sealedSender) {
 | 
			
		||||
        final UUID messageGuid = UUID.randomUUID();
 | 
			
		||||
 | 
			
		||||
        assertEquals(Optional.empty(), getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageGuid));
 | 
			
		||||
 | 
			
		||||
        final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
        getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = getMessagesCache().remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageGuid);
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(UserMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testGetMessages(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        final List<OutgoingMessageEntity> expectedMessages = new ArrayList<>(messageCount);
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
            final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
            final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
            final long                   messageId   = getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
 | 
			
		||||
            expectedMessages.add(UserMessagesCache.constructEntityFromEnvelope(messageId, message));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertEquals(expectedMessages, getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testClearQueueForDevice(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
 | 
			
		||||
            for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
                final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
                final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
                getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, deviceId, message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getMessagesCache().clear(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID);
 | 
			
		||||
 | 
			
		||||
        assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
        assertEquals(messageCount, getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID + 1, messageCount).size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testClearQueueForAccount(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
 | 
			
		||||
            for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
                final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
                final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
                getMessagesCache().insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, deviceId, message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getMessagesCache().clear(DESTINATION_ACCOUNT, DESTINATION_UUID);
 | 
			
		||||
 | 
			
		||||
        assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
        assertEquals(Collections.emptyList(), getMessagesCache().get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID + 1, messageCount));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final boolean sealedSender) {
 | 
			
		||||
        final MessageProtos.Envelope.Builder envelopeBuilder = MessageProtos.Envelope.newBuilder()
 | 
			
		||||
                .setTimestamp(serialTimestamp++)
 | 
			
		||||
                .setServerTimestamp(serialTimestamp++)
 | 
			
		||||
                .setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
 | 
			
		||||
                .setType(MessageProtos.Envelope.Type.CIPHERTEXT)
 | 
			
		||||
                .setServerGuid(messageGuid.toString());
 | 
			
		||||
 | 
			
		||||
        if (!sealedSender) {
 | 
			
		||||
            envelopeBuilder.setSourceDevice(random.nextInt(256))
 | 
			
		||||
                    .setSource("+1" + RandomStringUtils.randomNumeric(10));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return envelopeBuilder.build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,41 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.storage;
 | 
			
		||||
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
 | 
			
		||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
 | 
			
		||||
import redis.embedded.RedisServer;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
 | 
			
		||||
public class MessagesCacheTest extends AbstractMessagesCacheTest {
 | 
			
		||||
 | 
			
		||||
    private RedisServer   redisServer;
 | 
			
		||||
    private MessagesCache messagesCache;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception {
 | 
			
		||||
        redisServer = new RedisServer(AbstractRedisClusterTest.getNextRedisClusterPort());
 | 
			
		||||
        redisServer.start();
 | 
			
		||||
 | 
			
		||||
        final String              redisUrl      = String.format("redis://127.0.0.1:%d", redisServer.ports().get(0));
 | 
			
		||||
        final RedisClientFactory  clientFactory = new RedisClientFactory("message-cache-test", redisUrl, List.of(redisUrl), new CircuitBreakerConfiguration());
 | 
			
		||||
        final ReplicatedJedisPool jedisPool     = clientFactory.getRedisClientPool();
 | 
			
		||||
 | 
			
		||||
        messagesCache = new MessagesCache(jedisPool);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @After
 | 
			
		||||
    public void tearDown() {
 | 
			
		||||
        redisServer.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected UserMessagesCache getMessagesCache() {
 | 
			
		||||
        return messagesCache;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +1,24 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.storage;
 | 
			
		||||
 | 
			
		||||
import com.google.protobuf.ByteString;
 | 
			
		||||
import io.lettuce.core.cluster.SlotHash;
 | 
			
		||||
import junitparams.JUnitParamsRunner;
 | 
			
		||||
import junitparams.Parameters;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
| 
						 | 
				
			
			@ -17,15 +28,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		|||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
 | 
			
		||||
public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
 | 
			
		||||
@RunWith(JUnitParamsRunner.class)
 | 
			
		||||
public class RedisClusterMessagesCacheTest extends AbstractRedisClusterTest {
 | 
			
		||||
 | 
			
		||||
    private ExecutorService           notificationExecutorService;
 | 
			
		||||
    private RedisClusterMessagesCache messagesCache;
 | 
			
		||||
 | 
			
		||||
    private final Random random          = new Random();
 | 
			
		||||
    private       long   serialTimestamp = 0;
 | 
			
		||||
 | 
			
		||||
    private static final String DESTINATION_ACCOUNT   = "+18005551234";
 | 
			
		||||
    private static final UUID   DESTINATION_UUID      = UUID.randomUUID();
 | 
			
		||||
    private static final int    DESTINATION_DEVICE_ID = 7;
 | 
			
		||||
 | 
			
		||||
    private ExecutorService           notificationExecutorService;
 | 
			
		||||
    private RedisClusterMessagesCache messagesCache;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,9 +64,129 @@ public class RedisClusterMessagesCacheTest extends AbstractMessagesCacheTest {
 | 
			
		|||
        super.tearDown();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected UserMessagesCache getMessagesCache() {
 | 
			
		||||
        return messagesCache;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testInsert(final boolean sealedSender) {
 | 
			
		||||
        final UUID messageGuid = UUID.randomUUID();
 | 
			
		||||
        assertTrue(messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, generateRandomMessage(messageGuid, sealedSender)) > 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testRemoveById(final boolean sealedSender) {
 | 
			
		||||
        final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
        final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
        final long                            messageId           = messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageId);
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(RedisClusterMessagesCache.constructEntityFromEnvelope(messageId, message), maybeRemovedMessage.get());
 | 
			
		||||
        assertEquals(Optional.empty(), messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testRemoveBySender() {
 | 
			
		||||
        final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
        final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, false);
 | 
			
		||||
 | 
			
		||||
        messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp());
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(RedisClusterMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
 | 
			
		||||
        assertEquals(Optional.empty(), messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message.getSource(), message.getTimestamp()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testRemoveByUUID(final boolean sealedSender) {
 | 
			
		||||
        final UUID messageGuid = UUID.randomUUID();
 | 
			
		||||
 | 
			
		||||
        assertEquals(Optional.empty(), messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageGuid));
 | 
			
		||||
 | 
			
		||||
        final MessageProtos.Envelope message = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
        messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
        final Optional<OutgoingMessageEntity> maybeRemovedMessage = messagesCache.remove(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageGuid);
 | 
			
		||||
 | 
			
		||||
        assertTrue(maybeRemovedMessage.isPresent());
 | 
			
		||||
        assertEquals(RedisClusterMessagesCache.constructEntityFromEnvelope(0, message), maybeRemovedMessage.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testGetMessages(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        final List<OutgoingMessageEntity> expectedMessages = new ArrayList<>(messageCount);
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
            final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
            final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
            final long                   messageId   = messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, message);
 | 
			
		||||
 | 
			
		||||
            expectedMessages.add(RedisClusterMessagesCache.constructEntityFromEnvelope(messageId, message));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertEquals(expectedMessages, messagesCache.get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testClearQueueForDevice(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
 | 
			
		||||
            for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
                final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
                final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
                messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, deviceId, message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        messagesCache.clear(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID);
 | 
			
		||||
 | 
			
		||||
        assertEquals(Collections.emptyList(), messagesCache.get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
        assertEquals(messageCount, messagesCache.get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID + 1, messageCount).size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @Parameters({"true", "false"})
 | 
			
		||||
    public void testClearQueueForAccount(final boolean sealedSender) {
 | 
			
		||||
        final int messageCount = 100;
 | 
			
		||||
 | 
			
		||||
        for (final int deviceId : new int[] { DESTINATION_DEVICE_ID, DESTINATION_DEVICE_ID + 1 }) {
 | 
			
		||||
            for (int i = 0; i < messageCount; i++) {
 | 
			
		||||
                final UUID                   messageGuid = UUID.randomUUID();
 | 
			
		||||
                final MessageProtos.Envelope message     = generateRandomMessage(messageGuid, sealedSender);
 | 
			
		||||
 | 
			
		||||
                messagesCache.insert(messageGuid, DESTINATION_ACCOUNT, DESTINATION_UUID, deviceId, message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        messagesCache.clear(DESTINATION_ACCOUNT, DESTINATION_UUID);
 | 
			
		||||
 | 
			
		||||
        assertEquals(Collections.emptyList(), messagesCache.get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID, messageCount));
 | 
			
		||||
        assertEquals(Collections.emptyList(), messagesCache.get(DESTINATION_ACCOUNT, DESTINATION_UUID, DESTINATION_DEVICE_ID + 1, messageCount));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final boolean sealedSender) {
 | 
			
		||||
        final MessageProtos.Envelope.Builder envelopeBuilder = MessageProtos.Envelope.newBuilder()
 | 
			
		||||
                .setTimestamp(serialTimestamp++)
 | 
			
		||||
                .setServerTimestamp(serialTimestamp++)
 | 
			
		||||
                .setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
 | 
			
		||||
                .setType(MessageProtos.Envelope.Type.CIPHERTEXT)
 | 
			
		||||
                .setServerGuid(messageGuid.toString());
 | 
			
		||||
 | 
			
		||||
        if (!sealedSender) {
 | 
			
		||||
            envelopeBuilder.setSourceDevice(random.nextInt(256))
 | 
			
		||||
                    .setSource("+1" + RandomStringUtils.randomNumeric(10));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return envelopeBuilder.build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,84 +0,0 @@
 | 
			
		|||
package org.whispersystems.textsecuregcm.workers;
 | 
			
		||||
 | 
			
		||||
import com.google.protobuf.ByteString;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
			
		||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisSingletonTest;
 | 
			
		||||
import org.whispersystems.textsecuregcm.storage.Messages;
 | 
			
		||||
import org.whispersystems.textsecuregcm.storage.MessagesCache;
 | 
			
		||||
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.any;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.anyLong;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.anyString;
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
import static org.mockito.Mockito.times;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
 | 
			
		||||
public class ScourMessageCacheCommandTest extends AbstractRedisSingletonTest {
 | 
			
		||||
 | 
			
		||||
    private Messages                 messageDatabase;
 | 
			
		||||
    private MessagesCache            messagesCache;
 | 
			
		||||
    private ScourMessageCacheCommand scourMessageCacheCommand;
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setUp() throws Exception {
 | 
			
		||||
        super.setUp();
 | 
			
		||||
 | 
			
		||||
        messageDatabase          = mock(Messages.class);
 | 
			
		||||
        messagesCache            = new MessagesCache(getJedisPool());
 | 
			
		||||
        scourMessageCacheCommand = new ScourMessageCacheCommand();
 | 
			
		||||
 | 
			
		||||
        scourMessageCacheCommand.setMessageDatabase(messageDatabase);
 | 
			
		||||
        scourMessageCacheCommand.setRedisClient(getRedisClient());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testScourMessageCache() {
 | 
			
		||||
        final int messageCount = insertDetachedMessages(100, 1_000);
 | 
			
		||||
 | 
			
		||||
        scourMessageCacheCommand.scourMessageCache();
 | 
			
		||||
 | 
			
		||||
        verify(messageDatabase, times(messageCount)).store(any(UUID.class), any(MessageProtos.Envelope.class), anyString(), anyLong());
 | 
			
		||||
        assertEquals(0, (long)getRedisClient().withClient(connection -> connection.sync().dbsize()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("SameParameterValue")
 | 
			
		||||
    private int insertDetachedMessages(final int accounts, final int maxMessagesPerAccount) {
 | 
			
		||||
        int totalMessages = 0;
 | 
			
		||||
 | 
			
		||||
        final Random random = new Random();
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < accounts; i++) {
 | 
			
		||||
            final String accountNumber = String.format("+1800%07d", i);
 | 
			
		||||
            final UUID accountUuid     = UUID.randomUUID();
 | 
			
		||||
            final int messageCount     = random.nextInt(maxMessagesPerAccount);
 | 
			
		||||
 | 
			
		||||
            for (int j = 0; j < messageCount; j++) {
 | 
			
		||||
                final UUID messageGuid = UUID.randomUUID();
 | 
			
		||||
 | 
			
		||||
                final MessageProtos.Envelope envelope = MessageProtos.Envelope.newBuilder()
 | 
			
		||||
                        .setTimestamp(System.currentTimeMillis())
 | 
			
		||||
                        .setServerTimestamp(System.currentTimeMillis())
 | 
			
		||||
                        .setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
 | 
			
		||||
                        .setType(MessageProtos.Envelope.Type.CIPHERTEXT)
 | 
			
		||||
                        .setServerGuid(messageGuid.toString())
 | 
			
		||||
                        .build();
 | 
			
		||||
 | 
			
		||||
                messagesCache.insert(messageGuid, accountNumber, accountUuid, 1, envelope);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            totalMessages += messageCount;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        getRedisClient().useClient(connection -> connection.sync().del("user_queue_index"));
 | 
			
		||||
 | 
			
		||||
        return totalMessages;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue