diff --git a/service/pom.xml b/service/pom.xml index 82843ba0e..5602950e8 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -155,6 +155,14 @@ 0.13.1 test + + + com.fasterxml.uuid + java-uuid-generator + 3.2.0 + test + + diff --git a/service/protobuf/TextSecure.proto b/service/protobuf/TextSecure.proto index 4ef758e95..cf35801fe 100644 --- a/service/protobuf/TextSecure.proto +++ b/service/protobuf/TextSecure.proto @@ -31,6 +31,7 @@ message Envelope { optional Type type = 1; optional string source = 2; + optional string sourceUuid = 11; optional uint32 sourceDevice = 7; optional string relay = 3; optional uint64 timestamp = 5; @@ -57,6 +58,7 @@ message ServerCertificate { message SenderCertificate { message Certificate { optional string sender = 1; + optional string senderUuid = 6; optional uint32 senderDevice = 2; optional fixed64 expires = 3; optional bytes identityKey = 4; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index b983e96b0..2874c5b23 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -45,7 +45,6 @@ import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.controllers.ProfileController; import org.whispersystems.textsecuregcm.controllers.ProvisioningController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; -import org.whispersystems.textsecuregcm.controllers.TransparentDataController; import org.whispersystems.textsecuregcm.controllers.StickerController; import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -261,7 +260,6 @@ public class WhisperServerService extends Application 1 ? Long.parseLong(numberAndId[1]) : 1, password); } catch (NumberFormatException nfe) { @@ -79,12 +79,12 @@ public class AuthorizationHeader { } } - public String getNumber() { - return number; + public AmbiguousIdentifier getIdentifier() { + return identifier; } public long getDeviceId() { - return accountId; + return deviceId; } public String getPassword() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java index 49075ecee..ac494337b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/BaseAccountAuthenticator.java @@ -12,6 +12,7 @@ import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Util; import java.util.Optional; +import java.util.UUID; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.auth.basic.BasicCredentials; @@ -38,7 +39,7 @@ public class BaseAccountAuthenticator { public Optional authenticate(BasicCredentials basicCredentials, boolean enabledRequired) { try { AuthorizationHeader authorizationHeader = AuthorizationHeader.fromUserAndPassword(basicCredentials.getUsername(), basicCredentials.getPassword()); - Optional account = accountsManager.get(authorizationHeader.getNumber()); + Optional account = accountsManager.get(authorizationHeader.getIdentifier()); if (!account.isPresent()) { noSuchAccountMeter.mark(); @@ -73,7 +74,7 @@ public class BaseAccountAuthenticator { authenticationFailedMeter.mark(); return Optional.empty(); - } catch (InvalidAuthorizationHeaderException iahe) { + } catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) { invalidAuthHeaderMeter.mark(); return Optional.empty(); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java index 08ea7d63e..20f6c01e9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java @@ -31,6 +31,7 @@ public class CertificateGenerator { public byte[] createFor(Account account, Device device) throws IOException, InvalidKeyException { byte[] certificate = SenderCertificate.Certificate.newBuilder() .setSender(account.getNumber()) + .setSenderUuid(account.getUuid().toString()) .setSenderDevice(Math.toIntExact(device.getId())) .setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays)) .setIdentityKey(ByteString.copyFrom(Base64.decode(account.getIdentityKey()))) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 4a4e45727..af35ba872 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -33,6 +33,7 @@ import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnToken; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.entities.AccountAttributes; +import org.whispersystems.textsecuregcm.entities.AccountCreationResult; import org.whispersystems.textsecuregcm.entities.ApnRegistrationId; import org.whispersystems.textsecuregcm.entities.DeviceName; import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; @@ -78,6 +79,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; @@ -245,17 +247,21 @@ public class AccountController { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/code/{verification_code}") - public void verifyAccount(@PathParam("verification_code") String verificationCode, - @HeaderParam("Authorization") String authorizationHeader, - @HeaderParam("X-Signal-Agent") String userAgent, - @Valid AccountAttributes accountAttributes) + public AccountCreationResult verifyAccount(@PathParam("verification_code") String verificationCode, + @HeaderParam("Authorization") String authorizationHeader, + @HeaderParam("X-Signal-Agent") String userAgent, + @Valid AccountAttributes accountAttributes) throws RateLimitExceededException { try { AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader); - String number = header.getNumber(); + String number = header.getIdentifier().getNumber(); String password = header.getPassword(); + if (number == null) { + throw new WebApplicationException(400); + } + rateLimiters.getVerifyLimiter().validate(number); Optional storedVerificationCode = pendingAccounts.getCodeForNumber(number); @@ -308,9 +314,11 @@ public class AccountController { rateLimiters.getPinLimiter().clear(number); } - createAccount(number, password, userAgent, accountAttributes); + Account account = createAccount(number, password, userAgent, accountAttributes); metricRegistry.meter(name(AccountController.class, "verify", Util.getCountryCode(number))).mark(); + + return new AccountCreationResult(account.getUuid()); } catch (InvalidAuthorizationHeaderException e) { logger.info("Bad Authorization Header", e); throw new WebApplicationException(Response.status(401).build()); @@ -502,6 +510,13 @@ public class AccountController { accounts.update(account); } + @GET + @Path("/whoami") + @Produces(MediaType.APPLICATION_JSON) + public AccountCreationResult whoAmI(@Auth Account account) { + return new AccountCreationResult(account.getUuid()); + } + private CaptchaRequirement requiresCaptcha(String number, String transport, String forwardedFor, String requester, Optional captchaToken, @@ -576,7 +591,7 @@ public class AccountController { return false; } - private void createAccount(String number, String password, String userAgent, AccountAttributes accountAttributes) { + private Account createAccount(String number, String password, String userAgent, AccountAttributes accountAttributes) { Device device = new Device(); device.setId(Device.MASTER_ID); device.setAuthenticationCredentials(new AuthenticationCredentials(password)); @@ -591,6 +606,7 @@ public class AccountController { Account account = new Account(); account.setNumber(number); + account.setUuid(UUID.randomUUID()); account.addDevice(device); account.setPin(accountAttributes.getPin()); account.setUnidentifiedAccessKey(accountAttributes.getUnidentifiedAccessKey()); @@ -608,6 +624,8 @@ public class AccountController { messagesManager.clear(number); pendingAccounts.remove(number); + + return account; } @VisibleForTesting protected diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java index 9cdbc75cb..807bce9a8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DeviceController.java @@ -164,9 +164,11 @@ public class DeviceController { { try { AuthorizationHeader header = AuthorizationHeader.fromFullHeader(authorizationHeader); - String number = header.getNumber(); + String number = header.getIdentifier().getNumber(); String password = header.getPassword(); + if (number == null) throw new WebApplicationException(400); + rateLimiters.getVerifyDeviceLimiter().validate(number); Optional storedVerificationCode = pendingDevices.getCodeForNumber(number); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 95a6508b7..6622f5b86 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -19,6 +19,7 @@ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.Anonymous; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; @@ -115,11 +116,11 @@ public class KeysController { @Timed @GET - @Path("/{number}/{device_id}") + @Path("/{identifier}/{device_id}") @Produces(MediaType.APPLICATION_JSON) public Optional getDeviceKeys(@Auth Optional account, @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, - @PathParam("number") String number, + @PathParam("identifier") AmbiguousIdentifier targetName, @PathParam("device_id") String deviceId) throws RateLimitExceededException { @@ -127,13 +128,13 @@ public class KeysController { throw new WebApplicationException(Response.Status.UNAUTHORIZED); } - Optional target = accounts.get(number); + Optional target = accounts.get(targetName); OptionalAccess.verify(account, accessKey, target, deviceId); assert(target.isPresent()); if (account.isPresent()) { - rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "__" + number + "." + deviceId); + rateLimiters.getPreKeysLimiter().validate(account.get().getNumber() + "__" + target.get().getNumber() + "." + deviceId); } List targetKeys = getLocalKeys(target.get(), deviceId); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index b2eea9cb6..a529dd7b9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -23,6 +23,7 @@ import com.codahale.metrics.annotation.Timed; import com.google.protobuf.ByteString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.Anonymous; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.entities.IncomingMessage; @@ -109,7 +110,7 @@ public class MessageController { @Produces(MediaType.APPLICATION_JSON) public SendMessageResponse sendMessage(@Auth Optional source, @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, - @PathParam("destination") String destinationName, + @PathParam("destination") AmbiguousIdentifier destinationName, @Valid IncomingMessageList messages) throws RateLimitExceededException { @@ -117,18 +118,18 @@ public class MessageController { throw new WebApplicationException(Response.Status.UNAUTHORIZED); } - if (source.isPresent() && !source.get().getNumber().equals(destinationName)) { + if (source.isPresent() && !source.get().isFor(destinationName)) { rateLimiters.getMessagesLimiter().validate(source.get().getNumber() + "__" + destinationName); } - if (source.isPresent() && !source.get().getNumber().equals(destinationName)) { + if (source.isPresent() && !source.get().isFor(destinationName)) { identifiedMeter.mark(); - } else { + } else if (!source.isPresent()) { unidentifiedMeter.mark(); } try { - boolean isSyncMessage = source.isPresent() && source.get().getNumber().equals(destinationName); + boolean isSyncMessage = source.isPresent() && source.get().isFor(destinationName); Optional destination; @@ -246,6 +247,7 @@ public class MessageController { if (source.isPresent()) { messageBuilder.setSource(source.get().getNumber()) + .setSourceUuid(source.get().getUuid().toString()) .setSourceDevice((int)source.get().getAuthenticatedDevice().get().getId()); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 582ebcbf8..cac806dd0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -10,6 +10,7 @@ import com.codahale.metrics.annotation.Timed; import org.apache.commons.codec.binary.Base64; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.valuehandling.UnwrapValidatedValue; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.Anonymous; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; @@ -79,10 +80,10 @@ public class ProfileController { @Timed @GET @Produces(MediaType.APPLICATION_JSON) - @Path("/{number}") + @Path("/{identifier}") public Profile getProfile(@Auth Optional requestAccount, @HeaderParam(OptionalAccess.UNIDENTIFIED) Optional accessKey, - @PathParam("number") String number, + @PathParam("identifier") AmbiguousIdentifier identifier, @QueryParam("ca") boolean useCaCertificate) throws RateLimitExceededException { @@ -94,7 +95,7 @@ public class ProfileController { rateLimiters.getProfileLimiter().validate(requestAccount.get().getNumber()); } - Optional accountProfile = accountsManager.get(number); + Optional accountProfile = accountsManager.get(identifier); OptionalAccess.verify(requestAccount, accessKey, accountProfile); //noinspection ConstantConditions,OptionalGetWithoutIsPresent diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/TransparentDataController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/TransparentDataController.java deleted file mode 100644 index 0c0190b18..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/TransparentDataController.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.whispersystems.textsecuregcm.controllers; - -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.storage.PublicAccount; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import java.util.Map; -import java.util.Optional; - -@Path("/v1/transparency/") -public class TransparentDataController { - - private final AccountsManager accountsManager; - private final Map transparentDataIndex; - - public TransparentDataController(AccountsManager accountsManager, - Map transparentDataIndex) - { - this.accountsManager = accountsManager; - this.transparentDataIndex = transparentDataIndex; - } - - @GET - @Path("/account/{id}") - @Produces(MediaType.APPLICATION_JSON) - public Optional getAccount(@PathParam("id") String id) { - String index = transparentDataIndex.get(id); - - if (index != null) { - return accountsManager.get(index).map(PublicAccount::new); - } - - return Optional.empty(); - } - - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountCreationResult.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountCreationResult.java new file mode 100644 index 000000000..06625d388 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountCreationResult.java @@ -0,0 +1,21 @@ +package org.whispersystems.textsecuregcm.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class AccountCreationResult { + + @JsonProperty + private UUID uuid; + + public AccountCreationResult() {} + + public AccountCreationResult(UUID uuid) { + this.uuid = uuid; + } + + public UUID getUuid() { + return uuid; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ActiveUserTally.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ActiveUserTally.java index 5976db2c4..5cb88e7c0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ActiveUserTally.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ActiveUserTally.java @@ -19,10 +19,11 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; import java.util.HashMap; +import java.util.UUID; public class ActiveUserTally { @JsonProperty - private String fromNumber; + private UUID fromUuid; @JsonProperty private Map platforms; @@ -32,14 +33,14 @@ public class ActiveUserTally { public ActiveUserTally() {} - public ActiveUserTally(String fromNumber, Map platforms, Map countries) { - this.fromNumber = fromNumber; + public ActiveUserTally(UUID fromUuid, Map platforms, Map countries) { + this.fromUuid = fromUuid; this.platforms = platforms; this.countries = countries; } - public String getFromNumber() { - return this.fromNumber; + public UUID getFromUuid() { + return this.fromUuid; } public Map getPlatforms() { @@ -50,8 +51,8 @@ public class ActiveUserTally { return this.countries; } - public void setFromNumber(String fromNumber) { - this.fromNumber = fromNumber; + public void setFromUuid(UUID fromUuid) { + this.fromUuid = fromUuid; } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/DirectoryReconciliationRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/DirectoryReconciliationRequest.java index 2731347eb..9eeb6d565 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/DirectoryReconciliationRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/DirectoryReconciliationRequest.java @@ -19,14 +19,15 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; +import java.util.UUID; public class DirectoryReconciliationRequest { @JsonProperty - private String fromNumber; + private UUID fromUuid; @JsonProperty - private String toNumber; + private UUID toUuid; @JsonProperty private List numbers; @@ -34,18 +35,18 @@ public class DirectoryReconciliationRequest { public DirectoryReconciliationRequest() { } - public DirectoryReconciliationRequest(String fromNumber, String toNumber, List numbers) { - this.fromNumber = fromNumber; - this.toNumber = toNumber; - this.numbers = numbers; + public DirectoryReconciliationRequest(UUID fromUuid, UUID toUuid, List numbers) { + this.fromUuid = fromUuid; + this.toUuid = toUuid; + this.numbers = numbers; } - public String getFromNumber() { - return fromNumber; + public UUID getFromUuid() { + return fromUuid; } - public String getToNumber() { - return toNumber; + public UUID getToUuid() { + return toUuid; } public List getNumbers() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java index 5b52357ea..caf6bb002 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/MessageProtos.java @@ -36,6 +36,21 @@ public final class MessageProtos { com.google.protobuf.ByteString getSourceBytes(); + // optional string sourceUuid = 11; + /** + * optional string sourceUuid = 11; + */ + boolean hasSourceUuid(); + /** + * optional string sourceUuid = 11; + */ + java.lang.String getSourceUuid(); + /** + * optional string sourceUuid = 11; + */ + com.google.protobuf.ByteString + getSourceUuidBytes(); + // optional uint32 sourceDevice = 7; /** * optional uint32 sourceDevice = 7; @@ -200,40 +215,45 @@ public final class MessageProtos { break; } case 26: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; relay_ = input.readBytes(); break; } case 40: { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; timestamp_ = input.readUInt64(); break; } case 50: { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; legacyMessage_ = input.readBytes(); break; } case 56: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; sourceDevice_ = input.readUInt32(); break; } case 66: { - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; content_ = input.readBytes(); break; } case 74: { - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; serverGuid_ = input.readBytes(); break; } case 80: { - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; serverTimestamp_ = input.readUInt64(); break; } + case 90: { + bitField0_ |= 0x00000004; + sourceUuid_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -451,6 +471,49 @@ public final class MessageProtos { } } + // optional string sourceUuid = 11; + public static final int SOURCEUUID_FIELD_NUMBER = 11; + private java.lang.Object sourceUuid_; + /** + * optional string sourceUuid = 11; + */ + public boolean hasSourceUuid() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string sourceUuid = 11; + */ + public java.lang.String getSourceUuid() { + java.lang.Object ref = sourceUuid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + sourceUuid_ = s; + } + return s; + } + } + /** + * optional string sourceUuid = 11; + */ + public com.google.protobuf.ByteString + getSourceUuidBytes() { + java.lang.Object ref = sourceUuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sourceUuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + // optional uint32 sourceDevice = 7; public static final int SOURCEDEVICE_FIELD_NUMBER = 7; private int sourceDevice_; @@ -458,7 +521,7 @@ public final class MessageProtos { * optional uint32 sourceDevice = 7; */ public boolean hasSourceDevice() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional uint32 sourceDevice = 7; @@ -474,7 +537,7 @@ public final class MessageProtos { * optional string relay = 3; */ public boolean hasRelay() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional string relay = 3; @@ -517,7 +580,7 @@ public final class MessageProtos { * optional uint64 timestamp = 5; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional uint64 timestamp = 5; @@ -537,7 +600,7 @@ public final class MessageProtos { * */ public boolean hasLegacyMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional bytes legacyMessage = 6; @@ -561,7 +624,7 @@ public final class MessageProtos { * */ public boolean hasContent() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000080) == 0x00000080); } /** * optional bytes content = 8; @@ -581,7 +644,7 @@ public final class MessageProtos { * optional string serverGuid = 9; */ public boolean hasServerGuid() { - return ((bitField0_ & 0x00000080) == 0x00000080); + return ((bitField0_ & 0x00000100) == 0x00000100); } /** * optional string serverGuid = 9; @@ -624,7 +687,7 @@ public final class MessageProtos { * optional uint64 server_timestamp = 10; */ public boolean hasServerTimestamp() { - return ((bitField0_ & 0x00000100) == 0x00000100); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional uint64 server_timestamp = 10; @@ -636,6 +699,7 @@ public final class MessageProtos { private void initFields() { type_ = org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope.Type.UNKNOWN; source_ = ""; + sourceUuid_ = ""; sourceDevice_ = 0; relay_ = ""; timestamp_ = 0L; @@ -662,27 +726,30 @@ public final class MessageProtos { if (((bitField0_ & 0x00000002) == 0x00000002)) { output.writeBytes(2, getSourceBytes()); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeBytes(3, getRelayBytes()); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeUInt64(5, timestamp_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeBytes(6, legacyMessage_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeUInt32(7, sourceDevice_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { output.writeBytes(8, content_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { output.writeBytes(9, getServerGuidBytes()); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { output.writeUInt64(10, serverTimestamp_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(11, getSourceUuidBytes()); + } getUnknownFields().writeTo(output); } @@ -700,34 +767,38 @@ public final class MessageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, getSourceBytes()); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, getRelayBytes()); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(5, timestamp_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(6, legacyMessage_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(7, sourceDevice_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(8, content_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(9, getServerGuidBytes()); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(10, serverTimestamp_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(11, getSourceUuidBytes()); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -848,20 +919,22 @@ public final class MessageProtos { bitField0_ = (bitField0_ & ~0x00000001); source_ = ""; bitField0_ = (bitField0_ & ~0x00000002); - sourceDevice_ = 0; + sourceUuid_ = ""; bitField0_ = (bitField0_ & ~0x00000004); - relay_ = ""; + sourceDevice_ = 0; bitField0_ = (bitField0_ & ~0x00000008); - timestamp_ = 0L; + relay_ = ""; bitField0_ = (bitField0_ & ~0x00000010); - legacyMessage_ = com.google.protobuf.ByteString.EMPTY; + timestamp_ = 0L; bitField0_ = (bitField0_ & ~0x00000020); - content_ = com.google.protobuf.ByteString.EMPTY; + legacyMessage_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000040); - serverGuid_ = ""; + content_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000080); - serverTimestamp_ = 0L; + serverGuid_ = ""; bitField0_ = (bitField0_ & ~0x00000100); + serverTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000200); return this; } @@ -901,30 +974,34 @@ public final class MessageProtos { if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.sourceDevice_ = sourceDevice_; + result.sourceUuid_ = sourceUuid_; if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } - result.relay_ = relay_; + result.sourceDevice_ = sourceDevice_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000010; } - result.timestamp_ = timestamp_; + result.relay_ = relay_; if (((from_bitField0_ & 0x00000020) == 0x00000020)) { to_bitField0_ |= 0x00000020; } - result.legacyMessage_ = legacyMessage_; + result.timestamp_ = timestamp_; if (((from_bitField0_ & 0x00000040) == 0x00000040)) { to_bitField0_ |= 0x00000040; } - result.content_ = content_; + result.legacyMessage_ = legacyMessage_; if (((from_bitField0_ & 0x00000080) == 0x00000080)) { to_bitField0_ |= 0x00000080; } - result.serverGuid_ = serverGuid_; + result.content_ = content_; if (((from_bitField0_ & 0x00000100) == 0x00000100)) { to_bitField0_ |= 0x00000100; } + result.serverGuid_ = serverGuid_; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000200; + } result.serverTimestamp_ = serverTimestamp_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -950,11 +1027,16 @@ public final class MessageProtos { source_ = other.source_; onChanged(); } + if (other.hasSourceUuid()) { + bitField0_ |= 0x00000004; + sourceUuid_ = other.sourceUuid_; + onChanged(); + } if (other.hasSourceDevice()) { setSourceDevice(other.getSourceDevice()); } if (other.hasRelay()) { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; relay_ = other.relay_; onChanged(); } @@ -968,7 +1050,7 @@ public final class MessageProtos { setContent(other.getContent()); } if (other.hasServerGuid()) { - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; serverGuid_ = other.serverGuid_; onChanged(); } @@ -1112,13 +1194,87 @@ public final class MessageProtos { return this; } + // optional string sourceUuid = 11; + private java.lang.Object sourceUuid_ = ""; + /** + * optional string sourceUuid = 11; + */ + public boolean hasSourceUuid() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string sourceUuid = 11; + */ + public java.lang.String getSourceUuid() { + java.lang.Object ref = sourceUuid_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + sourceUuid_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string sourceUuid = 11; + */ + public com.google.protobuf.ByteString + getSourceUuidBytes() { + java.lang.Object ref = sourceUuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sourceUuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string sourceUuid = 11; + */ + public Builder setSourceUuid( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + sourceUuid_ = value; + onChanged(); + return this; + } + /** + * optional string sourceUuid = 11; + */ + public Builder clearSourceUuid() { + bitField0_ = (bitField0_ & ~0x00000004); + sourceUuid_ = getDefaultInstance().getSourceUuid(); + onChanged(); + return this; + } + /** + * optional string sourceUuid = 11; + */ + public Builder setSourceUuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + sourceUuid_ = value; + onChanged(); + return this; + } + // optional uint32 sourceDevice = 7; private int sourceDevice_ ; /** * optional uint32 sourceDevice = 7; */ public boolean hasSourceDevice() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional uint32 sourceDevice = 7; @@ -1130,7 +1286,7 @@ public final class MessageProtos { * optional uint32 sourceDevice = 7; */ public Builder setSourceDevice(int value) { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; sourceDevice_ = value; onChanged(); return this; @@ -1139,7 +1295,7 @@ public final class MessageProtos { * optional uint32 sourceDevice = 7; */ public Builder clearSourceDevice() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); sourceDevice_ = 0; onChanged(); return this; @@ -1151,7 +1307,7 @@ public final class MessageProtos { * optional string relay = 3; */ public boolean hasRelay() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional string relay = 3; @@ -1191,7 +1347,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; relay_ = value; onChanged(); return this; @@ -1200,7 +1356,7 @@ public final class MessageProtos { * optional string relay = 3; */ public Builder clearRelay() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); relay_ = getDefaultInstance().getRelay(); onChanged(); return this; @@ -1213,7 +1369,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; relay_ = value; onChanged(); return this; @@ -1225,7 +1381,7 @@ public final class MessageProtos { * optional uint64 timestamp = 5; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional uint64 timestamp = 5; @@ -1237,7 +1393,7 @@ public final class MessageProtos { * optional uint64 timestamp = 5; */ public Builder setTimestamp(long value) { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; timestamp_ = value; onChanged(); return this; @@ -1246,7 +1402,7 @@ public final class MessageProtos { * optional uint64 timestamp = 5; */ public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); timestamp_ = 0L; onChanged(); return this; @@ -1262,7 +1418,7 @@ public final class MessageProtos { * */ public boolean hasLegacyMessage() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional bytes legacyMessage = 6; @@ -1285,7 +1441,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; legacyMessage_ = value; onChanged(); return this; @@ -1298,7 +1454,7 @@ public final class MessageProtos { * */ public Builder clearLegacyMessage() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000040); legacyMessage_ = getDefaultInstance().getLegacyMessage(); onChanged(); return this; @@ -1314,7 +1470,7 @@ public final class MessageProtos { * */ public boolean hasContent() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000080) == 0x00000080); } /** * optional bytes content = 8; @@ -1337,7 +1493,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; content_ = value; onChanged(); return this; @@ -1350,7 +1506,7 @@ public final class MessageProtos { * */ public Builder clearContent() { - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000080); content_ = getDefaultInstance().getContent(); onChanged(); return this; @@ -1362,7 +1518,7 @@ public final class MessageProtos { * optional string serverGuid = 9; */ public boolean hasServerGuid() { - return ((bitField0_ & 0x00000080) == 0x00000080); + return ((bitField0_ & 0x00000100) == 0x00000100); } /** * optional string serverGuid = 9; @@ -1402,7 +1558,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; serverGuid_ = value; onChanged(); return this; @@ -1411,7 +1567,7 @@ public final class MessageProtos { * optional string serverGuid = 9; */ public Builder clearServerGuid() { - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); serverGuid_ = getDefaultInstance().getServerGuid(); onChanged(); return this; @@ -1424,7 +1580,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; serverGuid_ = value; onChanged(); return this; @@ -1436,7 +1592,7 @@ public final class MessageProtos { * optional uint64 server_timestamp = 10; */ public boolean hasServerTimestamp() { - return ((bitField0_ & 0x00000100) == 0x00000100); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional uint64 server_timestamp = 10; @@ -1448,7 +1604,7 @@ public final class MessageProtos { * optional uint64 server_timestamp = 10; */ public Builder setServerTimestamp(long value) { - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; serverTimestamp_ = value; onChanged(); return this; @@ -1457,7 +1613,7 @@ public final class MessageProtos { * optional uint64 server_timestamp = 10; */ public Builder clearServerTimestamp() { - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000200); serverTimestamp_ = 0L; onChanged(); return this; @@ -3049,6 +3205,21 @@ public final class MessageProtos { com.google.protobuf.ByteString getSenderBytes(); + // optional string senderUuid = 6; + /** + * optional string senderUuid = 6; + */ + boolean hasSenderUuid(); + /** + * optional string senderUuid = 6; + */ + java.lang.String getSenderUuid(); + /** + * optional string senderUuid = 6; + */ + com.google.protobuf.ByteString + getSenderUuidBytes(); + // optional uint32 senderDevice = 2; /** * optional uint32 senderDevice = 2; @@ -3150,23 +3321,23 @@ public final class MessageProtos { break; } case 16: { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; senderDevice_ = input.readUInt32(); break; } case 25: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; expires_ = input.readFixed64(); break; } case 34: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; identityKey_ = input.readBytes(); break; } case 42: { org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder subBuilder = null; - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { subBuilder = signer_.toBuilder(); } signer_ = input.readMessage(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.PARSER, extensionRegistry); @@ -3174,7 +3345,12 @@ public final class MessageProtos { subBuilder.mergeFrom(signer_); signer_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; + break; + } + case 50: { + bitField0_ |= 0x00000002; + senderUuid_ = input.readBytes(); break; } } @@ -3260,6 +3436,49 @@ public final class MessageProtos { } } + // optional string senderUuid = 6; + public static final int SENDERUUID_FIELD_NUMBER = 6; + private java.lang.Object senderUuid_; + /** + * optional string senderUuid = 6; + */ + public boolean hasSenderUuid() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string senderUuid = 6; + */ + public java.lang.String getSenderUuid() { + java.lang.Object ref = senderUuid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + senderUuid_ = s; + } + return s; + } + } + /** + * optional string senderUuid = 6; + */ + public com.google.protobuf.ByteString + getSenderUuidBytes() { + java.lang.Object ref = senderUuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + senderUuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + // optional uint32 senderDevice = 2; public static final int SENDERDEVICE_FIELD_NUMBER = 2; private int senderDevice_; @@ -3267,7 +3486,7 @@ public final class MessageProtos { * optional uint32 senderDevice = 2; */ public boolean hasSenderDevice() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 senderDevice = 2; @@ -3283,7 +3502,7 @@ public final class MessageProtos { * optional fixed64 expires = 3; */ public boolean hasExpires() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional fixed64 expires = 3; @@ -3299,7 +3518,7 @@ public final class MessageProtos { * optional bytes identityKey = 4; */ public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes identityKey = 4; @@ -3315,7 +3534,7 @@ public final class MessageProtos { * optional .textsecure.ServerCertificate signer = 5; */ public boolean hasSigner() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional .textsecure.ServerCertificate signer = 5; @@ -3332,6 +3551,7 @@ public final class MessageProtos { private void initFields() { sender_ = ""; + senderUuid_ = ""; senderDevice_ = 0; expires_ = 0L; identityKey_ = com.google.protobuf.ByteString.EMPTY; @@ -3352,18 +3572,21 @@ public final class MessageProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeBytes(1, getSenderBytes()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeUInt32(2, senderDevice_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeFixed64(3, expires_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeBytes(4, identityKey_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeMessage(5, signer_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(6, getSenderUuidBytes()); + } getUnknownFields().writeTo(output); } @@ -3377,22 +3600,26 @@ public final class MessageProtos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(1, getSenderBytes()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(2, senderDevice_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeFixed64Size(3, expires_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(4, identityKey_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(5, signer_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, getSenderUuidBytes()); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -3512,18 +3739,20 @@ public final class MessageProtos { super.clear(); sender_ = ""; bitField0_ = (bitField0_ & ~0x00000001); - senderDevice_ = 0; + senderUuid_ = ""; bitField0_ = (bitField0_ & ~0x00000002); - expires_ = 0L; + senderDevice_ = 0; bitField0_ = (bitField0_ & ~0x00000004); - identityKey_ = com.google.protobuf.ByteString.EMPTY; + expires_ = 0L; bitField0_ = (bitField0_ & ~0x00000008); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); if (signerBuilder_ == null) { signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance(); } else { signerBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -3559,18 +3788,22 @@ public final class MessageProtos { if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } - result.senderDevice_ = senderDevice_; + result.senderUuid_ = senderUuid_; if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.expires_ = expires_; + result.senderDevice_ = senderDevice_; if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } - result.identityKey_ = identityKey_; + result.expires_ = expires_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000010; } + result.identityKey_ = identityKey_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } if (signerBuilder_ == null) { result.signer_ = signer_; } else { @@ -3597,6 +3830,11 @@ public final class MessageProtos { sender_ = other.sender_; onChanged(); } + if (other.hasSenderUuid()) { + bitField0_ |= 0x00000002; + senderUuid_ = other.senderUuid_; + onChanged(); + } if (other.hasSenderDevice()) { setSenderDevice(other.getSenderDevice()); } @@ -3710,13 +3948,87 @@ public final class MessageProtos { return this; } + // optional string senderUuid = 6; + private java.lang.Object senderUuid_ = ""; + /** + * optional string senderUuid = 6; + */ + public boolean hasSenderUuid() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string senderUuid = 6; + */ + public java.lang.String getSenderUuid() { + java.lang.Object ref = senderUuid_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + senderUuid_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string senderUuid = 6; + */ + public com.google.protobuf.ByteString + getSenderUuidBytes() { + java.lang.Object ref = senderUuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + senderUuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string senderUuid = 6; + */ + public Builder setSenderUuid( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + senderUuid_ = value; + onChanged(); + return this; + } + /** + * optional string senderUuid = 6; + */ + public Builder clearSenderUuid() { + bitField0_ = (bitField0_ & ~0x00000002); + senderUuid_ = getDefaultInstance().getSenderUuid(); + onChanged(); + return this; + } + /** + * optional string senderUuid = 6; + */ + public Builder setSenderUuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + senderUuid_ = value; + onChanged(); + return this; + } + // optional uint32 senderDevice = 2; private int senderDevice_ ; /** * optional uint32 senderDevice = 2; */ public boolean hasSenderDevice() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 senderDevice = 2; @@ -3728,7 +4040,7 @@ public final class MessageProtos { * optional uint32 senderDevice = 2; */ public Builder setSenderDevice(int value) { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; senderDevice_ = value; onChanged(); return this; @@ -3737,7 +4049,7 @@ public final class MessageProtos { * optional uint32 senderDevice = 2; */ public Builder clearSenderDevice() { - bitField0_ = (bitField0_ & ~0x00000002); + bitField0_ = (bitField0_ & ~0x00000004); senderDevice_ = 0; onChanged(); return this; @@ -3749,7 +4061,7 @@ public final class MessageProtos { * optional fixed64 expires = 3; */ public boolean hasExpires() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional fixed64 expires = 3; @@ -3761,7 +4073,7 @@ public final class MessageProtos { * optional fixed64 expires = 3; */ public Builder setExpires(long value) { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; expires_ = value; onChanged(); return this; @@ -3770,7 +4082,7 @@ public final class MessageProtos { * optional fixed64 expires = 3; */ public Builder clearExpires() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); expires_ = 0L; onChanged(); return this; @@ -3782,7 +4094,7 @@ public final class MessageProtos { * optional bytes identityKey = 4; */ public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes identityKey = 4; @@ -3797,7 +4109,7 @@ public final class MessageProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; identityKey_ = value; onChanged(); return this; @@ -3806,7 +4118,7 @@ public final class MessageProtos { * optional bytes identityKey = 4; */ public Builder clearIdentityKey() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); identityKey_ = getDefaultInstance().getIdentityKey(); onChanged(); return this; @@ -3820,7 +4132,7 @@ public final class MessageProtos { * optional .textsecure.ServerCertificate signer = 5; */ public boolean hasSigner() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional .textsecure.ServerCertificate signer = 5; @@ -3845,7 +4157,7 @@ public final class MessageProtos { } else { signerBuilder_.setMessage(value); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -3859,7 +4171,7 @@ public final class MessageProtos { } else { signerBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -3867,7 +4179,7 @@ public final class MessageProtos { */ public Builder mergeSigner(org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate value) { if (signerBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010) && + if (((bitField0_ & 0x00000020) == 0x00000020) && signer_ != org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.getDefaultInstance()) { signer_ = org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.newBuilder(signer_).mergeFrom(value).buildPartial(); @@ -3878,7 +4190,7 @@ public final class MessageProtos { } else { signerBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -3891,14 +4203,14 @@ public final class MessageProtos { } else { signerBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); return this; } /** * optional .textsecure.ServerCertificate signer = 5; */ public org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate.Builder getSignerBuilder() { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; onChanged(); return getSignerFieldBuilder().getBuilder(); } @@ -4339,21 +4651,22 @@ public final class MessageProtos { descriptor; static { java.lang.String[] descriptorData = { - "\n\020TextSecure.proto\022\ntextsecure\"\301\002\n\010Envel" + + "\n\020TextSecure.proto\022\ntextsecure\"\325\002\n\010Envel" + "ope\022\'\n\004type\030\001 \001(\0162\031.textsecure.Envelope." + - "Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevice\030\007 \001" + - "(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(\004\022\025\n\r" + - "legacyMessage\030\006 \001(\014\022\017\n\007content\030\010 \001(\014\022\022\n\n" + - "serverGuid\030\t \001(\t\022\030\n\020server_timestamp\030\n \001" + - "(\004\"n\n\004Type\022\013\n\007UNKNOWN\020\000\022\016\n\nCIPHERTEXT\020\001\022" + - "\020\n\014KEY_EXCHANGE\020\002\022\021\n\rPREKEY_BUNDLE\020\003\022\013\n\007" + - "RECEIPT\020\005\022\027\n\023UNIDENTIFIED_SENDER\020\006\" \n\020Pr" + - "ovisioningUuid\022\014\n\004uuid\030\001 \001(\t\"c\n\021ServerCe", - "rtificate\022\023\n\013certificate\030\001 \001(\014\022\021\n\tsignat" + - "ure\030\002 \001(\014\032&\n\013Certificate\022\n\n\002id\030\001 \001(\r\022\013\n\003" + - "key\030\002 \001(\014\"\306\001\n\021SenderCertificate\022\023\n\013certi" + - "ficate\030\001 \001(\014\022\021\n\tsignature\030\002 \001(\014\032\210\001\n\013Cert" + - "ificate\022\016\n\006sender\030\001 \001(\t\022\024\n\014senderDevice\030" + + "Type\022\016\n\006source\030\002 \001(\t\022\022\n\nsourceUuid\030\013 \001(\t" + + "\022\024\n\014sourceDevice\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n" + + "\ttimestamp\030\005 \001(\004\022\025\n\rlegacyMessage\030\006 \001(\014\022" + + "\017\n\007content\030\010 \001(\014\022\022\n\nserverGuid\030\t \001(\t\022\030\n\020" + + "server_timestamp\030\n \001(\004\"n\n\004Type\022\013\n\007UNKNOW" + + "N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" + + "\rPREKEY_BUNDLE\020\003\022\013\n\007RECEIPT\020\005\022\027\n\023UNIDENT" + + "IFIED_SENDER\020\006\" \n\020ProvisioningUuid\022\014\n\004uu", + "id\030\001 \001(\t\"c\n\021ServerCertificate\022\023\n\013certifi" + + "cate\030\001 \001(\014\022\021\n\tsignature\030\002 \001(\014\032&\n\013Certifi" + + "cate\022\n\n\002id\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\"\332\001\n\021Sender" + + "Certificate\022\023\n\013certificate\030\001 \001(\014\022\021\n\tsign" + + "ature\030\002 \001(\014\032\234\001\n\013Certificate\022\016\n\006sender\030\001 " + + "\001(\t\022\022\n\nsenderUuid\030\006 \001(\t\022\024\n\014senderDevice\030" + "\002 \001(\r\022\017\n\007expires\030\003 \001(\006\022\023\n\013identityKey\030\004 " + "\001(\014\022-\n\006signer\030\005 \001(\0132\035.textsecure.ServerC" + "ertificateB:\n)org.whispersystems.textsec" + @@ -4369,7 +4682,7 @@ public final class MessageProtos { internal_static_textsecure_Envelope_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_Envelope_descriptor, - new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "LegacyMessage", "Content", "ServerGuid", "ServerTimestamp", }); + new java.lang.String[] { "Type", "Source", "SourceUuid", "SourceDevice", "Relay", "Timestamp", "LegacyMessage", "Content", "ServerGuid", "ServerTimestamp", }); internal_static_textsecure_ProvisioningUuid_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_textsecure_ProvisioningUuid_fieldAccessorTable = new @@ -4399,7 +4712,7 @@ public final class MessageProtos { internal_static_textsecure_SenderCertificate_Certificate_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_textsecure_SenderCertificate_Certificate_descriptor, - new java.lang.String[] { "Sender", "SenderDevice", "Expires", "IdentityKey", "Signer", }); + new java.lang.String[] { "Sender", "SenderUuid", "SenderDevice", "Expires", "IdentityKey", "Signer", }); return null; } }; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java index becf61557..3ac17c19f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntity.java @@ -28,6 +28,9 @@ public class OutgoingMessageEntity { @JsonProperty private String source; + @JsonProperty + private UUID sourceUuid; + @JsonProperty private int sourceDevice; @@ -44,8 +47,8 @@ public class OutgoingMessageEntity { public OutgoingMessageEntity(long id, boolean cached, UUID guid, int type, String relay, long timestamp, - String source, int sourceDevice, byte[] message, - byte[] content, long serverTimestamp) + String source, UUID sourceUuid, int sourceDevice, + byte[] message, byte[] content, long serverTimestamp) { this.id = id; this.cached = cached; @@ -54,6 +57,7 @@ public class OutgoingMessageEntity { this.relay = relay; this.timestamp = timestamp; this.source = source; + this.sourceUuid = sourceUuid; this.sourceDevice = sourceDevice; this.message = message; this.content = content; @@ -80,6 +84,10 @@ public class OutgoingMessageEntity { return source; } + public UUID getSourceUuid() { + return sourceUuid; + } + public int getSourceDevice() { return sourceDevice; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index ddbf225ac..f4a612ded 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -21,11 +21,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; + import javax.security.auth.Subject; import java.security.Principal; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; public class Account implements Principal { @@ -33,6 +36,9 @@ public class Account implements Principal { static final int MEMCACHE_VERION = 5; @JsonIgnore + private UUID uuid; + + @JsonProperty private String number; @JsonProperty @@ -71,8 +77,9 @@ public class Account implements Principal { public Account() {} @VisibleForTesting - public Account(String number, Set devices, byte[] unidentifiedAccessKey) { + public Account(String number, UUID uuid, Set devices, byte[] unidentifiedAccessKey) { this.number = number; + this.uuid = uuid; this.devices = devices; this.unidentifiedAccessKey = unidentifiedAccessKey; } @@ -85,6 +92,14 @@ public class Account implements Principal { this.authenticatedDevice = device; } + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + public void setNumber(String number) { this.number = number; } @@ -247,6 +262,12 @@ public class Account implements Principal { this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess; } + public boolean isFor(AmbiguousIdentifier identifier) { + if (identifier.hasUuid()) return identifier.getUuid().equals(uuid); + else if (identifier.hasNumber()) return identifier.getNumber().equals(number); + else throw new AssertionError(); + } + // Principal implementation @Override diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java index 9433a934c..53cd74f74 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountCleaner.java @@ -26,6 +26,7 @@ import org.whispersystems.textsecuregcm.util.Util; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; @@ -51,7 +52,7 @@ public class AccountCleaner implements AccountDatabaseCrawlerListener { } @Override - public void onCrawlChunk(Optional fromNumber, List chunkAccounts) { + public void onCrawlChunk(Optional fromUuid, List chunkAccounts) { int accountUpdateCount = 0; for (Account account : chunkAccounts) { if (needsExplicitRemoval(account)) { @@ -74,7 +75,7 @@ public class AccountCleaner implements AccountDatabaseCrawlerListener { } @Override - public void onCrawlEnd(Optional fromNumber) { + public void onCrawlEnd(Optional fromUuid) { } private boolean needsExplicitRemoval(Account account) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawler.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawler.java index 9a34e94dd..93d08d679 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawler.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawler.java @@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.codahale.metrics.MetricRegistry.name; import io.dropwizard.lifecycle.Managed; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class AccountDatabaseCrawler implements Managed, Runnable { private static final Logger logger = LoggerFactory.getLogger(AccountDatabaseCrawler.class); @@ -91,6 +92,7 @@ public class AccountDatabaseCrawler implements Managed, Runnable { sleepWhileRunning(accelerated ? ACCELERATED_CHUNK_INTERVAL : chunkIntervalMs); } catch (Throwable t) { logger.warn("error in database crawl: ", t); + Util.sleep(10000); } } @@ -120,26 +122,26 @@ public class AccountDatabaseCrawler implements Managed, Runnable { } private void processChunk() { - Optional fromNumber = cache.getLastNumber(); + Optional fromUuid = cache.getLastUuid(); - if (!fromNumber.isPresent()) { - listeners.forEach(listener -> { listener.onCrawlStart(); }); + if (!fromUuid.isPresent()) { + listeners.forEach(AccountDatabaseCrawlerListener::onCrawlStart); } - List chunkAccounts = readChunk(fromNumber, chunkSize); + List chunkAccounts = readChunk(fromUuid, chunkSize); if (chunkAccounts.isEmpty()) { - listeners.forEach(listener -> { listener.onCrawlEnd(fromNumber); }); - cache.setLastNumber(Optional.empty()); + listeners.forEach(listener -> listener.onCrawlEnd(fromUuid)); + cache.setLastUuid(Optional.empty()); cache.clearAccelerate(); } else { try { for (AccountDatabaseCrawlerListener listener : listeners) { - listener.onCrawlChunk(fromNumber, chunkAccounts); + listener.onCrawlChunk(fromUuid, chunkAccounts); } - cache.setLastNumber(Optional.of(chunkAccounts.get(chunkAccounts.size() - 1).getNumber())); + cache.setLastUuid(Optional.of(chunkAccounts.get(chunkAccounts.size() - 1).getUuid())); } catch (AccountDatabaseCrawlerRestartException e) { - cache.setLastNumber(Optional.empty()); + cache.setLastUuid(Optional.empty()); cache.clearAccelerate(); } @@ -147,12 +149,12 @@ public class AccountDatabaseCrawler implements Managed, Runnable { } - private List readChunk(Optional fromNumber, int chunkSize) { + private List readChunk(Optional fromUuid, int chunkSize) { try (Timer.Context timer = readChunkTimer.time()) { List chunkAccounts; - if (fromNumber.isPresent()) { - chunkAccounts = accounts.getAllFrom(fromNumber.get(), chunkSize); + if (fromUuid.isPresent()) { + chunkAccounts = accounts.getAllFrom(fromUuid.get(), chunkSize); } else { chunkAccounts = accounts.getAllFrom(chunkSize); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerCache.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerCache.java index 73e977c16..4b1402b1a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerCache.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerCache.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2018 Open WhisperSystems *

* This program is free software: you can redistribute it and/or modify @@ -18,17 +18,20 @@ package org.whispersystems.textsecuregcm.storage; import org.whispersystems.textsecuregcm.redis.LuaScript; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; -import redis.clients.jedis.Jedis; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.UUID; +import redis.clients.jedis.Jedis; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class AccountDatabaseCrawlerCache { private static final String ACTIVE_WORKER_KEY = "account_database_crawler_cache_active_worker"; - private static final String LAST_NUMBER_KEY = "account_database_crawler_cache_last_number"; + private static final String LAST_UUID_KEY = "account_database_crawler_cache_last_uuid"; private static final String ACCELERATE_KEY = "account_database_crawler_cache_accelerate"; private static final long LAST_NUMBER_TTL_MS = 86400_000L; @@ -65,18 +68,21 @@ public class AccountDatabaseCrawlerCache { luaScript.execute(keys, args); } - public Optional getLastNumber() { + public Optional getLastUuid() { try (Jedis jedis = jedisPool.getWriteResource()) { - return Optional.ofNullable(jedis.get(LAST_NUMBER_KEY)); + String lastUuidString = jedis.get(LAST_UUID_KEY); + + if (lastUuidString == null) return Optional.empty(); + else return Optional.of(UUID.fromString(lastUuidString)); } } - public void setLastNumber(Optional lastNumber) { + public void setLastUuid(Optional lastUuid) { try (Jedis jedis = jedisPool.getWriteResource()) { - if (lastNumber.isPresent()) { - jedis.psetex(LAST_NUMBER_KEY, LAST_NUMBER_TTL_MS, lastNumber.get()); + if (lastUuid.isPresent()) { + jedis.psetex(LAST_UUID_KEY, LAST_NUMBER_TTL_MS, lastUuid.get().toString()); } else { - jedis.del(LAST_NUMBER_KEY); + jedis.del(LAST_UUID_KEY); } } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerListener.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerListener.java index 051fc6638..590f61c38 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerListener.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountDatabaseCrawlerListener.java @@ -18,9 +18,11 @@ package org.whispersystems.textsecuregcm.storage; import java.util.List; import java.util.Optional; +import java.util.UUID; +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public interface AccountDatabaseCrawlerListener { void onCrawlStart(); - void onCrawlChunk(Optional fromNumber, List chunkAccounts) throws AccountDatabaseCrawlerRestartException; - void onCrawlEnd(Optional fromNumber); + void onCrawlChunk(Optional fromUuid, List chunkAccounts) throws AccountDatabaseCrawlerRestartException; + void onCrawlEnd(Optional fromUuid); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java index a0d0fe636..89a5a6417 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java @@ -28,24 +28,27 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; import java.util.List; import java.util.Optional; +import java.util.UUID; import static com.codahale.metrics.MetricRegistry.name; public class Accounts { public static final String ID = "id"; + public static final String UID = "uuid"; public static final String NUMBER = "number"; public static final String DATA = "data"; private static final ObjectMapper mapper = SystemMapper.getMapper(); private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private final Timer createTimer = metricRegistry.timer(name(Accounts.class, "create")); - private final Timer updateTimer = metricRegistry.timer(name(Accounts.class, "update")); - private final Timer getTimer = metricRegistry.timer(name(Accounts.class, "get")); - private final Timer getAllFromTimer = metricRegistry.timer(name(Accounts.class, "getAllFrom")); + private final Timer createTimer = metricRegistry.timer(name(Accounts.class, "create" )); + private final Timer updateTimer = metricRegistry.timer(name(Accounts.class, "update" )); + private final Timer getByNumberTimer = metricRegistry.timer(name(Accounts.class, "getByNumber" )); + private final Timer getByUuidTimer = metricRegistry.timer(name(Accounts.class, "getByUuid" )); + private final Timer getAllFromTimer = metricRegistry.timer(name(Accounts.class, "getAllFrom" )); private final Timer getAllFromOffsetTimer = metricRegistry.timer(name(Accounts.class, "getAllFromOffset")); - private final Timer vacuumTimer = metricRegistry.timer(name(Accounts.class, "vacuum")); + private final Timer vacuumTimer = metricRegistry.timer(name(Accounts.class, "vacuum" )); private final FaultTolerantDatabase database; @@ -57,16 +60,15 @@ public class Accounts { public boolean create(Account account) { return database.with(jdbi -> jdbi.inTransaction(TransactionIsolationLevel.SERIALIZABLE, handle -> { try (Timer.Context ignored = createTimer.time()) { - int rows = handle.createUpdate("DELETE FROM accounts WHERE " + NUMBER + " = :number") - .bind("number", account.getNumber()) - .execute(); + UUID uuid = handle.createQuery("INSERT INTO accounts (" + NUMBER + ", " + UID + ", " + DATA + ") VALUES (:number, :uuid, CAST(:data AS json)) ON CONFLICT(number) DO UPDATE SET data = EXCLUDED.data RETURNING uuid") + .bind("number", account.getNumber()) + .bind("uuid", account.getUuid()) + .bind("data", mapper.writeValueAsString(account)) + .mapTo(UUID.class) + .findOnly(); - handle.createUpdate("INSERT INTO accounts (" + NUMBER + ", " + DATA + ") VALUES (:number, CAST(:data AS json))") - .bind("number", account.getNumber()) - .bind("data", mapper.writeValueAsString(account)) - .execute(); - - return rows == 0; + account.setUuid(uuid); + return uuid.equals(account.getUuid()); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); } @@ -76,8 +78,8 @@ public class Accounts { public void update(Account account) { database.use(jdbi -> jdbi.useHandle(handle -> { try (Timer.Context ignored = updateTimer.time()) { - handle.createUpdate("UPDATE accounts SET " + DATA + " = CAST(:data AS json) WHERE " + NUMBER + " = :number") - .bind("number", account.getNumber()) + handle.createUpdate("UPDATE accounts SET " + DATA + " = CAST(:data AS json) WHERE " + UID + " = :uuid") + .bind("uuid", account.getUuid()) .bind("data", mapper.writeValueAsString(account)) .execute(); } catch (JsonProcessingException e) { @@ -88,7 +90,7 @@ public class Accounts { public Optional get(String number) { return database.with(jdbi -> jdbi.withHandle(handle -> { - try (Timer.Context ignored = getTimer.time()) { + try (Timer.Context ignored = getByNumberTimer.time()) { return handle.createQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number") .bind("number", number) .mapTo(Account.class) @@ -97,10 +99,21 @@ public class Accounts { })); } - public List getAllFrom(String from, int length) { + public Optional get(UUID uuid) { + return database.with(jdbi -> jdbi.withHandle(handle -> { + try (Timer.Context ignored = getByUuidTimer.time()) { + return handle.createQuery("SELECT * FROM accounts WHERE " + UID + " = :uuid") + .bind("uuid", uuid) + .mapTo(Account.class) + .findFirst(); + } + })); + } + + public List getAllFrom(UUID from, int length) { return database.with(jdbi -> jdbi.withHandle(handle -> { try (Timer.Context ignored = getAllFromOffsetTimer.time()) { - return handle.createQuery("SELECT * FROM accounts WHERE " + NUMBER + " > :from ORDER BY " + NUMBER + " LIMIT :limit") + return handle.createQuery("SELECT * FROM accounts WHERE " + UID + " > :from ORDER BY " + UID + " LIMIT :limit") .bind("from", from) .bind("limit", length) .mapTo(Account.class) @@ -112,7 +125,7 @@ public class Accounts { public List getAllFrom(int length) { return database.with(jdbi -> jdbi.withHandle(handle -> { try (Timer.Context ignored = getAllFromTimer.time()) { - return handle.createQuery("SELECT * FROM accounts ORDER BY " + NUMBER + " LIMIT :limit") + return handle.createQuery("SELECT * FROM accounts ORDER BY " + UID + " LIMIT :limit") .bind("limit", length) .mapTo(Account.class) .list(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 0697e47cb..38b07866d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.entities.ClientContact; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.util.Constants; @@ -33,6 +34,7 @@ import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; import java.util.List; import java.util.Optional; +import java.util.UUID; import static com.codahale.metrics.MetricRegistry.name; import redis.clients.jedis.Jedis; @@ -40,13 +42,15 @@ import redis.clients.jedis.exceptions.JedisException; public class AccountsManager { - private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); - private static final Timer createTimer = metricRegistry.timer(name(AccountsManager.class, "create" )); - private static final Timer updateTimer = metricRegistry.timer(name(AccountsManager.class, "update" )); - private static final Timer getTimer = metricRegistry.timer(name(AccountsManager.class, "get" )); + private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); + private static final Timer createTimer = metricRegistry.timer(name(AccountsManager.class, "create" )); + private static final Timer updateTimer = metricRegistry.timer(name(AccountsManager.class, "update" )); + private static final Timer getByNumberTimer = metricRegistry.timer(name(AccountsManager.class, "getByNumber")); + private static final Timer getByUuidTimer = metricRegistry.timer(name(AccountsManager.class, "getByUuid" )); - private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet" )); - private static final Timer redisGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisGet" )); + private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet" )); + private static final Timer redisNumberGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisNumberGet")); + private static final Timer redisUuidGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUuidGet" )); private final Logger logger = LoggerFactory.getLogger(AccountsManager.class); @@ -65,7 +69,7 @@ public class AccountsManager { public boolean create(Account account) { try (Timer.Context context = createTimer.time()) { boolean freshUser = databaseCreate(account); - redisSet(account.getNumber(), account, false); + redisSet(account); updateDirectory(account); return freshUser; @@ -74,31 +78,51 @@ public class AccountsManager { public void update(Account account) { try (Timer.Context context = updateTimer.time()) { - redisSet(account.getNumber(), account, false); + redisSet(account); databaseUpdate(account); updateDirectory(account); } } + public Optional get(AmbiguousIdentifier identifier) { + if (identifier.hasNumber()) return get(identifier.getNumber()); + else if (identifier.hasUuid()) return get(identifier.getUuid()); + else throw new AssertionError(); + } + public Optional get(String number) { - try (Timer.Context context = getTimer.time()) { + try (Timer.Context context = getByNumberTimer.time()) { Optional account = redisGet(number); if (!account.isPresent()) { account = databaseGet(number); - account.ifPresent(value -> redisSet(number, value, true)); + account.ifPresent(value -> redisSet(value)); } return account; } } + public Optional get(UUID uuid) { + try (Timer.Context context = getByUuidTimer.time()) { + Optional account = redisGet(uuid); + + if (!account.isPresent()) { + account = databaseGet(uuid); + account.ifPresent(value -> redisSet(value)); + } + + return account; + } + } + + public List getAllFrom(int length) { return accounts.getAllFrom(length); } - public List getAllFrom(String number, int length) { - return accounts.getAllFrom(number, length); + public List getAllFrom(UUID uuid, int length) { + return accounts.getAllFrom(uuid, length); } private void updateDirectory(Account account) { @@ -111,15 +135,20 @@ public class AccountsManager { } } - private String getKey(String number) { - return Account.class.getSimpleName() + Account.MEMCACHE_VERION + number; + private String getAccountMapKey(String number) { + return "AccountMap::" + number; } - private void redisSet(String number, Account account, boolean optional) { + private String getAccountEntityKey(UUID uuid) { + return "Account::" + uuid.toString(); + } + + private void redisSet(Account account) { try (Jedis jedis = cacheClient.getWriteResource(); Timer.Context timer = redisSetTimer.time()) { - jedis.set(getKey(number), mapper.writeValueAsString(account)); + jedis.set(getAccountMapKey(account.getNumber()), account.getUuid().toString()); + jedis.set(getAccountEntityKey(account.getUuid()), mapper.writeValueAsString(account)); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } @@ -127,20 +156,14 @@ public class AccountsManager { private Optional redisGet(String number) { try (Jedis jedis = cacheClient.getReadResource(); - Timer.Context timer = redisGetTimer.time()) + Timer.Context timer = redisNumberGetTimer.time()) { - String json = jedis.get(getKey(number)); + String uuid = jedis.get(getAccountMapKey(number)); - if (json != null) { - Account account = mapper.readValue(json, Account.class); - account.setNumber(number); - - return Optional.of(account); - } - - return Optional.empty(); - } catch (IOException e) { - logger.warn("AccountsManager", "Deserialization error", e); + if (uuid != null) return redisGet(UUID.fromString(uuid)); + else return Optional.empty(); + } catch (IllegalArgumentException e) { + logger.warn("Deserialization error", e); return Optional.empty(); } catch (JedisException e) { logger.warn("Redis failure", e); @@ -148,10 +171,38 @@ public class AccountsManager { } } + private Optional redisGet(UUID uuid) { + try (Jedis jedis = cacheClient.getReadResource(); + Timer.Context timer = redisUuidGetTimer.time()) + { + String json = jedis.get(getAccountEntityKey(uuid)); + + if (json != null) { + Account account = mapper.readValue(json, Account.class); + account.setUuid(uuid); + + return Optional.of(account); + } + + return Optional.empty(); + } catch (IOException e) { + logger.warn("Deserialization error", e); + return Optional.empty(); + } catch (JedisException e) { + logger.warn("Redis failure", e); + return Optional.empty(); + } + + } + private Optional databaseGet(String number) { return accounts.get(number); } + private Optional databaseGet(UUID uuid) { + return accounts.get(uuid); + } + private boolean databaseCreate(Account account) { return accounts.create(account); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ActiveUserCounter.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ActiveUserCounter.java index 8ef5cb6c6..099cae5d5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ActiveUserCounter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ActiveUserCounter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2018 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -20,22 +20,22 @@ import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.dropwizard.metrics.MetricsFactory; -import io.dropwizard.metrics.ReporterFactory; import org.whispersystems.textsecuregcm.entities.ActiveUserTally; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; -import redis.clients.jedis.Jedis; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + +import io.dropwizard.metrics.MetricsFactory; +import io.dropwizard.metrics.ReporterFactory; +import redis.clients.jedis.Jedis; public class ActiveUserCounter implements AccountDatabaseCrawlerListener { @@ -44,8 +44,6 @@ public class ActiveUserCounter implements AccountDatabaseCrawlerListener { private static final String PLATFORM_IOS = "ios"; private static final String PLATFORM_ANDROID = "android"; - private static final String FIRST_FROM_NUMBER = "+"; - private static final String INTERVALS[] = {"daily", "weekly", "monthly", "quarterly", "yearly"}; private final MetricsFactory metricsFactory; @@ -64,7 +62,8 @@ public class ActiveUserCounter implements AccountDatabaseCrawlerListener { } } - public void onCrawlChunk(Optional fromNumber, List chunkAccounts) { + @Override + public void onCrawlChunk(Optional fromNumber, List chunkAccounts) { long nowDays = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); long agoMs[] = {TimeUnit.DAYS.toMillis(nowDays - 1), TimeUnit.DAYS.toMillis(nowDays - 7), @@ -107,47 +106,40 @@ public class ActiveUserCounter implements AccountDatabaseCrawlerListener { } } - incrementTallies(fromNumber.orElse(FIRST_FROM_NUMBER), platformIncrements, countryIncrements); - + incrementTallies(fromNumber.orElse(UUID.randomUUID()), platformIncrements, countryIncrements); } - public void onCrawlEnd(Optional fromNumber) { - MetricRegistry metrics = new MetricRegistry(); - long intervalTallies[] = new long[INTERVALS.length]; - ActiveUserTally activeUserTally = getFinalTallies(); - Map platforms = activeUserTally.getPlatforms(); + @Override + public void onCrawlEnd(Optional fromNumber) { + MetricRegistry metrics = new MetricRegistry(); + long intervalTallies[] = new long[INTERVALS.length]; + ActiveUserTally activeUserTally = getFinalTallies(); + Map platforms = activeUserTally.getPlatforms(); + platforms.forEach((platform, platformTallies) -> { - for (int i = 0; i < INTERVALS.length; i++) { - final long tally = platformTallies[i]; - metrics.register(metricKey(platform, INTERVALS[i]), - new Gauge() { - @Override - public Long getValue() { return tally; } - }); - intervalTallies[i] += tally; - } - }); + for (int i = 0; i < INTERVALS.length; i++) { + final long tally = platformTallies[i]; + metrics.register(metricKey(platform, INTERVALS[i]), + (Gauge) () -> tally); + intervalTallies[i] += tally; + } + }); Map countries = activeUserTally.getCountries(); countries.forEach((country, countryTallies) -> { - for (int i = 0; i < INTERVALS.length; i++) { - final long tally = countryTallies[i]; - metrics.register(metricKey(country, INTERVALS[i]), - new Gauge() { - @Override - public Long getValue() { return tally; } - }); - } - }); + for (int i = 0; i < INTERVALS.length; i++) { + final long tally = countryTallies[i]; + metrics.register(metricKey(country, INTERVALS[i]), + (Gauge) () -> tally); + } + }); for (int i = 0; i < INTERVALS.length; i++) { final long intervalTotal = intervalTallies[i]; metrics.register(metricKey(INTERVALS[i]), - new Gauge() { - @Override - public Long getValue() { return intervalTotal; } - }); + (Gauge) () -> intervalTotal); } + for (ReporterFactory reporterFactory : metricsFactory.getReporters()) { reporterFactory.build(metrics).report(); } @@ -162,22 +154,25 @@ public class ActiveUserCounter implements AccountDatabaseCrawlerListener { return tally; } - private void incrementTallies(String fromNumber, Map platformIncrements, Map countryIncrements) { + private void incrementTallies(UUID fromUuid, Map platformIncrements, Map countryIncrements) { try (Jedis jedis = jedisPool.getWriteResource()) { String tallyValue = jedis.get(TALLY_KEY); ActiveUserTally activeUserTally; + if (tallyValue == null) { - activeUserTally = new ActiveUserTally(fromNumber, platformIncrements, countryIncrements); + activeUserTally = new ActiveUserTally(fromUuid, platformIncrements, countryIncrements); } else { activeUserTally = mapper.readValue(tallyValue, ActiveUserTally.class); - if (activeUserTally.getFromNumber() != fromNumber) { - activeUserTally.setFromNumber(fromNumber); + + if (!fromUuid.equals(activeUserTally.getFromUuid())) { + activeUserTally.setFromUuid(fromUuid); Map platformTallies = activeUserTally.getPlatforms(); addTallyMaps(platformTallies, platformIncrements); Map countryTallies = activeUserTally.getCountries(); addTallyMaps(countryTallies, countryIncrements); } } + jedis.set(TALLY_KEY, mapper.writeValueAsString(activeUserTally)); } catch (JsonProcessingException e) { throw new IllegalArgumentException(e); @@ -188,15 +183,15 @@ public class ActiveUserCounter implements AccountDatabaseCrawlerListener { private void addTallyMaps(Map tallyMap, Map incrementMap) { incrementMap.forEach((key, increments) -> { - long[] tallies = tallyMap.get(key); - if (tallies == null) { - tallyMap.put(key, increments); - } else { - for (int i = 0; i < INTERVALS.length; i++) { - tallies[i] += increments[i]; - } + long[] tallies = tallyMap.get(key); + if (tallies == null) { + tallyMap.put(key, increments); + } else { + for (int i = 0; i < INTERVALS.length; i++) { + tallies[i] += increments[i]; } - }); + } + }); } private ActiveUserTally getFinalTallies() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java index 972e9230f..f9718f02b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciler.java @@ -33,6 +33,7 @@ import javax.ws.rs.ProcessingException; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import static com.codahale.metrics.MetricRegistry.name; @@ -55,18 +56,16 @@ public class DirectoryReconciler implements AccountDatabaseCrawlerListener { public void onCrawlStart() { } - public void onCrawlEnd(Optional fromNumber) { - - DirectoryReconciliationRequest request = new DirectoryReconciliationRequest(fromNumber.orElse(null), null, Collections.emptyList()); + public void onCrawlEnd(Optional fromUuid) { + DirectoryReconciliationRequest request = new DirectoryReconciliationRequest(fromUuid.orElse(null), null, Collections.emptyList()); DirectoryReconciliationResponse response = sendChunk(request); - } - public void onCrawlChunk(Optional fromNumber, List chunkAccounts) throws AccountDatabaseCrawlerRestartException { + public void onCrawlChunk(Optional fromUuid, List chunkAccounts) throws AccountDatabaseCrawlerRestartException { updateDirectoryCache(chunkAccounts); - DirectoryReconciliationRequest request = createChunkRequest(fromNumber, chunkAccounts); + DirectoryReconciliationRequest request = createChunkRequest(fromUuid, chunkAccounts); DirectoryReconciliationResponse response = sendChunk(request); if (response.getStatus() == DirectoryReconciliationResponse.Status.MISSING) { throw new AccountDatabaseCrawlerRestartException("directory reconciler missing"); @@ -93,19 +92,19 @@ public class DirectoryReconciler implements AccountDatabaseCrawlerListener { } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private DirectoryReconciliationRequest createChunkRequest(Optional fromNumber, List accounts) { + private DirectoryReconciliationRequest createChunkRequest(Optional fromUuid, List accounts) { List numbers = accounts.stream() .filter(Account::isEnabled) .map(Account::getNumber) .collect(Collectors.toList()); - Optional toNumber = Optional.empty(); + Optional toUuid = Optional.empty(); if (!accounts.isEmpty()) { - toNumber = Optional.of(accounts.get(accounts.size() - 1).getNumber()); + toUuid = Optional.of(accounts.get(accounts.size() - 1).getUuid()); } - return new DirectoryReconciliationRequest(fromNumber.orElse(null), toNumber.orElse(null), numbers); + return new DirectoryReconciliationRequest(fromUuid.orElse(null), toUuid.orElse(null), numbers); } private DirectoryReconciliationResponse sendChunk(DirectoryReconciliationRequest request) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java index 7af026dc0..a1acbd2c8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Messages.java @@ -25,6 +25,7 @@ public class Messages { public static final String TIMESTAMP = "timestamp"; public static final String SERVER_TIMESTAMP = "server_timestamp"; public static final String SOURCE = "source"; + public static final String SOURCE_UUID = "source_uuid"; public static final String SOURCE_DEVICE = "source_device"; public static final String DESTINATION = "destination"; public static final String DESTINATION_DEVICE = "destination_device"; @@ -51,8 +52,8 @@ public class Messages { public void store(UUID guid, Envelope message, String destination, long destinationDevice) { database.use(jdbi ->jdbi.useHandle(handle -> { try (Timer.Context ignored = storeTimer.time()) { - handle.createUpdate("INSERT INTO messages (" + GUID + ", " + TYPE + ", " + RELAY + ", " + TIMESTAMP + ", " + SERVER_TIMESTAMP + ", " + SOURCE + ", " + SOURCE_DEVICE + ", " + DESTINATION + ", " + DESTINATION_DEVICE + ", " + MESSAGE + ", " + CONTENT + ") " + - "VALUES (:guid, :type, :relay, :timestamp, :server_timestamp, :source, :source_device, :destination, :destination_device, :message, :content)") + handle.createUpdate("INSERT INTO messages (" + GUID + ", " + TYPE + ", " + RELAY + ", " + TIMESTAMP + ", " + SERVER_TIMESTAMP + ", " + SOURCE + ", " + SOURCE_UUID + ", " + SOURCE_DEVICE + ", " + DESTINATION + ", " + DESTINATION_DEVICE + ", " + MESSAGE + ", " + CONTENT + ") " + + "VALUES (:guid, :type, :relay, :timestamp, :server_timestamp, :source, :source_uuid, :source_device, :destination, :destination_device, :message, :content)") .bind("guid", guid) .bind("destination", destination) .bind("destination_device", destinationDevice) @@ -61,6 +62,7 @@ public class Messages { .bind("timestamp", message.getTimestamp()) .bind("server_timestamp", message.getServerTimestamp()) .bind("source", message.hasSource() ? message.getSource() : null) + .bind("source_uuid", message.hasSourceUuid() ? UUID.fromString(message.getSourceUuid()) : null) .bind("source_device", message.hasSourceDevice() ? message.getSourceDevice() : null) .bind("message", message.hasLegacyMessage() ? message.getLegacyMessage().toByteArray() : null) .bind("content", message.hasContent() ? message.getContent().toByteArray() : null) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java index aef6cbf2b..0a3d2a8df 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesCache.java @@ -203,6 +203,7 @@ public class MessagesCache implements Managed { 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, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PublicAccount.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PublicAccount.java deleted file mode 100644 index 83514e408..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PublicAccount.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.whispersystems.textsecuregcm.storage; - -public class PublicAccount extends Account { - - public PublicAccount() {} - - public PublicAccount(Account account) { - setIdentityKey(account.getIdentityKey()); - setUnidentifiedAccessKey(account.getUnidentifiedAccessKey().orElse(null)); - setUnrestrictedUnidentifiedAccess(account.isUnrestrictedUnidentifiedAccess()); - setAvatar(account.getAvatar()); - setProfileName(account.getProfileName()); - setPin("******"); - - account.getDevices().forEach(this::addDevice); - } - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PushFeedbackProcessor.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PushFeedbackProcessor.java index 25e21083b..c656f1c95 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/PushFeedbackProcessor.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/PushFeedbackProcessor.java @@ -9,6 +9,7 @@ import org.whispersystems.textsecuregcm.util.Util; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; @@ -31,7 +32,7 @@ public class PushFeedbackProcessor implements AccountDatabaseCrawlerListener { public void onCrawlStart() {} @Override - public void onCrawlChunk(Optional fromNumber, List chunkAccounts) { + public void onCrawlChunk(Optional fromUuid, List chunkAccounts) { for (Account account : chunkAccounts) { boolean update = false; @@ -65,5 +66,5 @@ public class PushFeedbackProcessor implements AccountDatabaseCrawlerListener { } @Override - public void onCrawlEnd(Optional fromNumber) {} + public void onCrawlEnd(Optional toUuid) {} } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/AccountRowMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/AccountRowMapper.java index 7b608b1ac..be504999e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/AccountRowMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/AccountRowMapper.java @@ -10,6 +10,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.UUID; public class AccountRowMapper implements RowMapper { @@ -20,6 +21,7 @@ public class AccountRowMapper implements RowMapper { try { Account account = mapper.readValue(resultSet.getString(Accounts.DATA), Account.class); account.setNumber(resultSet.getString(Accounts.NUMBER)); + account.setUuid(UUID.fromString(resultSet.getString(Accounts.UID))); return account; } catch (IOException e) { throw new SQLException(e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/OutgoingMessageEntityRowMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/OutgoingMessageEntityRowMapper.java index 2f238ea98..3bea1b217 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/OutgoingMessageEntityRowMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/mappers/OutgoingMessageEntityRowMapper.java @@ -16,6 +16,7 @@ public class OutgoingMessageEntityRowMapper implements RowMapper + + + + + + + + CREATE UNIQUE INDEX CONCURRENTLY uuid_index ON accounts (uuid); + + diff --git a/service/src/main/resources/messagedb.xml b/service/src/main/resources/messagedb.xml index eacbe4611..fc90a278e 100644 --- a/service/src/main/resources/messagedb.xml +++ b/service/src/main/resources/messagedb.xml @@ -121,4 +121,10 @@ CREATE INDEX CONCURRENTLY guid_index ON messages (guid); + + + + + + diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index c1401608a..18368b503 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -14,9 +14,10 @@ import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; +import org.whispersystems.textsecuregcm.entities.AccountCreationResult; import org.whispersystems.textsecuregcm.entities.ApnRegistrationId; -import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.entities.DeprecatedPin; +import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.entities.RegistrationLock; import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure; import org.whispersystems.textsecuregcm.limits.RateLimiter; @@ -463,15 +464,15 @@ public class AccountControllerTest { @Test public void testVerifyCode() throws Exception { - Response response = + AccountCreationResult result = resources.getJerseyTest() .target(String.format("/v1/accounts/code/%s", "1234")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar")) .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null), - MediaType.APPLICATION_JSON_TYPE)); + MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); - assertThat(response.getStatus()).isEqualTo(204); + assertThat(result.getUuid()).isNotNull(); verify(accountsManager, times(1)).create(isA(Account.class)); verify(directoryQueue, times(1)).deleteRegisteredUser(eq(SENDER)); @@ -509,30 +510,30 @@ public class AccountControllerTest { @Test public void testVerifyPin() throws Exception { - Response response = + AccountCreationResult result = resources.getJerseyTest() .target(String.format("/v1/accounts/code/%s", "333333")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar")) .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, "31337"), - MediaType.APPLICATION_JSON_TYPE)); + MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); - assertThat(response.getStatus()).isEqualTo(204); + assertThat(result.getUuid()).isNotNull(); verify(pinLimiter).validate(eq(SENDER_PIN)); } @Test public void testVerifyRegistrationLock() throws Exception { - Response response = + AccountCreationResult result = resources.getJerseyTest() .target(String.format("/v1/accounts/code/%s", "666666")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_REG_LOCK, "bar")) .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null, null, Hex.toStringCondensed(registration_lock_key)), - MediaType.APPLICATION_JSON_TYPE)); + MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); - assertThat(response.getStatus()).isEqualTo(204); + assertThat(result.getUuid()).isNotNull(); verify(pinLimiter).validate(eq(SENDER_REG_LOCK)); } @@ -628,15 +629,15 @@ public class AccountControllerTest { try { when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)); - Response response = + AccountCreationResult result = resources.getJerseyTest() .target(String.format("/v1/accounts/code/%s", "444444")) .request() .header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar")) .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null), - MediaType.APPLICATION_JSON_TYPE)); + MediaType.APPLICATION_JSON_TYPE), AccountCreationResult.class); - assertThat(response.getStatus()).isEqualTo(204); + assertThat(result.getUuid()).isNotNull(); } finally { when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis()); @@ -666,7 +667,7 @@ public class AccountControllerTest { resources.getJerseyTest() .target("/v1/accounts/registration_lock/") .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) .put(Entity.json(new RegistrationLock("1234567890123456789012345678901234567890123456789012345678901234"))); assertThat(response.getStatus()).isEqualTo(204); @@ -745,7 +746,6 @@ public class AccountControllerTest { assertThat(response.getStatus()).isEqualTo(401); } - @Test public void testSetGcmId() throws Exception { Response response = @@ -761,6 +761,21 @@ public class AccountControllerTest { verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); } + @Test + public void testSetGcmIdByUuid() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/gcm/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_UUID.toString(), AuthHelper.DISABLED_PASSWORD)) + .put(Entity.json(new GcmRegistrationId("z000"))); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.DISABLED_DEVICE, times(1)).setGcmId(eq("z000")); + verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); + } + @Test public void testSetApnId() throws Exception { Response response = @@ -777,5 +792,32 @@ public class AccountControllerTest { verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); } + @Test + public void testSetApnIdByUuid() throws Exception { + Response response = + resources.getJerseyTest() + .target("/v1/accounts/apn/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_UUID.toString(), AuthHelper.DISABLED_PASSWORD)) + .put(Entity.json(new ApnRegistrationId("third", "fourth"))); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.DISABLED_DEVICE, times(1)).setApnId(eq("third")); + verify(AuthHelper.DISABLED_DEVICE, times(1)).setVoipApnId(eq("fourth")); + verify(accountsManager, times(1)).update(eq(AuthHelper.DISABLED_ACCOUNT)); + } + + @Test + public void testWhoAmI() { + AccountCreationResult response = + resources.getJerseyTest() + .target("/v1/accounts/whoami/") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .get(AccountCreationResult.class); + + assertThat(response.getUuid()).isEqualTo(AuthHelper.VALID_UUID); + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index e786fb8a5..81239758d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -75,6 +75,7 @@ public class CertificateControllerTest { assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER); assertEquals(certificate.getSenderDevice(), 1L); + assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString()); assertTrue(Arrays.equals(certificate.getIdentityKey().toByteArray(), Base64.decode(AuthHelper.VALID_IDENTITY))); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java index 3eb0b4be7..791a72626 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeyControllerTest.java @@ -6,6 +6,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.KeysController; @@ -32,6 +34,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.UUID; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit.ResourceTestRule; @@ -40,8 +43,11 @@ import static org.mockito.Mockito.*; public class KeyControllerTest { - private static final String EXISTS_NUMBER = "+14152222222"; + private static final String EXISTS_NUMBER = "+14152222222"; + private static final UUID EXISTS_UUID = UUID.randomUUID(); + private static String NOT_EXISTS_NUMBER = "+14152222220"; + private static UUID NOT_EXISTS_UUID = UUID.randomUUID(); private static int SAMPLE_REGISTRATION_ID = 999; private static int SAMPLE_REGISTRATION_ID2 = 1002; @@ -117,7 +123,14 @@ public class KeyControllerTest { when(existsAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of("1337".getBytes())); when(accounts.get(EXISTS_NUMBER)).thenReturn(Optional.of(existsAccount)); + when(accounts.get(EXISTS_UUID)).thenReturn(Optional.of(existsAccount)); + when(accounts.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(EXISTS_NUMBER)))).thenReturn(Optional.of(existsAccount)); + when(accounts.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(EXISTS_UUID)))).thenReturn(Optional.of(existsAccount)); + when(accounts.get(NOT_EXISTS_NUMBER)).thenReturn(Optional.empty()); + when(accounts.get(NOT_EXISTS_UUID)).thenReturn(Optional.empty()); + when(accounts.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(NOT_EXISTS_NUMBER)))).thenReturn(Optional.empty()); + when(accounts.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(NOT_EXISTS_UUID)))).thenReturn(Optional.empty()); when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter); @@ -141,7 +154,7 @@ public class KeyControllerTest { } @Test - public void validKeyStatusTestV2() throws Exception { + public void validKeyStatusTestByNumberV2() throws Exception { PreKeyCount result = resources.getJerseyTest() .target("/v2/keys") .request() @@ -155,7 +168,22 @@ public class KeyControllerTest { } @Test - public void getSignedPreKeyV2() throws Exception { + public void validKeyStatusTestByUuidV2() throws Exception { + PreKeyCount result = resources.getJerseyTest() + .target("/v2/keys") + .request() + .header("Authorization", + AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) + .get(PreKeyCount.class); + + assertThat(result.getCount()).isEqualTo(4); + + verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L)); + } + + + @Test + public void getSignedPreKeyV2ByNumber() throws Exception { SignedPreKey result = resources.getJerseyTest() .target("/v2/keys/signed") .request() @@ -168,7 +196,20 @@ public class KeyControllerTest { } @Test - public void putSignedPreKeyV2() throws Exception { + public void getSignedPreKeyV2ByUuid() throws Exception { + SignedPreKey result = resources.getJerseyTest() + .target("/v2/keys/signed") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) + .get(SignedPreKey.class); + + assertThat(result.getSignature()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getSignature()); + assertThat(result.getKeyId()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getKeyId()); + assertThat(result.getPublicKey()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getPublicKey()); + } + + @Test + public void putSignedPreKeyV2ByNumber() throws Exception { SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz"); Response response = resources.getJerseyTest() .target("/v2/keys/signed") @@ -183,7 +224,23 @@ public class KeyControllerTest { } @Test - public void disabledPutSignedPreKeyV2() throws Exception { + public void putSignedPreKeyV2ByUuid() throws Exception { + SignedPreKey test = new SignedPreKey(9998, "fooozzz", "baaarzzz"); + Response response = resources.getJerseyTest() + .target("/v2/keys/signed") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) + .put(Entity.entity(test, MediaType.APPLICATION_JSON_TYPE)); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(AuthHelper.VALID_DEVICE).setSignedPreKey(eq(test)); + verify(accounts).update(eq(AuthHelper.VALID_ACCOUNT)); + } + + + @Test + public void disabledPutSignedPreKeyV2ByNumber() throws Exception { SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz"); Response response = resources.getJerseyTest() .target("/v2/keys/signed") @@ -195,7 +252,20 @@ public class KeyControllerTest { } @Test - public void validSingleRequestTestV2() throws Exception { + public void disabledPutSignedPreKeyV2ByUuid() throws Exception { + SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz"); + Response response = resources.getJerseyTest() + .target("/v2/keys/signed") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_UUID.toString(), AuthHelper.DISABLED_PASSWORD)) + .put(Entity.entity(test, MediaType.APPLICATION_JSON_TYPE)); + + assertThat(response.getStatus()).isEqualTo(401); + } + + + @Test + public void validSingleRequestTestV2ByNumber() throws Exception { PreKeyResponse result = resources.getJerseyTest() .target(String.format("/v2/keys/%s/1", EXISTS_NUMBER)) .request() @@ -213,7 +283,26 @@ public class KeyControllerTest { } @Test - public void testUnidentifiedRequest() throws Exception { + public void validSingleRequestTestV2ByUuid() throws Exception { + PreKeyResponse result = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/1", EXISTS_UUID)) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) + .get(PreKeyResponse.class); + + assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey()); + assertThat(result.getDevicesCount()).isEqualTo(1); + assertThat(result.getDevice(1).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId()); + assertThat(result.getDevice(1).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey()); + assertThat(result.getDevice(1).getSignedPreKey()).isEqualTo(existsAccount.getDevice(1).get().getSignedPreKey()); + + verify(keys).get(eq(EXISTS_NUMBER), eq(1L)); + verifyNoMoreInteractions(keys); + } + + + @Test + public void testUnidentifiedRequestByNumber() throws Exception { PreKeyResponse result = resources.getJerseyTest() .target(String.format("/v2/keys/%s/1", EXISTS_NUMBER)) .request() @@ -230,6 +319,25 @@ public class KeyControllerTest { verifyNoMoreInteractions(keys); } + @Test + public void testUnidentifiedRequestByUuid() throws Exception { + PreKeyResponse result = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/1", EXISTS_UUID.toString())) + .request() + .header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1337".getBytes())) + .get(PreKeyResponse.class); + + assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey()); + assertThat(result.getDevicesCount()).isEqualTo(1); + assertThat(result.getDevice(1).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId()); + assertThat(result.getDevice(1).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey()); + assertThat(result.getDevice(1).getSignedPreKey()).isEqualTo(existsAccount.getDevice(1).get().getSignedPreKey()); + + verify(keys).get(eq(EXISTS_NUMBER), eq(1L)); + verifyNoMoreInteractions(keys); + } + + @Test public void testUnauthorizedUnidentifiedRequest() throws Exception { Response response = resources.getJerseyTest() @@ -256,7 +364,7 @@ public class KeyControllerTest { @Test - public void validMultiRequestTestV2() throws Exception { + public void validMultiRequestTestV2ByNumber() throws Exception { PreKeyResponse results = resources.getJerseyTest() .target(String.format("/v2/keys/%s/*", EXISTS_NUMBER)) .request() @@ -305,6 +413,57 @@ public class KeyControllerTest { verifyNoMoreInteractions(keys); } + @Test + public void validMultiRequestTestV2ByUuid() throws Exception { + PreKeyResponse results = resources.getJerseyTest() + .target(String.format("/v2/keys/%s/*", EXISTS_UUID.toString())) + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) + .get(PreKeyResponse.class); + + assertThat(results.getDevicesCount()).isEqualTo(3); + assertThat(results.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey()); + + PreKey signedPreKey = results.getDevice(1).getSignedPreKey(); + PreKey preKey = results.getDevice(1).getPreKey(); + long registrationId = results.getDevice(1).getRegistrationId(); + long deviceId = results.getDevice(1).getDeviceId(); + + assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId()); + assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey()); + assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID); + assertThat(signedPreKey.getKeyId()).isEqualTo(SAMPLE_SIGNED_KEY.getKeyId()); + assertThat(signedPreKey.getPublicKey()).isEqualTo(SAMPLE_SIGNED_KEY.getPublicKey()); + assertThat(deviceId).isEqualTo(1); + + signedPreKey = results.getDevice(2).getSignedPreKey(); + preKey = results.getDevice(2).getPreKey(); + registrationId = results.getDevice(2).getRegistrationId(); + deviceId = results.getDevice(2).getDeviceId(); + + assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId()); + assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey()); + assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID2); + assertThat(signedPreKey.getKeyId()).isEqualTo(SAMPLE_SIGNED_KEY2.getKeyId()); + assertThat(signedPreKey.getPublicKey()).isEqualTo(SAMPLE_SIGNED_KEY2.getPublicKey()); + assertThat(deviceId).isEqualTo(2); + + signedPreKey = results.getDevice(4).getSignedPreKey(); + preKey = results.getDevice(4).getPreKey(); + registrationId = results.getDevice(4).getRegistrationId(); + deviceId = results.getDevice(4).getDeviceId(); + + assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY4.getKeyId()); + assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY4.getPublicKey()); + assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID4); + assertThat(signedPreKey).isNull(); + assertThat(deviceId).isEqualTo(4); + + verify(keys).get(eq(EXISTS_NUMBER)); + verifyNoMoreInteractions(keys); + } + + @Test public void invalidRequestTestV2() throws Exception { Response response = resources.getJerseyTest() diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index 5ac266cdf..17114d058 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -7,6 +7,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.controllers.MessageController; @@ -55,7 +57,10 @@ import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixtur public class MessageControllerTest { private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111"; + private static final UUID SINGLE_DEVICE_UUID = UUID.randomUUID(); + private static final String MULTI_DEVICE_RECIPIENT = "+14152222222"; + private static final UUID MULTI_DEVICE_UUID = UUID.randomUUID(); private final PushSender pushSender = mock(PushSender.class ); private final ReceiptSender receiptSender = mock(ReceiptSender.class); @@ -89,11 +94,13 @@ public class MessageControllerTest { add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", true, 0)); }}; - Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList, "1234".getBytes()); - Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, multiDeviceList, "1234".getBytes()); + Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, SINGLE_DEVICE_UUID, singleDeviceList, "1234".getBytes()); + Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, MULTI_DEVICE_UUID, multiDeviceList, "1234".getBytes()); when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount)); + when(accountsManager.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(SINGLE_DEVICE_RECIPIENT)))).thenReturn(Optional.of(singleDeviceAccount)); when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount)); + when(accountsManager.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(MULTI_DEVICE_RECIPIENT)))).thenReturn(Optional.of(multiDeviceAccount)); when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter); } @@ -240,11 +247,12 @@ public class MessageControllerTest { final long timestampOne = 313377; final long timestampTwo = 313388; - final UUID uuidOne = UUID.randomUUID(); + final UUID messageGuidOne = UUID.randomUUID(); + final UUID sourceUuid = UUID.randomUUID(); - List messages = new LinkedList() {{ - add(new OutgoingMessageEntity(1L, false, uuidOne, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0)); - add(new OutgoingMessageEntity(2L, false, null, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0)); + List messages = new LinkedList<>() {{ + add(new OutgoingMessageEntity(1L, false, messageGuidOne, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", sourceUuid, 2, "hi there".getBytes(), null, 0)); + add(new OutgoingMessageEntity(2L, false, null, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", sourceUuid, 2, null, null, 0)); }}; OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false); @@ -254,7 +262,7 @@ public class MessageControllerTest { OutgoingMessageEntityList response = resources.getJerseyTest().target("/v1/messages/") .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) .accept(MediaType.APPLICATION_JSON_TYPE) .get(OutgoingMessageEntityList.class); @@ -267,8 +275,11 @@ public class MessageControllerTest { assertEquals(response.getMessages().get(0).getTimestamp(), timestampOne); assertEquals(response.getMessages().get(1).getTimestamp(), timestampTwo); - assertEquals(response.getMessages().get(0).getGuid(), uuidOne); - assertEquals(response.getMessages().get(1).getGuid(), null); + assertEquals(response.getMessages().get(0).getGuid(), messageGuidOne); + assertNull(response.getMessages().get(1).getGuid()); + + assertEquals(response.getMessages().get(0).getSourceUuid(), sourceUuid); + assertEquals(response.getMessages().get(1).getSourceUuid(), sourceUuid); } @Test @@ -277,8 +288,8 @@ public class MessageControllerTest { final long timestampTwo = 313388; List messages = new LinkedList() {{ - add(new OutgoingMessageEntity(1L, false, UUID.randomUUID(), Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0)); - add(new OutgoingMessageEntity(2L, false, UUID.randomUUID(), Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0)); + add(new OutgoingMessageEntity(1L, false, UUID.randomUUID(), Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", UUID.randomUUID(), 2, "hi there".getBytes(), null, 0)); + add(new OutgoingMessageEntity(2L, false, UUID.randomUUID(), Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", UUID.randomUUID(), 2, null, null, 0)); }}; OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false); @@ -288,7 +299,7 @@ public class MessageControllerTest { Response response = resources.getJerseyTest().target("/v1/messages/") .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.INVALID_PASSWORD)) .accept(MediaType.APPLICATION_JSON_TYPE) .get(); @@ -299,17 +310,19 @@ public class MessageControllerTest { public synchronized void testDeleteMessages() throws Exception { long timestamp = System.currentTimeMillis(); + UUID sourceUuid = UUID.randomUUID(); + when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31337)) .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null, Envelope.Type.CIPHERTEXT_VALUE, null, timestamp, - "+14152222222", 1, "hi".getBytes(), null, 0))); + "+14152222222", sourceUuid, 1, "hi".getBytes(), null, 0))); when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31338)) .thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null, Envelope.Type.RECEIPT_VALUE, null, System.currentTimeMillis(), - "+14152222222", 1, null, null, 0))); + "+14152222222", sourceUuid, 1, null, null, 0))); when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31339)) @@ -327,7 +340,7 @@ public class MessageControllerTest { response = resources.getJerseyTest() .target(String.format("/v1/messages/%s/%d", "+14152222222", 31338)) .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD)) + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID.toString(), AuthHelper.VALID_PASSWORD)) .delete(); assertThat("Good Response Code", response.getStatus(), is(equalTo(204))); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java index fab22bbd8..a03d3e164 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ProfileControllerTest.java @@ -5,6 +5,8 @@ import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; import org.whispersystems.textsecuregcm.controllers.ProfileController; @@ -63,6 +65,7 @@ public class ProfileControllerTest { when(profileAccount.isEnabled()).thenReturn(true); when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount)); + when(accountsManager.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER_TWO)))).thenReturn(Optional.of(profileAccount)); } @@ -78,7 +81,7 @@ public class ProfileControllerTest { assertThat(profile.getName()).isEqualTo("baz"); assertThat(profile.getAvatar()).isEqualTo("profiles/bang"); - verify(accountsManager, times(1)).get(AuthHelper.VALID_NUMBER_TWO); + verify(accountsManager, times(1)).get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(AuthHelper.VALID_NUMBER_TWO))); verify(rateLimiters, times(1)).getProfileLimiter(); verify(rateLimiter, times(1)).validate(eq(AuthHelper.VALID_NUMBER)); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java deleted file mode 100644 index 11ea728f8..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/TransparentDataControllerTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.whispersystems.textsecuregcm.tests.controllers; - -import com.google.common.collect.ImmutableSet; -import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; -import org.whispersystems.textsecuregcm.controllers.TransparentDataController; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; -import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.PublicAccount; -import org.whispersystems.textsecuregcm.tests.util.AuthHelper; -import org.whispersystems.textsecuregcm.util.SystemMapper; - -import javax.ws.rs.core.Response; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; -import io.dropwizard.testing.junit.ResourceTestRule; -import static junit.framework.TestCase.*; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; -import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson; -import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture; - -public class TransparentDataControllerTest { - - private final AccountsManager accountsManager = mock(AccountsManager.class); - private final Map indexMap = new HashMap<>(); - - @Rule - public final ResourceTestRule resources = ResourceTestRule.builder() - .addProvider(AuthHelper.getAuthFilter()) - .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class))) - .addProvider(new RateLimitExceededExceptionMapper()) - .setMapper(SystemMapper.getMapper()) - .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new TransparentDataController(accountsManager, indexMap)) - .build(); - - - @Before - public void setup() { - Account accountOne = new Account("+14151231111", Collections.singleton(new Device(1, "foo", "bar", "salt", "keykey", "gcm-id", "apn-id", "voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true, 0)), new byte[16]); - Account accountTwo = new Account("+14151232222", Collections.singleton(new Device(1, "2foo", "2bar", "2salt", "2keykey", "2gcm-id", "2apn-id", "2voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true, 0)), new byte[16]); - - accountOne.setProfileName("OneProfileName"); - accountOne.setIdentityKey("identity_key_value"); - accountTwo.setProfileName("TwoProfileName"); - accountTwo.setIdentityKey("different_identity_key_value"); - - - indexMap.put("1", "+14151231111"); - indexMap.put("2", "+14151232222"); - - when(accountsManager.get(eq("+14151231111"))).thenReturn(Optional.of(accountOne)); - when(accountsManager.get(eq("+14151232222"))).thenReturn(Optional.of(accountTwo)); - } - - @Test - public void testAccountOne() throws IOException { - Response response = resources.getJerseyTest() - .target(String.format("/v1/transparency/account/%s", "1")) - .request() - .get(); - - assertEquals(200, response.getStatus()); - - Account result = response.readEntity(PublicAccount.class); - - assertTrue(result.getPin().isPresent()); - assertEquals("******", result.getPin().get()); - assertNull(result.getNumber()); - assertEquals("OneProfileName", result.getProfileName()); - - assertThat("Account serialization works", - asJson(result), - is(equalTo(jsonFixture("fixtures/transparent_account.json")))); - - verify(accountsManager, times(1)).get(eq("+14151231111")); - verifyNoMoreInteractions(accountsManager); - } - - @Test - public void testAccountTwo() throws IOException { - Response response = resources.getJerseyTest() - .target(String.format("/v1/transparency/account/%s", "2")) - .request() - .get(); - - assertEquals(200, response.getStatus()); - - Account result = response.readEntity(PublicAccount.class); - - assertTrue(result.getPin().isPresent()); - assertEquals("******", result.getPin().get()); - assertNull(result.getNumber()); - assertEquals("TwoProfileName", result.getProfileName()); - - assertThat("Account serialization works 2", - asJson(result), - is(equalTo(jsonFixture("fixtures/transparent_account2.json")))); - - verify(accountsManager, times(1)).get(eq("+14151232222")); - } - - @Test - public void testAccountMissing() { - Response response = resources.getJerseyTest() - .target(String.format("/v1/transparency/account/%s", "3")) - .request() - .get(); - - assertEquals(404, response.getStatus()); - verifyNoMoreInteractions(accountsManager); - } - -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountDatabaseCrawlerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountDatabaseCrawlerTest.java index 41cb6d576..1ce7ef08a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountDatabaseCrawlerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountDatabaseCrawlerTest.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2018 Open WhisperSystems * * This program is free software: you can redistribute it and/or modify @@ -17,21 +17,19 @@ package org.whispersystems.textsecuregcm.tests.storage; +import org.junit.Before; +import org.junit.Test; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException; - -import org.junit.Before; -import org.junit.Test; import org.whispersystems.textsecuregcm.storage.AccountsManager; import java.util.Arrays; import java.util.Collections; -import java.util.List; import java.util.Optional; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -42,8 +40,8 @@ import static org.mockito.Mockito.*; public class AccountDatabaseCrawlerTest { - private static final String ACCOUNT1 = "+1"; - private static final String ACCOUNT2 = "+2"; + private static final UUID ACCOUNT1 = UUID.randomUUID(); + private static final UUID ACCOUNT2 = UUID.randomUUID(); private static final int CHUNK_SIZE = 1000; private static final long CHUNK_INTERVAL_MS = 30_000L; @@ -59,8 +57,8 @@ public class AccountDatabaseCrawlerTest { @Before public void setup() { - when(account1.getNumber()).thenReturn(ACCOUNT1); - when(account2.getNumber()).thenReturn(ACCOUNT2); + when(account1.getUuid()).thenReturn(ACCOUNT1); + when(account2.getUuid()).thenReturn(ACCOUNT2); when(accounts.getAllFrom(anyInt())).thenReturn(Arrays.asList(account1, account2)); when(accounts.getAllFrom(eq(ACCOUNT1), anyInt())).thenReturn(Arrays.asList(account2)); @@ -72,20 +70,20 @@ public class AccountDatabaseCrawlerTest { @Test public void testCrawlStart() throws AccountDatabaseCrawlerRestartException { - when(cache.getLastNumber()).thenReturn(Optional.empty()); + when(cache.getLastUuid()).thenReturn(Optional.empty()); boolean accelerated = crawler.doPeriodicWork(); assertThat(accelerated).isFalse(); verify(cache, times(1)).claimActiveWork(any(String.class), anyLong()); - verify(cache, times(1)).getLastNumber(); + verify(cache, times(1)).getLastUuid(); verify(listener, times(1)).onCrawlStart(); verify(accounts, times(1)).getAllFrom(eq(CHUNK_SIZE)); - verify(accounts, times(0)).getAllFrom(any(String.class), eq(CHUNK_SIZE)); - verify(account1, times(0)).getNumber(); - verify(account2, times(1)).getNumber(); + verify(accounts, times(0)).getAllFrom(any(UUID.class), eq(CHUNK_SIZE)); + verify(account1, times(0)).getUuid(); + verify(account2, times(1)).getUuid(); verify(listener, times(1)).onCrawlChunk(eq(Optional.empty()), eq(Arrays.asList(account1, account2))); - verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2))); + verify(cache, times(1)).setLastUuid(eq(Optional.of(ACCOUNT2))); verify(cache, times(1)).isAccelerated(); verify(cache, times(1)).releaseActiveWork(any(String.class)); @@ -98,18 +96,18 @@ public class AccountDatabaseCrawlerTest { @Test public void testCrawlChunk() throws AccountDatabaseCrawlerRestartException { - when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1)); + when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1)); boolean accelerated = crawler.doPeriodicWork(); assertThat(accelerated).isFalse(); verify(cache, times(1)).claimActiveWork(any(String.class), anyLong()); - verify(cache, times(1)).getLastNumber(); + verify(cache, times(1)).getLastUuid(); verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE)); verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE)); - verify(account2, times(1)).getNumber(); + verify(account2, times(1)).getUuid(); verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2))); - verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2))); + verify(cache, times(1)).setLastUuid(eq(Optional.of(ACCOUNT2))); verify(cache, times(1)).isAccelerated(); verify(cache, times(1)).releaseActiveWork(any(String.class)); @@ -124,18 +122,18 @@ public class AccountDatabaseCrawlerTest { @Test public void testCrawlChunkAccelerated() throws AccountDatabaseCrawlerRestartException { when(cache.isAccelerated()).thenReturn(true); - when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1)); + when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1)); boolean accelerated = crawler.doPeriodicWork(); assertThat(accelerated).isTrue(); verify(cache, times(1)).claimActiveWork(any(String.class), anyLong()); - verify(cache, times(1)).getLastNumber(); + verify(cache, times(1)).getLastUuid(); verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE)); verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE)); - verify(account2, times(1)).getNumber(); + verify(account2, times(1)).getUuid(); verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2))); - verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2))); + verify(cache, times(1)).setLastUuid(eq(Optional.of(ACCOUNT2))); verify(cache, times(1)).isAccelerated(); verify(cache, times(1)).releaseActiveWork(any(String.class)); @@ -149,19 +147,19 @@ public class AccountDatabaseCrawlerTest { @Test public void testCrawlChunkRestart() throws AccountDatabaseCrawlerRestartException { - when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1)); + when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1)); doThrow(AccountDatabaseCrawlerRestartException.class).when(listener).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2))); boolean accelerated = crawler.doPeriodicWork(); assertThat(accelerated).isFalse(); verify(cache, times(1)).claimActiveWork(any(String.class), anyLong()); - verify(cache, times(1)).getLastNumber(); + verify(cache, times(1)).getLastUuid(); verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE)); verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE)); verify(account2, times(0)).getNumber(); verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2))); - verify(cache, times(1)).setLastNumber(eq(Optional.empty())); + verify(cache, times(1)).setLastUuid(eq(Optional.empty())); verify(cache, times(1)).clearAccelerate(); verify(cache, times(1)).isAccelerated(); verify(cache, times(1)).releaseActiveWork(any(String.class)); @@ -176,19 +174,19 @@ public class AccountDatabaseCrawlerTest { @Test public void testCrawlEnd() { - when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT2)); + when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT2)); boolean accelerated = crawler.doPeriodicWork(); assertThat(accelerated).isFalse(); verify(cache, times(1)).claimActiveWork(any(String.class), anyLong()); - verify(cache, times(1)).getLastNumber(); + verify(cache, times(1)).getLastUuid(); verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE)); verify(accounts, times(1)).getAllFrom(eq(ACCOUNT2), eq(CHUNK_SIZE)); verify(account1, times(0)).getNumber(); verify(account2, times(0)).getNumber(); verify(listener, times(1)).onCrawlEnd(eq(Optional.of(ACCOUNT2))); - verify(cache, times(1)).setLastNumber(eq(Optional.empty())); + verify(cache, times(1)).setLastUuid(eq(Optional.empty())); verify(cache, times(1)).clearAccelerate(); verify(cache, times(1)).isAccelerated(); verify(cache, times(1)).releaseActiveWork(any(String.class)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java index e856f08a8..9a4e4d549 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountTest.java @@ -7,6 +7,7 @@ import org.whispersystems.textsecuregcm.storage.Device; import java.util.HashSet; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertFalse; @@ -47,21 +48,21 @@ public class AccountTest { @Test public void testAccountActive() { - Account recentAccount = new Account("+14152222222", new HashSet() {{ + Account recentAccount = new Account("+14152222222", UUID.randomUUID(), new HashSet() {{ add(recentMasterDevice); add(recentSecondaryDevice); }}, "1234".getBytes()); assertTrue(recentAccount.isEnabled()); - Account oldSecondaryAccount = new Account("+14152222222", new HashSet() {{ + Account oldSecondaryAccount = new Account("+14152222222", UUID.randomUUID(), new HashSet() {{ add(recentMasterDevice); add(agingSecondaryDevice); }}, "1234".getBytes()); assertTrue(oldSecondaryAccount.isEnabled()); - Account agingPrimaryAccount = new Account("+14152222222", new HashSet() {{ + Account agingPrimaryAccount = new Account("+14152222222", UUID.randomUUID(), new HashSet() {{ add(oldMasterDevice); add(agingSecondaryDevice); }}, "1234".getBytes()); @@ -71,7 +72,7 @@ public class AccountTest { @Test public void testAccountInactive() { - Account oldPrimaryAccount = new Account("+14152222222", new HashSet() {{ + Account oldPrimaryAccount = new Account("+14152222222", UUID.randomUUID(), new HashSet() {{ add(oldMasterDevice); add(oldSecondaryDevice); }}, "1234".getBytes()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsManagerTest.java index 1d68203f6..e49389821 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsManagerTest.java @@ -9,6 +9,7 @@ import org.whispersystems.textsecuregcm.storage.DirectoryManager; import java.util.HashSet; import java.util.Optional; +import java.util.UUID; import static junit.framework.TestCase.assertSame; import static junit.framework.TestCase.assertTrue; @@ -21,14 +22,17 @@ import redis.clients.jedis.exceptions.JedisException; public class AccountsManagerTest { @Test - public void testGetAccountInCache() { + public void testGetAccountByNumberInCache() { ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); Jedis jedis = mock(Jedis.class ); Accounts accounts = mock(Accounts.class ); DirectoryManager directoryManager = mock(DirectoryManager.class ); + UUID uuid = UUID.randomUUID(); + when(cacheClient.getReadResource()).thenReturn(jedis); - when(jedis.get(eq("Account5+14152222222"))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}"); + when(jedis.get(eq("AccountMap::+14152222222"))).thenReturn(uuid.toString()); + when(jedis.get(eq("Account::" + uuid.toString()))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}"); AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); Optional account = accountsManager.get("+14152222222"); @@ -37,23 +41,52 @@ public class AccountsManagerTest { assertEquals(account.get().getNumber(), "+14152222222"); assertEquals(account.get().getProfileName(), "test"); - verify(jedis, times(1)).get(eq("Account5+14152222222")); + verify(jedis, times(1)).get(eq("AccountMap::+14152222222")); + verify(jedis, times(1)).get(eq("Account::" + uuid.toString())); + verify(jedis, times(2)).close(); + verifyNoMoreInteractions(jedis); + verifyNoMoreInteractions(accounts); + } + + @Test + public void testGetAccountByUuidInCache() { + ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); + Jedis jedis = mock(Jedis.class ); + Accounts accounts = mock(Accounts.class ); + DirectoryManager directoryManager = mock(DirectoryManager.class ); + + UUID uuid = UUID.randomUUID(); + + when(cacheClient.getReadResource()).thenReturn(jedis); + when(jedis.get(eq("Account::" + uuid.toString()))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}"); + + AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); + Optional account = accountsManager.get(uuid); + + assertTrue(account.isPresent()); + assertEquals(account.get().getNumber(), "+14152222222"); + assertEquals(account.get().getUuid(), uuid); + assertEquals(account.get().getProfileName(), "test"); + + verify(jedis, times(1)).get(eq("Account::" + uuid.toString())); verify(jedis, times(1)).close(); verifyNoMoreInteractions(jedis); verifyNoMoreInteractions(accounts); } + @Test - public void testGetAccountNotInCache() { + public void testGetAccountByNumberNotInCache() { ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); Jedis jedis = mock(Jedis.class ); Accounts accounts = mock(Accounts.class ); DirectoryManager directoryManager = mock(DirectoryManager.class ); - Account account = new Account("+14152222222", new HashSet<>(), new byte[16]); + UUID uuid = UUID.randomUUID(); + Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]); when(cacheClient.getReadResource()).thenReturn(jedis); when(cacheClient.getWriteResource()).thenReturn(jedis); - when(jedis.get(eq("Account5+14152222222"))).thenReturn(null); + when(jedis.get(eq("AccountMap::+14152222222"))).thenReturn(null); when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account)); AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); @@ -62,8 +95,9 @@ public class AccountsManagerTest { assertTrue(retrieved.isPresent()); assertSame(retrieved.get(), account); - verify(jedis, times(1)).get(eq("Account5+14152222222")); - verify(jedis, times(1)).set(eq("Account5+14152222222"), anyString()); + verify(jedis, times(1)).get(eq("AccountMap::+14152222222")); + verify(jedis, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString())); + verify(jedis, times(1)).set(eq("Account::" + uuid.toString()), anyString()); verify(jedis, times(2)).close(); verifyNoMoreInteractions(jedis); @@ -72,16 +106,47 @@ public class AccountsManagerTest { } @Test - public void testGetAccountBrokenCache() { + public void testGetAccountByUuidNotInCache() { ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); Jedis jedis = mock(Jedis.class ); Accounts accounts = mock(Accounts.class ); DirectoryManager directoryManager = mock(DirectoryManager.class ); - Account account = new Account("+14152222222", new HashSet<>(), new byte[16]); + UUID uuid = UUID.randomUUID(); + Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]); when(cacheClient.getReadResource()).thenReturn(jedis); when(cacheClient.getWriteResource()).thenReturn(jedis); - when(jedis.get(eq("Account5+14152222222"))).thenThrow(new JedisException("Connection lost!")); + when(jedis.get(eq("Account::" + uuid))).thenReturn(null); + when(accounts.get(eq(uuid))).thenReturn(Optional.of(account)); + + AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); + Optional retrieved = accountsManager.get(uuid); + + assertTrue(retrieved.isPresent()); + assertSame(retrieved.get(), account); + + verify(jedis, times(1)).get(eq("Account::" + uuid)); + verify(jedis, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString())); + verify(jedis, times(1)).set(eq("Account::" + uuid.toString()), anyString()); + verify(jedis, times(2)).close(); + verifyNoMoreInteractions(jedis); + + verify(accounts, times(1)).get(eq(uuid)); + verifyNoMoreInteractions(accounts); + } + + @Test + public void testGetAccountByNumberBrokenCache() { + ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); + Jedis jedis = mock(Jedis.class ); + Accounts accounts = mock(Accounts.class ); + DirectoryManager directoryManager = mock(DirectoryManager.class ); + UUID uuid = UUID.randomUUID(); + Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]); + + when(cacheClient.getReadResource()).thenReturn(jedis); + when(cacheClient.getWriteResource()).thenReturn(jedis); + when(jedis.get(eq("AccountMap::+14152222222"))).thenThrow(new JedisException("Connection lost!")); when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account)); AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); @@ -90,8 +155,9 @@ public class AccountsManagerTest { assertTrue(retrieved.isPresent()); assertSame(retrieved.get(), account); - verify(jedis, times(1)).get(eq("Account5+14152222222")); - verify(jedis, times(1)).set(eq("Account5+14152222222"), anyString()); + verify(jedis, times(1)).get(eq("AccountMap::+14152222222")); + verify(jedis, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString())); + verify(jedis, times(1)).set(eq("Account::" + uuid.toString()), anyString()); verify(jedis, times(2)).close(); verifyNoMoreInteractions(jedis); @@ -99,6 +165,35 @@ public class AccountsManagerTest { verifyNoMoreInteractions(accounts); } + @Test + public void testGetAccountByUuidBrokenCache() { + ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class); + Jedis jedis = mock(Jedis.class ); + Accounts accounts = mock(Accounts.class ); + DirectoryManager directoryManager = mock(DirectoryManager.class ); + UUID uuid = UUID.randomUUID(); + Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]); + + when(cacheClient.getReadResource()).thenReturn(jedis); + when(cacheClient.getWriteResource()).thenReturn(jedis); + when(jedis.get(eq("Account::" + uuid))).thenThrow(new JedisException("Connection lost!")); + when(accounts.get(eq(uuid))).thenReturn(Optional.of(account)); + + AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient); + Optional retrieved = accountsManager.get(uuid); + + assertTrue(retrieved.isPresent()); + assertSame(retrieved.get(), account); + + verify(jedis, times(1)).get(eq("Account::" + uuid)); + verify(jedis, times(1)).set(eq("AccountMap::+14152222222"), eq(uuid.toString())); + verify(jedis, times(1)).set(eq("Account::" + uuid.toString()), anyString()); + verify(jedis, times(2)).close(); + verifyNoMoreInteractions(jedis); + + verify(accounts, times(1)).get(eq(uuid)); + verifyNoMoreInteractions(accounts); + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java index 24f7156d6..c8c893d28 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/AccountsTest.java @@ -1,5 +1,6 @@ package org.whispersystems.textsecuregcm.tests.storage; +import com.fasterxml.uuid.UUIDComparator; import com.opentable.db.postgres.embedded.LiquibasePreparer; import com.opentable.db.postgres.junit.EmbeddedPostgresRules; import com.opentable.db.postgres.junit.PreparedDbRule; @@ -17,6 +18,8 @@ import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase; import org.whispersystems.textsecuregcm.storage.mappers.AccountRowMapper; +import org.whispersystems.textsecuregcm.util.Conversions; +import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; import java.sql.PreparedStatement; @@ -25,11 +28,13 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.Set; +import java.util.UUID; import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -56,12 +61,12 @@ public class AccountsTest { @Test public void testStore() throws SQLException, IOException { Device device = generateDevice (1 ); - Account account = generateAccount("+14151112222", Collections.singleton(device)); + Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device)); accounts.create(account); PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?"); - verifyStoredState(statement, "+14151112222", account); + verifyStoredState(statement, "+14151112222", account.getUuid(), account); } @Test @@ -70,12 +75,12 @@ public class AccountsTest { devices.add(generateDevice(1)); devices.add(generateDevice(2)); - Account account = generateAccount("+14151112222", devices); + Account account = generateAccount("+14151112222", UUID.randomUUID(), devices); accounts.create(account); PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?"); - verifyStoredState(statement, "+14151112222", account); + verifyStoredState(statement, "+14151112222", account.getUuid(), account); } @Test @@ -84,13 +89,15 @@ public class AccountsTest { devicesFirst.add(generateDevice(1)); devicesFirst.add(generateDevice(2)); - Account accountFirst = generateAccount("+14151112222", devicesFirst); + UUID uuidFirst = UUID.randomUUID(); + Account accountFirst = generateAccount("+14151112222", uuidFirst, devicesFirst); Set devicesSecond = new HashSet<>(); devicesSecond.add(generateDevice(1)); devicesSecond.add(generateDevice(2)); - Account accountSecond = generateAccount("+14152221111", devicesSecond); + UUID uuidSecond = UUID.randomUUID(); + Account accountSecond = generateAccount("+14152221111", uuidSecond, devicesSecond); accounts.create(accountFirst); accounts.create(accountSecond); @@ -101,31 +108,43 @@ public class AccountsTest { assertThat(retrievedFirst.isPresent()).isTrue(); assertThat(retrievedSecond.isPresent()).isTrue(); - verifyStoredState("+14151112222", retrievedFirst.get(), accountFirst); - verifyStoredState("+14152221111", retrievedSecond.get(), accountSecond); + verifyStoredState("+14151112222", uuidFirst, retrievedFirst.get(), accountFirst); + verifyStoredState("+14152221111", uuidSecond, retrievedSecond.get(), accountSecond); + + retrievedFirst = accounts.get(uuidFirst); + retrievedSecond = accounts.get(uuidSecond); + + assertThat(retrievedFirst.isPresent()).isTrue(); + assertThat(retrievedSecond.isPresent()).isTrue(); + + verifyStoredState("+14151112222", uuidFirst, retrievedFirst.get(), accountFirst); + verifyStoredState("+14152221111", uuidSecond, retrievedSecond.get(), accountSecond); } @Test public void testOverwrite() throws Exception { Device device = generateDevice (1 ); - Account account = generateAccount("+14151112222", Collections.singleton(device)); + UUID firstUuid = UUID.randomUUID(); + Account account = generateAccount("+14151112222", firstUuid, Collections.singleton(device)); accounts.create(account); PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?"); - verifyStoredState(statement, "+14151112222", account); + verifyStoredState(statement, "+14151112222", account.getUuid(), account); + + UUID secondUuid = UUID.randomUUID(); device = generateDevice(1); - account = generateAccount("+14151112222", Collections.singleton(device)); + account = generateAccount("+14151112222", secondUuid, Collections.singleton(device)); accounts.create(account); - verifyStoredState(statement, "+14151112222", account); + verifyStoredState(statement, "+14151112222", firstUuid, account); } @Test public void testUpdate() { Device device = generateDevice (1 ); - Account account = generateAccount("+14151112222", Collections.singleton(device)); + Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device)); accounts.create(account); @@ -136,7 +155,12 @@ public class AccountsTest { Optional retrieved = accounts.get("+14151112222"); assertThat(retrieved.isPresent()).isTrue(); - verifyStoredState("+14151112222", retrieved.get(), account); + verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account); + + retrieved = accounts.get(account.getUuid()); + + assertThat(retrieved.isPresent()).isTrue(); + verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account); } @Test @@ -144,24 +168,26 @@ public class AccountsTest { List users = new ArrayList<>(); for (int i=1;i<=100;i++) { - Account account = generateAccount("+1" + String.format("%03d", i)); + Account account = generateAccount("+1" + String.format("%03d", i), UUID.randomUUID()); users.add(account); accounts.create(account); } + users.sort((account, t1) -> UUIDComparator.staticCompare(account.getUuid(), t1.getUuid())); + List retrieved = accounts.getAllFrom(10); assertThat(retrieved.size()).isEqualTo(10); for (int i=0;i retrieved = accounts.get("+14151112222"); assertThat(retrieved.isPresent()).isTrue(); - verifyStoredState("+14151112222", retrieved.get(), account); + verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account); } @Test public void testMissing() { Device device = generateDevice (1 ); - Account account = generateAccount("+14151112222", Collections.singleton(device)); + Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device)); accounts.create(account); Optional retrieved = accounts.get("+11111111"); assertThat(retrieved.isPresent()).isFalse(); + + retrieved = accounts.get(UUID.randomUUID()); + assertThat(retrieved.isPresent()).isFalse(); } @Test @@ -203,7 +232,7 @@ public class AccountsTest { configuration.setFailureRateThreshold(50); Accounts accounts = new Accounts(new FaultTolerantDatabase("testAccountBreaker", jdbi, configuration)); - Account account = generateAccount("+14151112222"); + Account account = generateAccount("+14151112222", UUID.randomUUID()); try { accounts.update(account); @@ -244,20 +273,20 @@ public class AccountsTest { return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt(), random.nextBoolean(), 0); } - private Account generateAccount(String number) { + private Account generateAccount(String number, UUID uuid) { Device device = generateDevice(1); - return generateAccount(number, Collections.singleton(device)); + return generateAccount(number, uuid, Collections.singleton(device)); } - private Account generateAccount(String number, Set devices) { + private Account generateAccount(String number, UUID uuid, Set devices) { byte[] unidentifiedAccessKey = new byte[16]; Random random = new Random(System.currentTimeMillis()); Arrays.fill(unidentifiedAccessKey, (byte)random.nextInt(255)); - return new Account(number, devices, unidentifiedAccessKey); + return new Account(number, uuid, devices, unidentifiedAccessKey); } - private void verifyStoredState(PreparedStatement statement, String number, Account expecting) + private void verifyStoredState(PreparedStatement statement, String number, UUID uuid, Account expecting) throws SQLException, IOException { statement.setString(1, number); @@ -269,7 +298,7 @@ public class AccountsTest { assertThat(data).isNotEmpty(); Account result = new AccountRowMapper().map(resultSet, null); - verifyStoredState(number, result, expecting); + verifyStoredState(number, uuid, result, expecting); } else { throw new AssertionError("No data"); } @@ -277,9 +306,10 @@ public class AccountsTest { assertThat(resultSet.next()).isFalse(); } - private void verifyStoredState(String number, Account result, Account expecting) { + private void verifyStoredState(String number, UUID uuid, Account result, Account expecting) { assertThat(result.getNumber()).isEqualTo(number); assertThat(result.getLastSeen()).isEqualTo(expecting.getLastSeen()); + assertThat(result.getUuid()).isEqualTo(uuid); assertThat(Arrays.equals(result.getUnidentifiedAccessKey().get(), expecting.getUnidentifiedAccessKey().get())).isTrue(); for (Device expectingDevice : expecting.getDevices()) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/ActiveUserCounterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/ActiveUserCounterTest.java index 77f7ebafc..934da0fb4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/ActiveUserCounterTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/ActiveUserCounterTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.Arrays; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.Optional; @@ -46,9 +47,13 @@ import static org.mockito.Mockito.when; public class ActiveUserCounterTest { - private final String NUMBER_IOS = "+15551234567"; - private final String NUMBER_ANDROID = "+5511987654321"; - private final String NUMBER_NODEVICE = "+5215551234567"; + private final UUID UUID_IOS = UUID.randomUUID(); + private final UUID UUID_ANDROID = UUID.randomUUID(); + private final UUID UUID_NODEVICE = UUID.randomUUID(); + + private final String ACCOUNT_NUMBER_IOS = "+15551234567"; + private final String ACCOUNT_NUMBER_ANDROID = "+5511987654321"; + private final String ACCOUNT_NUMBER_NODEVICE = "+5215551234567"; private final String TALLY_KEY = "active_user_tally"; @@ -79,14 +84,17 @@ public class ActiveUserCounterTest { when(iosDevice.getGcmId()).thenReturn(null); when(iosDevice.getLastSeen()).thenReturn(halfDayAgo); - when(iosAccount.getNumber()).thenReturn(NUMBER_IOS); + when(iosAccount.getUuid()).thenReturn(UUID_IOS); when(iosAccount.getMasterDevice()).thenReturn(Optional.of(iosDevice)); + when(iosAccount.getNumber()).thenReturn(ACCOUNT_NUMBER_IOS); - when(androidAccount.getNumber()).thenReturn(NUMBER_ANDROID); + when(androidAccount.getUuid()).thenReturn(UUID_ANDROID); when(androidAccount.getMasterDevice()).thenReturn(Optional.of(androidDevice)); + when(androidAccount.getNumber()).thenReturn(ACCOUNT_NUMBER_ANDROID); - when(noDeviceAccount.getNumber()).thenReturn(NUMBER_NODEVICE); + when(noDeviceAccount.getUuid()).thenReturn(UUID_NODEVICE); when(noDeviceAccount.getMasterDevice()).thenReturn(Optional.ofNullable(null)); + when(noDeviceAccount.getNumber()).thenReturn(ACCOUNT_NUMBER_NODEVICE); when(jedis.get(any(String.class))).thenReturn("{\"fromNumber\":\"+\",\"platforms\":{},\"countries\":{}}"); when(jedisPool.getWriteResource()).thenReturn(jedis); @@ -137,7 +145,7 @@ public class ActiveUserCounterTest { @Test public void testCrawlChunkValidAccount() throws AccountDatabaseCrawlerRestartException { - activeUserCounter.onCrawlChunk(Optional.of(NUMBER_IOS), Arrays.asList(iosAccount)); + activeUserCounter.onCrawlChunk(Optional.of(UUID_IOS), Arrays.asList(iosAccount)); verify(iosAccount, times(1)).getMasterDevice(); verify(iosAccount, times(1)).getNumber(); @@ -148,7 +156,7 @@ public class ActiveUserCounterTest { verify(jedisPool, times(1)).getWriteResource(); verify(jedis, times(1)).get(any(String.class)); - verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_IOS+"\",\"platforms\":{\"ios\":[1,1,1,1,1]},\"countries\":{\"1\":[1,1,1,1,1]}}")); + verify(jedis, times(1)).set(any(String.class), eq("{\"fromUuid\":\""+UUID_IOS.toString()+"\",\"platforms\":{\"ios\":[1,1,1,1,1]},\"countries\":{\"1\":[1,1,1,1,1]}}")); verify(jedis, times(1)).close(); verify(metricsFactory, times(0)).getReporters(); @@ -166,13 +174,13 @@ public class ActiveUserCounterTest { @Test public void testCrawlChunkNoDeviceAccount() throws AccountDatabaseCrawlerRestartException { - activeUserCounter.onCrawlChunk(Optional.of(NUMBER_NODEVICE), Arrays.asList(noDeviceAccount)); + activeUserCounter.onCrawlChunk(Optional.of(UUID_NODEVICE), Arrays.asList(noDeviceAccount)); verify(noDeviceAccount, times(1)).getMasterDevice(); verify(jedisPool, times(1)).getWriteResource(); verify(jedis, times(1)).get(eq(TALLY_KEY)); - verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_NODEVICE+"\",\"platforms\":{},\"countries\":{}}")); + verify(jedis, times(1)).set(any(String.class), eq("{\"fromUuid\":\""+UUID_NODEVICE+"\",\"platforms\":{},\"countries\":{}}")); verify(jedis, times(1)).close(); verify(metricsFactory, times(0)).getReporters(); @@ -190,7 +198,7 @@ public class ActiveUserCounterTest { @Test public void testCrawlChunkMixedAccount() throws AccountDatabaseCrawlerRestartException { - activeUserCounter.onCrawlChunk(Optional.of(NUMBER_IOS), Arrays.asList(iosAccount, androidAccount, noDeviceAccount)); + activeUserCounter.onCrawlChunk(Optional.of(UUID_IOS), Arrays.asList(iosAccount, androidAccount, noDeviceAccount)); verify(iosAccount, times(1)).getMasterDevice(); verify(iosAccount, times(1)).getNumber(); @@ -208,7 +216,7 @@ public class ActiveUserCounterTest { verify(jedisPool, times(1)).getWriteResource(); verify(jedis, times(1)).get(eq(TALLY_KEY)); - verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_IOS+"\",\"platforms\":{\"android\":[0,0,0,1,1],\"ios\":[1,1,1,1,1]},\"countries\":{\"55\":[0,0,0,1,1],\"1\":[1,1,1,1,1]}}")); + verify(jedis, times(1)).set(any(String.class), eq("{\"fromUuid\":\""+UUID_IOS+"\",\"platforms\":{\"android\":[0,0,0,1,1],\"ios\":[1,1,1,1,1]},\"countries\":{\"55\":[0,0,0,1,1],\"1\":[1,1,1,1,1]}}")); verify(jedis, times(1)).close(); verify(metricsFactory, times(0)).getReporters(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java index 3c7d0dc4a..b2c10f5a6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/DirectoryReconcilerTest.java @@ -17,23 +17,23 @@ package org.whispersystems.textsecuregcm.tests.storage; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.whispersystems.textsecuregcm.entities.ClientContact; import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationRequest; import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationResponse; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException; -import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationHandle; import org.whispersystems.textsecuregcm.storage.DirectoryManager; +import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationHandle; import org.whispersystems.textsecuregcm.storage.DirectoryReconciler; import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient; import org.whispersystems.textsecuregcm.util.Util; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - import java.util.Arrays; import java.util.Optional; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -41,8 +41,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; public class DirectoryReconcilerTest { - private static final String VALID_NUMBER = "valid"; - private static final String INACTIVE_NUMBER = "inactive"; + private static final UUID VALID_UUID = UUID.randomUUID(); + private static final String VALID_NUMBERRR = "+14152222222"; + private static final UUID INACTIVE_UUID = UUID.randomUUID(); + private static final String INACTIVE_NUMBERRR = "+14151111111"; private final Account activeAccount = mock(Account.class); private final Account inactiveAccount = mock(Account.class); @@ -56,9 +58,11 @@ public class DirectoryReconcilerTest { @Before public void setup() { - when(activeAccount.getNumber()).thenReturn(VALID_NUMBER); + when(activeAccount.getUuid()).thenReturn(VALID_UUID); when(activeAccount.isEnabled()).thenReturn(true); - when(inactiveAccount.getNumber()).thenReturn(INACTIVE_NUMBER); + when(activeAccount.getNumber()).thenReturn(VALID_NUMBERRR); + when(inactiveAccount.getUuid()).thenReturn(INACTIVE_UUID); + when(inactiveAccount.getNumber()).thenReturn(INACTIVE_NUMBERRR); when(inactiveAccount.isEnabled()).thenReturn(false); when(directoryManager.startBatchOperation()).thenReturn(batchOperationHandle); } @@ -66,27 +70,28 @@ public class DirectoryReconcilerTest { @Test public void testCrawlChunkValid() throws AccountDatabaseCrawlerRestartException { when(reconciliationClient.sendChunk(any())).thenReturn(successResponse); - directoryReconciler.onCrawlChunk(Optional.of(VALID_NUMBER), Arrays.asList(activeAccount, inactiveAccount)); + directoryReconciler.onCrawlChunk(Optional.of(VALID_UUID), Arrays.asList(activeAccount, inactiveAccount)); verify(activeAccount, times(2)).getNumber(); verify(activeAccount, times(2)).isEnabled(); - verify(inactiveAccount, times(2)).getNumber(); + verify(inactiveAccount, times(1)).getUuid(); + verify(inactiveAccount, times(1)).getNumber(); verify(inactiveAccount, times(2)).isEnabled(); ArgumentCaptor request = ArgumentCaptor.forClass(DirectoryReconciliationRequest.class); verify(reconciliationClient, times(1)).sendChunk(request.capture()); - assertThat(request.getValue().getFromNumber()).isEqualTo(VALID_NUMBER); - assertThat(request.getValue().getToNumber()).isEqualTo(INACTIVE_NUMBER); - assertThat(request.getValue().getNumbers()).isEqualTo(Arrays.asList(VALID_NUMBER)); + assertThat(request.getValue().getFromUuid()).isEqualTo(VALID_UUID); + assertThat(request.getValue().getToUuid()).isEqualTo(INACTIVE_UUID); + assertThat(request.getValue().getNumbers()).isEqualTo(Arrays.asList(VALID_NUMBERRR)); ArgumentCaptor addedContact = ArgumentCaptor.forClass(ClientContact.class); verify(directoryManager, times(1)).startBatchOperation(); verify(directoryManager, times(1)).add(eq(batchOperationHandle), addedContact.capture()); - verify(directoryManager, times(1)).remove(eq(batchOperationHandle), eq(INACTIVE_NUMBER)); + verify(directoryManager, times(1)).remove(eq(batchOperationHandle), eq(INACTIVE_NUMBERRR)); verify(directoryManager, times(1)).stopBatchOperation(eq(batchOperationHandle)); - assertThat(addedContact.getValue().getToken()).isEqualTo(Util.getContactToken(VALID_NUMBER)); + assertThat(addedContact.getValue().getToken()).isEqualTo(Util.getContactToken(VALID_NUMBERRR)); verifyNoMoreInteractions(activeAccount); verifyNoMoreInteractions(inactiveAccount); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PublicAccountTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PublicAccountTest.java deleted file mode 100644 index 6bd129631..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PublicAccountTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.whispersystems.textsecuregcm.tests.storage; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; -import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.PublicAccount; -import org.whispersystems.textsecuregcm.util.SystemMapper; - -import java.io.IOException; -import java.util.Collections; -import java.util.Set; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; - -public class PublicAccountTest { - - @Test - public void testPinSanitation() throws IOException { - Set devices = Collections.singleton(new Device(1, "foo", "bar", "12345", null, "gcm-1234", null, null, true, 1234, new SignedPreKey(1, "public-foo", "signature-foo"), 31337, 31336, "Android4Life", true, 0)); - Account account = new Account("+14151231234", devices, new byte[16]); - account.setPin("123456"); - - PublicAccount publicAccount = new PublicAccount(account); - - String serialized = SystemMapper.getMapper().writeValueAsString(publicAccount); - JsonNode result = SystemMapper.getMapper().readTree(serialized); - - assertEquals("******", result.get("pin").textValue()); - assertNull(result.get("number")); - } - - -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PushFeedbackProcessorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PushFeedbackProcessorTest.java index 87a59d1f0..8a39a7c4a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PushFeedbackProcessorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/PushFeedbackProcessorTest.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.mockito.Mockito.*; @@ -62,7 +63,7 @@ public class PushFeedbackProcessorTest { @Test public void testEmpty() { PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager, directoryQueue); - processor.onCrawlChunk(Optional.of("+14152222222"), Collections.emptyList()); + processor.onCrawlChunk(Optional.of(UUID.randomUUID()), Collections.emptyList()); verifyZeroInteractions(accountsManager); verifyZeroInteractions(directoryQueue); @@ -71,7 +72,7 @@ public class PushFeedbackProcessorTest { @Test public void testUpdate() { PushFeedbackProcessor processor = new PushFeedbackProcessor(accountsManager, directoryQueue); - processor.onCrawlChunk(Optional.of("+14153333333"), List.of(uninstalledAccount, mixedAccount, stillActiveAccount, freshAccount, cleanAccount)); + processor.onCrawlChunk(Optional.of(UUID.randomUUID()), List.of(uninstalledAccount, mixedAccount, stillActiveAccount, freshAccount, cleanAccount)); verify(uninstalledDevice).setApnId(isNull()); verify(uninstalledDevice).setGcmId(isNull()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index 644dc2efb..1a994b3dd 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -1,7 +1,9 @@ package org.whispersystems.textsecuregcm.tests.util; import com.google.common.collect.ImmutableMap; +import org.mockito.ArgumentMatcher; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; +import org.whispersystems.textsecuregcm.auth.AmbiguousIdentifier; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; @@ -11,26 +13,32 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.util.Base64; import java.util.Optional; +import java.util.UUID; import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.PolymorphicAuthDynamicFeature; import io.dropwizard.auth.basic.BasicCredentialAuthFilter; import io.dropwizard.auth.basic.BasicCredentials; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class AuthHelper { public static final String VALID_NUMBER = "+14150000000"; + public static final UUID VALID_UUID = UUID.randomUUID(); public static final String VALID_PASSWORD = "foo"; public static final String VALID_NUMBER_TWO = "+201511111110"; + public static final UUID VALID_UUID_TWO = UUID.randomUUID(); public static final String VALID_PASSWORD_TWO = "baz"; public static final String INVVALID_NUMBER = "+14151111111"; + public static final UUID INVALID_UUID = UUID.randomUUID(); public static final String INVALID_PASSWORD = "bar"; public static final String DISABLED_NUMBER = "+78888888"; + public static final UUID DISABLED_UUID = UUID.randomUUID(); public static final String DISABLED_PASSWORD = "poof"; public static final String VALID_IDENTITY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"; @@ -76,8 +84,11 @@ public class AuthHelper { when(VALID_ACCOUNT_TWO.getEnabledDeviceCount()).thenReturn(6); when(VALID_ACCOUNT.getNumber()).thenReturn(VALID_NUMBER); + when(VALID_ACCOUNT.getUuid()).thenReturn(VALID_UUID); when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO); + when(VALID_ACCOUNT_TWO.getUuid()).thenReturn(VALID_UUID_TWO); when(DISABLED_ACCOUNT.getNumber()).thenReturn(DISABLED_NUMBER); + when(DISABLED_ACCOUNT.getUuid()).thenReturn(DISABLED_UUID); when(VALID_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE)); when(VALID_ACCOUNT_TWO.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE_TWO)); @@ -91,9 +102,21 @@ public class AuthHelper { when(DISABLED_ACCOUNT.isEnabled()).thenReturn(false); when(VALID_ACCOUNT.getIdentityKey()).thenReturn(VALID_IDENTITY); + when(ACCOUNTS_MANAGER.get(VALID_NUMBER)).thenReturn(Optional.of(VALID_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(VALID_UUID)).thenReturn(Optional.of(VALID_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(VALID_NUMBER)))).thenReturn(Optional.of(VALID_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(VALID_UUID)))).thenReturn(Optional.of(VALID_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); + when(ACCOUNTS_MANAGER.get(VALID_UUID_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(VALID_NUMBER_TWO)))).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(VALID_UUID_TWO)))).thenReturn(Optional.of(VALID_ACCOUNT_TWO)); + when(ACCOUNTS_MANAGER.get(DISABLED_NUMBER)).thenReturn(Optional.of(DISABLED_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(DISABLED_UUID)).thenReturn(Optional.of(DISABLED_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasNumber() && identifier.getNumber().equals(DISABLED_NUMBER)))).thenReturn(Optional.of(DISABLED_ACCOUNT)); + when(ACCOUNTS_MANAGER.get(argThat((ArgumentMatcher) identifier -> identifier != null && identifier.hasUuid() && identifier.getUuid().equals(DISABLED_UUID)))).thenReturn(Optional.of(DISABLED_ACCOUNT)); AuthFilter accountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(new AccountAuthenticator(ACCOUNTS_MANAGER)).buildAuthFilter (); AuthFilter disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder().setAuthenticator(new DisabledPermittedAccountAuthenticator(ACCOUNTS_MANAGER)).buildAuthFilter(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java index 9871c0f82..034712d0e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/websocket/WebSocketConnectionTest.java @@ -105,10 +105,13 @@ public class WebSocketConnectionTest { public void testOpen() throws Exception { MessagesManager storedMessages = mock(MessagesManager.class); + UUID senderOneUuid = UUID.randomUUID(); + UUID senderTwoUuid = UUID.randomUUID(); + List outgoingMessages = new LinkedList () {{ - add(createMessage(1L, false, "sender1", 1111, false, "first")); - add(createMessage(2L, false, "sender1", 2222, false, "second")); - add(createMessage(3L, false, "sender2", 3333, false, "third")); + add(createMessage(1L, false, "sender1", senderOneUuid, 1111, false, "first")); + add(createMessage(2L, false, "sender1", senderOneUuid, 2222, false, "second")); + add(createMessage(3L, false, "sender2", senderTwoUuid, 3333, false, "third")); }}; OutgoingMessageEntityList outgoingMessagesList = new OutgoingMessageEntityList(outgoingMessages, false); @@ -121,7 +124,7 @@ public class WebSocketConnectionTest { final Device sender1device = mock(Device.class); - Set sender1devices = new HashSet() {{ + Set sender1devices = new HashSet<>() {{ add(sender1device); }}; @@ -275,6 +278,7 @@ public class WebSocketConnectionTest { final Envelope firstMessage = Envelope.newBuilder() .setLegacyMessage(ByteString.copyFrom("first".getBytes())) .setSource("sender1") + .setSourceUuid(UUID.randomUUID().toString()) .setTimestamp(System.currentTimeMillis()) .setSourceDevice(1) .setType(Envelope.Type.CIPHERTEXT) @@ -283,6 +287,7 @@ public class WebSocketConnectionTest { final Envelope secondMessage = Envelope.newBuilder() .setLegacyMessage(ByteString.copyFrom("second".getBytes())) .setSource("sender2") + .setSourceUuid(UUID.randomUUID().toString()) .setTimestamp(System.currentTimeMillis()) .setSourceDevice(2) .setType(Envelope.Type.CIPHERTEXT) @@ -290,11 +295,11 @@ public class WebSocketConnectionTest { List pendingMessages = new LinkedList() {{ add(new OutgoingMessageEntity(1, true, UUID.randomUUID(), firstMessage.getType().getNumber(), firstMessage.getRelay(), - firstMessage.getTimestamp(), firstMessage.getSource(), + firstMessage.getTimestamp(), firstMessage.getSource(), UUID.fromString(firstMessage.getSourceUuid()), firstMessage.getSourceDevice(), firstMessage.getLegacyMessage().toByteArray(), firstMessage.getContent().toByteArray(), 0)); add(new OutgoingMessageEntity(2, false, UUID.randomUUID(), secondMessage.getType().getNumber(), secondMessage.getRelay(), - secondMessage.getTimestamp(), secondMessage.getSource(), + secondMessage.getTimestamp(), secondMessage.getSource(), UUID.fromString(secondMessage.getSourceUuid()), secondMessage.getSourceDevice(), secondMessage.getLegacyMessage().toByteArray(), secondMessage.getContent().toByteArray(), 0)); }}; @@ -359,9 +364,9 @@ public class WebSocketConnectionTest { } - private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, long timestamp, boolean receipt, String content) { + private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, UUID senderUuid, long timestamp, boolean receipt, String content) { return new OutgoingMessageEntity(id, cached, UUID.randomUUID(), receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE, - null, timestamp, sender, 1, content.getBytes(), null, 0); + null, timestamp, sender, senderUuid, 1, content.getBytes(), null, 0); } }