1package eu.siacs.conversations.generator;
  2
  3import android.util.Base64;
  4import eu.siacs.conversations.BuildConfig;
  5import eu.siacs.conversations.Config;
  6import eu.siacs.conversations.R;
  7import eu.siacs.conversations.crypto.axolotl.AxolotlService;
  8import eu.siacs.conversations.entities.Account;
  9import eu.siacs.conversations.services.XmppConnectionService;
 10import eu.siacs.conversations.xml.Namespace;
 11import eu.siacs.conversations.xmpp.XmppConnection;
 12import java.security.MessageDigest;
 13import java.security.NoSuchAlgorithmException;
 14import java.text.SimpleDateFormat;
 15import java.util.ArrayList;
 16import java.util.Arrays;
 17import java.util.Collections;
 18import java.util.List;
 19import java.util.Locale;
 20import java.util.TimeZone;
 21
 22public abstract class AbstractGenerator {
 23    private static final SimpleDateFormat DATE_FORMAT =
 24            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
 25    private final String[] STATIC_FEATURES = {
 26        Namespace.JINGLE,
 27        Namespace.JINGLE_APPS_FILE_TRANSFER,
 28        Namespace.JINGLE_TRANSPORTS_S5B,
 29        Namespace.JINGLE_TRANSPORTS_IBB,
 30        Namespace.JINGLE_ENCRYPTED_TRANSPORT,
 31        Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO,
 32        "http://jabber.org/protocol/muc",
 33        "jabber:x:conference",
 34        Namespace.OOB,
 35        "http://jabber.org/protocol/caps",
 36        "http://jabber.org/protocol/disco#info",
 37        "urn:xmpp:avatar:metadata+notify",
 38        Namespace.NICK + "+notify",
 39        "urn:xmpp:ping",
 40        "jabber:iq:version",
 41        "http://jabber.org/protocol/chatstates",
 42        Namespace.REACTIONS
 43    };
 44    private final String[] MESSAGE_CONFIRMATION_FEATURES = {
 45        "urn:xmpp:chat-markers:0", "urn:xmpp:receipts"
 46    };
 47    private final String[] MESSAGE_CORRECTION_FEATURES = {"urn:xmpp:message-correct:0"};
 48    private final String[] PRIVACY_SENSITIVE = {
 49        "urn:xmpp:time" // XEP-0202: Entity Time leaks time zone
 50    };
 51    private final String[] VOIP_NAMESPACES = {
 52        Namespace.JINGLE_TRANSPORT_ICE_UDP,
 53        Namespace.JINGLE_FEATURE_AUDIO,
 54        Namespace.JINGLE_FEATURE_VIDEO,
 55        Namespace.JINGLE_APPS_RTP,
 56        Namespace.JINGLE_APPS_DTLS,
 57        Namespace.JINGLE_MESSAGE
 58    };
 59    protected XmppConnectionService mXmppConnectionService;
 60
 61    AbstractGenerator(XmppConnectionService service) {
 62        this.mXmppConnectionService = service;
 63    }
 64
 65    public static String getTimestamp(long time) {
 66        DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
 67        return DATE_FORMAT.format(time);
 68    }
 69
 70    String getIdentityVersion() {
 71        return BuildConfig.VERSION_NAME;
 72    }
 73
 74    String getIdentityName() {
 75        return BuildConfig.APP_NAME;
 76    }
 77
 78    String getIdentityType() {
 79        if ("chromium".equals(android.os.Build.BRAND)) {
 80            return "pc";
 81        } else {
 82            return mXmppConnectionService.getString(R.string.default_resource).toLowerCase();
 83        }
 84    }
 85
 86    String getCapHash(final Account account) {
 87        StringBuilder s = new StringBuilder();
 88        s.append("client/")
 89                .append(getIdentityType())
 90                .append("//")
 91                .append(getIdentityName())
 92                .append('<');
 93        MessageDigest md;
 94        try {
 95            md = MessageDigest.getInstance("SHA-1");
 96        } catch (NoSuchAlgorithmException e) {
 97            return null;
 98        }
 99
100        for (String feature : getFeatures(account)) {
101            s.append(feature).append('<');
102        }
103        final byte[] sha1 = md.digest(s.toString().getBytes());
104        return Base64.encodeToString(sha1, Base64.NO_WRAP);
105    }
106
107    public List<String> getFeatures(final Account account) {
108        final XmppConnection connection = account.getXmppConnection();
109        final ArrayList<String> features = new ArrayList<>(Arrays.asList(STATIC_FEATURES));
110        features.add("http://jabber.org/protocol/xhtml-im");
111        features.add("urn:xmpp:bob");
112        if (Config.MESSAGE_DISPLAYED_SYNCHRONIZATION) {
113            features.add(Namespace.MDS_DISPLAYED + "+notify");
114        }
115        if (mXmppConnectionService.confirmMessages()) {
116            features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
117        }
118        if (mXmppConnectionService.allowMessageCorrection()) {
119            features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES));
120        }
121        if (Config.supportOmemo()) {
122            features.add(AxolotlService.PEP_DEVICE_LIST_NOTIFY);
123        }
124        if (!mXmppConnectionService.useTorToConnect() && !account.isOnion()) {
125            features.addAll(Arrays.asList(PRIVACY_SENSITIVE));
126            features.addAll(Arrays.asList(VOIP_NAMESPACES));
127            features.add(Namespace.JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL);
128        }
129        if (mXmppConnectionService.broadcastLastActivity()) {
130            features.add(Namespace.IDLE);
131        }
132        if (connection != null && connection.getFeatures().bookmarks2()) {
133            features.add(Namespace.BOOKMARKS2 + "+notify");
134        } else {
135            features.add(Namespace.BOOKMARKS + "+notify");
136        }
137
138        Collections.sort(features);
139        return features;
140    }
141}