]> nmode's Git Repositories - signal-cli/commitdiff
Cleanup utils
authorAsamK <asamk@gmx.de>
Tue, 29 Dec 2020 21:48:39 +0000 (22:48 +0100)
committerAsamK <asamk@gmx.de>
Tue, 29 Dec 2020 22:01:58 +0000 (23:01 +0100)
19 files changed:
src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/ProvisioningManager.java
src/main/java/org/asamk/signal/manager/ServiceConfig.java
src/main/java/org/asamk/signal/manager/Utils.java [deleted file]
src/main/java/org/asamk/signal/manager/groups/GroupIdV1.java
src/main/java/org/asamk/signal/manager/groups/GroupLinkPassword.java
src/main/java/org/asamk/signal/manager/helper/GroupHelper.java
src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
src/main/java/org/asamk/signal/manager/storage/groups/JsonGroupStore.java
src/main/java/org/asamk/signal/manager/storage/protocol/JsonIdentityKeyStore.java
src/main/java/org/asamk/signal/manager/storage/protocol/JsonSessionStore.java
src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/util/IOUtils.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/util/KeyUtils.java [moved from src/main/java/org/asamk/signal/manager/KeyUtils.java with 79% similarity]
src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/util/Utils.java [new file with mode: 0644]
src/main/java/org/asamk/signal/util/IOUtils.java
src/main/java/org/asamk/signal/util/Util.java

diff --git a/src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java b/src/main/java/org/asamk/signal/manager/DeviceLinkInfo.java
new file mode 100644 (file)
index 0000000..5b9fbe2
--- /dev/null
@@ -0,0 +1,60 @@
+package org.asamk.signal.manager;
+
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+import org.whispersystems.util.Base64;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
+
+public class DeviceLinkInfo {
+
+    final String deviceIdentifier;
+    final ECPublicKey deviceKey;
+
+    public static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
+        Map<String, String> query = getQueryMap(linkUri.getRawQuery());
+        String deviceIdentifier = query.get("uuid");
+        String publicKeyEncoded = query.get("pub_key");
+
+        if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
+            throw new RuntimeException("Invalid device link uri");
+        }
+
+        ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
+
+        return new DeviceLinkInfo(deviceIdentifier, deviceKey);
+    }
+
+    private static Map<String, String> getQueryMap(String query) {
+        String[] params = query.split("&");
+        Map<String, String> map = new HashMap<>();
+        for (String param : params) {
+            final String[] paramParts = param.split("=");
+            String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
+            String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
+            map.put(name, value);
+        }
+        return map;
+    }
+
+    public DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
+        this.deviceIdentifier = deviceIdentifier;
+        this.deviceKey = deviceKey;
+    }
+
+    public String createDeviceLinkUri() {
+        return "tsdevice:/?uuid="
+                + URLEncoder.encode(deviceIdentifier, StandardCharsets.UTF_8)
+                + "&pub_key="
+                + URLEncoder.encode(Base64.encodeBytesWithoutPadding(deviceKey.serialize()), StandardCharsets.UTF_8);
+    }
+}
index e00f44acaa119f951798ee857a9763f194d81db5..5ed8fc4e506d852fccafb4144174e69d8025402a 100644 (file)
@@ -37,8 +37,11 @@ import org.asamk.signal.manager.storage.profiles.SignalProfile;
 import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
 import org.asamk.signal.manager.storage.protocol.IdentityInfo;
 import org.asamk.signal.manager.storage.stickers.Sticker;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.AttachmentUtils;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.MessageCacheUtils;
+import org.asamk.signal.manager.util.Utils;
 import org.signal.libsignal.metadata.InvalidMetadataMessageException;
 import org.signal.libsignal.metadata.InvalidMetadataVersionException;
 import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
@@ -50,6 +53,7 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
 import org.signal.libsignal.metadata.ProtocolNoSessionException;
 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
 import org.signal.libsignal.metadata.SelfSendException;
+import org.signal.libsignal.metadata.certificate.CertificateValidator;
 import org.signal.storageservice.protos.groups.GroupChange;
 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
 import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
@@ -125,6 +129,7 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.api.util.SleepTimer;
 import org.whispersystems.signalservice.api.util.StreamDetails;
 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
@@ -185,6 +190,7 @@ public class Manager implements Closeable {
     final static Logger logger = LoggerFactory.getLogger(Manager.class);
 
     private final SleepTimer timer = new UptimeSleepTimer();
+    private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
 
     private final SignalServiceConfiguration serviceConfiguration;
     private final String userAgent;
@@ -419,7 +425,7 @@ public class Manager implements Closeable {
     }
 
     public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
-        Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
+        DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
 
         addDevice(info.deviceIdentifier, info.deviceKey);
     }
@@ -696,7 +702,7 @@ public class Manager implements Closeable {
             return Optional.absent();
         }
 
-        return Optional.of(Utils.createAttachment(file));
+        return Optional.of(AttachmentUtils.createAttachment(file));
     }
 
     private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
@@ -705,7 +711,7 @@ public class Manager implements Closeable {
             return Optional.absent();
         }
 
-        return Optional.of(Utils.createAttachment(file));
+        return Optional.of(AttachmentUtils.createAttachment(file));
     }
 
     private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
@@ -751,7 +757,7 @@ public class Manager implements Closeable {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .withBody(messageText);
         if (attachments != null) {
-            messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
+            messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
         }
 
         return sendGroupMessage(messageBuilder, groupId);
@@ -928,7 +934,7 @@ public class Manager implements Closeable {
                     newE164Members.remove(contact.getNumber());
                 }
                 throw new IOException("Failed to add members "
-                        + Util.join(", ", newE164Members)
+                        + String.join(", ", newE164Members)
                         + " to group: Not registered on Signal");
             }
 
@@ -971,7 +977,7 @@ public class Manager implements Closeable {
         File aFile = getGroupAvatarFile(g.getGroupId());
         if (aFile.exists()) {
             try {
-                group.withAvatar(Utils.createAttachment(aFile));
+                group.withAvatar(AttachmentUtils.createAttachment(aFile));
             } catch (IOException e) {
                 throw new AttachmentInvalidException(aFile.toString(), e);
             }
@@ -1022,7 +1028,7 @@ public class Manager implements Closeable {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .withBody(messageText);
         if (attachments != null) {
-            List<SignalServiceAttachment> attachmentStreams = Utils.getSignalServiceAttachments(attachments);
+            List<SignalServiceAttachment> attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
 
             // Upload attachments here, so we only upload once even for multiple recipients
             SignalServiceMessageSender messageSender = createMessageSender();
@@ -1510,7 +1516,7 @@ public class Manager implements Closeable {
     private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
         SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(),
                 account.getSignalProtocolStore(),
-                Utils.getCertificateValidator());
+                certificateValidator);
         try {
             return cipher.decrypt(envelope);
         } catch (ProtocolUntrustedIdentityException e) {
@@ -1820,7 +1826,7 @@ public class Manager implements Closeable {
     ) {
         SignalServiceEnvelope envelope;
         try {
-            envelope = Utils.loadEnvelope(fileEntry);
+            envelope = MessageCacheUtils.loadEnvelope(fileEntry);
             if (envelope == null) {
                 return;
             }
@@ -1887,7 +1893,7 @@ public class Manager implements Closeable {
                     try {
                         String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
                         File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
-                        Utils.storeEnvelope(envelope1, cacheFile);
+                        MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
                     } catch (IOException e) {
                         logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
                     }
@@ -2240,7 +2246,7 @@ public class Manager implements Closeable {
             return retrieveAttachment(pointer, getContactAvatarFile(number), false);
         } else {
             SignalServiceAttachmentStream stream = attachment.asStream();
-            return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
+            return AttachmentUtils.retrieveAttachment(stream, getContactAvatarFile(number));
         }
     }
 
@@ -2257,7 +2263,7 @@ public class Manager implements Closeable {
             return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
         } else {
             SignalServiceAttachmentStream stream = attachment.asStream();
-            return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
+            return AttachmentUtils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
         }
     }
 
@@ -2509,7 +2515,7 @@ public class Manager implements Closeable {
     }
 
     public ContactInfo getContact(String number) {
-        return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
+        return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number));
     }
 
     public GroupInfo getGroup(GroupId groupId) {
@@ -2613,7 +2619,8 @@ public class Manager implements Closeable {
     public String computeSafetyNumber(
             SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
     ) {
-        return Utils.computeSafetyNumber(account.getSelfAddress(),
+        return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
+                account.getSelfAddress(),
                 getIdentityKeyPair().getPublicKey(),
                 theirAddress,
                 theirIdentityKey);
@@ -2626,12 +2633,12 @@ public class Manager implements Closeable {
     public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
         String canonicalizedNumber = UuidUtil.isUuid(identifier)
                 ? identifier
-                : Util.canonicalizeNumber(identifier, account.getUsername());
+                : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
         return resolveSignalServiceAddress(canonicalizedNumber);
     }
 
     public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
-        SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+        SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier);
 
         return resolveSignalServiceAddress(address);
     }
index 95e92c7aef106ad1aeba3c74a820c22f4d1bdf43..8b3f0eb47c48c9550fca6e02c3baa6795aec7ad8 100644 (file)
@@ -17,6 +17,7 @@
 package org.asamk.signal.manager;
 
 import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.util.KeyUtils;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.profiles.ProfileKey;
 import org.whispersystems.libsignal.IdentityKeyPair;
@@ -71,8 +72,7 @@ public class ProvisioningManager {
     public String getDeviceLinkUri() throws TimeoutException, IOException {
         String deviceUuid = accountManager.getNewDeviceUuid();
 
-        return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid,
-                identityKey.getPublicKey().getPublicKey()));
+        return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri();
     }
 
     public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
index 353670ae97dbf793ec158aa33e1f614459de65b4..939d5b5b5bc279c611c9612410c954df2ee36b11 100644 (file)
@@ -1,6 +1,9 @@
 package org.asamk.signal.manager;
 
 import org.signal.zkgroup.ServerPublicParams;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.api.account.AccountAttributes;
 import org.whispersystems.signalservice.api.push.TrustStore;
@@ -106,6 +109,14 @@ public class ServiceConfig {
         }
     }
 
+    static ECPublicKey getUnidentifiedSenderTrustRoot() {
+        try {
+            return Curve.decodePoint(Base64.decode(UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
+        } catch (InvalidKeyException | IOException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     private static Map<Integer, SignalCdnUrl[]> makeSignalCdnUrlMapFor(
             SignalCdnUrl[] cdn0Urls, SignalCdnUrl[] cdn2Urls
     ) {
diff --git a/src/main/java/org/asamk/signal/manager/Utils.java b/src/main/java/org/asamk/signal/manager/Utils.java
deleted file mode 100644 (file)
index 0a815ea..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-package org.asamk.signal.manager;
-
-import org.signal.libsignal.metadata.certificate.CertificateValidator;
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.fingerprint.Fingerprint;
-import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
-import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
-import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
-import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.util.StreamDetails;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
-import org.whispersystems.util.Base64;
-
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URLConnection;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
-
-class Utils {
-
-    static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
-        List<SignalServiceAttachment> signalServiceAttachments = null;
-        if (attachments != null) {
-            signalServiceAttachments = new ArrayList<>(attachments.size());
-            for (String attachment : attachments) {
-                try {
-                    signalServiceAttachments.add(createAttachment(new File(attachment)));
-                } catch (IOException e) {
-                    throw new AttachmentInvalidException(attachment, e);
-                }
-            }
-        }
-        return signalServiceAttachments;
-    }
-
-    static String getFileMimeType(File file, String defaultMimeType) throws IOException {
-        String mime = Files.probeContentType(file.toPath());
-        if (mime == null) {
-            try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
-                mime = URLConnection.guessContentTypeFromStream(bufferedStream);
-            }
-        }
-        if (mime == null) {
-            return defaultMimeType;
-        }
-        return mime;
-    }
-
-    static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
-        InputStream attachmentStream = new FileInputStream(attachmentFile);
-        final long attachmentSize = attachmentFile.length();
-        final String mime = getFileMimeType(attachmentFile, "application/octet-stream");
-        // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
-        final long uploadTimestamp = System.currentTimeMillis();
-        Optional<byte[]> preview = Optional.absent();
-        Optional<String> caption = Optional.absent();
-        Optional<String> blurHash = Optional.absent();
-        final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
-        return new SignalServiceAttachmentStream(attachmentStream,
-                mime,
-                attachmentSize,
-                Optional.of(attachmentFile.getName()),
-                false,
-                false,
-                preview,
-                0,
-                0,
-                uploadTimestamp,
-                caption,
-                blurHash,
-                null,
-                null,
-                resumableUploadSpec);
-    }
-
-    static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
-        InputStream stream = new FileInputStream(file);
-        final long size = file.length();
-        String mime = Files.probeContentType(file.toPath());
-        if (mime == null) {
-            mime = "application/octet-stream";
-        }
-        return new StreamDetails(stream, mime, size);
-    }
-
-    static CertificateValidator getCertificateValidator() {
-        try {
-            ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT),
-                    0);
-            return new CertificateValidator(unidentifiedSenderTrustRoot);
-        } catch (InvalidKeyException | IOException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    private static Map<String, String> getQueryMap(String query) {
-        String[] params = query.split("&");
-        Map<String, String> map = new HashMap<>();
-        for (String param : params) {
-            final String[] paramParts = param.split("=");
-            String name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
-            String value = URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
-            map.put(name, value);
-        }
-        return map;
-    }
-
-    static String createDeviceLinkUri(DeviceLinkInfo info) {
-        return "tsdevice:/?uuid="
-                + URLEncoder.encode(info.deviceIdentifier, StandardCharsets.UTF_8)
-                + "&pub_key="
-                + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()),
-                StandardCharsets.UTF_8);
-    }
-
-    static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
-        Map<String, String> query = getQueryMap(linkUri.getRawQuery());
-        String deviceIdentifier = query.get("uuid");
-        String publicKeyEncoded = query.get("pub_key");
-
-        if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
-            throw new RuntimeException("Invalid device link uri");
-        }
-
-        ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
-
-        return new DeviceLinkInfo(deviceIdentifier, deviceKey);
-    }
-
-    static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
-        try (FileInputStream f = new FileInputStream(file)) {
-            DataInputStream in = new DataInputStream(f);
-            int version = in.readInt();
-            if (version > 4) {
-                return null;
-            }
-            int type = in.readInt();
-            String source = in.readUTF();
-            UUID sourceUuid = null;
-            if (version >= 3) {
-                sourceUuid = UuidUtil.parseOrNull(in.readUTF());
-            }
-            int sourceDevice = in.readInt();
-            if (version == 1) {
-                // read legacy relay field
-                in.readUTF();
-            }
-            long timestamp = in.readLong();
-            byte[] content = null;
-            int contentLen = in.readInt();
-            if (contentLen > 0) {
-                content = new byte[contentLen];
-                in.readFully(content);
-            }
-            byte[] legacyMessage = null;
-            int legacyMessageLen = in.readInt();
-            if (legacyMessageLen > 0) {
-                legacyMessage = new byte[legacyMessageLen];
-                in.readFully(legacyMessage);
-            }
-            long serverReceivedTimestamp = 0;
-            String uuid = null;
-            if (version >= 2) {
-                serverReceivedTimestamp = in.readLong();
-                uuid = in.readUTF();
-                if ("".equals(uuid)) {
-                    uuid = null;
-                }
-            }
-            long serverDeliveredTimestamp = 0;
-            if (version >= 4) {
-                serverDeliveredTimestamp = in.readLong();
-            }
-            Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
-                    ? Optional.absent()
-                    : Optional.of(new SignalServiceAddress(sourceUuid, source));
-            return new SignalServiceEnvelope(type,
-                    addressOptional,
-                    sourceDevice,
-                    timestamp,
-                    legacyMessage,
-                    content,
-                    serverReceivedTimestamp,
-                    serverDeliveredTimestamp,
-                    uuid);
-        }
-    }
-
-    static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
-        try (FileOutputStream f = new FileOutputStream(file)) {
-            try (DataOutputStream out = new DataOutputStream(f)) {
-                out.writeInt(4); // version
-                out.writeInt(envelope.getType());
-                out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
-                out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
-                out.writeInt(envelope.getSourceDevice());
-                out.writeLong(envelope.getTimestamp());
-                if (envelope.hasContent()) {
-                    out.writeInt(envelope.getContent().length);
-                    out.write(envelope.getContent());
-                } else {
-                    out.writeInt(0);
-                }
-                if (envelope.hasLegacyMessage()) {
-                    out.writeInt(envelope.getLegacyMessage().length);
-                    out.write(envelope.getLegacyMessage());
-                } else {
-                    out.writeInt(0);
-                }
-                out.writeLong(envelope.getServerReceivedTimestamp());
-                String uuid = envelope.getUuid();
-                out.writeUTF(uuid == null ? "" : uuid);
-                out.writeLong(envelope.getServerDeliveredTimestamp());
-            }
-        }
-    }
-
-    static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
-        InputStream input = stream.getInputStream();
-
-        try (OutputStream output = new FileOutputStream(outputFile)) {
-            byte[] buffer = new byte[4096];
-            int read;
-
-            while ((read = input.read(buffer)) != -1) {
-                output.write(buffer, 0, read);
-            }
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return outputFile;
-    }
-
-    static String computeSafetyNumber(
-            SignalServiceAddress ownAddress,
-            IdentityKey ownIdentityKey,
-            SignalServiceAddress theirAddress,
-            IdentityKey theirIdentityKey
-    ) {
-        int version;
-        byte[] ownId;
-        byte[] theirId;
-
-        if (ServiceConfig.capabilities.isUuid() && ownAddress.getUuid().isPresent() && theirAddress.getUuid()
-                .isPresent()) {
-            // Version 2: UUID user
-            version = 2;
-            ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
-            theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
-        } else {
-            // Version 1: E164 user
-            version = 1;
-            if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
-                return "INVALID ID";
-            }
-            ownId = ownAddress.getNumber().get().getBytes();
-            theirId = theirAddress.getNumber().get().getBytes();
-        }
-
-        Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
-                ownId,
-                ownIdentityKey,
-                theirId,
-                theirIdentityKey);
-        return fingerprint.getDisplayableFingerprint().getDisplayText();
-    }
-
-    static class DeviceLinkInfo {
-
-        final String deviceIdentifier;
-        final ECPublicKey deviceKey;
-
-        DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
-            this.deviceIdentifier = deviceIdentifier;
-            this.deviceKey = deviceKey;
-        }
-    }
-}
index d865356ef262bac6ea30f8d56c905d8a028fea3b..237a34b6f8fa374b9615330cf3ac77c6f25eb506 100644 (file)
@@ -1,6 +1,6 @@
 package org.asamk.signal.manager.groups;
 
-import static org.asamk.signal.manager.KeyUtils.getSecretBytes;
+import static org.asamk.signal.manager.util.KeyUtils.getSecretBytes;
 
 public class GroupIdV1 extends GroupId {
 
index 41be672a7b65b386a3502e65f1038d095d0158fa..7edc7afb389e5dfbb479c10ce3b4f25e294c114f 100644 (file)
@@ -1,6 +1,6 @@
 package org.asamk.signal.manager.groups;
 
-import org.asamk.signal.manager.KeyUtils;
+import org.asamk.signal.manager.util.KeyUtils;
 
 import java.util.Arrays;
 
index 394eba57da748fcc76b9d6e9767b7ba4623e5494..8a2320e0141b06fd29c70de48d42194255ddfd26 100644 (file)
@@ -7,7 +7,7 @@ import org.asamk.signal.manager.groups.GroupLinkPassword;
 import org.asamk.signal.manager.groups.GroupUtils;
 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
 import org.asamk.signal.manager.storage.profiles.SignalProfile;
-import org.asamk.signal.util.IOUtils;
+import org.asamk.signal.manager.util.IOUtils;
 import org.signal.storageservice.protos.groups.AccessControl;
 import org.signal.storageservice.protos.groups.GroupChange;
 import org.signal.storageservice.protos.groups.Member;
index c3573209b2523f472bcb6777891273f53145c1b2..c787471fc44f1c3571fa47ae9b29d04cc41f6a08 100644 (file)
@@ -25,8 +25,8 @@ import org.asamk.signal.manager.storage.protocol.SignalServiceAddressResolver;
 import org.asamk.signal.manager.storage.stickers.StickerStore;
 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
 import org.asamk.signal.manager.storage.threads.ThreadInfo;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.Utils;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.profiles.ProfileKey;
 import org.slf4j.Logger;
@@ -211,28 +211,28 @@ public class SignalAccount implements Closeable {
             deviceId = node.asInt();
         }
         if (rootNode.has("isMultiDevice")) {
-            isMultiDevice = Util.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
+            isMultiDevice = Utils.getNotNullNode(rootNode, "isMultiDevice").asBoolean();
         }
-        username = Util.getNotNullNode(rootNode, "username").asText();
-        password = Util.getNotNullNode(rootNode, "password").asText();
+        username = Utils.getNotNullNode(rootNode, "username").asText();
+        password = Utils.getNotNullNode(rootNode, "password").asText();
         JsonNode pinNode = rootNode.get("registrationLockPin");
         registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
         if (rootNode.has("signalingKey")) {
-            signalingKey = Util.getNotNullNode(rootNode, "signalingKey").asText();
+            signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText();
         }
         if (rootNode.has("preKeyIdOffset")) {
-            preKeyIdOffset = Util.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
+            preKeyIdOffset = Utils.getNotNullNode(rootNode, "preKeyIdOffset").asInt(0);
         } else {
             preKeyIdOffset = 0;
         }
         if (rootNode.has("nextSignedPreKeyId")) {
-            nextSignedPreKeyId = Util.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
+            nextSignedPreKeyId = Utils.getNotNullNode(rootNode, "nextSignedPreKeyId").asInt();
         } else {
             nextSignedPreKeyId = 0;
         }
         if (rootNode.has("profileKey")) {
             try {
-                profileKey = new ProfileKey(Base64.decode(Util.getNotNullNode(rootNode, "profileKey").asText()));
+                profileKey = new ProfileKey(Base64.decode(Utils.getNotNullNode(rootNode, "profileKey").asText()));
             } catch (InvalidInputException e) {
                 throw new IOException(
                         "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
@@ -240,9 +240,9 @@ public class SignalAccount implements Closeable {
             }
         }
 
-        signalProtocolStore = jsonProcessor.convertValue(Util.getNotNullNode(rootNode, "axolotlStore"),
+        signalProtocolStore = jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
                 JsonSignalProtocolStore.class);
-        registered = Util.getNotNullNode(rootNode, "registered").asBoolean();
+        registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
         JsonNode groupStoreNode = rootNode.get("groupStore");
         if (groupStoreNode != null) {
             groupStore = jsonProcessor.convertValue(groupStoreNode, JsonGroupStore.class);
index 2b4dbcf5ee785760051ff6e972bb1596055172e0..fdcd28a3d1738c79fb6e6b68968ceb646c931efa 100644 (file)
@@ -16,8 +16,8 @@ import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdV1;
 import org.asamk.signal.manager.groups.GroupIdV2;
 import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.util.IOUtils;
 import org.asamk.signal.util.Hex;
-import org.asamk.signal.util.IOUtils;
 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.groups.GroupMasterKey;
index 517b384e50d3e7a450260015f802df07e0d683b0..5bc1c11f6d915c716e59887d14455df8af77e7c4 100644 (file)
@@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
 
 import org.asamk.signal.manager.TrustLevel;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.libsignal.IdentityKey;
@@ -51,7 +51,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
         if (resolver != null) {
             return resolver.resolveSignalServiceAddress(identifier);
         } else {
-            return Util.getSignalServiceAddressFromIdentifier(identifier);
+            return Utils.getSignalServiceAddressFromIdentifier(identifier);
         }
     }
 
@@ -213,7 +213,7 @@ public class JsonIdentityKeyStore implements IdentityKeyStore {
                         UUID uuid = trustedKey.hasNonNull("uuid") ? UuidUtil.parseOrNull(trustedKey.get("uuid")
                                 .asText()) : null;
                         final SignalServiceAddress serviceAddress = uuid == null
-                                ? Util.getSignalServiceAddressFromIdentifier(trustedKeyName)
+                                ? Utils.getSignalServiceAddressFromIdentifier(trustedKeyName)
                                 : new SignalServiceAddress(uuid, trustedKeyName);
                         try {
                             IdentityKey id = new IdentityKey(Base64.decode(trustedKey.get("identityKey").asText()), 0);
index f55aff14f63eb54615b85d6c7d02e1c65cc6311d..6e300214c68f8992e3f828427c185ef9c667062d 100644 (file)
@@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
 
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.util.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.libsignal.SignalProtocolAddress;
@@ -43,7 +43,7 @@ class JsonSessionStore implements SessionStore {
         if (resolver != null) {
             return resolver.resolveSignalServiceAddress(identifier);
         } else {
-            return Util.getSignalServiceAddressFromIdentifier(identifier);
+            return Utils.getSignalServiceAddressFromIdentifier(identifier);
         }
     }
 
@@ -147,7 +147,7 @@ class JsonSessionStore implements SessionStore {
 
                     UUID uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
                     final SignalServiceAddress serviceAddress = uuid == null
-                            ? Util.getSignalServiceAddressFromIdentifier(sessionName)
+                            ? Utils.getSignalServiceAddressFromIdentifier(sessionName)
                             : new SignalServiceAddress(uuid, sessionName);
                     final int deviceId = session.get("deviceId").asInt();
                     final String record = session.get("record").asText();
diff --git a/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java
new file mode 100644 (file)
index 0000000..b9a9707
--- /dev/null
@@ -0,0 +1,79 @@
+package org.asamk.signal.manager.util;
+
+import org.asamk.signal.manager.AttachmentInvalidException;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
+import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AttachmentUtils {
+
+    public static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
+        List<SignalServiceAttachment> signalServiceAttachments = null;
+        if (attachments != null) {
+            signalServiceAttachments = new ArrayList<>(attachments.size());
+            for (String attachment : attachments) {
+                try {
+                    signalServiceAttachments.add(createAttachment(new File(attachment)));
+                } catch (IOException e) {
+                    throw new AttachmentInvalidException(attachment, e);
+                }
+            }
+        }
+        return signalServiceAttachments;
+    }
+
+    public static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
+        InputStream attachmentStream = new FileInputStream(attachmentFile);
+        final long attachmentSize = attachmentFile.length();
+        final String mime = Utils.getFileMimeType(attachmentFile, "application/octet-stream");
+        // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option
+        final long uploadTimestamp = System.currentTimeMillis();
+        Optional<byte[]> preview = Optional.absent();
+        Optional<String> caption = Optional.absent();
+        Optional<String> blurHash = Optional.absent();
+        final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
+        return new SignalServiceAttachmentStream(attachmentStream,
+                mime,
+                attachmentSize,
+                Optional.of(attachmentFile.getName()),
+                false,
+                false,
+                preview,
+                0,
+                0,
+                uploadTimestamp,
+                caption,
+                blurHash,
+                null,
+                null,
+                resumableUploadSpec);
+    }
+
+    public static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
+        InputStream input = stream.getInputStream();
+
+        try (OutputStream output = new FileOutputStream(outputFile)) {
+            byte[] buffer = new byte[4096];
+            int read;
+
+            while ((read = input.read(buffer)) != -1) {
+                output.write(buffer, 0, read);
+            }
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+        return outputFile;
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/util/IOUtils.java b/src/main/java/org/asamk/signal/manager/util/IOUtils.java
new file mode 100644 (file)
index 0000000..06f8aa2
--- /dev/null
@@ -0,0 +1,72 @@
+package org.asamk.signal.manager.util;
+
+import org.whispersystems.signalservice.internal.util.Util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.Set;
+
+import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
+
+public class IOUtils {
+
+    public static File createTempFile() throws IOException {
+        return File.createTempFile("signal_tmp_", ".tmp");
+    }
+
+    public static byte[] readFully(InputStream in) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Util.copy(in, baos);
+        return baos.toByteArray();
+    }
+
+    public static void createPrivateDirectories(File file) throws IOException {
+        if (file.exists()) {
+            return;
+        }
+
+        final Path path = file.toPath();
+        try {
+            Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
+            Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
+        } catch (UnsupportedOperationException e) {
+            Files.createDirectories(path);
+        }
+    }
+
+    public static void createPrivateFile(File path) throws IOException {
+        final Path file = path.toPath();
+        try {
+            Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
+            Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
+        } catch (UnsupportedOperationException e) {
+            Files.createFile(file);
+        }
+    }
+
+    public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
+        copyStreamToFile(input, outputFile, 8192);
+    }
+
+    public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
+        try (OutputStream output = new FileOutputStream(outputFile)) {
+            byte[] buffer = new byte[bufferSize];
+            int read;
+
+            while ((read = input.read(buffer)) != -1) {
+                output.write(buffer, 0, read);
+            }
+        }
+    }
+}
similarity index 79%
rename from src/main/java/org/asamk/signal/manager/KeyUtils.java
rename to src/main/java/org/asamk/signal/manager/util/KeyUtils.java
index 6ac093db7d65a2dd0bd86cec713f6da4fbf6aec9..2b4bc3716192c267abec1369e465cf3d454f3c2c 100644 (file)
@@ -1,4 +1,4 @@
-package org.asamk.signal.manager;
+package org.asamk.signal.manager.util;
 
 import org.asamk.signal.util.RandomUtils;
 import org.signal.zkgroup.InvalidInputException;
@@ -10,11 +10,11 @@ public class KeyUtils {
     private KeyUtils() {
     }
 
-    static String createSignalingKey() {
+    public static String createSignalingKey() {
         return getSecret(52);
     }
 
-    static ProfileKey createProfileKey() {
+    public static ProfileKey createProfileKey() {
         try {
             return new ProfileKey(getSecretBytes(32));
         } catch (InvalidInputException e) {
@@ -22,11 +22,11 @@ public class KeyUtils {
         }
     }
 
-    static String createPassword() {
+    public static String createPassword() {
         return getSecret(18);
     }
 
-    static byte[] createStickerUploadKey() {
+    public static byte[] createStickerUploadKey() {
         return getSecretBytes(32);
     }
 
diff --git a/src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java b/src/main/java/org/asamk/signal/manager/util/MessageCacheUtils.java
new file mode 100644 (file)
index 0000000..8661c10
--- /dev/null
@@ -0,0 +1,105 @@
+package org.asamk.signal.manager.util;
+
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+public class MessageCacheUtils {
+
+    public static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
+        try (FileInputStream f = new FileInputStream(file)) {
+            DataInputStream in = new DataInputStream(f);
+            int version = in.readInt();
+            if (version > 4) {
+                return null;
+            }
+            int type = in.readInt();
+            String source = in.readUTF();
+            UUID sourceUuid = null;
+            if (version >= 3) {
+                sourceUuid = UuidUtil.parseOrNull(in.readUTF());
+            }
+            int sourceDevice = in.readInt();
+            if (version == 1) {
+                // read legacy relay field
+                in.readUTF();
+            }
+            long timestamp = in.readLong();
+            byte[] content = null;
+            int contentLen = in.readInt();
+            if (contentLen > 0) {
+                content = new byte[contentLen];
+                in.readFully(content);
+            }
+            byte[] legacyMessage = null;
+            int legacyMessageLen = in.readInt();
+            if (legacyMessageLen > 0) {
+                legacyMessage = new byte[legacyMessageLen];
+                in.readFully(legacyMessage);
+            }
+            long serverReceivedTimestamp = 0;
+            String uuid = null;
+            if (version >= 2) {
+                serverReceivedTimestamp = in.readLong();
+                uuid = in.readUTF();
+                if ("".equals(uuid)) {
+                    uuid = null;
+                }
+            }
+            long serverDeliveredTimestamp = 0;
+            if (version >= 4) {
+                serverDeliveredTimestamp = in.readLong();
+            }
+            Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
+                    ? Optional.absent()
+                    : Optional.of(new SignalServiceAddress(sourceUuid, source));
+            return new SignalServiceEnvelope(type,
+                    addressOptional,
+                    sourceDevice,
+                    timestamp,
+                    legacyMessage,
+                    content,
+                    serverReceivedTimestamp,
+                    serverDeliveredTimestamp,
+                    uuid);
+        }
+    }
+
+    public static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
+        try (FileOutputStream f = new FileOutputStream(file)) {
+            try (DataOutputStream out = new DataOutputStream(f)) {
+                out.writeInt(4); // version
+                out.writeInt(envelope.getType());
+                out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
+                out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
+                out.writeInt(envelope.getSourceDevice());
+                out.writeLong(envelope.getTimestamp());
+                if (envelope.hasContent()) {
+                    out.writeInt(envelope.getContent().length);
+                    out.write(envelope.getContent());
+                } else {
+                    out.writeInt(0);
+                }
+                if (envelope.hasLegacyMessage()) {
+                    out.writeInt(envelope.getLegacyMessage().length);
+                    out.write(envelope.getLegacyMessage());
+                } else {
+                    out.writeInt(0);
+                }
+                out.writeLong(envelope.getServerReceivedTimestamp());
+                String uuid = envelope.getUuid();
+                out.writeUTF(uuid == null ? "" : uuid);
+                out.writeLong(envelope.getServerDeliveredTimestamp());
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/asamk/signal/manager/util/Utils.java b/src/main/java/org/asamk/signal/manager/util/Utils.java
new file mode 100644 (file)
index 0000000..e68b5ce
--- /dev/null
@@ -0,0 +1,97 @@
+package org.asamk.signal.manager.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.fingerprint.Fingerprint;
+import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.StreamDetails;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.net.URLConnection;
+import java.nio.file.Files;
+
+public class Utils {
+
+    public static String getFileMimeType(File file, String defaultMimeType) throws IOException {
+        String mime = Files.probeContentType(file.toPath());
+        if (mime == null) {
+            try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
+                mime = URLConnection.guessContentTypeFromStream(bufferedStream);
+            }
+        }
+        if (mime == null) {
+            return defaultMimeType;
+        }
+        return mime;
+    }
+
+    public static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
+        InputStream stream = new FileInputStream(file);
+        final long size = file.length();
+        String mime = Files.probeContentType(file.toPath());
+        if (mime == null) {
+            mime = "application/octet-stream";
+        }
+        return new StreamDetails(stream, mime, size);
+    }
+
+    public static String computeSafetyNumber(
+            boolean isUuidCapable,
+            SignalServiceAddress ownAddress,
+            IdentityKey ownIdentityKey,
+            SignalServiceAddress theirAddress,
+            IdentityKey theirIdentityKey
+    ) {
+        int version;
+        byte[] ownId;
+        byte[] theirId;
+
+        if (isUuidCapable && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) {
+            // Version 2: UUID user
+            version = 2;
+            ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
+            theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
+        } else {
+            // Version 1: E164 user
+            version = 1;
+            if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
+                return "INVALID ID";
+            }
+            ownId = ownAddress.getNumber().get().getBytes();
+            theirId = theirAddress.getNumber().get().getBytes();
+        }
+
+        Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version,
+                ownId,
+                ownIdentityKey,
+                theirId,
+                theirIdentityKey);
+        return fingerprint.getDisplayableFingerprint().getDisplayText();
+    }
+
+    public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
+        if (UuidUtil.isUuid(identifier)) {
+            return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
+        } else {
+            return new SignalServiceAddress(null, identifier);
+        }
+    }
+
+    public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
+        JsonNode node = parent.get(name);
+        if (node == null) {
+            throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
+                    name));
+        }
+
+        return node;
+    }
+}
index 59727a9a773314a9cd81e164d1cdaa2f1f85346d..766d19057b2d5123ed70f7df6cb0a9be879b33fd 100644 (file)
@@ -1,35 +1,16 @@
 package org.asamk.signal.util;
 
-import org.whispersystems.signalservice.internal.util.Util;
-
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.util.EnumSet;
-import java.util.Set;
-
-import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
-import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
 
 public class IOUtils {
 
     private IOUtils() {
     }
 
-    public static File createTempFile() throws IOException {
-        return File.createTempFile("signal_tmp_", ".tmp");
-    }
-
     public static String readAll(InputStream in, Charset charset) throws IOException {
         StringWriter output = new StringWriter();
         byte[] buffer = new byte[4096];
@@ -40,36 +21,6 @@ public class IOUtils {
         return output.toString();
     }
 
-    public static byte[] readFully(InputStream in) throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        Util.copy(in, baos);
-        return baos.toByteArray();
-    }
-
-    public static void createPrivateDirectories(File file) throws IOException {
-        if (file.exists()) {
-            return;
-        }
-
-        final Path path = file.toPath();
-        try {
-            Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
-            Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
-        } catch (UnsupportedOperationException e) {
-            Files.createDirectories(path);
-        }
-    }
-
-    public static void createPrivateFile(File path) throws IOException {
-        final Path file = path.toPath();
-        try {
-            Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
-            Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
-        } catch (UnsupportedOperationException e) {
-            Files.createFile(file);
-        }
-    }
-
     public static File getDataHomeDir() {
         String dataHome = System.getenv("XDG_DATA_HOME");
         if (dataHome != null) {
@@ -78,19 +29,4 @@ public class IOUtils {
 
         return new File(new File(System.getProperty("user.home"), ".local"), "share");
     }
-
-    public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
-        copyStreamToFile(input, outputFile, 8192);
-    }
-
-    public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
-        try (OutputStream output = new FileOutputStream(outputFile)) {
-            byte[] buffer = new byte[bufferSize];
-            int read;
-
-            while ((read = input.read(buffer)) != -1) {
-                output.write(buffer, 0, read);
-            }
-        }
-    }
 }
index 79a6587b5083733c442547f9e7c212601dffd06f..92bfae7bbada88a51544c83e5c229c0683290a7f 100644 (file)
@@ -1,15 +1,7 @@
 package org.asamk.signal.util;
 
-import com.fasterxml.jackson.databind.JsonNode;
-
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-
-import java.io.InvalidObjectException;
 
 public class Util {
 
@@ -26,41 +18,7 @@ public class Util {
         return f.toString();
     }
 
-    public static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
-        StringBuilder buf = new StringBuilder();
-        for (CharSequence str : list) {
-            if (buf.length() > 0) {
-                buf.append(separator);
-            }
-            buf.append(str);
-        }
-
-        return buf.toString();
-    }
-
-    public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
-        JsonNode node = parent.get(name);
-        if (node == null) {
-            throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
-                    name));
-        }
-
-        return node;
-    }
-
     public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
         return GroupId.fromBase64(groupId);
     }
-
-    public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
-        return PhoneNumberFormatter.formatNumber(number, localNumber);
-    }
-
-    public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
-        if (UuidUtil.isUuid(identifier)) {
-            return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
-        } else {
-            return new SignalServiceAddress(null, identifier);
-        }
-    }
 }