Detailed changes
@@ -33,6 +33,10 @@ configurations {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
+ implementation project(':libs:annotation')
+ annotationProcessor project(':libs:annotation-processor')
+
+
implementation 'androidx.viewpager:viewpager:1.0.0'
playstoreImplementation('com.google.firebase:firebase-messaging:24.0.0') {
@@ -0,0 +1,20 @@
+apply plugin: "java-library"
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+dependencies {
+
+ implementation project(':libs:annotation')
+
+ annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
+ api 'com.google.auto.service:auto-service-annotations:1.0.1'
+ implementation 'com.google.guava:guava:31.1-jre'
+
+}
@@ -0,0 +1,185 @@
+package im.conversations.android.annotation.processor;
+
+import com.google.auto.service.AutoService;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.annotation.XmlPackage;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.JavaFileObject;
+
+@AutoService(Processor.class)
+@SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedAnnotationTypes("im.conversations.android.annotation.XmlElement")
+public class XmlElementProcessor extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
+ final Set<? extends Element> elements =
+ roundEnvironment.getElementsAnnotatedWith(XmlElement.class);
+ final ImmutableMap.Builder<Id, String> builder = ImmutableMap.builder();
+ for (final Element element : elements) {
+ if (element instanceof final TypeElement typeElement) {
+ final Id id = of(typeElement);
+ builder.put(id, typeElement.getQualifiedName().toString());
+ }
+ }
+ final ImmutableMap<Id, String> maps = builder.build();
+ if (maps.isEmpty()) {
+ return false;
+ }
+ final JavaFileObject extensionFile;
+ try {
+ extensionFile =
+ processingEnv
+ .getFiler()
+ .createSourceFile("im.conversations.android.xmpp.Extensions");
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ try (final PrintWriter out = new PrintWriter(extensionFile.openWriter())) {
+ out.println("package im.conversations.android.xmpp;");
+ out.println("import com.google.common.collect.BiMap;");
+ out.println("import com.google.common.collect.ImmutableBiMap;");
+ out.println("import im.conversations.android.xmpp.ExtensionFactory;");
+ out.println("import im.conversations.android.xmpp.model.Extension;");
+ out.print("\n");
+ out.println("public final class Extensions {");
+ out.println(
+ "public static final BiMap<ExtensionFactory.Id, Class<? extends Extension>>"
+ + " EXTENSION_CLASS_MAP;");
+ out.println("static {");
+ out.println(
+ "final var builder = new ImmutableBiMap.Builder<ExtensionFactory.Id, Class<?"
+ + " extends Extension>>();");
+ for (final Map.Entry<Id, String> entry : maps.entrySet()) {
+ Id id = entry.getKey();
+ String clazz = entry.getValue();
+ out.format(
+ "builder.put(new ExtensionFactory.Id(\"%s\",\"%s\"),%s.class);",
+ id.name, id.namespace, clazz);
+ out.print("\n");
+ }
+ out.println("EXTENSION_CLASS_MAP = builder.build();");
+ out.println("}");
+ out.println(" private Extensions() {}");
+ out.println("}");
+ // writing generated file to out …
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ private static Id of(final TypeElement typeElement) {
+ final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class);
+ final PackageElement packageElement = getPackageElement(typeElement);
+ final XmlPackage xmlPackage =
+ packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class);
+ if (xmlElement == null) {
+ throw new IllegalStateException(
+ String.format(
+ "%s is not annotated as @XmlElement",
+ typeElement.getQualifiedName().toString()));
+ }
+ final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace();
+ final String elementName = xmlElement.name();
+ final String elementNamespace = xmlElement.namespace();
+ final String namespace;
+ if (!Strings.isNullOrEmpty(elementNamespace)) {
+ namespace = elementNamespace;
+ } else if (!Strings.isNullOrEmpty(packageNamespace)) {
+ namespace = packageNamespace;
+ } else {
+ throw new IllegalStateException(
+ String.format(
+ "%s does not declare a namespace",
+ typeElement.getQualifiedName().toString()));
+ }
+ if (!hasEmptyDefaultConstructor(typeElement)) {
+ throw new IllegalStateException(
+ String.format(
+ "%s does not have an empty default constructor",
+ typeElement.getQualifiedName().toString()));
+ }
+ final String name;
+ if (Strings.isNullOrEmpty(elementName)) {
+ name =
+ CaseFormat.UPPER_CAMEL.to(
+ CaseFormat.LOWER_HYPHEN, typeElement.getSimpleName().toString());
+ } else {
+ name = elementName;
+ }
+ return new Id(name, namespace);
+ }
+
+ private static PackageElement getPackageElement(final TypeElement typeElement) {
+ final Element parent = typeElement.getEnclosingElement();
+ if (parent instanceof PackageElement) {
+ return (PackageElement) parent;
+ } else {
+ final Element nextParent = parent.getEnclosingElement();
+ if (nextParent instanceof PackageElement) {
+ return (PackageElement) nextParent;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private static boolean hasEmptyDefaultConstructor(final TypeElement typeElement) {
+ final List<ExecutableElement> constructors =
+ ElementFilter.constructorsIn(typeElement.getEnclosedElements());
+ for (final ExecutableElement constructor : constructors) {
+ if (constructor.getParameters().isEmpty()
+ && constructor.getModifiers().contains(Modifier.PUBLIC)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static class Id {
+ public final String name;
+ public final String namespace;
+
+ public Id(String name, String namespace) {
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Id id = (Id) o;
+ return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name, namespace);
+ }
+ }
+}
@@ -0,0 +1,6 @@
+apply plugin: "java-library"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
@@ -0,0 +1,15 @@
+package im.conversations.android.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+public @interface XmlElement {
+
+ String name() default "";
+
+ String namespace() default "";
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.PACKAGE)
+public @interface XmlPackage {
+ String namespace();
+}
@@ -1,6 +1,7 @@
-dontobfuscate
-keep class eu.siacs.conversations.**
+-keep class im.conversations.**
-keep class org.whispersystems.**
@@ -1 +1,3 @@
+include ':libs:annotation', ':libs:annotation-processor:'
+
rootProject.name = 'Conversations'
@@ -1,7 +1,6 @@
package eu.siacs.conversations.services;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Conversation;
public class PushManagementService {
@@ -11,11 +10,7 @@ public class PushManagementService {
this.mXmppConnectionService = service;
}
- void registerPushTokenOnServer(Account account) {
- //stub implementation. only affects playstore flavor
- }
-
- void unregisterChannel(Account account, String hash) {
+ public void registerPushTokenOnServer(Account account) {
//stub implementation. only affects playstore flavor
}
@@ -26,8 +21,4 @@ public class PushManagementService {
public boolean isStub() {
return true;
}
-
- public boolean availableAndUseful(Account account) {
- return false;
- }
}
@@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
@@ -70,8 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@@ -392,20 +390,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
return;
}
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
- Element item = mXmppConnectionService.getIqParser().getItem(packet);
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
- registerDevices(account.getJid().asBareJid(), deviceIds);
- }
+ Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
+ mXmppConnectionService.sendIqPacket(account, packet, response -> {
+ if (packet.getType() == Iq.Type.TIMEOUT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
+ } else {
+ //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
+ final Element item = IqParser.getItem(packet);
+ final Set<Integer> deviceIds = IqParser.deviceIds(item);
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
+ registerDevices(account.getJid().asBareJid(), deviceIds);
}
+
});
}
@@ -455,40 +451,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
- final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
- if (firstAttempt && preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
- mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- publishDeviceIdsAndRefineAccessModel(ids, false);
- }
-
- @Override
- public void onPushFailed() {
- publishDeviceIdsAndRefineAccessModel(ids, false);
- }
- });
- } else {
- if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode");
- account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
- mXmppConnectionService.databaseBackend.updateAccount(account);
+ final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
+ mXmppConnectionService.sendIqPacket(account, publish, response -> {
+ final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
+ final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
+ if (firstAttempt && preConditionNotMet) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
+ mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ publishDeviceIdsAndRefineAccessModel(ids, false);
}
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- if (preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
- } else if (error != null) {
- pepBroken = true;
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
- }
+ @Override
+ public void onPushFailed() {
+ publishDeviceIdsAndRefineAccessModel(ids, false);
}
+ });
+ } else {
+ if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode");
+ account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ }
+ if (response.getType() == Iq.Type.ERROR) {
+ if (preConditionNotMet) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
+ } else if (error != null) {
+ pepBroken = true;
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + response.findChild("error"));
+ }
+
}
}
});
@@ -506,26 +499,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign();
- IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
+ final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, IqPacket packet) {
- String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
+ mXmppConnectionService.sendIqPacket(account, packet, response -> {
+ String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
+ mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+ }
- @Override
- public void onPushFailed() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- }
+ @Override
+ public void onPushFailed() {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+ }
+ });
});
} catch (Exception e) {
e.printStackTrace();
@@ -549,109 +539,106 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (this.changeAccessMode.get()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
}
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
+ final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, response -> {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
+ if (response.getType() == Iq.Type.TIMEOUT) {
+ return; //ignore timeout. do nothing
+ }
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
- return;
- }
+ if (response.getType() == Iq.Type.ERROR) {
+ Element error = response.findChild("error");
+ if (error == null || !error.hasChild("item-not-found")) {
+ pepBroken = true;
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + response);
+ return;
}
+ }
- PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
- Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
+ PreKeyBundle bundle = IqParser.bundle(response);
+ final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
+ boolean flush = false;
+ if (bundle == null) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + response);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
+ flush = true;
+ }
+ if (keys == null) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + response);
+ }
+ try {
+ boolean changed = false;
+ // Validate IdentityKey
+ IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
+ if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
+ changed = true;
}
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
- } catch (InvalidKeyIdException e) {
+ // Validate signedPreKeyRecord + ID
+ SignedPreKeyRecord signedPreKeyRecord;
+ int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
+ try {
+ signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
+ if (flush
+ || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
changed = true;
}
+ } catch (InvalidKeyIdException e) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
- // Validate PreKeys
- Set<PreKeyRecord> preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
- try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
- }
- } catch (InvalidKeyIdException ignored) {
+ // Validate PreKeys
+ Set<PreKeyRecord> preKeyRecords = new HashSet<>();
+ if (keys != null) {
+ for (Integer id : keys.keySet()) {
+ try {
+ PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
+ if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
+ preKeyRecords.add(preKeyRecord);
}
+ } catch (InvalidKeyIdException ignored) {
}
}
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
+ }
+ int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
+ if (newKeys > 0) {
+ List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
+ preKeyRecords.addAll(newRecords);
+ for (PreKeyRecord record : newRecords) {
+ axolotlStore.storePreKey(record.getId(), record);
}
+ changed = true;
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
+ }
- if (changed || changeAccessMode.get()) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
+ if (changed || changeAccessMode.get()) {
+ if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
+ mXmppConnectionService.publishDisplayName(account);
+ publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
} else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announce) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
}
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
}
+ } catch (InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
}
});
}
@@ -669,44 +656,41 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final boolean wipe,
final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
- final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
+ final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles(
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
preKeyRecords, getOwnDeviceId(), publishOptions);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, IqPacket packet) {
- final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
- if (firstAttempt && preconditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
- final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
- }
-
- @Override
- public void onPushFailed() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
- }
- });
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announceAfter) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
+ mXmppConnectionService.sendIqPacket(account, publish, response -> {
+ final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
+ if (firstAttempt && preconditionNotMet) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
+ final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
+ mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
}
- } else if (packet.getType() == IqPacket.TYPE.ERROR) {
- if (preconditionNotMet) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString());
+
+ @Override
+ public void onPushFailed() {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
}
- pepBroken = true;
+ });
+ } else if (response.getType() == Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announceAfter) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ } else if (response.getType() == Iq.Type.ERROR) {
+ if (preconditionNotMet) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + response.toString());
}
+ pepBroken = true;
}
});
}
@@ -759,9 +743,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return Futures.immediateFuture(session);
}
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
- final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
- Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
+ final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, (response) -> {
+ Pair<X509Certificate[], byte[]> verification = IqParser.verification(response);
if (verification != null) {
try {
Signature verifier = Signature.getInstance("sha256WithRSA");
@@ -846,7 +830,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
- IqPacket packet;
+ final Iq packet;
synchronized (this.fetchDeviceIdsMap) {
List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
if (callbacks != null) {
@@ -866,11 +850,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
if (packet != null) {
- mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ mXmppConnectionService.sendIqPacket(account, packet, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
fetchDeviceListStatus.put(jid, true);
- Element item = mXmppConnectionService.getIqParser().getItem(response);
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ final Element item = IqParser.getItem(response);
+ final Set<Integer> deviceIds = IqParser.deviceIds(item);
registerDevices(jid, deviceIds);
final List<OnDeviceIdsFetched> callbacks;
synchronized (fetchDeviceIdsMap) {
@@ -882,7 +866,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
} else {
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (response.getType() == Iq.Type.TIMEOUT) {
fetchDeviceListStatus.remove(jid);
} else {
fetchDeviceListStatus.put(jid, false);
@@ -929,16 +913,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
final Jid jid = Jid.of(address.getName());
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
- IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, bundlesPacket, (packet) -> {
+ if (packet.getType() == Iq.Type.TIMEOUT) {
fetchStatusMap.put(address, FetchStatus.TIMEOUT);
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
+ } else if (packet.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
- final IqParser parser = mXmppConnectionService.getIqParser();
- final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
- final PreKeyBundle bundle = parser.bundle(packet);
+ final List<PreKeyBundle> preKeyBundleList = IqParser.preKeys(packet);
+ final PreKeyBundle bundle = IqParser.bundle(packet);
if (preKeyBundleList.isEmpty() || bundle == null) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
fetchStatusMap.put(address, FetchStatus.ERROR);
@@ -1544,7 +1527,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
axolotlMessage.addDevice(session, true);
try {
final Jid jid = Jid.of(session.getRemoteAddress().getName());
- MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
+ final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
mXmppConnectionService.sendMessagePacket(account, packet);
} catch (IllegalArgumentException e) {
throw new Error("Remote addresses are created from jid and should convert back to jid", e);
@@ -24,7 +24,7 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
public class ServiceDiscoveryResult {
public static final String TABLENAME = "discovery_results";
@@ -36,7 +36,7 @@ public class ServiceDiscoveryResult {
protected final List<String> features;
protected final List<Data> forms;
private final List<Identity> identities;
- public ServiceDiscoveryResult(final IqPacket packet) {
+ public ServiceDiscoveryResult(final Iq packet) {
this.identities = new ArrayList<>();
this.features = new ArrayList<>();
this.forms = new ArrayList<>();
@@ -275,7 +275,7 @@ public class ServiceDiscoveryResult {
return values;
}
- public static class Identity implements Comparable {
+ public static class Identity implements Comparable<Identity> {
protected final String type;
protected final String lang;
protected final String name;
@@ -323,8 +323,21 @@ public class ServiceDiscoveryResult {
return this.name;
}
- public int compareTo(@NonNull Object other) {
- Identity o = (Identity) other;
+ JSONObject toJSON() {
+ try {
+ JSONObject o = new JSONObject();
+ o.put("category", this.getCategory());
+ o.put("type", this.getType());
+ o.put("lang", this.getLang());
+ o.put("name", this.getName());
+ return o;
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public int compareTo(final Identity o) {
int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
if (r == 0) {
r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
@@ -338,18 +351,5 @@ public class ServiceDiscoveryResult {
return r;
}
-
- JSONObject toJSON() {
- try {
- JSONObject o = new JSONObject();
- o.put("category", this.getCategory());
- o.put("type", this.getType());
- o.put("lang", this.getLang());
- o.put("name", this.getName());
- return o;
- } catch (JSONException e) {
- return null;
- }
- }
}
}
@@ -34,7 +34,7 @@ import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.pep.Avatar;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
public class IqGenerator extends AbstractGenerator {
@@ -42,8 +42,8 @@ public class IqGenerator extends AbstractGenerator {
super(service);
}
- public IqPacket discoResponse(final Account account, final IqPacket request) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
+ public Iq discoResponse(final Account account, final Iq request) {
+ final var packet = new Iq(Iq.Type.RESULT);
packet.setId(request.getId());
packet.setTo(request.getFrom());
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
@@ -58,8 +58,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket versionResponse(final IqPacket request) {
- final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
+ public Iq versionResponse(final Iq request) {
+ final var packet = request.generateResponse(Iq.Type.RESULT);
Element query = packet.query("jabber:iq:version");
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
query.addChild("version").setContent(getIdentityVersion());
@@ -71,8 +71,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket entityTimeResponse(IqPacket request) {
- final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
+ public Iq entityTimeResponse(final Iq request) {
+ final Iq packet = request.generateResponse(Iq.Type.RESULT);
Element time = packet.addChild("time", "urn:xmpp:time");
final long now = System.currentTimeMillis();
time.addChild("utc").setContent(getTimestamp(now));
@@ -91,14 +91,14 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket purgeOfflineMessages() {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public static Iq purgeOfflineMessages() {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet;
}
- protected IqPacket publish(final String node, final Element item, final Bundle options) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ protected Iq publish(final String node, final Element item, final Bundle options) {
+ final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element publish = pubsub.addChild("publish");
publish.setAttribute("node", node);
@@ -110,12 +110,12 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- protected IqPacket publish(final String node, final Element item) {
+ protected Iq publish(final String node, final Element item) {
return publish(node, item, null);
}
- private IqPacket retrieve(String node, Element item) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ private Iq retrieve(String node, Element item) {
+ final var packet = new Iq(Iq.Type.GET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element items = pubsub.addChild("items");
items.setAttribute("node", node);
@@ -125,30 +125,30 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket retrieveBookmarks() {
+ public Iq retrieveBookmarks() {
return retrieve(Namespace.BOOKMARKS2, null);
}
- public IqPacket retrieveMds() {
+ public Iq retrieveMds() {
return retrieve(Namespace.MDS_DISPLAYED, null);
}
- public IqPacket publishNick(String nick) {
+ public Iq publishNick(String nick) {
final Element item = new Element("item");
item.setAttribute("id", "current");
item.addChild("nick", Namespace.NICK).setContent(nick);
return publish(Namespace.NICK, item);
}
- public IqPacket deleteNode(final String node) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq deleteNode(final String node) {
+ final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
pubsub.addChild("delete").setAttribute("node", node);
return packet;
}
- public IqPacket deleteItem(final String node, final String id) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq deleteItem(final String node, final String id) {
+ final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element retract = pubsub.addChild("retract");
retract.setAttribute("node", node);
@@ -157,7 +157,7 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket publishAvatar(Avatar avatar, Bundle options) {
+ public Iq publishAvatar(Avatar avatar, Bundle options) {
final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum);
final Element data = item.addChild("data", Namespace.AVATAR_DATA);
@@ -165,14 +165,14 @@ public class IqGenerator extends AbstractGenerator {
return publish(Namespace.AVATAR_DATA, item, options);
}
- public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
+ public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) {
final Element item = new Element("item");
item.setAttribute("id", id);
item.addChild(element);
return publish(namespace, item, options);
}
- public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) {
+ public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) {
final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum);
final Element metadata = item
@@ -186,57 +186,57 @@ public class IqGenerator extends AbstractGenerator {
return publish(Namespace.AVATAR_METADATA, item, options);
}
- public IqPacket retrievePepAvatar(final Avatar avatar) {
+ public Iq retrievePepAvatar(final Avatar avatar) {
final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum);
- final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item);
+ final var packet = retrieve(Namespace.AVATAR_DATA, item);
packet.setTo(avatar.owner);
return packet;
}
- public IqPacket retrieveVcardAvatar(final Avatar avatar) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq retrieveVcardAvatar(final Avatar avatar) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(avatar.owner);
packet.addChild("vCard", "vcard-temp");
return packet;
}
- public IqPacket retrieveVcardAvatar(final Jid to) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq retrieveVcardAvatar(final Jid to) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(to);
packet.addChild("vCard", "vcard-temp");
return packet;
}
- public IqPacket retrieveAvatarMetaData(final Jid to) {
- final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
+ public Iq retrieveAvatarMetaData(final Jid to) {
+ final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
if (to != null) {
packet.setTo(to);
}
return packet;
}
- public IqPacket retrieveDeviceIds(final Jid to) {
- final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
+ public Iq retrieveDeviceIds(final Jid to) {
+ final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
if (to != null) {
packet.setTo(to);
}
return packet;
}
- public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
- final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
+ public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
+ final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
packet.setTo(to);
return packet;
}
- public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
- final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
+ public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
+ final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
packet.setTo(to);
return packet;
}
- public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
+ public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
final Element item = new Element("item");
item.setAttribute("id", "current");
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
@@ -286,7 +286,7 @@ public class IqGenerator extends AbstractGenerator {
return displayed;
}
- public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
+ public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element item = new Element("item");
item.setAttribute("id", "current");
@@ -310,7 +310,7 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
}
- public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
+ public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
final Element item = new Element("item");
item.setAttribute("id", "current");
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
@@ -328,8 +328,8 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
}
- public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
+ final Iq packet = new Iq(Iq.Type.SET);
final Element query = packet.query(mam.version.namespace);
query.setAttribute("queryid", mam.getQueryId());
final Data data = new Data();
@@ -359,15 +359,15 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket generateGetBlockList() {
- final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
+ public Iq generateGetBlockList() {
+ final Iq iq = new Iq(Iq.Type.GET);
iq.addChild("blocklist", Namespace.BLOCKING);
return iq;
}
- public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
- final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
+ final Iq iq = new Iq(Iq.Type.SET);
final Element block = iq.addChild("block", Namespace.BLOCKING);
final Element item = block.addChild("item").setAttribute("jid", jid);
if (reportSpam) {
@@ -383,15 +383,15 @@ public class IqGenerator extends AbstractGenerator {
return iq;
}
- public IqPacket generateSetUnblockRequest(final Jid jid) {
- final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ public Iq generateSetUnblockRequest(final Jid jid) {
+ final Iq iq = new Iq(Iq.Type.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
block.addChild("item").setAttribute("jid", jid);
return iq;
}
- public IqPacket generateSetPassword(final Account account, final String newPassword) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq generateSetPassword(final Account account, final String newPassword) {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(account.getDomain());
final Element query = packet.addChild("query", Namespace.REGISTER);
final Jid jid = account.getJid();
@@ -400,14 +400,14 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
+ public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) {
List<Jid> jids = new ArrayList<>();
jids.add(jid);
return changeAffiliation(conference, jids, affiliation);
}
- public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(conference.getJid().asBareJid());
packet.setFrom(conference.getAccount().getJid());
Element query = packet.query("http://jabber.org/protocol/muc#admin");
@@ -419,8 +419,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket changeRole(Conversation conference, String nick, String role) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq changeRole(Conversation conference, String nick, String role) {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(conference.getJid().asBareJid());
packet.setFrom(conference.getAccount().getJid());
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
@@ -429,8 +429,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(host);
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
request.setAttribute("filename", convertFilename(file.getName()));
@@ -439,8 +439,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(host);
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
request.addChild("filename").setContent(convertFilename(file.getName()));
@@ -466,8 +466,8 @@ public class IqGenerator extends AbstractGenerator {
}
}
- public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
- final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
+ public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) {
+ final Iq register = new Iq(Iq.Type.SET);
register.setFrom(account.getJid().asBareJid());
register.setTo(account.getDomain());
register.setId(id);
@@ -478,12 +478,12 @@ public class IqGenerator extends AbstractGenerator {
return register;
}
- public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
+ public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
return pushTokenToAppServer(appServer, token, deviceId, null);
}
- public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "register-push-fcm");
@@ -499,8 +499,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
- final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
+ final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "unregister-push-fcm");
@@ -513,8 +513,8 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket enablePush(final Jid jid, final String node, final String secret) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq enablePush(final Jid jid, final String node, final String secret) {
+ final Iq packet = new Iq(Iq.Type.SET);
Element enable = packet.addChild("enable", Namespace.PUSH);
enable.setAttribute("jid", jid);
enable.setAttribute("node", node);
@@ -528,16 +528,16 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket disablePush(final Jid jid, final String node) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+ public Iq disablePush(final Jid jid, final String node) {
+ Iq packet = new Iq(Iq.Type.SET);
Element disable = packet.addChild("disable", Namespace.PUSH);
disable.setAttribute("jid", jid);
disable.setAttribute("node", node);
return packet;
}
- public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq queryAffiliation(Conversation conversation, String affiliation) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(conversation.getJid().asBareJid());
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
return packet;
@@ -570,16 +570,16 @@ public class IqGenerator extends AbstractGenerator {
return options;
}
- public IqPacket requestPubsubConfiguration(Jid jid, String node) {
+ public Iq requestPubsubConfiguration(Jid jid, String node) {
return pubsubConfiguration(jid, node, null);
}
- public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) {
+ public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
return pubsubConfiguration(jid, node, data);
}
- private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
- IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
+ private Iq pubsubConfiguration(Jid jid, String node, Data data) {
+ final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
packet.setTo(jid);
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
Element configure = pubsub.addChild("configure").setAttribute("node", node);
@@ -589,15 +589,15 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public IqPacket queryDiscoItems(Jid jid) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq queryDiscoItems(final Jid jid) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(jid);
packet.addChild("query",Namespace.DISCO_ITEMS);
return packet;
}
- public IqPacket queryDiscoInfo(Jid jid) {
- IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+ public Iq queryDiscoInfo(final Jid jid) {
+ final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(jid);
packet.addChild("query",Namespace.DISCO_INFO);
return packet;
@@ -22,7 +22,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageGenerator extends AbstractGenerator {
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
@@ -32,25 +31,25 @@ public class MessageGenerator extends AbstractGenerator {
super(service);
}
- private MessagePacket preparePacket(Message message) {
+ private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message) {
Conversation conversation = (Conversation) message.getConversation();
Account account = conversation.getAccount();
- MessagePacket packet = new MessagePacket();
+ im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
final boolean isWithSelf = conversation.getContact().isSelf();
if (conversation.getMode() == Conversation.MODE_SINGLE) {
packet.setTo(message.getCounterpart());
- packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
if (!isWithSelf) {
packet.addChild("request", "urn:xmpp:receipts");
}
} else if (message.isPrivateMessage()) {
packet.setTo(message.getCounterpart());
- packet.setType(MessagePacket.TYPE_CHAT);
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.addChild("x", "http://jabber.org/protocol/muc#user");
packet.addChild("request", "urn:xmpp:receipts");
} else {
packet.setTo(message.getCounterpart().asBareJid());
- packet.setType(MessagePacket.TYPE_GROUPCHAT);
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
}
if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
packet.addChild("markable", "urn:xmpp:chat-markers:0");
@@ -66,7 +65,7 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public void addDelay(MessagePacket packet, long timestamp) {
+ public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) {
final SimpleDateFormat mDateFormat = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -75,8 +74,8 @@ public class MessageGenerator extends AbstractGenerator {
delay.setAttribute("stamp", mDateFormat.format(date));
}
- public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
- MessagePacket packet = preparePacket(message);
+ public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
+ im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message);
if (axolotlMessage == null) {
return null;
}
@@ -89,17 +88,17 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
- MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT);
+ public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
+ im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(to);
packet.setAxolotlMessage(axolotlMessage.toElement());
packet.addChild("store", "urn:xmpp:hints");
return packet;
}
- public MessagePacket generateChat(Message message) {
- MessagePacket packet = preparePacket(message);
+ public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) {
+ im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message);
String content;
if (message.hasFileOnRemoteHost()) {
final Message.FileParams fileParams = message.getFileParams();
@@ -112,8 +111,8 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket generatePgpChat(Message message) {
- MessagePacket packet = preparePacket(message);
+ public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) {
+ final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message);
if (message.hasFileOnRemoteHost()) {
Message.FileParams fileParams = message.getFileParams();
final String url = fileParams.url;
@@ -134,10 +133,10 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket generateChatState(Conversation conversation) {
+ public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) {
final Account account = conversation.getAccount();
- MessagePacket packet = new MessagePacket();
- packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(conversation.getJid().asBareJid());
packet.setFrom(account.getJid());
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
@@ -146,11 +145,11 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket confirm(final Message message) {
+ public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) {
final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI;
final Jid to = message.getCounterpart();
- final MessagePacket packet = new MessagePacket();
- packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(groupChat ? to.asBareJid() : to);
final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
if (groupChat) {
@@ -168,18 +167,18 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket conferenceSubject(Conversation conversation, String subject) {
- MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_GROUPCHAT);
+ public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) {
+ im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
packet.setTo(conversation.getJid().asBareJid());
packet.addChild("subject").setContent(subject);
packet.setFrom(conversation.getAccount().getJid().asBareJid());
return packet;
}
- public MessagePacket directInvite(final Conversation conversation, final Jid contact) {
- MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_NORMAL);
+ public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) {
+ im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
packet.setTo(contact);
packet.setFrom(conversation.getAccount().getJid());
Element x = packet.addChild("x", "jabber:x:conference");
@@ -195,8 +194,8 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket invite(final Conversation conversation, final Jid contact) {
- final MessagePacket packet = new MessagePacket();
+ public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) {
+ final var packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setTo(conversation.getJid().asBareJid());
packet.setFrom(conversation.getAccount().getJid());
Element x = new Element("x");
@@ -208,8 +207,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket received(Account account, final Jid from, final String id, ArrayList<String> namespaces, int type) {
- final MessagePacket receivedPacket = new MessagePacket();
+ public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList<String> namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) {
+ final var receivedPacket =
+ new im.conversations.android.xmpp.model.stanza.Message();
receivedPacket.setType(type);
receivedPacket.setTo(from);
receivedPacket.setFrom(account.getJid());
@@ -220,8 +220,8 @@ public class MessageGenerator extends AbstractGenerator {
return receivedPacket;
}
- public MessagePacket received(Account account, Jid to, String id) {
- MessagePacket packet = new MessagePacket();
+ public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) {
+ im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setFrom(account.getJid());
packet.setTo(to);
packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
@@ -229,10 +229,10 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket sessionFinish(
+ public im.conversations.android.xmpp.model.stanza.Message sessionFinish(
final Jid with, final String sessionId, final Reason reason) {
- final MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT);
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(with);
final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
finish.setAttribute("id", sessionId);
@@ -242,9 +242,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
- final MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
+ public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(proposal.with);
packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
@@ -257,9 +257,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
- final MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
+ public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(proposal.with);
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
propose.setAttribute("id", proposal.sessionId);
@@ -268,9 +268,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket sessionReject(final Jid with, final String sessionId) {
- final MessagePacket packet = new MessagePacket();
- packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
+ public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) {
+ final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
+ packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(with);
final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
propose.setAttribute("id", sessionId);
@@ -9,7 +9,6 @@ import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class PresenceGenerator extends AbstractGenerator {
@@ -17,20 +16,20 @@ public class PresenceGenerator extends AbstractGenerator {
super(service);
}
- private PresencePacket subscription(String type, Contact contact) {
- PresencePacket packet = new PresencePacket();
+ private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) {
+ im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
packet.setAttribute("type", type);
packet.setTo(contact.getJid());
packet.setFrom(contact.getAccount().getJid().asBareJid());
return packet;
}
- public PresencePacket requestPresenceUpdatesFrom(final Contact contact) {
+ public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) {
return requestPresenceUpdatesFrom(contact, null);
}
- public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
- PresencePacket packet = subscription("subscribe", contact);
+ public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
+ im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact);
String displayName = contact.getAccount().getDisplayName();
if (!TextUtils.isEmpty(displayName)) {
packet.addChild("nick", Namespace.NICK).setContent(displayName);
@@ -41,24 +40,24 @@ public class PresenceGenerator extends AbstractGenerator {
return packet;
}
- public PresencePacket stopPresenceUpdatesFrom(Contact contact) {
+ public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact contact) {
return subscription("unsubscribe", contact);
}
- public PresencePacket stopPresenceUpdatesTo(Contact contact) {
+ public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo(Contact contact) {
return subscription("unsubscribed", contact);
}
- public PresencePacket sendPresenceUpdatesTo(Contact contact) {
+ public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact contact) {
return subscription("subscribed", contact);
}
- public PresencePacket selfPresence(Account account, Presence.Status status) {
+ public im.conversations.android.xmpp.model.stanza.Presence selfPresence(Account account, Presence.Status status) {
return selfPresence(account, status, true);
}
- public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal) {
- final PresencePacket packet = new PresencePacket();
+ public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal) {
+ final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
if (personal) {
final String sig = account.getPgpSignature();
final String message = account.getPresenceStatusMessage();
@@ -83,16 +82,16 @@ public class PresenceGenerator extends AbstractGenerator {
return packet;
}
- public PresencePacket leave(final MucOptions mucOptions) {
- PresencePacket presencePacket = new PresencePacket();
- presencePacket.setTo(mucOptions.getSelf().getFullJid());
- presencePacket.setFrom(mucOptions.getAccount().getJid());
- presencePacket.setAttribute("type", "unavailable");
- return presencePacket;
+ public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) {
+ im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence();
+ presence.setTo(mucOptions.getSelf().getFullJid());
+ presence.setFrom(mucOptions.getAccount().getJid());
+ presence.setAttribute("type", "unavailable");
+ return presence;
}
- public PresencePacket sendOfflinePresence(Account account) {
- PresencePacket packet = new PresencePacket();
+ public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) {
+ im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
packet.setFrom(account.getJid());
packet.setAttribute("type", "unavailable");
return packet;
@@ -43,7 +43,7 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.IqResponseException;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import okhttp3.Headers;
import okhttp3.HttpUrl;
@@ -67,9 +67,9 @@ public class SlotRequester {
private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
final SettableFuture<Slot> future = SettableFuture.create();
- final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
- service.sendIqPacket(account, request, (a, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
+ final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
+ service.sendIqPacket(account, request, (packet) -> {
+ if (packet.getType() == Iq.Type.RESULT) {
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
if (slotElement != null) {
try {
@@ -97,9 +97,9 @@ public class SlotRequester {
private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) {
final SettableFuture<Slot> future = SettableFuture.create();
- final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime);
- service.sendIqPacket(account, request, (a, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
+ final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, mime);
+ service.sendIqPacket(account, request, (packet) -> {
+ if (packet.getType() == Iq.Type.RESULT) {
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
if (slotElement != null) {
try {
@@ -16,14 +16,16 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
+import im.conversations.android.xmpp.model.stanza.Stanza;
public abstract class AbstractParser {
- protected XmppConnectionService mXmppConnectionService;
+ protected final XmppConnectionService mXmppConnectionService;
+ protected final Account account;
- protected AbstractParser(XmppConnectionService service) {
+ protected AbstractParser(final XmppConnectionService service, final Account account) {
this.mXmppConnectionService = service;
+ this.account = account;
}
public static Long parseTimestamp(Element element, Long d) {
@@ -34,8 +36,8 @@ public abstract class AbstractParser {
long min = Long.MAX_VALUE;
boolean returnDefault = true;
final Jid to;
- if (ignoreCsiAndSm && element instanceof AbstractStanza) {
- to = ((AbstractStanza) element).getTo();
+ if (ignoreCsiAndSm && element instanceof Stanza stanza) {
+ to = stanza.getTo();
} else {
to = null;
}
@@ -123,7 +125,7 @@ public abstract class AbstractParser {
contact.setLastResource(from.isBareJid() ? "" : from.getResource());
}
- protected String avatarData(Element items) {
+ protected static String avatarData(Element items) {
Element item = items.findChild("item");
if (item == null) {
return null;
@@ -26,6 +26,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -37,18 +38,17 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.forms.Data;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
-public class IqParser extends AbstractParser implements OnIqPacketReceived {
+public class IqParser extends AbstractParser implements Consumer<Iq> {
- public IqParser(final XmppConnectionService service) {
- super(service);
+ public IqParser(final XmppConnectionService service, final Account account) {
+ super(service, account);
}
- public static List<Jid> items(IqPacket packet) {
+ public static List<Jid> items(final Iq packet) {
ArrayList<Jid> items = new ArrayList<>();
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) {
@@ -65,7 +65,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return items;
}
- public static Room parseRoom(IqPacket packet) {
+ public static Room parseRoom(Iq packet) {
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
if (query == null) {
return null;
@@ -143,7 +143,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.syncRoster(account);
}
- public String avatarData(final IqPacket packet) {
+ public static String avatarData(final Iq packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
@@ -152,10 +152,10 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
if (items == null) {
return null;
}
- return super.avatarData(items);
+ return AbstractParser.avatarData(items);
}
- public Element getItem(final IqPacket packet) {
+ public static Element getItem(final Iq packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) {
return null;
@@ -168,7 +168,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
@NonNull
- public Set<Integer> deviceIds(final Element item) {
+ public static Set<Integer> deviceIds(final Element item) {
Set<Integer> deviceIds = new HashSet<>();
if (item != null) {
final Element list = item.findChild("list");
@@ -189,7 +189,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return deviceIds;
}
- private Integer signedPreKeyId(final Element bundle) {
+ private static Integer signedPreKeyId(final Element bundle) {
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if (signedPreKeyPublic == null) {
return null;
@@ -201,7 +201,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
- private ECPublicKey signedPreKeyPublic(final Element bundle) {
+ private static ECPublicKey signedPreKeyPublic(final Element bundle) {
ECPublicKey publicKey = null;
final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
if (signedPreKeyPublic == null) {
@@ -215,7 +215,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return publicKey;
}
- private byte[] signedPreKeySignature(final Element bundle) {
+ private static byte[] signedPreKeySignature(final Element bundle) {
final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
if (signedPreKeySignature == null) {
return null;
@@ -228,7 +228,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
- private IdentityKey identityKey(final Element bundle) {
+ private static IdentityKey identityKey(final Element bundle) {
final String identityKey = bundle.findChildContent("identityKey");
if (identityKey == null) {
return null;
@@ -241,7 +241,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
- public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
+ public static Map<Integer, ECPublicKey> preKeyPublics(final Iq packet) {
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
Element item = getItem(packet);
if (item == null) {
@@ -284,7 +284,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input));
}
- public Pair<X509Certificate[], byte[]> verification(final IqPacket packet) {
+ public static Pair<X509Certificate[], byte[]> verification(final Iq packet) {
Element item = getItem(packet);
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null;
@@ -312,7 +312,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
- public PreKeyBundle bundle(final IqPacket bundle) {
+ public static PreKeyBundle bundle(final Iq bundle) {
final Element bundleItem = getItem(bundle);
if (bundleItem == null) {
return null;
@@ -336,7 +336,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
}
- public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
+ public static List<PreKeyBundle> preKeys(final Iq preKeys) {
List<PreKeyBundle> bundles = new ArrayList<>();
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
if (preKeyPublics != null) {
@@ -351,15 +351,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
@Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- final boolean isGet = packet.getType() == IqPacket.TYPE.GET;
- if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ public void accept(final Iq packet) {
+ final boolean isGet = packet.getType() == Iq.Type.GET;
+ if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) {
return;
}
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
final Element query = packet.findChild("query");
// If this is in response to a query for the whole roster:
- if (packet.getType() == IqPacket.TYPE.RESULT) {
+ if (packet.getType() == Iq.Type.RESULT) {
account.getRoster().markAllAsNotInRoster();
}
this.rosterItems(account, query);
@@ -373,7 +373,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
(block != null ? block.getChildren() : null);
// If this is a response to a blocklist query, clear the block list and replace with the new one.
// Otherwise, just update the existing blocklist.
- if (packet.getType() == IqPacket.TYPE.RESULT) {
+ if (packet.getType() == Iq.Type.RESULT) {
account.clearBlocklist();
account.getXmppConnection().getFeatures().setBlockListRequested(true);
}
@@ -389,7 +389,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
account.getBlocklist().addAll(jids);
- if (packet.getType() == IqPacket.TYPE.SET) {
+ if (packet.getType() == Iq.Type.SET) {
boolean removed = false;
for (Jid jid : jids) {
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
@@ -401,15 +401,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
// Update the UI
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
- if (packet.getType() == IqPacket.TYPE.SET) {
- final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
+ if (packet.getType() == Iq.Type.SET) {
+ final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
}
} else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
- packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
+ packet.fromServer(account) && packet.getType() == Iq.Type.SET) {
Log.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
- if (items.size() == 0) {
+ if (items.isEmpty()) {
// No children to unblock == unblock all
account.getBlocklist().clear();
} else {
@@ -425,7 +425,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
account.getBlocklist().removeAll(jids);
}
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
- final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
+ final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
@@ -433,18 +433,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
- final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
+ final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
- final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
+ final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
- final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
+ final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
- final IqPacket response;
+ final Iq response;
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
- response = packet.generateResponse(IqPacket.TYPE.ERROR);
+ response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
@@ -452,18 +452,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
}
mXmppConnectionService.sendIqPacket(account, response, null);
- } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == IqPacket.TYPE.SET) {
+ } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) {
final Jid transport = packet.getFrom();
final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH);
final boolean success =
push != null
&& mXmppConnectionService.processUnifiedPushMessage(
account, transport, push);
- final IqPacket response;
+ final Iq response;
if (success) {
- response = packet.generateResponse(IqPacket.TYPE.RESULT);
+ response = packet.generateResponse(Iq.Type.RESULT);
} else {
- response = packet.generateResponse(IqPacket.TYPE.ERROR);
+ response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.setAttribute("code", "404");
@@ -471,8 +471,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
mXmppConnectionService.sendIqPacket(account, response, null);
} else {
- if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
- final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
+ if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) {
+ final Iq response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", "cancel");
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");
@@ -15,6 +15,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.function.Consumer;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
@@ -49,17 +50,20 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.pep.Avatar;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.carbons.Received;
+import im.conversations.android.xmpp.model.carbons.Sent;
+import im.conversations.android.xmpp.model.forward.Forwarded;
-public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
+public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
- public MessageParser(XmppConnectionService service) {
- super(service);
+ public MessageParser(final XmppConnectionService service, final Account account) {
+ super(service, account);
}
private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
@@ -98,7 +102,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return result != null ? result : fallback;
}
- private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
+ private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) {
ChatState state = ChatState.parse(packet);
if (state != null && c != null) {
final Account account = c.getAccount();
@@ -240,7 +244,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
Element item = items.findChild("item");
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ final Set<Integer> deviceIds = IqParser.deviceIds(item);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
final AxolotlService axolotlService = account.getAxolotlService();
axolotlService.registerDevices(from, deviceIds);
@@ -347,10 +351,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.updateAccountUi();
}
- private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
- if (packet.getType() == MessagePacket.TYPE_ERROR) {
+ private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
+ if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
if (packet.fromServer(account)) {
- final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
+ final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS);
if (forwarded != null) {
return handleErrorMessage(account, forwarded.first);
}
@@ -393,11 +397,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
@Override
- public void onMessagePacketReceived(Account account, MessagePacket original) {
+ public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
if (handleErrorMessage(account, original)) {
return;
}
- final MessagePacket packet;
+ final im.conversations.android.xmpp.model.stanza.Message packet;
Long timestamp = null;
boolean isCarbon = false;
String serverMsgId = null;
@@ -411,7 +415,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
if (query != null && query.validFrom(original.getFrom())) {
- final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
+ final var f = getForwardedMessagePacket(original,"result", query.version.namespace);
if (f == null) {
return;
}
@@ -426,9 +430,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
return;
} else if (original.fromServer(account)) {
- Pair<MessagePacket, Long> f;
- f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
- f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
+ Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
+ f = getForwardedMessagePacket(original, Received.class);
+ f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
packet = f != null ? f.first : original;
if (handleErrorMessage(account, packet)) {
return;
@@ -468,7 +472,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return;
}
- boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
+ boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
if (query != null && !query.muc() && isTypeGroupChat) {
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
return;
@@ -1106,6 +1110,34 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
+ private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class<? extends Extension> clazz) {
+ final var extension = original.getExtension(clazz);
+ final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class);
+ if (forwarded == null) {
+ return null;
+ }
+ final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
+ final var forwardedMessage = forwarded.getMessage();
+ if (forwardedMessage == null) {
+ return null;
+ }
+ return new Pair<>(forwardedMessage,timestamp);
+ }
+
+ private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) {
+ final Element wrapper = original.findChild(name, namespace);
+ final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD);
+ if (forwardedElement instanceof Forwarded forwarded) {
+ final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
+ final var forwardedMessage = forwarded.getMessage();
+ if (forwardedMessage == null) {
+ return null;
+ }
+ return new Pair<>(forwardedMessage,timestamp);
+ }
+ return null;
+ }
+
private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
if (conversation != null && (query == null || query.isCatchup())) {
@@ -1118,7 +1150,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
- private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) {
+ private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) {
final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
if (query == null) {
@@ -1130,7 +1162,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
receiptsNamespaces.add("urn:xmpp:receipts");
}
if (receiptsNamespaces.size() > 0) {
- final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
+ final var receipt = mXmppConnectionService.getMessageGenerator().received(account,
packet.getFrom(),
remoteMsgId,
receiptsNamespaces,
@@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.pep.Avatar;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
-public class PresenceParser extends AbstractParser implements OnPresencePacketReceived {
+public class PresenceParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> {
- public PresenceParser(XmppConnectionService service) {
- super(service);
+ public PresenceParser(final XmppConnectionService service, final Account account) {
+ super(service, account);
}
- public void parseConferencePresence(PresencePacket packet, Account account) {
+ public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) {
final Conversation conversation =
packet.getFrom() == null
? null
@@ -58,7 +57,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
}
}
- private void processConferencePresence(PresencePacket packet, Conversation conversation) {
+ private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) {
final Account account = conversation.getAccount();
final MucOptions mucOptions = conversation.getMucOptions();
final Jid jid = conversation.getAccount().getJid();
@@ -297,7 +296,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
return codes;
}
- private void parseContactPresence(final PresencePacket packet, final Account account) {
+ private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) {
final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
final Jid from = packet.getFrom();
if (from == null || from.equals(account.getJid())) {
@@ -431,7 +430,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
}
@Override
- public void onPresencePacketReceived(Account account, PresencePacket packet) {
+ public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
if (packet.hasChild("x", Namespace.MUC_USER)) {
this.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
@@ -20,10 +20,9 @@ import eu.siacs.conversations.http.services.MuclumbusService;
import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.utils.TLSSocketFactory;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.XmppConnection;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
@@ -202,7 +201,7 @@ public class ChannelDiscoveryService {
final String query, final OnChannelSearchResultsFound listener) {
final Map<Jid, Account> localMucService = getLocalMucServices();
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
- if (localMucService.size() == 0) {
+ if (localMucService.isEmpty()) {
listener.onChannelSearchResultsFound(Collections.emptyList());
return;
}
@@ -216,39 +215,36 @@ public class ChannelDiscoveryService {
}
final AtomicInteger queriesInFlight = new AtomicInteger();
final List<Room> rooms = new ArrayList<>();
- for (Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
- IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
+ for (final Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
+ Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
queriesInFlight.incrementAndGet();
+ final var account = entry.getValue();
service.sendIqPacket(
- entry.getValue(),
+ account,
itemsRequest,
- (account, itemsResponse) -> {
- if (itemsResponse.getType() == IqPacket.TYPE.RESULT) {
+ (itemsResponse) -> {
+ if (itemsResponse.getType() == Iq.Type.RESULT) {
final List<Jid> items = IqParser.items(itemsResponse);
- for (Jid item : items) {
- IqPacket infoRequest =
+ for (final Jid item : items) {
+ final Iq infoRequest =
service.getIqGenerator().queryDiscoInfo(item);
queriesInFlight.incrementAndGet();
service.sendIqPacket(
account,
infoRequest,
- new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(
- Account account, IqPacket infoResponse) {
- if (infoResponse.getType()
- == IqPacket.TYPE.RESULT) {
- final Room room =
- IqParser.parseRoom(infoResponse);
- if (room != null) {
- rooms.add(room);
- }
- if (queriesInFlight.decrementAndGet() <= 0) {
- finishDiscoSearch(rooms, query, listener);
- }
- } else {
- queriesInFlight.decrementAndGet();
+ infoResponse -> {
+ if (infoResponse.getType()
+ == Iq.Type.RESULT) {
+ final Room room =
+ IqParser.parseRoom(infoResponse);
+ if (room != null) {
+ rooms.add(room);
}
+ if (queriesInFlight.decrementAndGet() <= 0) {
+ finishDiscoSearch(rooms, query, listener);
+ }
+ } else {
+ queriesInFlight.decrementAndGet();
}
});
}
@@ -23,8 +23,8 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
import eu.siacs.conversations.xmpp.mam.MamReference;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.stanza.Message;
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
@@ -81,7 +81,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return false;
}
- public static Element findResult(MessagePacket packet) {
+ public static Element findResult(Message packet) {
for (Version version : values()) {
Element result = packet.findChild("result", version.namespace);
if (result != null) {
@@ -234,17 +234,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
throw new IllegalStateException("Attempted to run MAM query for archived conversation");
}
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
- final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
- this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> {
+ final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
+ this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> {
final Element fin = p.findChild("fin", query.version.namespace);
- if (p.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (p.getType() == Iq.Type.TIMEOUT) {
synchronized (this.queries) {
this.queries.remove(query);
if (query.hasCallback()) {
query.callback(false);
}
}
- } else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) {
+ } else if (p.getType() == Iq.Type.RESULT && fin != null) {
final boolean running;
synchronized (this.queries) {
running = this.queries.contains(query);
@@ -254,10 +254,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed");
}
- } else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) {
+ } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) {
//do nothing
} else {
- Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
+ Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
try {
finalizeQuery(query, true);
} catch (final IllegalStateException e) {
@@ -304,7 +304,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
- boolean inCatchup(Account account) {
+ public boolean inCatchup(Account account) {
synchronized (this.queries) {
for (Query query : queries) {
if (query.account == account && query.isCatchup() && query.getWith() == null) {
@@ -32,8 +32,9 @@ import eu.siacs.conversations.receiver.UnifiedPushDistributor;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.stanza.Presence;
+
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.List;
@@ -82,7 +83,7 @@ public class UnifiedPushBroker {
}
private void sendDirectedPresence(final Account account, Jid to) {
- final PresencePacket presence = new PresencePacket();
+ final var presence = new Presence();
presence.setTo(to);
service.sendPresencePacket(account, presence);
}
@@ -146,7 +147,7 @@ public class UnifiedPushBroker {
UnifiedPushDistributor.hash(account.getUuid(), renewal.application);
final String hashedInstance =
UnifiedPushDistributor.hash(account.getUuid(), renewal.instance);
- final IqPacket registration = new IqPacket(IqPacket.TYPE.SET);
+ final Iq registration = new Iq(Iq.Type.SET);
registration.setTo(transport.transport);
final Element register = registration.addChild("register", Namespace.UNIFIED_PUSH);
register.setAttribute("application", hashedApplication);
@@ -160,7 +161,7 @@ public class UnifiedPushBroker {
this.service.sendIqPacket(
account,
registration,
- (a, response) -> processRegistration(transport, renewal, messenger, response));
+ (response) -> processRegistration(transport, renewal, messenger, response));
}
}
@@ -168,8 +169,8 @@ public class UnifiedPushBroker {
final Transport transport,
final UnifiedPushDatabase.PushTarget renewal,
final Messenger messenger,
- final IqPacket response) {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Iq response) {
+ if (response.getType() == Iq.Type.RESULT) {
final Element registered = response.findChild("registered", Namespace.UNIFIED_PUSH);
if (registered == null) {
return;
@@ -52,7 +52,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.RemoteInput;
import androidx.core.content.ContextCompat;
-import androidx.core.util.Consumer;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
@@ -92,6 +91,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
@@ -123,8 +123,6 @@ import eu.siacs.conversations.generator.PresenceGenerator;
import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.parser.AbstractParser;
import eu.siacs.conversations.parser.IqParser;
-import eu.siacs.conversations.parser.MessageParser;
-import eu.siacs.conversations.parser.PresenceParser;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
@@ -141,7 +139,6 @@ import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.ConversationsFileObserver;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.EasyOnboardingInvite;
-import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.utils.QuickLoader;
@@ -160,11 +157,8 @@ import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnBindListener;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
-import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
-import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
@@ -178,9 +172,7 @@ import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
import eu.siacs.conversations.xmpp.mam.MamReference;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.pep.PublishOptions;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import me.leolin.shortcutbadger.ShortcutBadger;
public class XmppConnectionService extends Service {
@@ -225,12 +217,12 @@ public class XmppConnectionService extends Service {
private final Set<String> mInProgressAvatarFetches = new HashSet<>();
private final Set<String> mOmittedPepAvatarFetches = new HashSet<>();
private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
- private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> {
- if (packet.getType() != IqPacket.TYPE.RESULT) {
- Element error = packet.findChild("error");
+ private final Consumer<Iq> mDefaultIqHandler = (packet) -> {
+ if (packet.getType() != Iq.Type.RESULT) {
+ final var error = packet.getError();
String text = error != null ? error.findChildContent("text") : null;
if (text != null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received iq error - " + text);
+ Log.d(Config.LOGTAG, "received iq error: " + text);
}
}
};
@@ -247,9 +239,6 @@ public class XmppConnectionService extends Service {
private final AtomicBoolean mOngoingVideoTranscoding = new AtomicBoolean(false);
private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false);
private final AtomicReference<OngoingCall> ongoingCall = new AtomicReference<>();
- private final OnMessagePacketReceived mMessageParser = new MessageParser(this);
- private final OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
- private final IqParser mIqParser = new IqParser(this);
private final MessageGenerator mMessageGenerator = new MessageGenerator(this);
public OnContactStatusChanged onContactStatusChanged = (contact, online) -> {
Conversation conversation = find(getConversations(), contact);
@@ -330,79 +319,6 @@ public class XmppConnectionService extends Service {
public final Set<String> FILENAMES_TO_IGNORE_DELETION = new HashSet<>();
- private final OnBindListener mOnBindListener = new OnBindListener() {
-
- @Override
- public void onBind(final Account account) {
- synchronized (mInProgressAvatarFetches) {
- for (Iterator<String> iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) {
- final String KEY = iterator.next();
- if (KEY.startsWith(account.getJid().asBareJid() + "_")) {
- iterator.remove();
- }
- }
- }
- boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true);
- boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, account.getXmppConnection().getFeatures().httpUpload(0));
- if (loggedInSuccessfully || gainedFeature) {
- databaseBackend.updateAccount(account);
- }
-
- if (loggedInSuccessfully) {
- if (!TextUtils.isEmpty(account.getDisplayName())) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing");
- publishDisplayName(account);
- }
- }
-
- account.getRoster().clearPresences();
- synchronized (account.inProgressConferenceJoins) {
- account.inProgressConferenceJoins.clear();
- }
- synchronized (account.inProgressConferencePings) {
- account.inProgressConferencePings.clear();
- }
- mJingleConnectionManager.notifyRebound(account);
- mQuickConversationsService.considerSyncBackground(false);
- fetchRosterFromServer(account);
-
- final XmppConnection connection = account.getXmppConnection();
-
- if (connection.getFeatures().bookmarks2()) {
- fetchBookmarks2(account);
- } else if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
- fetchBookmarks(account);
- }
-
- if (connection.getFeatures().mds()) {
- fetchMessageDisplayedSynchronization(account);
- } else {
- Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds");
- }
- final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
- final boolean catchup = getMessageArchiveService().inCatchup(account);
- final boolean trackOfflineMessageRetrieval;
- if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) {
- trackOfflineMessageRetrieval = false;
- sendIqPacket(account, mIqGenerator.purgeOfflineMessages(), (acc, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, acc.getJid().asBareJid() + ": successfully purged offline messages");
- }
- });
- } else {
- trackOfflineMessageRetrieval = true;
- }
- sendPresence(account);
- account.getXmppConnection().trackOfflineMessageRetrieval(trackOfflineMessageRetrieval);
- if (mPushManagementService.available(account)) {
- mPushManagementService.registerPushTokenOnServer(account);
- }
- connectMultiModeConversations(account);
- syncDirtyContacts(account);
-
- unifiedPushBroker.renewUnifiedPushEndpointsOnBind(account);
- }
- };
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
private final LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
@@ -1636,12 +1552,8 @@ public class XmppConnectionService extends Service {
public XmppConnection createConnection(final Account account) {
final XmppConnection connection = new XmppConnection(account, this);
- connection.setOnMessagePacketReceivedListener(this.mMessageParser);
connection.setOnStatusChangedListener(this.statusListener);
- connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
- connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
- connection.setOnBindListener(this.mOnBindListener);
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService);
@@ -1654,7 +1566,7 @@ public class XmppConnectionService extends Service {
public void sendChatState(Conversation conversation) {
if (sendChatStates()) {
- MessagePacket packet = mMessageGenerator.generateChatState(conversation);
+ final var packet = mMessageGenerator.generateChatState(conversation);
sendMessagePacket(conversation.getAccount(), packet);
}
}
@@ -1692,7 +1604,7 @@ public class XmppConnectionService extends Service {
}
}
- MessagePacket packet = null;
+ im.conversations.android.xmpp.model.stanza.Message packet = null;
final boolean addToConversation = !message.edited();
boolean saveInDb = addToConversation;
message.setStatus(Message.STATUS_WAITING);
@@ -1866,13 +1778,13 @@ public class XmppConnectionService extends Service {
callback.inviteRequestFailed(getString(R.string.server_does_not_support_easy_onboarding_invites));
return;
}
- final IqPacket request = new IqPacket(IqPacket.TYPE.SET);
+ final Iq request = new Iq(Iq.Type.SET);
request.setTo(jid);
final Element command = request.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", Namespace.EASY_ONBOARDING_INVITE);
command.setAttribute("action", "execute");
- sendIqPacket(account, request, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ sendIqPacket(account, request, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
final Element resultCommand = response.findChild("command", Namespace.COMMANDS);
final Element x = resultCommand == null ? null : resultCommand.findChild("x", Namespace.DATA);
if (x != null) {
@@ -1887,7 +1799,7 @@ public class XmppConnectionService extends Service {
}
callback.inviteRequestFailed(getString(R.string.unable_to_parse_invite));
Log.d(Config.LOGTAG, response.toString());
- } else if (response.getType() == IqPacket.TYPE.ERROR) {
+ } else if (response.getType() == Iq.Type.ERROR) {
callback.inviteRequestFailed(IqParser.errorMessage(response));
} else {
callback.inviteRequestFailed(getString(R.string.remote_server_timeout));
@@ -1896,54 +1808,42 @@ public class XmppConnectionService extends Service {
}
- public void fetchRosterFromServer(final Account account) {
- final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
- if (!"".equals(account.getRosterVersion())) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid()
- + ": fetching roster version " + account.getRosterVersion());
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster");
- }
- iqPacket.query(Namespace.ROSTER).setAttribute("ver", account.getRosterVersion());
- sendIqPacket(account, iqPacket, mIqParser);
- }
-
public void fetchBookmarks(final Account account) {
- final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
+ final Iq iqPacket = new Iq(Iq.Type.GET);
final Element query = iqPacket.query("jabber:iq:private");
query.addChild("storage", Namespace.BOOKMARKS);
- final OnIqPacketReceived callback = (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Consumer<Iq> callback = (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
final Element query1 = response.query();
final Element storage = query1.findChild("storage", "storage:bookmarks");
Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
- processBookmarksInitial(a, bookmarks, false);
+ processBookmarksInitial(account, bookmarks, false);
} else {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fetch bookmarks");
}
};
sendIqPacket(account, iqPacket, callback);
}
public void fetchBookmarks2(final Account account) {
- final IqPacket retrieve = mIqGenerator.retrieveBookmarks();
- sendIqPacket(account, retrieve, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Iq retrieve = mIqGenerator.retrieveBookmarks();
+ sendIqPacket(account, retrieve, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
- final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromPubsub(pubsub, a);
- processBookmarksInitial(a, bookmarks, true);
+ final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromPubsub(pubsub, account);
+ processBookmarksInitial(account, bookmarks, true);
}
});
}
- private void fetchMessageDisplayedSynchronization(final Account account) {
+ public void fetchMessageDisplayedSynchronization(final Account account) {
Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds");
final var retrieve = mIqGenerator.retrieveMds();
sendIqPacket(
account,
retrieve,
- (a, response) -> {
- if (response.getType() != IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() != Iq.Type.RESULT) {
return;
}
final var pubSub = response.findChild("pubsub", Namespace.PUBSUB);
@@ -2096,11 +1996,11 @@ public class XmppConnectionService extends Service {
account.removeBookmark(bookmark);
final XmppConnection connection = account.getXmppConnection();
if (connection.getFeatures().bookmarks2()) {
- final IqPacket request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString());
+ final Iq request = mIqGenerator.deleteItem(Namespace.BOOKMARKS2, bookmark.getJid().asBareJid().toEscapedString());
Log.d(Config.LOGTAG,account.getJid().asBareJid() + ": removing bookmark via Bookmarks 2");
- sendIqPacket(account, request, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.ERROR) {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition());
+ sendIqPacket(account, request, (response) -> {
+ if (response.getType() == Iq.Type.ERROR) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to delete bookmark " + response.getErrorCondition());
}
});
} else if (connection.getFeatures().bookmarksConversion()) {
@@ -2112,7 +2012,7 @@ public class XmppConnectionService extends Service {
private void pushBookmarksPrivateXml(Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
- IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
+ final Iq iqPacket = new Iq(Iq.Type.SET);
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
for (final Bookmark bookmark : account.getBookmarks()) {
@@ -2137,9 +2037,9 @@ public class XmppConnectionService extends Service {
}
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options, final boolean retry) {
- final IqPacket packet = mIqGenerator.publishElement(node, element, id, options);
- sendIqPacket(account, packet, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Iq packet = mIqGenerator.publishElement(node, element, id, options);
+ sendIqPacket(account, packet, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
return;
}
if (retry && PublishOptions.preconditionNotMet(response)) {
@@ -2610,6 +2510,10 @@ public class XmppConnectionService extends Service {
return this.unifiedPushBroker.renewUnifiedPushEndpoints(null);
}
+ public UnifiedPushBroker getUnifiedPushBroker() {
+ return this.unifiedPushBroker;
+ }
+
private void provisionAccount(final String address, final String password) {
final Jid jid = Jid.ofEscaped(address);
final Account account = new Account(jid, password);
@@ -2708,12 +2612,12 @@ public class XmppConnectionService extends Service {
}
public void updateAccountPasswordOnServer(final Account account, final String newPassword, final OnAccountPasswordChanged callback) {
- final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword);
- sendIqPacket(account, iq, (a, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- a.setPassword(newPassword);
- a.setOption(Account.OPTION_MAGIC_CREATE, false);
- databaseBackend.updateAccount(a);
+ final Iq iq = getIqGenerator().generateSetPassword(account, newPassword);
+ sendIqPacket(account, iq, (packet) -> {
+ if (packet.getType() == Iq.Type.RESULT) {
+ account.setPassword(newPassword);
+ account.setOption(Account.OPTION_MAGIC_CREATE, false);
+ databaseBackend.updateAccount(account);
callback.onPasswordChangeSucceeded();
} else {
callback.onPasswordChangeFailed();
@@ -2722,12 +2626,12 @@ public class XmppConnectionService extends Service {
}
public void unregisterAccount(final Account account, final Consumer<Boolean> callback) {
- final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
+ final Iq iqPacket = new Iq(Iq.Type.SET);
final Element query = iqPacket.addChild("query",Namespace.REGISTER);
query.addChild("remove");
- sendIqPacket(account, iqPacket, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
- deleteAccount(a);
+ sendIqPacket(account, iqPacket, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
+ deleteAccount(account);
callback.accept(true);
} else {
callback.accept(false);
@@ -3055,7 +2959,7 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, "app switched into background");
}
- private void connectMultiModeConversations(Account account) {
+ public void connectMultiModeConversations(Account account) {
List<Conversation> conversations = getConversations();
for (Conversation conversation : conversations) {
if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) {
@@ -3079,20 +2983,20 @@ public class XmppConnectionService extends Service {
}
}
final Jid self = conversation.getMucOptions().getSelf().getFullJid();
- final IqPacket ping = new IqPacket(IqPacket.TYPE.GET);
+ final Iq ping = new Iq(Iq.Type.GET);
ping.setTo(self);
ping.addChild("ping", Namespace.PING);
- sendIqPacket(conversation.getAccount(), ping, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.ERROR) {
- Element error = response.findChild("error");
+ sendIqPacket(conversation.getAccount(), ping, (response) -> {
+ if (response.getType() == Iq.Type.ERROR) {
+ final var error = response.getError();
if (error == null || error.hasChild("service-unavailable") || error.hasChild("feature-not-implemented") || error.hasChild("item-not-found")) {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back as ignorable error");
} else {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " failed. attempting rejoin");
joinMuc(conversation);
}
- } else if (response.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": ping to " + self + " came back fine");
+ } else if (response.getType() == Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ping to " + self + " came back fine");
}
synchronized (account.inProgressConferencePings) {
account.inProgressConferencePings.remove(conversation);
@@ -3151,7 +3055,7 @@ public class XmppConnectionService extends Service {
final Jid joinJid = mucOptions.getSelf().getFullJid();
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": joining conversation " + joinJid.toString());
- PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null);
+ final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous() || onConferenceJoined != null);
packet.setTo(joinJid);
Element x = packet.addChild("x", "http://jabber.org/protocol/muc");
if (conversation.getMucOptions().getPassword() != null) {
@@ -3240,16 +3144,16 @@ public class XmppConnectionService extends Service {
final Account account = conversation.getAccount();
final AxolotlService axolotlService = account.getAxolotlService();
final String[] affiliations = {"member", "admin", "owner"};
- OnIqPacketReceived callback = new OnIqPacketReceived() {
+ final Consumer<Iq> callback = new Consumer<Iq>() {
private int i = 0;
private boolean success = true;
@Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
+ public void accept(Iq response) {
final boolean omemoEnabled = conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL;
- Element query = packet.query("http://jabber.org/protocol/muc#admin");
- if (packet.getType() == IqPacket.TYPE.RESULT && query != null) {
+ Element query = response.query("http://jabber.org/protocol/muc#admin");
+ if (response.getType() == Iq.Type.RESULT && query != null) {
for (Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
MucOptions.User user = AbstractParser.parseItem(conversation, child);
@@ -3335,29 +3239,29 @@ public class XmppConnectionService extends Service {
}
private void deletePepNode(final Account account, final String node, final Runnable runnable) {
- final IqPacket request = mIqGenerator.deleteNode(node);
- sendIqPacket(account, request, (a, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG,a.getJid().asBareJid()+": successfully deleted pep node "+node);
+ final Iq request = mIqGenerator.deleteNode(node);
+ sendIqPacket(account, request, (packet) -> {
+ if (packet.getType() == Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted pep node "+node);
if (runnable != null) {
runnable.run();
}
} else {
- Log.d(Config.LOGTAG,a.getJid().asBareJid()+": failed to delete "+ packet);
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": failed to delete "+ packet);
}
});
}
private void deleteVcardAvatar(final Account account, @NonNull final Runnable runnable) {
- final IqPacket retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid());
- sendIqPacket(account, retrieveVcard, (a, response) -> {
- if (response.getType() != IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do");
+ final Iq retrieveVcard = mIqGenerator.retrieveVcardAvatar(account.getJid().asBareJid());
+ sendIqPacket(account, retrieveVcard, (response) -> {
+ if (response.getType() != Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do");
return;
}
final Element vcard = response.findChild("vCard", "vcard-temp");
if (vcard == null) {
- Log.d(Config.LOGTAG,a.getJid().asBareJid()+": no vCard set. nothing to do");
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": no vCard set. nothing to do");
return;
}
Element photo = vcard.findChild("PHOTO");
@@ -3365,12 +3269,12 @@ public class XmppConnectionService extends Service {
photo = vcard.addChild("PHOTO");
}
photo.clearChildren();
- IqPacket publication = new IqPacket(IqPacket.TYPE.SET);
- publication.setTo(a.getJid().asBareJid());
+ final Iq publication = new Iq(Iq.Type.SET);
+ publication.setTo(account.getJid().asBareJid());
publication.addChild(vcard);
- sendIqPacket(account, publication, (a1, publicationResponse) -> {
- if (publicationResponse.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG,a1.getJid().asBareJid()+": successfully deleted vcard avatar");
+ sendIqPacket(account, publication, (publicationResponse) -> {
+ if (publicationResponse.getType() == Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": successfully deleted vcard avatar");
runnable.run();
} else {
Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition());
@@ -3450,7 +3354,7 @@ public class XmppConnectionService extends Service {
}
});
- final PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous());
+ final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, options.nonanonymous());
packet.setTo(joinJid);
sendPresencePacket(account, packet);
} else {
@@ -3610,39 +3514,37 @@ public class XmppConnectionService extends Service {
}
public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) {
- IqPacket request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid());
- sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- final MucOptions mucOptions = conversation.getMucOptions();
- final Bookmark bookmark = conversation.getBookmark();
- final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName());
+ final Iq request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid());
+ final var account = conversation.getAccount();
+ sendIqPacket(account, request, response -> {
+ if (response.getType() == Iq.Type.RESULT) {
+ final MucOptions mucOptions = conversation.getMucOptions();
+ final Bookmark bookmark = conversation.getBookmark();
+ final boolean sameBefore = StringUtils.equals(bookmark == null ? null : bookmark.getBookmarkName(), mucOptions.getName());
- if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(packet))) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid());
- updateConversation(conversation);
- }
+ if (mucOptions.updateConfiguration(new ServiceDiscoveryResult(response))) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": muc configuration changed for " + conversation.getJid().asBareJid());
+ updateConversation(conversation);
+ }
- if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) {
- if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) {
- createBookmark(account, bookmark);
- }
+ if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) {
+ if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) {
+ createBookmark(account, bookmark);
}
+ }
- if (callback != null) {
- callback.onConferenceConfigurationFetched(conversation);
- }
+ if (callback != null) {
+ callback.onConferenceConfigurationFetched(conversation);
+ }
- updateConversationUi();
- } else if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch");
- } else {
- if (callback != null) {
- callback.onFetchFailed(conversation, packet.getErrorCondition());
- }
+ updateConversationUi();
+ } else if (response.getType() == Iq.Type.TIMEOUT) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received timeout waiting for conference configuration fetch");
+ } else {
+ if (callback != null) {
+ callback.onFetchFailed(conversation, response.getErrorCondition());
}
}
});
@@ -3654,33 +3556,27 @@ public class XmppConnectionService extends Service {
public void pushNodeConfiguration(Account account, final Jid jid, final String node, final Bundle options, final OnConfigurationPushed callback) {
Log.d(Config.LOGTAG, "pushing node configuration");
- sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
- Element configuration = pubsub == null ? null : pubsub.findChild("configure");
- Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA);
- if (x != null) {
- final Data data = Data.parse(x);
- data.submit(options);
- sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT && callback != null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node);
- callback.onPushSucceeded();
- } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) {
- callback.onPushFailed();
- }
- }
- });
- } else if (callback != null) {
- callback.onPushFailed();
- }
- } else if (packet.getType() == IqPacket.TYPE.ERROR && callback != null) {
+ sendIqPacket(account, mIqGenerator.requestPubsubConfiguration(jid, node), responseToRequest -> {
+ if (responseToRequest.getType() == Iq.Type.RESULT) {
+ Element pubsub = responseToRequest.findChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
+ Element configuration = pubsub == null ? null : pubsub.findChild("configure");
+ Element x = configuration == null ? null : configuration.findChild("x", Namespace.DATA);
+ if (x != null) {
+ final Data data = Data.parse(x);
+ data.submit(options);
+ sendIqPacket(account, mIqGenerator.publishPubsubConfiguration(jid, node, data), responseToPublish -> {
+ if (responseToPublish.getType() == Iq.Type.RESULT && callback != null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully changed node configuration for node " + node);
+ callback.onPushSucceeded();
+ } else if (responseToPublish.getType() == Iq.Type.ERROR && callback != null) {
+ callback.onPushFailed();
+ }
+ });
+ } else if (callback != null) {
callback.onPushFailed();
}
+ } else if (responseToRequest.getType() == Iq.Type.ERROR && callback != null) {
+ callback.onPushFailed();
}
});
}
@@ -3700,50 +3596,45 @@ public class XmppConnectionService extends Service {
options.putString("allow_private_messages", allow ? "1" : "0");
options.putString("allow_private_messages_from_visitors", allow ? "anyone" : "nobody");
}
- final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+ final var account = conversation.getAccount();
+ final Iq request = new Iq(Iq.Type.GET);
request.setTo(conversation.getJid().asBareJid());
request.query("http://jabber.org/protocol/muc#owner");
- sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- final Data data = Data.parse(packet.query().findChild("x", Namespace.DATA));
- data.submit(options);
- final IqPacket set = new IqPacket(IqPacket.TYPE.SET);
- set.setTo(conversation.getJid().asBareJid());
- set.query("http://jabber.org/protocol/muc#owner").addChild(data);
- sendIqPacket(account, set, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (callback != null) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- callback.onPushSucceeded();
- } else {
- Log.d(Config.LOGTAG,"failed: "+packet.toString());
- callback.onPushFailed();
- }
- }
- }
- });
- } else {
+ sendIqPacket(account, request, response -> {
+ if (response.getType() == Iq.Type.RESULT) {
+ final Data data = Data.parse(response.query().findChild("x", Namespace.DATA));
+ data.submit(options);
+ final Iq set = new Iq(Iq.Type.SET);
+ set.setTo(conversation.getJid().asBareJid());
+ set.query("http://jabber.org/protocol/muc#owner").addChild(data);
+ sendIqPacket(account, set, packet -> {
if (callback != null) {
- callback.onPushFailed();
+ if (packet.getType() == Iq.Type.RESULT) {
+ callback.onPushSucceeded();
+ } else {
+ Log.d(Config.LOGTAG,"failed: "+packet.toString());
+ callback.onPushFailed();
+ }
}
+ });
+ } else {
+ if (callback != null) {
+ callback.onPushFailed();
}
}
});
}
public void pushSubjectToConference(final Conversation conference, final String subject) {
- MessagePacket packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject));
+ final var packet = this.getMessageGenerator().conferenceSubject(conference, StringUtils.nullOnEmpty(subject));
this.sendMessagePacket(conference.getAccount(), packet);
}
public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
final Jid jid = user.asBareJid();
- final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
- sendIqPacket(conference.getAccount(), request, (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Iq request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
+ sendIqPacket(conference.getAccount(), request, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
conference.getMucOptions().changeAffiliation(jid, affiliation);
getAvatarService().clear(conference);
if (callback != null) {
@@ -3760,29 +3651,27 @@ public class XmppConnectionService extends Service {
}
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) {
- IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
- sendIqPacket(conference.getAccount(), request, (account, packet) -> {
- if (packet.getType() != IqPacket.TYPE.RESULT) {
+ final var account =conference.getAccount();
+ final Iq request = this.mIqGenerator.changeRole(conference, nick, role.toString());
+ sendIqPacket(account, request, (packet) -> {
+ if (packet.getType() != Iq.Type.RESULT) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick);
}
});
}
public void destroyRoom(final Conversation conversation, final OnRoomDestroy callback) {
- IqPacket request = new IqPacket(IqPacket.TYPE.SET);
+ final Iq request = new Iq(Iq.Type.SET);
request.setTo(conversation.getJid().asBareJid());
request.query("http://jabber.org/protocol/muc#owner").addChild("destroy");
- sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- if (callback != null) {
- callback.onRoomDestroySucceeded();
- }
- } else if (packet.getType() == IqPacket.TYPE.ERROR) {
- if (callback != null) {
- callback.onRoomDestroyFailed();
- }
+ sendIqPacket(conversation.getAccount(), request, response -> {
+ if (response.getType() == Iq.Type.RESULT) {
+ if (callback != null) {
+ callback.onRoomDestroySucceeded();
+ }
+ } else if (response.getType() == Iq.Type.ERROR) {
+ if (callback != null) {
+ callback.onRoomDestroyFailed();
}
}
});
@@ -3832,7 +3721,7 @@ public class XmppConnectionService extends Service {
updateConversationUi();
}
- protected void syncDirtyContacts(Account account) {
+ public void syncDirtyContacts(Account account) {
for (Contact contact : account.getRoster().getContacts()) {
if (contact.getOption(Contact.Options.DIRTY_PUSH)) {
pushContactToServer(contact);
@@ -3868,7 +3757,7 @@ public class XmppConnectionService extends Service {
final boolean sendUpdates = contact
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)
&& contact.getOption(Contact.Options.PREEMPTIVE_GRANT);
- final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ final Iq iq = new Iq(Iq.Type.SET);
iq.query(Namespace.ROSTER).addChild(contact.asElement());
account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler);
if (sendUpdates) {
@@ -3920,10 +3809,11 @@ public class XmppConnectionService extends Service {
}
private void publishMucAvatar(Conversation conversation, Avatar avatar, OnAvatarPublication callback) {
- final IqPacket retrieve = mIqGenerator.retrieveVcardAvatar(avatar);
- sendIqPacket(conversation.getAccount(), retrieve, (account, response) -> {
- boolean itemNotFound = response.getType() == IqPacket.TYPE.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found");
- if (response.getType() == IqPacket.TYPE.RESULT || itemNotFound) {
+ final var account = conversation.getAccount();
+ final Iq retrieve = mIqGenerator.retrieveVcardAvatar(avatar);
+ sendIqPacket(account, retrieve, (response) -> {
+ boolean itemNotFound = response.getType() == Iq.Type.ERROR && response.hasChild("error") && response.findChild("error").hasChild("item-not-found");
+ if (response.getType() == Iq.Type.RESULT || itemNotFound) {
Element vcard = response.findChild("vCard", "vcard-temp");
if (vcard == null) {
vcard = new Element("vCard", "vcard-temp");
@@ -3935,11 +3825,11 @@ public class XmppConnectionService extends Service {
photo.clearChildren();
photo.addChild("TYPE").setContent(avatar.type);
photo.addChild("BINVAL").setContent(avatar.image);
- IqPacket publication = new IqPacket(IqPacket.TYPE.SET);
+ final Iq publication = new Iq(Iq.Type.SET);
publication.setTo(conversation.getJid().asBareJid());
publication.addChild(vcard);
- sendIqPacket(account, publication, (a1, publicationResponse) -> {
- if (publicationResponse.getType() == IqPacket.TYPE.RESULT) {
+ sendIqPacket(account, publication, (publicationResponse) -> {
+ if (publicationResponse.getType() == Iq.Type.RESULT) {
callback.onAvatarPublicationSucceeded();
} else {
Log.d(Config.LOGTAG, "failed to publish vcard " + publicationResponse.getErrorCondition());
@@ -3965,71 +3855,64 @@ public class XmppConnectionService extends Service {
public void publishAvatar(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": publishing avatar. options=" + options);
- IqPacket packet = this.mIqGenerator.publishAvatar(avatar, options);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- publishAvatarMetadata(account, avatar, options, true, callback);
- } else if (retry && PublishOptions.preconditionNotMet(result)) {
- pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node");
- publishAvatar(account, avatar, options, false, callback);
- }
+ final Iq packet = this.mIqGenerator.publishAvatar(avatar, options);
+ this.sendIqPacket(account, packet, result -> {
+ if (result.getType() == Iq.Type.RESULT) {
+ publishAvatarMetadata(account, avatar, options, true, callback);
+ } else if (retry && PublishOptions.preconditionNotMet(result)) {
+ pushNodeConfiguration(account, Namespace.AVATAR_DATA, options, new OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar node");
+ publishAvatar(account, avatar, options, false, callback);
+ }
- @Override
- public void onPushFailed() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node");
- publishAvatar(account, avatar, null, false, callback);
- }
- });
- } else {
- Element error = result.findChild("error");
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : ""));
- if (callback != null) {
- callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject);
+ @Override
+ public void onPushFailed() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar node");
+ publishAvatar(account, avatar, null, false, callback);
}
+ });
+ } else {
+ Element error = result.findChild("error");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : ""));
+ if (callback != null) {
+ callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject);
}
}
});
}
public void publishAvatarMetadata(Account account, final Avatar avatar, final Bundle options, final boolean retry, final OnAvatarPublication callback) {
- final IqPacket packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- if (account.setAvatar(avatar.getFilename())) {
- getAvatarService().clear(account);
- databaseBackend.updateAccount(account);
- notifyAccountAvatarHasChanged(account);
- }
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB");
- if (callback != null) {
- callback.onAvatarPublicationSucceeded();
+ final Iq packet = XmppConnectionService.this.mIqGenerator.publishAvatarMetadata(avatar, options);
+ sendIqPacket(account, packet, result -> {
+ if (result.getType() == Iq.Type.RESULT) {
+ if (account.setAvatar(avatar.getFilename())) {
+ getAvatarService().clear(account);
+ databaseBackend.updateAccount(account);
+ notifyAccountAvatarHasChanged(account);
+ }
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB");
+ if (callback != null) {
+ callback.onAvatarPublicationSucceeded();
+ }
+ } else if (retry && PublishOptions.preconditionNotMet(result)) {
+ pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node");
+ publishAvatarMetadata(account, avatar, options, false, callback);
}
- } else if (retry && PublishOptions.preconditionNotMet(result)) {
- pushNodeConfiguration(account, Namespace.AVATAR_METADATA, options, new OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": changed node configuration for avatar meta data node");
- publishAvatarMetadata(account, avatar, options, false, callback);
- }
- @Override
- public void onPushFailed() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node");
- publishAvatarMetadata(account, avatar, null, false, callback);
- }
- });
- } else {
- if (callback != null) {
- callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject);
+ @Override
+ public void onPushFailed() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to change node configuration for avatar meta data node");
+ publishAvatarMetadata(account, avatar, null, false, callback);
}
+ });
+ } else {
+ if (callback != null) {
+ callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject);
}
}
});
@@ -4040,10 +3923,10 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping republication of avatar because pep is broken");
return;
}
- IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null);
+ this.sendIqPacket(account, packet, new Consumer<Iq>() {
- private Avatar parseAvatar(IqPacket packet) {
+ private Avatar parseAvatar(Iq packet) {
Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub");
if (pubsub != null) {
Element items = pubsub.findChild("items");
@@ -4054,16 +3937,16 @@ public class XmppConnectionService extends Service {
return null;
}
- private boolean errorIsItemNotFound(IqPacket packet) {
+ private boolean errorIsItemNotFound(Iq packet) {
Element error = packet.findChild("error");
- return packet.getType() == IqPacket.TYPE.ERROR
+ return packet.getType() == Iq.Type.ERROR
&& error != null
&& error.hasChild("item-not-found");
}
@Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) {
+ public void accept(final Iq packet) {
+ if (packet.getType() == Iq.Type.RESULT || errorIsItemNotFound(packet)) {
Avatar serverAvatar = parseAvatar(packet);
if (serverAvatar == null && account.getAvatar() != null) {
Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar());
@@ -4079,6 +3962,17 @@ public class XmppConnectionService extends Service {
});
}
+ public void cancelAvatarFetches(final Account account) {
+ synchronized (mInProgressAvatarFetches) {
+ for (final Iterator<String> iterator = mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) {
+ final String KEY = iterator.next();
+ if (KEY.startsWith(account.getJid().asBareJid() + "_")) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
public void fetchAvatar(Account account, Avatar avatar) {
fetchAvatar(account, avatar, null);
}
@@ -4105,26 +3999,26 @@ public class XmppConnectionService extends Service {
}
}
- private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar);
- sendIqPacket(account, packet, (a, result) -> {
+ private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ final Iq packet = this.mIqGenerator.retrievePepAvatar(avatar);
+ sendIqPacket(account, packet, (result) -> {
synchronized (mInProgressAvatarFetches) {
- mInProgressAvatarFetches.remove(generateFetchKey(a, avatar));
+ mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
}
- final String ERROR = a.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed ";
- if (result.getType() == IqPacket.TYPE.RESULT) {
- avatar.image = mIqParser.avatarData(result);
+ final String ERROR = account.getJid().asBareJid() + ": fetching avatar for " + avatar.owner + " failed ";
+ if (result.getType() == Iq.Type.RESULT) {
+ avatar.image = IqParser.avatarData(result);
if (avatar.image != null) {
if (getFileBackend().save(avatar)) {
- if (a.getJid().asBareJid().equals(avatar.owner)) {
- if (a.setAvatar(avatar.getFilename())) {
- databaseBackend.updateAccount(a);
+ if (account.getJid().asBareJid().equals(avatar.owner)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ databaseBackend.updateAccount(account);
}
- getAvatarService().clear(a);
+ getAvatarService().clear(account);
updateConversationUi();
updateAccountUi();
} else {
- final Contact contact = a.getRoster().getContact(avatar.owner);
+ final Contact contact = account.getRoster().getContact(avatar.owner);
contact.setAvatar(avatar);
syncRoster(account);
getAvatarService().clear(contact);
@@ -494,14 +494,9 @@ public abstract class XmppActivity extends ActionBarActivity {
}
protected boolean isOptimizingBattery() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
- return pm != null
- && !pm.isIgnoringBatteryOptimizations(getPackageName());
- } else {
- return false;
- }
- }
+ final PowerManager pm = getSystemService(PowerManager.class);
+ return !pm.isIgnoringBatteryOptimizations(getPackageName());
+}
protected boolean isAffectedByDataSaver() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -3,7 +3,9 @@ package eu.siacs.conversations.xml;
import androidx.annotation.NonNull;
import com.google.common.base.Optional;
+import com.google.common.base.Strings;
import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
import java.util.ArrayList;
import java.util.Hashtable;
@@ -12,7 +14,7 @@ import java.util.List;
import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.stanza.Message;
public class Element {
private final String name;
@@ -136,6 +138,10 @@ public class Element {
return this;
}
+ public void setAttribute(final String name, final boolean value) {
+ this.setAttribute(name, value ? "1" : "0");
+ }
+
public void removeAttribute(final String name) {
this.attributes.remove(name);
}
@@ -153,6 +159,11 @@ public class Element {
}
}
+ public long getLongAttribute(final String name) {
+ final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name)));
+ return value == null ? 0 : value;
+ }
+
public Optional<Integer> getOptionalIntAttribute(final String name) {
final String value = getAttribute(name);
if (value == null) {
@@ -167,7 +178,7 @@ public class Element {
try {
return Jid.ofEscaped(jid);
} catch (final IllegalArgumentException e) {
- return InvalidJid.of(jid, this instanceof MessagePacket);
+ return InvalidJid.of(jid, this instanceof Message);
}
}
return null;
@@ -180,7 +191,7 @@ public class Element {
@NonNull
public String toString() {
final StringBuilder elementOutput = new StringBuilder();
- if ((content == null) && (children.size() == 0)) {
+ if (content == null && children.isEmpty()) {
final Tag emptyTag = Tag.empty(name);
emptyTag.setAttributes(this.attributes);
elementOutput.append(emptyTag);
@@ -37,7 +37,7 @@ public class LocalizedContent {
}
}
}
- if (contents.size() == 0) {
+ if (contents.isEmpty()) {
return null;
}
final String userLanguage = Locale.getDefault().getLanguage();
@@ -1,8 +1,29 @@
package eu.siacs.conversations.xml;
public final class Namespace {
+ public static final String ADDRESSING = "http://jabber.org/protocol/address";
+ public static final String AXOLOTL = "eu.siacs.conversations.axolotl";
+ public static final String PGP_SIGNED = "jabber:x:signed";
+ public static final String PGP_ENCRYPTED = "jabber:x:encrypted";
+ public static final String AXOLOTL_BUNDLES = AXOLOTL + ".bundles";
+ public static final String AXOLOTL_DEVICE_LIST = AXOLOTL + ".devicelist";
+ public static final String HINTS = "urn:xmpp:hints";
+ public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2";
+ public static final String VERSION = "jabber:iq:version";
+ public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0";
+ public static final String RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm";
+ public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";
+ public static final String CHAT_STATES = "http://jabber.org/protocol/chatstates";
+ public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts";
+ public static final String REACTIONS = "urn:xmpp:reactions:0";
+ public static final String VCARD_TEMP = "vcard-temp";
+ public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update";
+ public static final String DELAY = "urn:xmpp:delay";
+ public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
public static final String STREAMS = "http://etherx.jabber.org/streams";
+ public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
public static final String JABBER_CLIENT = "jabber:client";
+ public static final String FORWARD = "urn:xmpp:forward:0";
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2";
@@ -23,12 +44,15 @@ public final class Namespace {
public static final String FAST = "urn:xmpp:fast:0";
public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
+ public static final String PUBSUB_EVENT = PUBSUB + "#event";
+ public static final String MUC = "http://jabber.org/protocol/muc";
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max";
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
public static final String NICK = "http://jabber.org/protocol/nick";
- public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
+ public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL =
+ "http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
public static final String BIND2 = "urn:xmpp:bind:0";
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
@@ -38,7 +62,7 @@ public final class Namespace {
public static final String BOOKMARKS = "storage:bookmarks";
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
public static final String AVATAR_DATA = "urn:xmpp:avatar:data";
- public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata";
+ public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata";
public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
public static final String JINGLE = "urn:xmpp:jingle:1";
public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1";
@@ -48,7 +72,8 @@ public final class Namespace {
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1";
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
- public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = "urn:xmpp:jingle:transports:webrtc-datachannel:1";
+ public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL =
+ "urn:xmpp:jingle:transports:webrtc-datachannel:1";
public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1";
public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
@@ -57,9 +82,12 @@ public final class Namespace {
public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
- public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
- public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
- public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = "urn:xmpp:jingle:apps:rtp:ssma:0";
+ public static final String JINGLE_RTP_HEADER_EXTENSIONS =
+ "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
+ public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION =
+ "urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
+ public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES =
+ "urn:xmpp:jingle:apps:rtp:ssma:0";
public static final String IBB = "http://jabber.org/protocol/ibb";
public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0";
@@ -70,8 +98,10 @@ public final class Namespace {
public static final String INVITE = "urn:xmpp:invite";
public static final String PARS = "urn:xmpp:pars:0";
public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite";
- public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
- public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
+ public static final String OMEMO_DTLS_SRTP_VERIFICATION =
+ "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
+ public static final String JINGLE_TRANSPORT_ICE_OPTION =
+ "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
public static final String REPORTING = "urn:xmpp:reporting:1";
public static final String REPORTING_REASON_SPAM = "urn:xmpp:reporting:spam";
@@ -79,4 +109,7 @@ public final class Namespace {
public static final String HASHES = "urn:xmpp:hashes:2";
public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0";
public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0";
+
+ public static final String ENTITY_CAPABILITIES = "http://jabber.org/protocol/caps";
+ public static final String ENTITY_CAPABILITIES_2 = "urn:xmpp:caps";
}
@@ -10,13 +10,14 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
+import im.conversations.android.xmpp.model.StreamElement;
public class TagWriter {
private OutputStreamWriter outputStream;
private boolean finished = false;
- private final LinkedBlockingQueue<AbstractStanza> writeQueue = new LinkedBlockingQueue<AbstractStanza>();
+
+ private final LinkedBlockingQueue<StreamElement> writeQueue = new LinkedBlockingQueue<>();
private CountDownLatch stanzaWriterCountDownLatch = null;
private final Thread asyncStanzaWriter = new Thread() {
@@ -25,13 +26,13 @@ public class TagWriter {
public void run() {
stanzaWriterCountDownLatch = new CountDownLatch(1);
while (!isInterrupted()) {
- if (finished && writeQueue.size() == 0) {
+ if (finished && writeQueue.isEmpty()) {
break;
}
try {
- AbstractStanza output = writeQueue.take();
+ final var output = writeQueue.take();
outputStream.write(output.toString());
- if (writeQueue.size() == 0) {
+ if (writeQueue.isEmpty()) {
outputStream.flush();
}
} catch (Exception e) {
@@ -74,7 +75,7 @@ public class TagWriter {
}
}
- public synchronized void writeElement(Element element) throws IOException {
+ public synchronized void writeElement(final StreamElement element) throws IOException {
if (outputStream == null) {
throw new IOException("output stream was null");
}
@@ -82,7 +83,7 @@ public class TagWriter {
outputStream.flush();
}
- public void writeStanzaAsync(AbstractStanza stanza) {
+ public void writeStanzaAsync(StreamElement stanza) {
if (finished) {
Log.d(Config.LOGTAG, "attempting to write stanza to finished TagWriter");
} else {
@@ -3,6 +3,12 @@ package eu.siacs.conversations.xml;
import android.util.Log;
import android.util.Xml;
+import eu.siacs.conversations.Config;
+
+import im.conversations.android.xmpp.ExtensionFactory;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.StreamElement;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -11,8 +17,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import eu.siacs.conversations.Config;
-
public class XmlReader implements Closeable {
private final XmlPullParser parser;
private InputStream is;
@@ -87,8 +91,21 @@ public class XmlReader implements Closeable {
return null;
}
- public Element readElement(Tag currentTag) throws IOException {
- Element element = new Element(currentTag.getName());
+ public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
+ throws IOException {
+ final Element element = readElement(current);
+ if (clazz.isInstance(element)) {
+ return clazz.cast(element);
+ }
+ throw new IOException(
+ String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
+ }
+
+ public Element readElement(final Tag currentTag) throws IOException {
+ final var attributes = currentTag.getAttributes();
+ final var namespace = attributes.get("xmlns");
+ final var name = currentTag.getName();
+ final Element element = ExtensionFactory.create(name, namespace);
element.setAttributes(currentTag.getAttributes());
Tag nextTag = this.readTag();
if (nextTag == null) {
@@ -31,7 +31,7 @@ package eu.siacs.conversations.xmpp;
import androidx.annotation.NonNull;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
+import im.conversations.android.xmpp.model.stanza.Stanza;
public class InvalidJid implements Jid {
@@ -137,10 +137,10 @@ public class InvalidJid implements Jid {
}
public static boolean isValid(Jid jid) {
- return !(jid != null && jid instanceof InvalidJid);
+ return !(jid instanceof InvalidJid);
}
- public static boolean hasValidFrom(AbstractStanza stanza) {
+ public static boolean hasValidFrom(Stanza stanza) {
final String from = stanza.getAttribute("from");
if (from == null) {
return false;
@@ -1,8 +0,0 @@
-package eu.siacs.conversations.xmpp;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-
-public interface OnIqPacketReceived extends PacketReceived {
- void onIqPacketReceived(Account account, IqPacket packet);
-}
@@ -1,8 +1,7 @@
package eu.siacs.conversations.xmpp;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.stanza.Message;
-public interface OnMessagePacketReceived extends PacketReceived {
- void onMessagePacketReceived(Account account, MessagePacket packet);
+public interface OnMessagePacketReceived {
+ void onMessagePacketReceived(Message packet);
}
@@ -1,8 +0,0 @@
-package eu.siacs.conversations.xmpp;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
-
-public interface OnPresencePacketReceived extends PacketReceived {
- void onPresencePacketReceived(Account account, PresencePacket packet);
-}
@@ -1,5 +0,0 @@
-package eu.siacs.conversations.xmpp;
-
-public interface PacketReceived {
-
-}
@@ -36,6 +36,9 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.http.HttpConnectionManager;
+import eu.siacs.conversations.parser.IqParser;
+import eu.siacs.conversations.parser.MessageParser;
+import eu.siacs.conversations.parser.PresenceParser;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.MemorizingTrustManager;
import eu.siacs.conversations.services.MessageArchiveService;
@@ -58,18 +61,37 @@ import eu.siacs.conversations.xml.XmlReader;
import eu.siacs.conversations.xmpp.bind.Bind2;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
-import eu.siacs.conversations.xmpp.stanzas.AbstractAcknowledgeableStanza;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
-import eu.siacs.conversations.xmpp.stanzas.csi.ActivePacket;
-import eu.siacs.conversations.xmpp.stanzas.csi.InactivePacket;
-import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket;
-import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket;
-import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
-import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
+
+import im.conversations.android.xmpp.model.AuthenticationStreamFeature;
+import im.conversations.android.xmpp.model.StreamElement;
+import im.conversations.android.xmpp.model.bind2.Bind;
+import im.conversations.android.xmpp.model.bind2.Bound;
+import im.conversations.android.xmpp.model.csi.Active;
+import im.conversations.android.xmpp.model.csi.Inactive;
+import im.conversations.android.xmpp.model.error.Condition;
+import im.conversations.android.xmpp.model.fast.Fast;
+import im.conversations.android.xmpp.model.fast.RequestToken;
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.sasl.Auth;
+import im.conversations.android.xmpp.model.sasl.Mechanisms;
+import im.conversations.android.xmpp.model.sasl.Response;
+import im.conversations.android.xmpp.model.sasl.Success;
+import im.conversations.android.xmpp.model.sasl2.Authenticate;
+import im.conversations.android.xmpp.model.sasl2.Authentication;
+import im.conversations.android.xmpp.model.sm.Ack;
+import im.conversations.android.xmpp.model.sm.Enable;
+import im.conversations.android.xmpp.model.sm.Enabled;
+import im.conversations.android.xmpp.model.sm.Failed;
+import im.conversations.android.xmpp.model.sm.Request;
+import im.conversations.android.xmpp.model.sm.Resume;
+import im.conversations.android.xmpp.model.sm.Resumed;
+import im.conversations.android.xmpp.model.sm.StreamManagement;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.stanza.Presence;
+import im.conversations.android.xmpp.model.stanza.Stanza;
+import im.conversations.android.xmpp.model.tls.Proceed;
+import im.conversations.android.xmpp.model.tls.StartTls;
+import im.conversations.android.xmpp.processor.BindProcessor;
import okhttp3.HttpUrl;
@@ -104,6 +126,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import javax.net.ssl.KeyManager;
@@ -116,46 +139,12 @@ import javax.net.ssl.X509TrustManager;
public class XmppConnection implements Runnable {
- private static final int PACKET_IQ = 0;
- private static final int PACKET_MESSAGE = 1;
- private static final int PACKET_PRESENCE = 2;
- public final OnIqPacketReceived registrationResponseListener =
- (account, packet) -> {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- account.setOption(Account.OPTION_REGISTER, false);
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": successfully registered new account on server");
- throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
- } else {
- final List<String> PASSWORD_TOO_WEAK_MSGS =
- Arrays.asList(
- "The password is too weak", "Please use a longer password.");
- Element error = packet.findChild("error");
- Account.State state = Account.State.REGISTRATION_FAILED;
- if (error != null) {
- if (error.hasChild("conflict")) {
- state = Account.State.REGISTRATION_CONFLICT;
- } else if (error.hasChild("resource-constraint")
- && "wait".equals(error.getAttribute("type"))) {
- state = Account.State.REGISTRATION_PLEASE_WAIT;
- } else if (error.hasChild("not-acceptable")
- && PASSWORD_TOO_WEAK_MSGS.contains(
- error.findChildContent("text"))) {
- state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
- }
- }
- throw new StateChangingError(state);
- }
- };
protected final Account account;
private final Features features = new Features(this);
private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
private final HashMap<String, Jid> commands = new HashMap<>();
- private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
- private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks =
- new Hashtable<>();
+ private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
+ private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
private final Set<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners =
new HashSet<>();
private final AppSettings appSettings;
@@ -168,8 +157,8 @@ public class XmppConnection implements Runnable {
private boolean quickStartInProgress = false;
private boolean isBound = false;
private boolean offlineMessagesRetrieved = false;
- private Element streamFeatures;
- private Element boundStreamFeatures;
+ private im.conversations.android.xmpp.model.streams.Features streamFeatures;
+ private im.conversations.android.xmpp.model.streams.Features boundStreamFeatures;
private StreamId streamId = null;
private int stanzasReceived = 0;
private int stanzasSent = 0;
@@ -186,12 +175,13 @@ public class XmppConnection implements Runnable {
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
private boolean mInteractive = false;
private int attempt = 0;
- private OnPresencePacketReceived presenceListener = null;
private OnJinglePacketReceived jingleListener = null;
- private OnIqPacketReceived unregisteredIqListener = null;
- private OnMessagePacketReceived messageListener = null;
+
+ private final Consumer<Presence> presenceListener;
+ private final Consumer<Iq> unregisteredIqListener;
+ private final Consumer<im.conversations.android.xmpp.model.stanza.Message> messageListener;
private OnStatusChanged statusListener = null;
- private OnBindListener bindListener = null;
+ private final Runnable bindListener;
private OnMessageAcknowledged acknowledgedListener = null;
private LoginInfo loginInfo;
private HashedToken.Mechanism hashTokenRequest;
@@ -206,6 +196,10 @@ public class XmppConnection implements Runnable {
this.account = account;
this.mXmppConnectionService = service;
this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext());
+ this.presenceListener = new PresenceParser(service, account);
+ this.unregisteredIqListener = new IqParser(service, account);
+ this.messageListener = new MessageParser(service, account);
+ this.bindListener = new BindProcessor(service, account);
}
private static void fixResource(final Context context, final Account account) {
@@ -606,7 +600,7 @@ public class XmppConnection implements Runnable {
} else if (nextTag.isStart("features", Namespace.STREAMS)) {
processStreamFeatures(nextTag);
} else if (nextTag.isStart("proceed", Namespace.TLS)) {
- switchOverToTls();
+ switchOverToTls(nextTag);
} else if (nextTag.isStart("failure", Namespace.TLS)) {
throw new StateChangingException(Account.State.TLS_ERROR);
} else if (account.isOptionSet(Account.OPTION_REGISTER)
@@ -632,10 +626,10 @@ public class XmppConnection implements Runnable {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} else if (this.streamId != null
&& nextTag.isStart("resumed", Namespace.STREAM_MANAGEMENT)) {
- final Element resumed = tagReader.readElement(nextTag);
+ final Resumed resumed = tagReader.readElement(nextTag, Resumed.class);
processResumed(resumed);
} else if (nextTag.isStart("failed", Namespace.STREAM_MANAGEMENT)) {
- final Element failed = tagReader.readElement(nextTag);
+ final Failed failed = tagReader.readElement(nextTag, Failed.class);
processFailed(failed, true);
} else if (nextTag.isStart("iq", Namespace.JABBER_CLIENT)) {
processIq(nextTag);
@@ -651,7 +645,7 @@ public class XmppConnection implements Runnable {
} else if (nextTag.isStart("presence", Namespace.JABBER_CLIENT)) {
processPresence(nextTag);
} else if (nextTag.isStart("enabled", Namespace.STREAM_MANAGEMENT)) {
- final Element enabled = tagReader.readElement(nextTag);
+ final var enabled = tagReader.readElement(nextTag, Enabled.class);
processEnabled(enabled);
} else if (nextTag.isStart("r", Namespace.STREAM_MANAGEMENT)) {
tagReader.readElement(nextTag);
@@ -662,7 +656,7 @@ public class XmppConnection implements Runnable {
+ ": acknowledging stanza #"
+ this.stanzasReceived);
}
- final AckPacket ack = new AckPacket(this.stanzasReceived);
+ final Ack ack = new Ack(this.stanzasReceived);
tagWriter.writeStanzaAsync(ack);
} else if (nextTag.isStart("a", Namespace.STREAM_MANAGEMENT)) {
boolean accountUiNeedsRefresh = false;
@@ -689,11 +683,11 @@ public class XmppConnection implements Runnable {
if (accountUiNeedsRefresh) {
mXmppConnectionService.updateAccountUi();
}
- final Element ack = tagReader.readElement(nextTag);
+ final var ack = tagReader.readElement(nextTag, Ack.class);
lastPacketReceived = SystemClock.elapsedRealtime();
final boolean acknowledgedMessages;
synchronized (this.mStanzaQueue) {
- final Optional<Integer> serverSequence = ack.getOptionalIntAttribute("h");
+ final Optional<Integer> serverSequence = ack.getHandled();
if (serverSequence.isPresent()) {
acknowledgedMessages = acknowledgeStanzaUpTo(serverSequence.get());
} else {
@@ -729,11 +723,11 @@ public class XmppConnection implements Runnable {
} catch (final IllegalArgumentException e) {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
- final Element response;
+ final StreamElement response;
if (version == SaslMechanism.Version.SASL) {
- response = new Element("response", Namespace.SASL);
+ response = new Response();
} else if (version == SaslMechanism.Version.SASL_2) {
- response = new Element("response", Namespace.SASL_2);
+ response = new im.conversations.android.xmpp.model.sasl2.Response();
} else {
throw new AssertionError("Missing implementation for " + version);
}
@@ -753,26 +747,23 @@ public class XmppConnection implements Runnable {
tagWriter.writeElement(response);
}
- private boolean processSuccess(final Element success)
+ private boolean processSuccess(final Element element)
throws IOException, XmlPullParserException {
- final SaslMechanism.Version version;
- try {
- version = SaslMechanism.Version.of(success);
- } catch (final IllegalArgumentException e) {
- throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
- }
final LoginInfo currentLoginInfo = this.loginInfo;
final SaslMechanism currentSaslMechanism = LoginInfo.mechanism(currentLoginInfo);
if (currentLoginInfo == null || currentSaslMechanism == null) {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
+ final SaslMechanism.Version version;
final String challenge;
- if (version == SaslMechanism.Version.SASL) {
+ if (element instanceof Success success) {
challenge = success.getContent();
- } else if (version == SaslMechanism.Version.SASL_2) {
+ version = SaslMechanism.Version.SASL;
+ } else if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) {
challenge = success.findChildContent("additional-data");
+ version = SaslMechanism.Version.SASL_2;
} else {
- throw new AssertionError("Missing implementation for " + version);
+ throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
try {
currentLoginInfo.success(challenge, sslSocketOrNull(socket));
@@ -786,47 +777,24 @@ public class XmppConnection implements Runnable {
if (SaslMechanism.pin(currentSaslMechanism)) {
account.setPinnedMechanism(currentSaslMechanism);
}
- if (version == SaslMechanism.Version.SASL_2) {
- final String authorizationIdentifier =
- success.findChildContent("authorization-identifier");
- final Jid authorizationJid;
- try {
- authorizationJid =
- Strings.isNullOrEmpty(authorizationIdentifier)
- ? null
- : Jid.ofEscaped(authorizationIdentifier);
- } catch (final IllegalArgumentException e) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": SASL 2.0 authorization identifier was not a valid jid");
- throw new StateChangingException(Account.State.BIND_FAILURE);
- }
- if (authorizationJid == null) {
- throw new StateChangingException(Account.State.BIND_FAILURE);
- }
+ if (element instanceof im.conversations.android.xmpp.model.sasl2.Success success) {
+ final var authorizationJid = success.getAuthorizationIdentifier();
+ checkAssignedDomainOrThrow(authorizationJid);
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": SASL 2.0 authorization identifier was "
+ authorizationJid);
- if (!account.getJid().getDomain().equals(authorizationJid.getDomain())) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": server tried to re-assign domain to "
- + authorizationJid.getDomain());
- throw new StateChangingError(Account.State.BIND_FAILURE);
- }
+ // TODO this should only happen when we used Bind 2
if (authorizationJid.isFullJid() && account.setJid(authorizationJid)) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
+ ": jid changed during SASL 2.0. updating database");
}
- final Element bound = success.findChild("bound", Namespace.BIND2);
- final Element resumed = success.findChild("resumed", Namespace.STREAM_MANAGEMENT);
- final Element failed = success.findChild("failed", Namespace.STREAM_MANAGEMENT);
+ final Bound bound = success.getExtension(Bound.class);
+ final Resumed resumed = success.getExtension(Resumed.class);
+ final Failed failed = success.getExtension(Failed.class);
final Element tokenWrapper = success.findChild("token", Namespace.FAST);
final String token = tokenWrapper == null ? null : tokenWrapper.getAttribute("token");
if (bound != null && resumed != null) {
@@ -853,8 +821,7 @@ public class XmppConnection implements Runnable {
this.isBound = true;
processNopStreamFeatures();
this.boundStreamFeatures = this.streamFeatures;
- final Element streamManagementEnabled =
- bound.findChild("enabled", Namespace.STREAM_MANAGEMENT);
+ final Enabled streamManagementEnabled = bound.getExtension(Enabled.class);
final Element carbonsEnabled = bound.findChild("enabled", Namespace.CARBONS);
final boolean waitForDisco;
if (streamManagementEnabled != null) {
@@ -939,7 +906,7 @@ public class XmppConnection implements Runnable {
private void resetOutboundStanzaQueue() {
synchronized (this.mStanzaQueue) {
- final ImmutableList.Builder<AbstractAcknowledgeableStanza> intermediateStanzasBuilder =
+ final ImmutableList.Builder<Stanza> intermediateStanzasBuilder =
new ImmutableList.Builder<>();
if (Config.EXTENDED_SM_LOGGING) {
Log.d(
@@ -949,7 +916,7 @@ public class XmppConnection implements Runnable {
+ this.stanzasSentBeforeAuthentication);
}
for (int i = this.stanzasSentBeforeAuthentication + 1; i <= this.stanzasSent; ++i) {
- final AbstractAcknowledgeableStanza stanza = this.mStanzaQueue.get(i);
+ final Stanza stanza = this.mStanzaQueue.get(i);
if (stanza != null) {
intermediateStanzasBuilder.add(stanza);
}
@@ -973,7 +940,9 @@ public class XmppConnection implements Runnable {
private void processNopStreamFeatures() throws IOException {
final Tag tag = tagReader.readTag();
if (tag != null && tag.isStart("features", Namespace.STREAMS)) {
- this.streamFeatures = tagReader.readElement(tag);
+ this.streamFeatures =
+ tagReader.readElement(
+ tag, im.conversations.android.xmpp.model.streams.Features.class);
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
@@ -1043,22 +1012,8 @@ public class XmppConnection implements Runnable {
}
}
- private void processEnabled(final Element enabled) {
- final String id;
- if (enabled.getAttributeAsBoolean("resume")) {
- id = enabled.getAttribute("id");
- } else {
- id = null;
- }
- final String locationAttribute = enabled.getAttribute("location");
- final Resolver.Result currentResolverResult = this.currentResolverResult;
- final Resolver.Result location;
- if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) {
- location = null;
- } else {
- location = currentResolverResult.seeOtherHost(locationAttribute);
- }
- final StreamId streamId = id == null ? null : new StreamId(id, location);
+ private void processEnabled(final Enabled enabled) {
+ final StreamId streamId = getStreamId(enabled);
if (streamId == null) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": stream management enabled");
} else {
@@ -1071,16 +1026,30 @@ public class XmppConnection implements Runnable {
this.streamId = streamId;
this.stanzasReceived = 0;
this.inSmacksSession = true;
- final RequestPacket r = new RequestPacket();
+ final var r = new Request();
tagWriter.writeStanzaAsync(r);
}
- private void processResumed(final Element resumed) throws StateChangingException {
+ @Nullable
+ private StreamId getStreamId(final Enabled enabled) {
+ final Optional<String> id = enabled.getResumeId();
+ final String locationAttribute = enabled.getLocation();
+ final Resolver.Result currentResolverResult = this.currentResolverResult;
+ final Resolver.Result location;
+ if (Strings.isNullOrEmpty(locationAttribute) || currentResolverResult == null) {
+ location = null;
+ } else {
+ location = currentResolverResult.seeOtherHost(locationAttribute);
+ }
+ return id.isPresent() ? new StreamId(id.get(), location) : null;
+ }
+
+ private void processResumed(final Resumed resumed) throws StateChangingException {
this.inSmacksSession = true;
this.isBound = true;
- this.tagWriter.writeStanzaAsync(new RequestPacket());
+ this.tagWriter.writeStanzaAsync(new Request());
lastPacketReceived = SystemClock.elapsedRealtime();
- final Optional<Integer> h = resumed.getOptionalIntAttribute("h");
+ final Optional<Integer> h = resumed.getHandled();
final int serverCount;
if (h.isPresent()) {
serverCount = h.get();
@@ -1088,7 +1057,7 @@ public class XmppConnection implements Runnable {
resetStreamId();
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
- final ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>();
+ final ArrayList<Stanza> failedStanzas = new ArrayList<>();
final boolean acknowledgedMessages;
synchronized (this.mStanzaQueue) {
if (serverCount < stanzasSent) {
@@ -1111,8 +1080,8 @@ public class XmppConnection implements Runnable {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid() + ": resending " + failedStanzas.size() + " stanzas");
- for (final AbstractAcknowledgeableStanza packet : failedStanzas) {
- if (packet instanceof MessagePacket message) {
+ for (final Stanza packet : failedStanzas) {
+ if (packet instanceof im.conversations.android.xmpp.model.stanza.Message message) {
mXmppConnectionService.markMessage(
account,
message.getTo().asBareJid(),
@@ -1131,8 +1100,8 @@ public class XmppConnection implements Runnable {
changeStatus(Account.State.ONLINE);
}
- private void processFailed(final Element failed, final boolean sendBindRequest) {
- final Optional<Integer> serverCount = failed.getOptionalIntAttribute("h");
+ private void processFailed(final Failed failed, final boolean sendBindRequest) {
+ final Optional<Integer> serverCount = failed.getHandled();
if (serverCount.isPresent()) {
Log.d(
Config.LOGTAG,
@@ -1179,8 +1148,9 @@ public class XmppConnection implements Runnable {
+ ": server acknowledged stanza #"
+ mStanzaQueue.keyAt(i));
}
- final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i);
- if (stanza instanceof MessagePacket packet && acknowledgedListener != null) {
+ final Stanza stanza = mStanzaQueue.valueAt(i);
+ if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet
+ && acknowledgedListener != null) {
final String id = packet.getId();
final Jid to = packet.getTo();
if (id != null && to != null) {
@@ -1195,29 +1165,9 @@ public class XmppConnection implements Runnable {
return acknowledgedMessages;
}
- private @NonNull Element processPacket(final Tag currentTag, final int packetType)
+ private <S extends Stanza> @NonNull S processPacket(final Tag currentTag, final Class<S> clazz)
throws IOException {
- final Element element =
- switch (packetType) {
- case PACKET_IQ -> new IqPacket();
- case PACKET_MESSAGE -> new MessagePacket();
- case PACKET_PRESENCE -> new PresencePacket();
- default -> throw new AssertionError("Should never encounter invalid type");
- };
- element.setAttributes(currentTag.getAttributes());
- Tag nextTag = tagReader.readTag();
- if (nextTag == null) {
- throw new IOException("interrupted mid tag");
- }
- while (!nextTag.isEnd(element.getName())) {
- if (!nextTag.isNo()) {
- element.addChild(tagReader.readElement(nextTag));
- }
- nextTag = tagReader.readTag();
- if (nextTag == null) {
- throw new IOException("interrupted mid tag");
- }
- }
+ final S stanza = tagReader.readElement(currentTag, clazz);
if (stanzasReceived == Integer.MAX_VALUE) {
resetStreamId();
throw new IOException("time to restart the session. cant handle >2 billion pcks");
@@ -1229,25 +1179,19 @@ public class XmppConnection implements Runnable {
Config.LOGTAG,
account.getJid().asBareJid()
+ ": not counting stanza("
- + element.getClass().getSimpleName()
+ + stanza.getClass().getSimpleName()
+ "). Not in smacks session.");
}
lastPacketReceived = SystemClock.elapsedRealtime();
if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) {
- Log.d(Config.LOGTAG, "[background stanza] " + element);
- }
- if (element instanceof IqPacket
- && (((IqPacket) element).getType() == IqPacket.TYPE.SET)
- && element.hasChild("jingle", Namespace.JINGLE)) {
- return JinglePacket.upgrade((IqPacket) element);
- } else {
- return element;
+ Log.d(Config.LOGTAG, "[background stanza] " + stanza);
}
+ return stanza;
}
private void processIq(final Tag currentTag) throws IOException {
- final IqPacket packet = (IqPacket) processPacket(currentTag, PACKET_IQ);
- if (!packet.valid()) {
+ final Iq packet = processPacket(currentTag, Iq.class);
+ if (packet.isInvalid()) {
Log.e(
Config.LOGTAG,
"encountered invalid iq from='"
@@ -1263,12 +1207,12 @@ public class XmppConnection implements Runnable {
account.getJid().asBareJid() + "Not processing iq. Thread was interrupted");
return;
}
- if (packet instanceof JinglePacket jinglePacket && isBound) {
+ if (packet.hasExtension(Jingle.class) && packet.getType() == Iq.Type.SET && isBound) {
if (this.jingleListener != null) {
- this.jingleListener.onJinglePacketReceived(account, jinglePacket);
+ this.jingleListener.onJinglePacketReceived(account, packet);
}
} else {
- final OnIqPacketReceived callback = getIqPacketReceivedCallback(packet);
+ final var callback = getIqPacketReceivedCallback(packet);
if (callback == null) {
Log.d(
Config.LOGTAG,
@@ -1278,17 +1222,17 @@ public class XmppConnection implements Runnable {
return;
}
try {
- callback.onIqPacketReceived(account, packet);
+ callback.accept(packet);
} catch (final StateChangingError error) {
throw new StateChangingException(error.state);
}
}
}
- private OnIqPacketReceived getIqPacketReceivedCallback(final IqPacket stanza)
+ private Consumer<Iq> getIqPacketReceivedCallback(final Iq stanza)
throws StateChangingException {
final boolean isRequest =
- stanza.getType() == IqPacket.TYPE.GET || stanza.getType() == IqPacket.TYPE.SET;
+ stanza.getType() == Iq.Type.GET || stanza.getType() == Iq.Type.SET;
if (isRequest) {
if (isBound) {
return this.unregisteredIqListener;
@@ -1328,8 +1272,9 @@ public class XmppConnection implements Runnable {
}
private void processMessage(final Tag currentTag) throws IOException {
- final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
- if (!packet.valid()) {
+ final var packet =
+ processPacket(currentTag, im.conversations.android.xmpp.model.stanza.Message.class);
+ if (packet.isInvalid()) {
Log.e(
Config.LOGTAG,
"encountered invalid message from='"
@@ -1346,12 +1291,12 @@ public class XmppConnection implements Runnable {
+ "Not processing message. Thread was interrupted");
return;
}
- this.messageListener.onMessagePacketReceived(account, packet);
+ this.messageListener.accept(packet);
}
private void processPresence(final Tag currentTag) throws IOException {
- final PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
- if (!packet.valid()) {
+ final var packet = processPacket(currentTag, Presence.class);
+ if (packet.isInvalid()) {
Log.e(
Config.LOGTAG,
"encountered invalid presence from='"
@@ -1368,17 +1313,15 @@ public class XmppConnection implements Runnable {
+ "Not processing presence. Thread was interrupted");
return;
}
- this.presenceListener.onPresencePacketReceived(account, packet);
+ this.presenceListener.accept(packet);
}
private void sendStartTLS() throws IOException {
- final Tag startTLS = Tag.empty("starttls");
- startTLS.setAttribute("xmlns", Namespace.TLS);
- tagWriter.writeTag(startTLS);
+ tagWriter.writeElement(new StartTls());
}
- private void switchOverToTls() throws XmlPullParserException, IOException {
- tagReader.readTag();
+ private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
+ tagReader.readElement(currentTag, Proceed.class);
final Socket socket = this.socket;
final SSLSocket sslSocket = upgradeSocketToTls(socket);
this.socket = sslSocket;
@@ -1439,11 +1382,13 @@ public class XmppConnection implements Runnable {
}
private void processStreamFeatures(final Tag currentTag) throws IOException {
- this.streamFeatures = tagReader.readElement(currentTag);
+ this.streamFeatures =
+ tagReader.readElement(
+ currentTag, im.conversations.android.xmpp.model.streams.Features.class);
final boolean isSecure = isSecure();
final boolean needsBinding = !isBound && !account.isOptionSet(Account.OPTION_REGISTER);
if (this.quickStartInProgress) {
- if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) {
+ if (this.streamFeatures.hasStreamFeature(Authentication.class)) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
@@ -1452,8 +1397,7 @@ public class XmppConnection implements Runnable {
if (SaslMechanism.hashedToken(LoginInfo.mechanism(this.loginInfo))) {
return;
}
- if (isFastTokenAvailable(
- this.streamFeatures.findChild("authentication", Namespace.SASL_2))) {
+ if (isFastTokenAvailable(this.streamFeatures.getExtension(Authentication.class))) {
Log.d(
Config.LOGTAG,
account.getJid().asBareJid()
@@ -1471,8 +1415,7 @@ public class XmppConnection implements Runnable {
mXmppConnectionService.databaseBackend.updateAccount(account);
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
- if (this.streamFeatures.hasChild("starttls", Namespace.TLS)
- && !features.encryptionEnabled) {
+ if (this.streamFeatures.hasExtension(StartTls.class) && !features.encryptionEnabled) {
sendStartTLS();
} else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
&& account.isOptionSet(Account.OPTION_REGISTER)) {
@@ -1489,15 +1432,15 @@ public class XmppConnection implements Runnable {
} else if (!this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
&& account.isOptionSet(Account.OPTION_REGISTER)) {
throw new StateChangingException(Account.State.REGISTRATION_NOT_SUPPORTED);
- } else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)
+ } else if (this.streamFeatures.hasStreamFeature(Authentication.class)
&& shouldAuthenticate
&& isSecure) {
authenticate(SaslMechanism.Version.SASL_2);
- } else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL)
+ } else if (this.streamFeatures.hasStreamFeature(Mechanisms.class)
&& shouldAuthenticate
&& isSecure) {
authenticate(SaslMechanism.Version.SASL);
- } else if (this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT)
+ } else if (this.streamFeatures.streamManagement()
&& isSecure
&& LoginInfo.isSuccess(loginInfo)
&& streamId != null
@@ -1509,7 +1452,7 @@ public class XmppConnection implements Runnable {
+ ": resuming after stanza #"
+ stanzasReceived);
}
- final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived);
+ final var resume = new Resume(this.streamId.id, stanzasReceived);
this.mSmCatchupMessageCounter.set(0);
this.mWaitingForSmCatchup.set(true);
this.tagWriter.writeStanzaAsync(resume);
@@ -1537,9 +1480,9 @@ public class XmppConnection implements Runnable {
private void authenticate() throws IOException {
final boolean isSecure = isSecure();
- if (isSecure && this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) {
+ if (isSecure && this.streamFeatures.hasStreamFeature(Authentication.class)) {
authenticate(SaslMechanism.Version.SASL_2);
- } else if (isSecure && this.streamFeatures.hasChild("mechanisms", Namespace.SASL)) {
+ } else if (isSecure && this.streamFeatures.hasStreamFeature(Mechanisms.class)) {
authenticate(SaslMechanism.Version.SASL);
} else {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
@@ -1551,13 +1494,13 @@ public class XmppConnection implements Runnable {
}
private void authenticate(final SaslMechanism.Version version) throws IOException {
- final Element authElement;
+ final AuthenticationStreamFeature authElement;
if (version == SaslMechanism.Version.SASL) {
- authElement = this.streamFeatures.findChild("mechanisms", Namespace.SASL);
+ authElement = this.streamFeatures.getExtension(Mechanisms.class);
} else {
- authElement = this.streamFeatures.findChild("authentication", Namespace.SASL_2);
+ authElement = this.streamFeatures.getExtension(Authentication.class);
}
- final Collection<String> mechanisms = SaslMechanism.mechanisms(authElement);
+ final Collection<String> mechanisms = authElement.getMechanismNames();
final Element cbElement =
this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING);
final Collection<ChannelBinding> channelBindings = ChannelBinding.of(cbElement);
@@ -1569,26 +1512,27 @@ public class XmppConnection implements Runnable {
final String firstMessage =
saslMechanism.getClientFirstMessage(sslSocketOrNull(this.socket));
final boolean usingFast = SaslMechanism.hashedToken(saslMechanism);
- final Element authenticate;
+ final StreamElement authenticate;
if (version == SaslMechanism.Version.SASL) {
- authenticate = new Element("auth", Namespace.SASL);
+ authenticate = new Auth();
if (!Strings.isNullOrEmpty(firstMessage)) {
authenticate.setContent(firstMessage);
}
quickStartAvailable = false;
this.loginInfo = new LoginInfo(saslMechanism, version, Collections.emptyList());
} else if (version == SaslMechanism.Version.SASL_2) {
- final Element inline = authElement.findChild("inline", Namespace.SASL_2);
- final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT);
+ final Authentication authentication = (Authentication) authElement;
+ final var inline = authentication.getInline();
+ final boolean sm = inline != null && inline.hasExtension(StreamManagement.class);
final HashedToken.Mechanism hashTokenRequest;
if (usingFast) {
hashTokenRequest = null;
- } else {
- final Element fast =
- inline == null ? null : inline.findChild("fast", Namespace.FAST);
- final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
+ } else if (inline != null) {
hashTokenRequest =
- HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
+ HashedToken.Mechanism.best(
+ inline.getFastMechanisms(), SSLSockets.version(this.socket));
+ } else {
+ hashTokenRequest = null;
}
final Collection<String> bindFeatures = Bind2.features(inline);
quickStartAvailable =
@@ -1633,9 +1577,9 @@ public class XmppConnection implements Runnable {
}
}
- private static boolean isFastTokenAvailable(final Element authentication) {
- final Element inline = authentication == null ? null : authentication.findChild("inline");
- return inline != null && inline.hasChild("fast", Namespace.FAST);
+ private static boolean isFastTokenAvailable(final Authentication authentication) {
+ final var inline = authentication == null ? null : authentication.getInline();
+ return inline != null && inline.hasExtension(Fast.class);
}
private void validate(
@@ -1649,7 +1593,7 @@ public class XmppConnection implements Runnable {
+ mechanisms);
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
}
- validateRequireChannelBinding(saslMechanism);
+ checkRequireChannelBinding(saslMechanism);
if (SaslMechanism.hashedToken(saslMechanism)) {
return;
}
@@ -1668,7 +1612,7 @@ public class XmppConnection implements Runnable {
}
}
- private void validateRequireChannelBinding(@NonNull final SaslMechanism mechanism)
+ private void checkRequireChannelBinding(@NonNull final SaslMechanism mechanism)
throws StateChangingException {
if (appSettings.isRequireChannelBinding()) {
if (mechanism instanceof ChannelBindingMechanism) {
@@ -1679,19 +1623,44 @@ public class XmppConnection implements Runnable {
}
}
- private Element generateAuthenticationRequest(
+ private void checkAssignedDomainOrThrow(final Jid jid) throws StateChangingException {
+ if (jid == null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": bind response is missing jid");
+ throw new StateChangingException(Account.State.BIND_FAILURE);
+ }
+ final var current = this.account.getJid().getDomain();
+ if (jid.getDomain().equals(current)) {
+ return;
+ }
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": server tried to re-assign domain to "
+ + jid.getDomain());
+ throw new StateChangingException(Account.State.BIND_FAILURE);
+ }
+
+ private void checkAssignedDomain(final Jid jid) {
+ try {
+ checkAssignedDomainOrThrow(jid);
+ } catch (final StateChangingException e) {
+ throw new StateChangingError(e.state);
+ }
+ }
+
+ private Authenticate generateAuthenticationRequest(
final String firstMessage, final boolean usingFast) {
return generateAuthenticationRequest(
firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
}
- private Element generateAuthenticationRequest(
+ private Authenticate generateAuthenticationRequest(
final String firstMessage,
final boolean usingFast,
final HashedToken.Mechanism hashedTokenRequest,
final Collection<String> bind,
final boolean inlineStreamManagement) {
- final Element authenticate = new Element("authenticate", Namespace.SASL_2);
+ final var authenticate = new Authenticate();
if (!Strings.isNullOrEmpty(firstMessage)) {
authenticate.addChild("initial-response").setContent(firstMessage);
}
@@ -1712,31 +1681,29 @@ public class XmppConnection implements Runnable {
authenticate.addChild(generateBindRequest(bind));
}
if (inlineStreamManagement && streamId != null) {
- final ResumePacket resume = new ResumePacket(this.streamId.id, stanzasReceived);
+ final var resume = new Resume(this.streamId.id, stanzasReceived);
this.mSmCatchupMessageCounter.set(0);
this.mWaitingForSmCatchup.set(true);
- authenticate.addChild(resume);
+ authenticate.addExtension(resume);
}
if (hashedTokenRequest != null) {
- authenticate
- .addChild("request-token", Namespace.FAST)
- .setAttribute("mechanism", hashedTokenRequest.name());
+ authenticate.addExtension(new RequestToken(hashedTokenRequest));
}
if (usingFast) {
- authenticate.addChild("fast", Namespace.FAST);
+ authenticate.addExtension(new Fast());
}
return authenticate;
}
- private Element generateBindRequest(final Collection<String> bindFeatures) {
+ private Bind generateBindRequest(final Collection<String> bindFeatures) {
Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures);
- final Element bind = new Element("bind", Namespace.BIND2);
+ final var bind = new Bind();
bind.addChild("tag").setContent(mXmppConnectionService.getString(R.string.app_name));
if (bindFeatures.contains(Namespace.CARBONS)) {
- bind.addChild("enable", Namespace.CARBONS);
+ bind.addExtension(new im.conversations.android.xmpp.model.carbons.Enable());
}
if (bindFeatures.contains(Namespace.STREAM_MANAGEMENT)) {
- bind.addChild(new EnablePacket());
+ bind.addExtension(new Enable());
}
return bind;
}
@@ -1744,12 +1711,12 @@ public class XmppConnection implements Runnable {
private void register() {
final String preAuth = account.getKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN);
if (preAuth != null && features.invite()) {
- final IqPacket preAuthRequest = new IqPacket(IqPacket.TYPE.SET);
+ final Iq preAuthRequest = new Iq(Iq.Type.SET);
preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
sendUnmodifiedIqPacket(
preAuthRequest,
- (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
sendRegistryRequest();
} else {
final String error = response.getErrorCondition();
@@ -1768,21 +1735,21 @@ public class XmppConnection implements Runnable {
}
private void sendRegistryRequest() {
- final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
+ final Iq register = new Iq(Iq.Type.GET);
register.query(Namespace.REGISTER);
register.setTo(account.getDomain());
sendUnmodifiedIqPacket(
register,
- (account, packet) -> {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ (packet) -> {
+ if (packet.getType() == Iq.Type.TIMEOUT) {
return;
}
- if (packet.getType() == IqPacket.TYPE.ERROR) {
+ if (packet.getType() == Iq.Type.ERROR) {
throw new StateChangingError(Account.State.REGISTRATION_FAILED);
}
final Element query = packet.query(Namespace.REGISTER);
if (query.hasChild("username") && (query.hasChild("password"))) {
- final IqPacket register1 = new IqPacket(IqPacket.TYPE.SET);
+ final Iq register1 = new Iq(Iq.Type.SET);
final Element username =
new Element("username").setContent(account.getUsername());
final Element password =
@@ -1790,7 +1757,7 @@ public class XmppConnection implements Runnable {
register1.query(Namespace.REGISTER).addChild(username);
register1.query().addChild(password);
register1.setFrom(account.getJid().asBareJid());
- sendUnmodifiedIqPacket(register1, registrationResponseListener, true);
+ sendUnmodifiedIqPacket(register1, this::processRegistrationResponse, true);
} else if (query.hasChild("x", Namespace.DATA)) {
final Data data = Data.parse(query.findChild("x", Namespace.DATA));
final Element blob = query.findChild("data", "urn:xmpp:bob");
@@ -1858,6 +1825,45 @@ public class XmppConnection implements Runnable {
true);
}
+ public void sendCreateAccountWithCaptchaPacket(final String id, final Data data) {
+ final Iq request = IqGenerator.generateCreateAccountWithCaptcha(account, id, data);
+ this.sendUnmodifiedIqPacket(request, this::processRegistrationResponse, true);
+ }
+
+ private void processRegistrationResponse(final Iq response) {
+ if (response.getType() == Iq.Type.RESULT) {
+ account.setOption(Account.OPTION_REGISTER, false);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": successfully registered new account on server");
+ throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
+ } else {
+ final Account.State state = getRegistrationFailedState(response);
+ throw new StateChangingError(state);
+ }
+ }
+
+ @NonNull
+ private static Account.State getRegistrationFailedState(final Iq response) {
+ final List<String> PASSWORD_TOO_WEAK_MESSAGES =
+ Arrays.asList("The password is too weak", "Please use a longer password.");
+ final var error = response.getError();
+ final var condition = error == null ? null : error.getCondition();
+ final Account.State state;
+ if (condition instanceof Condition.Conflict) {
+ state = Account.State.REGISTRATION_CONFLICT;
+ } else if (condition instanceof Condition.ResourceConstraint) {
+ state = Account.State.REGISTRATION_PLEASE_WAIT;
+ } else if (condition instanceof Condition.NotAcceptable
+ && PASSWORD_TOO_WEAK_MESSAGES.contains(error.getTextAsString())) {
+ state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
+ } else {
+ state = Account.State.REGISTRATION_FAILED;
+ }
+ return state;
+ }
+
private void setAccountCreationFailed(final String url) {
final HttpUrl httpUrl = url == null ? null : HttpUrl.parse(url);
if (httpUrl != null && httpUrl.isHttps()) {
@@ -1905,61 +1911,36 @@ public class XmppConnection implements Runnable {
} else {
fixResource(mXmppConnectionService, account);
}
- final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ final Iq iq = new Iq(Iq.Type.SET);
final String resource =
Config.USE_RANDOM_RESOURCE_ON_EVERY_BIND ? nextRandomId() : account.getResource();
- iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
+ iq.addExtension(new im.conversations.android.xmpp.model.bind.Bind()).setResource(resource);
this.sendUnmodifiedIqPacket(
iq,
- (account, packet) -> {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ (packet) -> {
+ if (packet.getType() == Iq.Type.TIMEOUT) {
return;
}
- final Element bind = packet.findChild("bind");
- if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
+ final var bind =
+ packet.getExtension(
+ im.conversations.android.xmpp.model.bind.Bind.class);
+ if (bind != null && packet.getType() == Iq.Type.RESULT) {
isBound = true;
- final Element jid = bind.findChild("jid");
- if (jid != null && jid.getContent() != null) {
- try {
- Jid assignedJid = Jid.ofEscaped(jid.getContent());
- if (!account.getJid().getDomain().equals(assignedJid.getDomain())) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": server tried to re-assign domain to "
- + assignedJid.getDomain());
- throw new StateChangingError(Account.State.BIND_FAILURE);
- }
- if (account.setJid(assignedJid)) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": jid changed during bind. updating database");
- mXmppConnectionService.databaseBackend.updateAccount(account);
- }
- if (streamFeatures.hasChild("session")
- && !streamFeatures
- .findChild("session")
- .hasChild("optional")) {
- sendStartSession();
- } else {
- final boolean waitForDisco = enableStreamManagement();
- sendPostBindInitialization(waitForDisco, false);
- }
- return;
- } catch (final IllegalArgumentException e) {
- Log.d(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": server reported invalid jid ("
- + jid.getContent()
- + ") on bind");
- }
- } else {
+ final Jid assignedJid = bind.getJid();
+ checkAssignedDomain(assignedJid);
+ if (account.setJid(assignedJid)) {
Log.d(
Config.LOGTAG,
- account.getJid()
- + ": disconnecting because of bind failure. (no jid)");
+ account.getJid().asBareJid()
+ + ": jid changed during bind. updating database");
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ }
+ if (streamFeatures.hasChild("session")
+ && !streamFeatures.findChild("session").hasChild("optional")) {
+ sendStartSession();
+ } else {
+ final boolean waitForDisco = enableStreamManagement();
+ sendPostBindInitialization(waitForDisco, false);
}
} else {
Log.d(
@@ -8,7 +8,8 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.stanza.Iq;
import java.util.List;
import java.util.Map;
@@ -47,8 +48,9 @@ public abstract class AbstractContentMap<
return ImmutableList.copyOf(contents.keySet());
}
- JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) {
- final JinglePacket jinglePacket = new JinglePacket(action, sessionId);
+ Iq toJinglePacket(final Jingle.Action action, final String sessionId) {
+ final Iq iq = new Iq(Iq.Type.SET);
+ final var jinglePacket = iq.addExtension(new Jingle(action, sessionId));
for (final Map.Entry<String, DescriptionTransport<D, T>> entry : this.contents.entrySet()) {
final DescriptionTransport<D, T> descriptionTransport = entry.getValue();
final Content content =
@@ -65,7 +67,7 @@ public abstract class AbstractContentMap<
if (this.group != null) {
jinglePacket.addGroup(this.group);
}
- return jinglePacket;
+ return iq;
}
void requireContentDescriptions() {
@@ -19,9 +19,9 @@ import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.stanza.Iq;
import java.util.Arrays;
import java.util.Collection;
@@ -184,10 +184,10 @@ public abstract class AbstractJingleConnection {
return TERMINATED.contains(this.state);
}
- abstract void deliverPacket(JinglePacket jinglePacket);
+ abstract void deliverPacket(Iq jinglePacket);
protected void receiveOutOfOrderAction(
- final JinglePacket jinglePacket, final JinglePacket.Action action) {
+ final Iq jinglePacket, final Jingle.Action action) {
Log.d(
Config.LOGTAG,
String.format(
@@ -205,7 +205,7 @@ public abstract class AbstractJingleConnection {
}
}
- protected void terminateWithOutOfOrder(final JinglePacket jinglePacket) {
+ protected void terminateWithOutOfOrder(final Iq jinglePacket) {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid() + ": terminating session with out-of-order");
@@ -235,37 +235,38 @@ public abstract class AbstractJingleConnection {
if (previous != State.NULL && trigger != null) {
trigger.accept(target);
}
- final JinglePacket jinglePacket =
- new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
+ final var iq = new Iq(Iq.Type.SET);
+ final var jinglePacket =
+ iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
jinglePacket.setReason(reason, text);
- send(jinglePacket);
+ send(iq);
finish();
}
- protected void send(final JinglePacket jinglePacket) {
+ protected void send(final Iq jinglePacket) {
jinglePacket.setTo(id.with);
xmppConnectionService.sendIqPacket(id.account, jinglePacket, this::handleIqResponse);
}
- protected void respondOk(final JinglePacket jinglePacket) {
+ protected void respondOk(final Iq jinglePacket) {
xmppConnectionService.sendIqPacket(
- id.account, jinglePacket.generateResponse(IqPacket.TYPE.RESULT), null);
+ id.account, jinglePacket.generateResponse(Iq.Type.RESULT), null);
}
- protected void respondWithTieBreak(final JinglePacket jinglePacket) {
+ protected void respondWithTieBreak(final Iq jinglePacket) {
respondWithJingleError(jinglePacket, "tie-break", "conflict", "cancel");
}
- protected void respondWithOutOfOrder(final JinglePacket jinglePacket) {
+ protected void respondWithOutOfOrder(final Iq jinglePacket) {
respondWithJingleError(jinglePacket, "out-of-order", "unexpected-request", "wait");
}
- protected void respondWithItemNotFound(final JinglePacket jinglePacket) {
+ protected void respondWithItemNotFound(final Iq jinglePacket) {
respondWithJingleError(jinglePacket, null, "item-not-found", "cancel");
}
private void respondWithJingleError(
- final IqPacket original,
+ final Iq original,
String jingleCondition,
String condition,
String conditionType) {
@@ -273,18 +274,18 @@ public abstract class AbstractJingleConnection {
id.account, original, jingleCondition, condition, conditionType);
}
- private synchronized void handleIqResponse(final Account account, final IqPacket response) {
- if (response.getType() == IqPacket.TYPE.ERROR) {
+ private synchronized void handleIqResponse(final Iq response) {
+ if (response.getType() == Iq.Type.ERROR) {
handleIqErrorResponse(response);
return;
}
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response);
}
}
- protected void handleIqErrorResponse(final IqPacket response) {
- Preconditions.checkArgument(response.getType() == IqPacket.TYPE.ERROR);
+ protected void handleIqErrorResponse(final Iq response) {
+ Preconditions.checkArgument(response.getType() == Iq.Type.ERROR);
final String errorCondition = response.getErrorCondition();
Log.d(
Config.LOGTAG,
@@ -316,8 +317,8 @@ public abstract class AbstractJingleConnection {
this.finish();
}
- protected void handleIqTimeoutResponse(final IqPacket response) {
- Preconditions.checkArgument(response.getType() == IqPacket.TYPE.TIMEOUT);
+ protected void handleIqTimeoutResponse(final Iq response) {
+ Preconditions.checkArgument(response.getType() == Iq.Type.TIMEOUT);
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
@@ -361,8 +362,8 @@ public abstract class AbstractJingleConnection {
this.sessionId = sessionId;
}
- public static Id of(Account account, JinglePacket jinglePacket) {
- return new Id(account, jinglePacket.getFrom(), jinglePacket.getSessionId());
+ public static Id of(Account account, Iq iq, final Jingle jingle) {
+ return new Id(account, iq.getFrom(), jingle.getSessionId());
}
public static Id of(Account account, Jid with, final String sessionId) {
@@ -13,11 +13,10 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
-import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo;
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
+import im.conversations.android.xmpp.model.jingle.Jingle;
import java.util.Arrays;
import java.util.Collections;
@@ -39,7 +38,7 @@ public class FileTransferContentMap
super(group, contents);
}
- public static FileTransferContentMap of(final JinglePacket jinglePacket) {
+ public static FileTransferContentMap of(final Jingle jinglePacket) {
final Map<String, DescriptionTransport<FileTransferDescription, GenericTransportInfo>>
contents = of(jinglePacket.getJingleContents());
return new FileTransferContentMap(jinglePacket.getGroup(), contents);
@@ -10,7 +10,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.IP;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import org.webrtc.PeerConnection;
@@ -20,9 +20,9 @@ import java.util.List;
public final class IceServers {
- public static List<PeerConnection.IceServer> parse(final IqPacket response) {
+ public static List<PeerConnection.IceServer> parse(final Iq response) {
ImmutableList.Builder<PeerConnection.IceServer> listBuilder = new ImmutableList.Builder<>();
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ if (response.getType() == Iq.Type.RESULT) {
final Element services =
response.findChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
final List<Element> children =
@@ -34,14 +34,13 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.stanza.Iq;
import java.lang.ref.WeakReference;
import java.security.SecureRandom;
@@ -77,9 +76,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
}
- public void deliverPacket(final Account account, final JinglePacket packet) {
- final String sessionId = packet.getSessionId();
- final JinglePacket.Action action = packet.getAction();
+ public void deliverPacket(final Account account, final Iq packet) {
+ final var jingle = packet.getExtension(Jingle.class);
+ Preconditions.checkNotNull(jingle,"Passed iq packet w/o jingle extension to Connection Manager");
+ final String sessionId = jingle.getSessionId();
+ final Jingle.Action action = jingle.getAction();
if (sessionId == null) {
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
return;
@@ -88,13 +89,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
respondWithJingleError(account, packet, null, "bad-request", "cancel");
return;
}
- final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet);
+ final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet, jingle);
final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection != null) {
existingJingleConnection.deliverPacket(packet);
- } else if (action == JinglePacket.Action.SESSION_INITIATE) {
+ } else if (action == Jingle.Action.SESSION_INITIATE) {
final Jid from = packet.getFrom();
- final Content content = packet.getJingleContent();
+ final Content content = jingle.getJingleContent();
final String descriptionNamespace =
content == null ? null : content.getDescriptionNamespace();
final AbstractJingleConnection connection;
@@ -162,14 +163,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
private void sendSessionTerminate(
- final Account account, final IqPacket request, final AbstractJingleConnection.Id id) {
+ final Account account, final Iq request, final AbstractJingleConnection.Id id) {
mXmppConnectionService.sendIqPacket(
- account, request.generateResponse(IqPacket.TYPE.RESULT), null);
- final JinglePacket sessionTermination =
- new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
- sessionTermination.setTo(id.with);
+ account, request.generateResponse(Iq.Type.RESULT), null);
+ final var iq = new Iq(Iq.Type.SET);
+ iq.setTo(id.with);
+ final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
sessionTermination.setReason(Reason.BUSY, null);
- mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
+ mXmppConnectionService.sendIqPacket(account, iq, null);
}
private boolean isUsingClearNet(final Account account) {
@@ -263,11 +264,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
void respondWithJingleError(
final Account account,
- final IqPacket original,
+ final Iq original,
final String jingleCondition,
final String condition,
final String conditionType) {
- final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
+ final Iq response = original.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", conditionType);
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
@@ -438,7 +439,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final int activeDevices = account.activeDevicesWithRtpCapability();
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
if (activeDevices == 0) {
- final MessagePacket reject =
+ final var reject =
mXmppConnectionService
.getMessageGenerator()
.sessionReject(from, sessionId);
@@ -492,10 +493,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (remoteMsgId == null) {
return;
}
- final MessagePacket errorMessage = new MessagePacket();
+ final var errorMessage =
+ new im.conversations.android.xmpp.model.stanza.Message();
errorMessage.setTo(from);
errorMessage.setId(remoteMsgId);
- errorMessage.setType(MessagePacket.TYPE_ERROR);
+ errorMessage.setType(im.conversations.android.xmpp.model.stanza.Message.Type.ERROR);
final Element error = errorMessage.addChild("error");
error.setAttribute("code", "404");
error.setAttribute("type", "cancel");
@@ -720,7 +722,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
rtpSessionProposal.sessionId,
RtpEndUserState.RETRACTED);
}
- final MessagePacket messagePacket =
+ final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
writeLogMissedOutgoing(
account,
@@ -790,7 +792,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
- final MessagePacket messagePacket =
+ final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket);
return proposal;
@@ -800,7 +802,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public void sendJingleMessageFinish(
final Contact contact, final String sessionId, final Reason reason) {
final var account = contact.getAccount();
- final MessagePacket messagePacket =
+ final var messagePacket =
mXmppConnectionService
.getMessageGenerator()
.sessionFinish(contact.getJid(), sessionId, reason);
@@ -842,7 +844,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return false;
}
- public void deliverIbbPacket(final Account account, final IqPacket packet) {
+ public void deliverIbbPacket(final Account account, final Iq packet) {
final String sid;
final Element payload;
final InbandBytestreamsTransport.PacketType packetType;
@@ -868,7 +870,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
Config.LOGTAG,
account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid");
account.getXmppConnection()
- .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
return;
}
for (final AbstractJingleConnection connection : this.connections.values()) {
@@ -879,11 +881,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) {
account.getXmppConnection()
.sendIqPacket(
- packet.generateResponse(IqPacket.TYPE.RESULT), null);
+ packet.generateResponse(Iq.Type.RESULT), null);
} else {
account.getXmppConnection()
.sendIqPacket(
- packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ packet.generateResponse(Iq.Type.ERROR), null);
}
return;
}
@@ -894,7 +896,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
Config.LOGTAG,
account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid);
account.getXmppConnection()
- .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
}
public void notifyRebound(final Account account) {
@@ -945,7 +947,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
account.getJid().asBareJid()
+ ": resending session proposal to "
+ proposal.with);
- final MessagePacket messagePacket =
+ final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket);
}
@@ -31,7 +31,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo;
@@ -39,7 +38,9 @@ import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
import eu.siacs.conversations.xmpp.jingle.transports.SocksByteStreamsTransport;
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
import eu.siacs.conversations.xmpp.jingle.transports.WebRTCDataChannelTransport;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.stanza.Iq;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
@@ -112,22 +113,23 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
@Override
- void deliverPacket(final JinglePacket jinglePacket) {
- switch (jinglePacket.getAction()) {
- case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket);
- case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket);
- case SESSION_INFO -> receiveSessionInfo(jinglePacket);
- case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket);
- case TRANSPORT_ACCEPT -> receiveTransportAccept(jinglePacket);
- case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket);
- case TRANSPORT_REPLACE -> receiveTransportReplace(jinglePacket);
+ void deliverPacket(final Iq iq) {
+ final var jingle = iq.getExtension(Jingle.class);
+ switch (jingle.getAction()) {
+ case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle);
+ case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle);
+ case SESSION_INFO -> receiveSessionInfo(iq, jingle);
+ case SESSION_TERMINATE -> receiveSessionTerminate(iq, jingle);
+ case TRANSPORT_ACCEPT -> receiveTransportAccept(iq, jingle);
+ case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle);
+ case TRANSPORT_REPLACE -> receiveTransportReplace(iq, jingle);
default -> {
- respondOk(jinglePacket);
+ respondOk(iq);
Log.d(
Config.LOGTAG,
String.format(
"%s: received unhandled jingle action %s",
- id.account.getJid().asBareJid(), jinglePacket.getAction()));
+ id.account.getJid().asBareJid(), jingle.getAction()));
}
}
}
@@ -203,33 +205,34 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
if (transition(
State.SESSION_INITIALIZED,
() -> this.initiatorFileTransferContentMap = contentMap)) {
- final var jinglePacket =
- contentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
+ final var iq =
+ contentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
+ final var jingle = iq.getExtension(Jingle.class);
if (xmppAxolotlMessage != null) {
this.transportSecurity =
new TransportSecurity(
xmppAxolotlMessage.getInnerKey(), xmppAxolotlMessage.getIV());
- final var contents = jinglePacket.getJingleContents();
+ final var contents = jingle.getJingleContents();
final var rawContent =
contents.get(Iterables.getOnlyElement(contentMap.contents.keySet()));
if (rawContent != null) {
rawContent.setSecurity(xmppAxolotlMessage);
}
}
- jinglePacket.setTo(id.with);
+ iq.setTo(id.with);
xmppConnectionService.sendIqPacket(
id.account,
- jinglePacket,
- (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ iq,
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
xmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
return;
}
- if (response.getType() == IqPacket.TYPE.ERROR) {
+ if (response.getType() == Iq.Type.ERROR) {
handleIqErrorResponse(response);
return;
}
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response);
}
});
@@ -237,15 +240,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
}
- private void receiveSessionAccept(final JinglePacket jinglePacket) {
+ private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) {
Log.d(Config.LOGTAG, "receive file transfer session accept");
if (isResponder()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
return;
}
final FileTransferContentMap contentMap;
try {
- contentMap = FileTransferContentMap.of(jinglePacket);
+ contentMap = FileTransferContentMap.of(jingle);
contentMap.requireOnlyFileTransferDescription();
} catch (final RuntimeException e) {
Log.d(
@@ -261,7 +264,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
private void receiveSessionAccept(
- final JinglePacket jinglePacket, final FileTransferContentMap contentMap) {
+ final Iq jinglePacket, final FileTransferContentMap contentMap) {
if (transition(State.SESSION_ACCEPTED, () -> setRemoteContentMap(contentMap))) {
respondOk(jinglePacket);
final var transport = this.transport;
@@ -280,7 +283,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid() + ": receive out of order session-accept");
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
}
}
@@ -309,16 +312,16 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
}
- private void receiveSessionInitiate(final JinglePacket jinglePacket) {
+ private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) {
if (isInitiator()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
return;
}
Log.d(Config.LOGTAG, "receive session initiate " + jinglePacket);
final FileTransferContentMap contentMap;
final FileTransferDescription.File file;
try {
- contentMap = FileTransferContentMap.of(jinglePacket);
+ contentMap = FileTransferContentMap.of(jingle);
contentMap.requireContentDescriptions();
file = contentMap.requireOnlyFile();
// TODO check is offer
@@ -332,7 +335,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
return;
}
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
- final var contents = jinglePacket.getJingleContents();
+ final var contents = jingle.getJingleContents();
final var rawContent = contents.get(Iterables.getOnlyElement(contentMap.contents.keySet()));
final var security =
rawContent == null ? null : rawContent.getSecurity(jinglePacket.getFrom());
@@ -349,7 +352,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
private void receiveSessionInitiate(
- final JinglePacket jinglePacket,
+ final Iq jinglePacket,
final FileTransferContentMap contentMap,
final FileTransferDescription.File file,
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage) {
@@ -396,7 +399,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid() + ": receive out of order session-initiate");
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
}
}
@@ -453,9 +456,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
private void sendSessionAccept(final FileTransferContentMap contentMap) {
setLocalContentMap(contentMap);
- final var jinglePacket =
- contentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
- send(jinglePacket);
+ final var iq =
+ contentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
+ send(iq);
// this needs to come after session-accept or else our candidate-error might arrive first
this.transport.connect();
this.transport.readyToSentAdditionalCandidates();
@@ -541,9 +544,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
sendSessionTerminate(Reason.ofThrowable(rootCause), rootCause.getMessage());
}
- private void receiveSessionInfo(final JinglePacket jinglePacket) {
+ private void receiveSessionInfo(final Iq jinglePacket, final Jingle jingle) {
respondOk(jinglePacket);
- final var sessionInfo = FileTransferDescription.getSessionInfo(jinglePacket);
+ final var sessionInfo = FileTransferDescription.getSessionInfo(jingle);
if (sessionInfo instanceof FileTransferDescription.Checksum checksum) {
receiveSessionInfoChecksum(checksum);
} else if (sessionInfo instanceof FileTransferDescription.Received received) {
@@ -559,9 +562,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.d(Config.LOGTAG, "peer confirmed received " + received);
}
- private void receiveSessionTerminate(final JinglePacket jinglePacket) {
+ private void receiveSessionTerminate(final Iq jinglePacket, final Jingle jingle) {
respondOk(jinglePacket);
- final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
+ final Jingle.ReasonWrapper wrapper = jingle.getReason();
final State previous = this.state;
Log.d(
Config.LOGTAG,
@@ -590,15 +593,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
finish();
}
- private void receiveTransportAccept(final JinglePacket jinglePacket) {
+ private void receiveTransportAccept(final Iq jinglePacket, final Jingle jingle) {
if (isResponder()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT);
return;
}
Log.d(Config.LOGTAG, "receive transport accept " + jinglePacket);
final GenericTransportInfo transportInfo;
try {
- transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo();
+ transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo();
} catch (final RuntimeException e) {
Log.d(
Config.LOGTAG,
@@ -610,15 +613,15 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
return;
}
if (isInState(State.SESSION_ACCEPTED)) {
- final var group = jinglePacket.getGroup();
+ final var group = jingle.getGroup();
receiveTransportAccept(jinglePacket, new Transport.TransportInfo(transportInfo, group));
} else {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_ACCEPT);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_ACCEPT);
}
}
private void receiveTransportAccept(
- final JinglePacket jinglePacket, final Transport.TransportInfo transportInfo) {
+ final Iq jinglePacket, final Transport.TransportInfo transportInfo) {
final FileTransferContentMap remoteContentMap =
getRemoteContentMap().withTransport(transportInfo);
setRemoteContentMap(remoteContentMap);
@@ -637,11 +640,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
}
- private void receiveTransportInfo(final JinglePacket jinglePacket) {
+ private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) {
final FileTransferContentMap contentMap;
final GenericTransportInfo transportInfo;
try {
- contentMap = FileTransferContentMap.of(jinglePacket);
+ contentMap = FileTransferContentMap.of(jingle);
transportInfo = contentMap.requireOnlyTransportInfo();
} catch (final RuntimeException e) {
Log.d(
@@ -725,14 +728,14 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
}
- private void receiveTransportReplace(final JinglePacket jinglePacket) {
+ private void receiveTransportReplace(final Iq jinglePacket, final Jingle jingle) {
if (isInitiator()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE);
return;
}
final GenericTransportInfo transportInfo;
try {
- transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo();
+ transportInfo = FileTransferContentMap.of(jingle).requireOnlyTransportInfo();
} catch (final RuntimeException e) {
Log.d(
Config.LOGTAG,
@@ -746,12 +749,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
if (isInState(State.SESSION_ACCEPTED)) {
receiveTransportReplace(jinglePacket, transportInfo);
} else {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.TRANSPORT_REPLACE);
}
}
private void receiveTransportReplace(
- final JinglePacket jinglePacket, final GenericTransportInfo transportInfo) {
+ final Iq jinglePacket, final GenericTransportInfo transportInfo) {
respondOk(jinglePacket);
final Transport currentTransport = this.transport;
if (currentTransport != null) {
@@ -796,11 +799,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
private void sendTransportAccept(final FileTransferContentMap contentMap) {
setLocalContentMap(contentMap);
- final var jinglePacket =
+ final var iq =
contentMap
.transportInfo()
- .toJinglePacket(JinglePacket.Action.TRANSPORT_ACCEPT, id.sessionId);
- send(jinglePacket);
+ .toJinglePacket(Jingle.Action.TRANSPORT_ACCEPT, id.sessionId);
+ send(iq);
transport.connect();
}
@@ -982,11 +985,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
}
private void sendSessionInfo(final FileTransferDescription.SessionInfo sessionInfo) {
- final var jinglePacket =
- new JinglePacket(JinglePacket.Action.SESSION_INFO, this.id.sessionId);
- jinglePacket.addJingleChild(sessionInfo.asElement());
- jinglePacket.setTo(this.id.with);
- send(jinglePacket);
+ final var iq = new Iq(Iq.Type.SET);
+ final var jinglePacket = iq.addExtension(new Jingle(Jingle.Action.SESSION_INFO, this.id.sessionId));
+ jinglePacket.addChild(sessionInfo.asElement());
+ send(iq);
}
@Override
@@ -1039,11 +1041,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
private void sendTransportReplace(final FileTransferContentMap contentMap) {
setLocalContentMap(contentMap);
- final var jinglePacket =
+ final var iq =
contentMap
.transportInfo()
- .toJinglePacket(JinglePacket.Action.TRANSPORT_REPLACE, id.sessionId);
- send(jinglePacket);
+ .toJinglePacket(Jingle.Action.TRANSPORT_REPLACE, id.sessionId);
+ send(iq);
}
@Override
@@ -1068,9 +1070,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
+ contentName);
return;
}
- final JinglePacket jinglePacket =
- transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- send(jinglePacket);
+ final Iq iq =
+ transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ send(iq);
}
@Override
@@ -1081,12 +1083,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.e(Config.LOGTAG, "local content map is null on candidate used");
return;
}
- final var jinglePacket =
+ final var iq =
contentMap
.candidateUsed(streamId, candidate.cid)
- .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- Log.d(Config.LOGTAG, "sending candidate used " + jinglePacket);
- send(jinglePacket);
+ .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ Log.d(Config.LOGTAG, "sending candidate used " + iq);
+ send(iq);
}
@Override
@@ -1096,12 +1098,12 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.e(Config.LOGTAG, "local content map is null on candidate used");
return;
}
- final var jinglePacket =
+ final var iq =
contentMap
.candidateError(streamId)
- .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- Log.d(Config.LOGTAG, "sending candidate error " + jinglePacket);
- send(jinglePacket);
+ .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ Log.d(Config.LOGTAG, "sending candidate error " + iq);
+ send(iq);
}
@Override
@@ -1111,11 +1113,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
Log.e(Config.LOGTAG, "local content map is null on candidate used");
return;
}
- final var jinglePacket =
+ final var iq =
contentMap
.proxyActivated(streamId, candidate.cid)
- .toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- send(jinglePacket);
+ .toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ send(iq);
}
@Override
@@ -1251,10 +1253,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection
message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED);
}
terminateTransport();
- final JinglePacket jinglePacket =
- new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
- jinglePacket.setReason(reason, "User requested to stop file transfer");
- send(jinglePacket);
+ final Iq iq = new Iq(Iq.Type.SET);
+ final var jingle = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
+ jingle.setReason(reason, "User requested to stop file transfer");
+ send(iq);
finish();
return true;
} else {
@@ -43,13 +43,13 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed;
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+
+import im.conversations.android.xmpp.model.jingle.Jingle;
+import im.conversations.android.xmpp.model.stanza.Iq;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
@@ -139,24 +139,25 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
@Override
- synchronized void deliverPacket(final JinglePacket jinglePacket) {
- switch (jinglePacket.getAction()) {
- case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket);
- case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket);
- case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket);
- case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket);
- case CONTENT_ADD -> receiveContentAdd(jinglePacket);
- case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket);
- case CONTENT_REJECT -> receiveContentReject(jinglePacket);
- case CONTENT_REMOVE -> receiveContentRemove(jinglePacket);
- case CONTENT_MODIFY -> receiveContentModify(jinglePacket);
+ synchronized void deliverPacket(final Iq iq) {
+ final var jingle = iq.getExtension(Jingle.class);
+ switch (jingle.getAction()) {
+ case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle);
+ case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle);
+ case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle);
+ case SESSION_TERMINATE -> receiveSessionTerminate(iq);
+ case CONTENT_ADD -> receiveContentAdd(iq, jingle);
+ case CONTENT_ACCEPT -> receiveContentAccept(iq);
+ case CONTENT_REJECT -> receiveContentReject(iq, jingle);
+ case CONTENT_REMOVE -> receiveContentRemove(iq, jingle);
+ case CONTENT_MODIFY -> receiveContentModify(iq, jingle);
default -> {
- respondOk(jinglePacket);
+ respondOk(iq);
Log.d(
Config.LOGTAG,
String.format(
"%s: received unhandled jingle action %s",
- id.account.getJid().asBareJid(), jinglePacket.getAction()));
+ id.account.getJid().asBareJid(), jingle.getAction()));
}
}
}
@@ -183,9 +184,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- private void receiveSessionTerminate(final JinglePacket jinglePacket) {
+ private void receiveSessionTerminate(final Iq jinglePacket) {
respondOk(jinglePacket);
- final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
+ final var jingle = jinglePacket.getExtension(Jingle.class);
+ final Jingle.ReasonWrapper wrapper = jingle.getReason();
final State previous = this.state;
Log.d(
Config.LOGTAG,
@@ -214,7 +216,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
finish();
}
- private void receiveTransportInfo(final JinglePacket jinglePacket) {
+ private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) {
// Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
// INITIALIZED only after transport-info has been received
if (isInState(
@@ -225,7 +227,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap;
try {
- contentMap = RtpContentMap.of(jinglePacket);
+ contentMap = RtpContentMap.of(jingle);
} catch (final IllegalArgumentException | NullPointerException e) {
Log.d(
Config.LOGTAG,
@@ -255,7 +257,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void receiveTransportInfo(
- final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ final Iq jinglePacket, final RtpContentMap contentMap) {
final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>>
candidates = contentMap.contents.entrySet();
final RtpContentMap remote = getRemoteContentMap();
@@ -294,17 +296,17 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- private void receiveContentAdd(final JinglePacket jinglePacket) {
+ private void receiveContentAdd(final Iq iq, final Jingle jingle) {
final RtpContentMap modification;
try {
- modification = RtpContentMap.of(jinglePacket);
+ modification = RtpContentMap.of(jingle);
modification.requireContentDescriptions();
} catch (final RuntimeException e) {
Log.d(
Config.LOGTAG,
id.getAccount().getJid().asBareJid() + ": improperly formatted contents",
Throwables.getRootCause(e));
- respondOk(jinglePacket);
+ respondOk(iq);
webRTCWrapper.close();
sendSessionTerminate(Reason.of(e), e.getMessage());
return;
@@ -320,12 +322,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
new FutureCallback<>() {
@Override
public void onSuccess(final RtpContentMap rtpContentMap) {
- receiveContentAdd(jinglePacket, rtpContentMap);
+ receiveContentAdd(iq, rtpContentMap);
}
@Override
public void onFailure(@NonNull Throwable throwable) {
- respondOk(jinglePacket);
+ respondOk(iq);
final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d(
Config.LOGTAG,
@@ -339,12 +341,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
},
MoreExecutors.directExecutor());
} else {
- terminateWithOutOfOrder(jinglePacket);
+ terminateWithOutOfOrder(iq);
}
}
private void receiveContentAdd(
- final JinglePacket jinglePacket, final RtpContentMap modification) {
+ final Iq jinglePacket, final RtpContentMap modification) {
final RtpContentMap remote = getRemoteContentMap();
if (!Collections.disjoint(modification.getNames(), remote.getNames())) {
respondOk(jinglePacket);
@@ -396,10 +398,11 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- private void receiveContentAccept(final JinglePacket jinglePacket) {
+ private void receiveContentAccept(final Iq jinglePacket) {
+ final var jingle = jinglePacket.getExtension(Jingle.class);
final RtpContentMap receivedContentAccept;
try {
- receivedContentAccept = RtpContentMap.of(jinglePacket);
+ receivedContentAccept = RtpContentMap.of(jingle);
receivedContentAccept.requireContentDescriptions();
} catch (final RuntimeException e) {
Log.d(
@@ -484,14 +487,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
updateEndUserState();
}
- private void receiveContentModify(final JinglePacket jinglePacket) {
+ private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) {
if (this.state != State.SESSION_ACCEPTED) {
terminateWithOutOfOrder(jinglePacket);
return;
}
final Map<String, Content.Senders> modification =
Maps.transformEntries(
- jinglePacket.getJingleContents(), (key, value) -> value.getSenders());
+ jingle.getJingleContents(), (key, value) -> value.getSenders());
final boolean isInitiator = isInitiator();
final RtpContentMap currentOutgoing = this.outgoingContentAdd;
final RtpContentMap remoteContentMap = this.getRemoteContentMap();
@@ -594,10 +597,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
return candidateBuilder.build();
}
- private void receiveContentReject(final JinglePacket jinglePacket) {
+ private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) {
final RtpContentMap receivedContentReject;
try {
- receivedContentReject = RtpContentMap.of(jinglePacket);
+ receivedContentReject = RtpContentMap.of(jingle);
} catch (final RuntimeException e) {
Log.d(
Config.LOGTAG,
@@ -650,10 +653,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
+ summary);
}
- private void receiveContentRemove(final JinglePacket jinglePacket) {
+ private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) {
final RtpContentMap receivedContentRemove;
try {
- receivedContentRemove = RtpContentMap.of(jinglePacket);
+ receivedContentRemove = RtpContentMap.of(jingle);
receivedContentRemove.requireContentDescriptions();
} catch (final RuntimeException e) {
Log.d(
@@ -687,8 +690,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
String.format(
"%s only supports %s as a means to retract a not yet accepted %s",
BuildConfig.APP_NAME,
- JinglePacket.Action.CONTENT_REMOVE,
- JinglePacket.Action.CONTENT_ADD));
+ Jingle.Action.CONTENT_REMOVE,
+ Jingle.Action.CONTENT_ADD));
}
}
@@ -713,10 +716,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
return;
}
this.outgoingContentAdd = null;
- final JinglePacket retract =
+ final Iq retract =
outgoingContentAdd
.toStub()
- .toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId);
+ .toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId);
this.send(retract);
Log.d(
Config.LOGTAG,
@@ -772,16 +775,16 @@ public class JingleRtpConnection extends AbstractJingleConnection
"content addition is receive only. we want to upgrade to 'both'");
final RtpContentMap modifiedSenders =
incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
- final JinglePacket proposedContentModification =
+ final Iq proposedContentModification =
modifiedSenders
.toStub()
- .toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId);
+ .toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId);
proposedContentModification.setTo(id.with);
xmppConnectionService.sendIqPacket(
id.account,
proposedContentModification,
- (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
Log.d(
Config.LOGTAG,
id.account.getJid().asBareJid()
@@ -875,7 +878,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
@Override
public void onFailure(@NonNull final Throwable throwable) {
- failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable);
+ failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable);
}
},
MoreExecutors.directExecutor());
@@ -887,9 +890,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void sendContentAccept(final RtpContentMap contentAcceptMap) {
- final JinglePacket jinglePacket =
- contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId);
- send(jinglePacket);
+ final Iq iq =
+ contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId);
+ send(iq);
}
public synchronized void rejectContentAdd() {
@@ -903,20 +906,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void rejectContentAdd(final RtpContentMap contentMap) {
- final JinglePacket jinglePacket =
+ final Iq iq =
contentMap
.toStub()
- .toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId);
+ .toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId);
Log.d(
Config.LOGTAG,
id.getAccount().getJid().asBareJid()
+ ": rejecting content "
+ ContentAddition.summary(contentMap));
- send(jinglePacket);
+ send(iq);
}
private boolean checkForIceRestart(
- final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
+ final Iq jinglePacket, final RtpContentMap rtpContentMap) {
final RtpContentMap existing = getRemoteContentMap();
final Set<IceUdpTransportInfo.Credentials> existingCredentials;
final IceUdpTransportInfo.Credentials newCredentials;
@@ -995,7 +998,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private boolean applyIceRestart(
- final JinglePacket jinglePacket,
+ final Iq jinglePacket,
final RtpContentMap restartContentMap,
final boolean isOffer)
throws ExecutionException, InterruptedException {
@@ -1096,7 +1099,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private ListenableFuture<RtpContentMap> receiveRtpContentMap(
- final JinglePacket jinglePacket, final boolean expectVerification) {
+ final Jingle jinglePacket, final boolean expectVerification) {
try {
return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
} catch (final Exception e) {
@@ -1139,12 +1142,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- private void receiveSessionInitiate(final JinglePacket jinglePacket) {
+ private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) {
if (isInitiator()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
return;
}
- final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
+ final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jingle, false);
Futures.addCallback(
future,
new FutureCallback<>() {
@@ -1163,7 +1166,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void receiveSessionInitiate(
- final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ final Iq jinglePacket, final RtpContentMap contentMap) {
try {
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint(true);
@@ -1223,13 +1226,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- private void receiveSessionAccept(final JinglePacket jinglePacket) {
+ private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) {
if (isResponder()) {
- receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT);
+ receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
return;
}
final ListenableFuture<RtpContentMap> future =
- receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
+ receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint());
Futures.addCallback(
future,
new FutureCallback<>() {
@@ -1254,7 +1257,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void receiveSessionAccept(
- final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ final Iq jinglePacket, final RtpContentMap contentMap) {
try {
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint();
@@ -1399,7 +1402,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void failureToPerformAction(
- final JinglePacket.Action action, final Throwable throwable) {
+ final Jingle.Action action, final Throwable throwable) {
if (isTerminated()) {
return;
}
@@ -1470,8 +1473,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
return;
}
transitionOrThrow(State.SESSION_ACCEPTED);
- final JinglePacket sessionAccept =
- rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
+ final Iq sessionAccept =
+ rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
send(sessionAccept);
}
@@ -1939,8 +1942,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
return;
}
this.transitionOrThrow(targetState);
- final JinglePacket sessionInitiate =
- rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
+ final Iq sessionInitiate =
+ rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
send(sessionInitiate);
}
@@ -2008,9 +2011,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
+ contentName);
return;
}
- final JinglePacket jinglePacket =
- transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- send(jinglePacket);
+ final Iq iq =
+ transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ send(iq);
}
public RtpEndUserState getEndUserState() {
@@ -2364,8 +2367,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
private void sendJingleMessage(final String action, final Jid to) {
- final MessagePacket messagePacket = new MessagePacket();
- messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those
+ final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message();
+ messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those
messagePacket.setTo(to);
final Element intent =
messagePacket
@@ -2386,7 +2389,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void sendJingleMessageFinish(final Reason reason) {
final var account = id.getAccount();
- final MessagePacket messagePacket =
+ final var messagePacket =
xmppConnectionService
.getMessageGenerator()
.sessionFinish(id.with, id.sessionId, reason);
@@ -2545,34 +2548,34 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void initiateIceRestart(final RtpContentMap rtpContentMap) {
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
- final JinglePacket jinglePacket =
- transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
- Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket);
- jinglePacket.setTo(id.with);
+ final Iq iq =
+ transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
+ Log.d(Config.LOGTAG, "initiating ice restart: " + iq);
+ iq.setTo(id.with);
xmppConnectionService.sendIqPacket(
id.account,
- jinglePacket,
- (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ iq,
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, "received success to our ice restart");
setLocalContentMap(rtpContentMap);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
return;
}
- if (response.getType() == IqPacket.TYPE.ERROR) {
+ if (response.getType() == Iq.Type.ERROR) {
if (isTieBreak(response)) {
Log.d(Config.LOGTAG, "received tie-break as result of ice restart");
return;
}
handleIqErrorResponse(response);
}
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response);
}
});
}
- private boolean isTieBreak(final IqPacket response) {
+ private boolean isTieBreak(final Iq response) {
final Element error = response.findChild("error");
return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS);
}
@@ -2593,7 +2596,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
@Override
public void onFailure(@NonNull Throwable throwable) {
- failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable);
+ failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable);
}
},
MoreExecutors.directExecutor());
@@ -2601,21 +2604,21 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void sendContentAdd(final RtpContentMap contentAdd) {
- final JinglePacket jinglePacket =
- contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId);
- jinglePacket.setTo(id.with);
+ final Iq iq =
+ contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId);
+ iq.setTo(id.with);
xmppConnectionService.sendIqPacket(
id.account,
- jinglePacket,
- (connection, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ iq,
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
Log.d(
Config.LOGTAG,
id.getAccount().getJid().asBareJid()
+ ": received ACK to our content-add");
return;
}
- if (response.getType() == IqPacket.TYPE.ERROR) {
+ if (response.getType() == Iq.Type.ERROR) {
if (isTieBreak(response)) {
this.outgoingContentAdd = null;
Log.d(Config.LOGTAG, "received tie-break as result of our content-add");
@@ -2623,7 +2626,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
handleIqErrorResponse(response);
}
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response);
}
});
@@ -2821,13 +2824,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) {
- final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+ final Iq request = new Iq(Iq.Type.GET);
request.setTo(id.account.getDomain());
request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
xmppConnectionService.sendIqPacket(
id.account,
request,
- (account, response) -> {
+ (response) -> {
final var iceServers = IceServers.parse(response);
if (iceServers.isEmpty()) {
Log.w(
@@ -1,9 +1,8 @@
package eu.siacs.conversations.xmpp.jingle;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xmpp.PacketReceived;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
-public interface OnJinglePacketReceived extends PacketReceived {
- void onJinglePacketReceived(Account account, JinglePacket packet);
+public interface OnJinglePacketReceived {
+ void onJinglePacketReceived(Account account, Iq packet);
}
@@ -18,9 +18,9 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
+import im.conversations.android.xmpp.model.jingle.Jingle;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -38,7 +38,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
super(group, contents);
}
- public static RtpContentMap of(final JinglePacket jinglePacket) {
+ public static RtpContentMap of(final Jingle jinglePacket) {
final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents =
of(jinglePacket.getJingleContents());
if (isOmemoVerified(contents)) {
@@ -52,7 +52,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) {
final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values =
contents.values();
- if (values.size() == 0) {
+ if (values.isEmpty()) {
return false;
}
for (final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport :
@@ -15,6 +15,7 @@ import com.google.common.primitives.Longs;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.xmpp.model.jingle.Jingle;
import java.util.List;
@@ -55,15 +56,11 @@ public class FileTransferDescription extends GenericDescription {
return new File(size, name, mediaType, hashes);
}
- public static SessionInfo getSessionInfo(@NonNull final JinglePacket jinglePacket) {
- Preconditions.checkNotNull(jinglePacket);
+ public static SessionInfo getSessionInfo(@NonNull final Jingle jingle) {
+ Preconditions.checkNotNull(jingle);
Preconditions.checkArgument(
- jinglePacket.getAction() == JinglePacket.Action.SESSION_INFO,
+ jingle.getAction() == Jingle.Action.SESSION_INFO,
"jingle packet is not a session-info");
- final Element jingle = jinglePacket.findChild("jingle", Namespace.JINGLE);
- if (jingle == null) {
- return null;
- }
final Element checksum = jingle.findChild("checksum", Namespace.JINGLE_APPS_FILE_TRANSFER);
if (checksum != null) {
final Element file = checksum.findChild("file", Namespace.JINGLE_APPS_FILE_TRANSFER);
@@ -16,7 +16,7 @@ import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import java.io.Closeable;
import java.io.IOException;
@@ -96,7 +96,7 @@ public class InbandBytestreamsTransport implements Transport {
}
private void openInBandTransport() {
- final var iqPacket = new IqPacket(IqPacket.TYPE.SET);
+ final var iqPacket = new Iq(Iq.Type.SET);
iqPacket.setTo(with);
final var open = iqPacket.addChild("open", Namespace.IBB);
open.setAttribute("block-size", this.blockSize);
@@ -106,8 +106,8 @@ public class InbandBytestreamsTransport implements Transport {
xmppConnection.sendIqPacket(iqPacket, this::receiveResponseToOpen);
}
- private void receiveResponseToOpen(final Account account, final IqPacket response) {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ private void receiveResponseToOpen(final Iq response) {
+ if (response.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, "ibb open was accepted");
this.transportCallback.onTransportEstablished();
this.blockSenderThread.start();
@@ -284,7 +284,7 @@ public class InbandBytestreamsTransport implements Transport {
private void sendIbbBlock(final int sequence, final byte[] block) {
Log.d(Config.LOGTAG, "sending ibb block #" + sequence + " " + block.length + " bytes");
- final var iqPacket = new IqPacket(IqPacket.TYPE.SET);
+ final var iqPacket = new Iq(Iq.Type.SET);
iqPacket.setTo(with);
final var data = iqPacket.addChild("data", Namespace.IBB);
data.setAttribute("sid", this.streamId);
@@ -292,8 +292,8 @@ public class InbandBytestreamsTransport implements Transport {
data.setContent(BaseEncoding.base64().encode(block));
this.xmppConnection.sendIqPacket(
iqPacket,
- (a, response) -> {
- if (response.getType() != IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() != Iq.Type.RESULT) {
Log.d(
Config.LOGTAG,
"received iq error in response to data block #" + sequence);
@@ -32,7 +32,7 @@ import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
import eu.siacs.conversations.xmpp.jingle.DirectConnectionUtils;
import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import java.io.IOException;
import java.io.InputStream;
@@ -250,7 +250,7 @@ public class SocksByteStreamsTransport implements Transport {
private ListenableFuture<String> activateProxy(final Candidate candidate) {
Log.d(Config.LOGTAG, "trying to activate our proxy " + candidate);
final SettableFuture<String> iqFuture = SettableFuture.create();
- final IqPacket proxyActivation = new IqPacket(IqPacket.TYPE.SET);
+ final Iq proxyActivation = new Iq(Iq.Type.SET);
proxyActivation.setTo(candidate.jid);
final Element query = proxyActivation.addChild("query", Namespace.BYTE_STREAMS);
query.setAttribute("sid", this.streamId);
@@ -258,17 +258,18 @@ public class SocksByteStreamsTransport implements Transport {
activate.setContent(id.with.toEscapedString());
xmppConnection.sendIqPacket(
proxyActivation,
- (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, "our proxy has been activated");
transportCallback.onProxyActivated(this.streamId, candidate);
iqFuture.set(candidate.cid);
- } else if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ } else if (response.getType() == Iq.Type.TIMEOUT) {
iqFuture.setException(new TimeoutException());
} else {
+ final var account = id.account;
Log.d(
Config.LOGTAG,
- a.getJid().asBareJid()
+ account.getJid().asBareJid()
+ ": failed to activate proxy on "
+ candidate.jid);
iqFuture.setException(new IllegalStateException("Proxy activation failed"));
@@ -314,14 +315,14 @@ public class SocksByteStreamsTransport implements Transport {
return Futures.immediateFailedFuture(
new IllegalStateException("No proxy/streamer found"));
}
- final IqPacket iqRequest = new IqPacket(IqPacket.TYPE.GET);
+ final Iq iqRequest = new Iq(Iq.Type.GET);
iqRequest.setTo(streamer);
iqRequest.query(Namespace.BYTE_STREAMS);
final SettableFuture<Candidate> candidateFuture = SettableFuture.create();
xmppConnection.sendIqPacket(
iqRequest,
- (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
final Element query = response.findChild("query", Namespace.BYTE_STREAMS);
final Element streamHost =
query == null
@@ -349,7 +350,7 @@ public class SocksByteStreamsTransport implements Transport {
655360 + (initiator ? 0 : 15),
CandidateType.PROXY));
- } else if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ } else if (response.getType() == Iq.Type.TIMEOUT) {
candidateFuture.setException(new TimeoutException());
} else {
candidateFuture.setException(
@@ -22,7 +22,7 @@ import eu.siacs.conversations.xmpp.jingle.IceServers;
import eu.siacs.conversations.xmpp.jingle.WebRTCWrapper;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import org.webrtc.CandidatePairChangeEvent;
import org.webrtc.DataChannel;
@@ -234,14 +234,14 @@ public class WebRTCDataChannelTransport implements Transport {
if (xmppConnection.getFeatures().externalServiceDiscovery()) {
final SettableFuture<List<PeerConnection.IceServer>> iceServerFuture =
SettableFuture.create();
- final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+ final Iq request = new Iq(Iq.Type.GET);
request.setTo(this.account.getDomain());
request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
xmppConnection.sendIqPacket(
request,
- (account, response) -> {
+ (response) -> {
final var iceServers = IceServers.parse(response);
- if (iceServers.size() == 0) {
+ if (iceServers.isEmpty()) {
Log.w(
Config.LOGTAG,
account.getJid().asBareJid()
@@ -4,7 +4,7 @@ import android.os.Bundle;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
public class PublishOptions {
@@ -37,8 +37,8 @@ public class PublishOptions {
return options;
}
- public static boolean preconditionNotMet(IqPacket response) {
- final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null;
+ public static boolean preconditionNotMet(Iq response) {
+ final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR);
}
@@ -1,42 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas;
-
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.InvalidJid;
-
-abstract public class AbstractAcknowledgeableStanza extends AbstractStanza {
-
- protected AbstractAcknowledgeableStanza(String name) {
- super(name);
- }
-
-
- public String getId() {
- return this.getAttribute("id");
- }
-
- public void setId(final String id) {
- setAttribute("id", id);
- }
-
- private Element getErrorConditionElement() {
- final Element error = findChild("error");
- if (error == null) {
- return null;
- }
- for (final Element element : error.getChildren()) {
- if (!element.getName().equals("text")) {
- return element;
- }
- }
- return null;
- }
-
- public String getErrorCondition() {
- final Element condition = getErrorConditionElement();
- return condition == null ? null : condition.getName();
- }
-
- public boolean valid() {
- return InvalidJid.isValid(getFrom()) && InvalidJid.isValid(getTo());
- }
-}
@@ -1,53 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class AbstractStanza extends Element {
-
- protected AbstractStanza(final String name) {
- super(name);
- }
-
- public Jid getTo() {
- return getAttributeAsJid("to");
- }
-
- public Jid getFrom() {
- return getAttributeAsJid("from");
- }
-
- public void setTo(final Jid to) {
- if (to != null) {
- setAttribute("to", to);
- }
- }
-
- public void setFrom(final Jid from) {
- if (from != null) {
- setAttribute("from", from);
- }
- }
-
- public boolean fromServer(final Account account) {
- final Jid from = getFrom();
- return from == null
- || from.equals(account.getDomain())
- || from.equals(account.getJid().asBareJid())
- || from.equals(account.getJid());
- }
-
- public boolean toServer(final Account account) {
- final Jid to = getTo();
- return to == null
- || to.equals(account.getDomain())
- || to.equals(account.getJid().asBareJid())
- || to.equals(account.getJid());
- }
-
- public boolean fromAccount(final Account account) {
- final Jid from = getFrom();
- return from != null && from.asBareJid().equals(account.getJid().asBareJid());
- }
-}
@@ -1,75 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas;
-
-import eu.siacs.conversations.xml.Element;
-
-public class IqPacket extends AbstractAcknowledgeableStanza {
-
- public enum TYPE {
- ERROR,
- SET,
- RESULT,
- GET,
- INVALID,
- TIMEOUT
- }
-
- public IqPacket(final TYPE type) {
- super("iq");
- if (type != TYPE.INVALID) {
- this.setAttribute("type", type.toString().toLowerCase());
- }
- }
-
- public IqPacket() {
- super("iq");
- }
-
- public Element query() {
- Element query = findChild("query");
- if (query == null) {
- query = addChild("query");
- }
- return query;
- }
-
- public Element query(final String xmlns) {
- final Element query = query();
- query.setAttribute("xmlns", xmlns);
- return query();
- }
-
- public TYPE getType() {
- final String type = getAttribute("type");
- if (type == null) {
- return TYPE.INVALID;
- }
- switch (type) {
- case "error":
- return TYPE.ERROR;
- case "result":
- return TYPE.RESULT;
- case "set":
- return TYPE.SET;
- case "get":
- return TYPE.GET;
- case "timeout":
- return TYPE.TIMEOUT;
- default:
- return TYPE.INVALID;
- }
- }
-
- public IqPacket generateResponse(final TYPE type) {
- final IqPacket packet = new IqPacket(type);
- packet.setTo(this.getFrom());
- packet.setId(this.getId());
- return packet;
- }
-
- @Override
- public boolean valid() {
- String id = getId();
- return id != null && super.valid();
- }
-
-}
@@ -1,100 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas;
-
-import android.util.Pair;
-
-import eu.siacs.conversations.parser.AbstractParser;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.LocalizedContent;
-
-public class MessagePacket extends AbstractAcknowledgeableStanza {
- public static final int TYPE_CHAT = 0;
- public static final int TYPE_NORMAL = 2;
- public static final int TYPE_GROUPCHAT = 3;
- public static final int TYPE_ERROR = 4;
- public static final int TYPE_HEADLINE = 5;
-
- public MessagePacket() {
- super("message");
- }
-
- public LocalizedContent getBody() {
- return findInternationalizedChildContentInDefaultNamespace("body");
- }
-
- public void setBody(String text) {
- this.children.remove(findChild("body"));
- Element body = new Element("body");
- body.setContent(text);
- this.children.add(0, body);
- }
-
- public void setAxolotlMessage(Element axolotlMessage) {
- this.children.remove(findChild("body"));
- this.children.add(0, axolotlMessage);
- }
-
- public void setType(int type) {
- switch (type) {
- case TYPE_CHAT:
- this.setAttribute("type", "chat");
- break;
- case TYPE_GROUPCHAT:
- this.setAttribute("type", "groupchat");
- break;
- case TYPE_NORMAL:
- break;
- case TYPE_ERROR:
- this.setAttribute("type","error");
- break;
- default:
- this.setAttribute("type", "chat");
- break;
- }
- }
-
- public int getType() {
- String type = getAttribute("type");
- if (type == null) {
- return TYPE_NORMAL;
- } else if (type.equals("normal")) {
- return TYPE_NORMAL;
- } else if (type.equals("chat")) {
- return TYPE_CHAT;
- } else if (type.equals("groupchat")) {
- return TYPE_GROUPCHAT;
- } else if (type.equals("error")) {
- return TYPE_ERROR;
- } else if (type.equals("headline")) {
- return TYPE_HEADLINE;
- } else {
- return TYPE_NORMAL;
- }
- }
-
- public Pair<MessagePacket,Long> getForwardedMessagePacket(String name, String namespace) {
- Element wrapper = findChild(name, namespace);
- if (wrapper == null) {
- return null;
- }
- Element forwarded = wrapper.findChild("forwarded", "urn:xmpp:forward:0");
- if (forwarded == null) {
- return null;
- }
- MessagePacket packet = create(forwarded.findChild("message"));
- if (packet == null) {
- return null;
- }
- Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
- return new Pair(packet,timestamp);
- }
-
- public static MessagePacket create(Element element) {
- if (element == null) {
- return null;
- }
- MessagePacket packet = new MessagePacket();
- packet.setAttributes(element.getAttributes());
- packet.setChildren(element.getChildren());
- return packet;
- }
-}
@@ -1,8 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas;
-
-public class PresencePacket extends AbstractAcknowledgeableStanza {
-
- public PresencePacket() {
- super("presence");
- }
-}
@@ -1,11 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.csi;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class ActivePacket extends AbstractStanza {
- public ActivePacket() {
- super("active");
- setAttribute("xmlns", Namespace.CSI);
- }
-}
@@ -1,11 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.csi;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class InactivePacket extends AbstractStanza {
- public InactivePacket() {
- super("inactive");
- setAttribute("xmlns", Namespace.CSI);
- }
-}
@@ -1,14 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.streammgmt;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class AckPacket extends AbstractStanza {
-
- public AckPacket(final int sequence) {
- super("a");
- this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT);
- this.setAttribute("h", Integer.toString(sequence));
- }
-
-}
@@ -1,14 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.streammgmt;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class EnablePacket extends AbstractStanza {
-
- public EnablePacket() {
- super("enable");
- this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT);
- this.setAttribute("resume", "true");
- }
-
-}
@@ -1,13 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.streammgmt;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class RequestPacket extends AbstractStanza {
-
- public RequestPacket() {
- super("r");
- this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT);
- }
-
-}
@@ -1,15 +0,0 @@
-package eu.siacs.conversations.xmpp.stanzas.streammgmt;
-
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
-
-public class ResumePacket extends AbstractStanza {
-
- public ResumePacket(final String id, final int sequence) {
- super("resume");
- this.setAttribute("xmlns", Namespace.STREAM_MANAGEMENT);
- this.setAttribute("previd", id);
- this.setAttribute("h", Integer.toString(sequence));
- }
-
-}
@@ -0,0 +1,34 @@
+package im.conversations.android.xmpp;
+
+import org.jxmpp.jid.Jid;
+
+public abstract class Entity {
+
+ public final Jid address;
+
+ private Entity(final Jid address) {
+ this.address = address;
+ }
+
+ public static class DiscoItem extends Entity {
+
+ private DiscoItem(Jid address) {
+ super(address);
+ }
+ }
+
+ public static class Presence extends Entity {
+
+ private Presence(Jid address) {
+ super(address);
+ }
+ }
+
+ public static Presence presence(final Jid address) {
+ return new Presence(address);
+ }
+
+ public static DiscoItem discoItem(final Jid address) {
+ return new DiscoItem(address);
+ }
+}
@@ -0,0 +1,133 @@
+package im.conversations.android.xmpp;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Ordering;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
+import im.conversations.android.xmpp.model.data.Data;
+import im.conversations.android.xmpp.model.data.Field;
+import im.conversations.android.xmpp.model.disco.info.Feature;
+import im.conversations.android.xmpp.model.disco.info.Identity;
+import im.conversations.android.xmpp.model.disco.info.InfoQuery;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public final class EntityCapabilities {
+ public static EntityCapsHash hash(final InfoQuery info) {
+ final StringBuilder s = new StringBuilder();
+ final List<Identity> orderedIdentities =
+ Ordering.from(
+ (Comparator<Identity>)
+ (a, b) ->
+ ComparisonChain.start()
+ .compare(
+ blankNull(a.getCategory()),
+ blankNull(b.getCategory()))
+ .compare(
+ blankNull(a.getType()),
+ blankNull(b.getType()))
+ .compare(
+ blankNull(a.getLang()),
+ blankNull(b.getLang()))
+ .compare(
+ blankNull(a.getIdentityName()),
+ blankNull(b.getIdentityName()))
+ .result())
+ .sortedCopy(info.getIdentities());
+
+ for (final Identity id : orderedIdentities) {
+ s.append(blankNull(id.getCategory()))
+ .append("/")
+ .append(blankNull(id.getType()))
+ .append("/")
+ .append(blankNull(id.getLang()))
+ .append("/")
+ .append(blankNull(id.getIdentityName()))
+ .append("<");
+ }
+
+ final List<String> features =
+ Ordering.natural()
+ .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar));
+ for (final String feature : features) {
+ s.append(clean(feature)).append("<");
+ }
+
+ final List<Data> extensions =
+ Ordering.from(Comparator.comparing(Data::getFormType))
+ .sortedCopy(info.getExtensions(Data.class));
+
+ for (final Data extension : extensions) {
+ s.append(clean(extension.getFormType())).append("<");
+ final List<Field> fields =
+ Ordering.from(
+ Comparator.comparing(
+ (Field lhs) -> Strings.nullToEmpty(lhs.getFieldName())))
+ .sortedCopy(extension.getFields());
+ for (final Field field : fields) {
+ s.append(Strings.nullToEmpty(field.getFieldName())).append("<");
+ final List<String> values = Ordering.natural().sortedCopy(field.getValues());
+ for (final String value : values) {
+ s.append(blankNull(value)).append("<");
+ }
+ }
+ }
+ return new EntityCapsHash(
+ Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes());
+ }
+
+ private static String clean(String s) {
+ return s.replace("<", "<");
+ }
+
+ private static String blankNull(String s) {
+ return s == null ? "" : clean(s);
+ }
+
+ public abstract static class Hash {
+ public final byte[] hash;
+
+ protected Hash(byte[] hash) {
+ this.hash = hash;
+ }
+
+ public String encoded() {
+ return BaseEncoding.base64().encode(hash);
+ }
+
+ public abstract String capabilityNode(final String node);
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Hash hash1 = (Hash) o;
+ return Arrays.equals(hash, hash1.hash);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(hash);
+ }
+ }
+
+ public static class EntityCapsHash extends Hash {
+
+ protected EntityCapsHash(byte[] hash) {
+ super(hash);
+ }
+
+ @Override
+ public String capabilityNode(String node) {
+ return String.format("%s#%s", node, encoded());
+ }
+
+ public static EntityCapsHash of(final String encoded) {
+ return new EntityCapsHash(BaseEncoding.base64().decode(encoded));
+ }
+ }
+}
@@ -0,0 +1,185 @@
+package im.conversations.android.xmpp;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Ordering;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
+import com.google.common.primitives.Bytes;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.xmpp.model.Hash;
+import im.conversations.android.xmpp.model.data.Data;
+import im.conversations.android.xmpp.model.data.Field;
+import im.conversations.android.xmpp.model.data.Value;
+import im.conversations.android.xmpp.model.disco.info.Feature;
+import im.conversations.android.xmpp.model.disco.info.Identity;
+import im.conversations.android.xmpp.model.disco.info.InfoQuery;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Objects;
+
+public class EntityCapabilities2 {
+
+ private static final char UNIT_SEPARATOR = 0x1f;
+ private static final char RECORD_SEPARATOR = 0x1e;
+
+ private static final char GROUP_SEPARATOR = 0x1d;
+
+ private static final char FILE_SEPARATOR = 0x1c;
+
+ public static EntityCaps2Hash hash(final InfoQuery info) {
+ return hash(Hash.Algorithm.SHA_256, info);
+ }
+
+ public static EntityCaps2Hash hash(final Hash.Algorithm algorithm, final InfoQuery info) {
+ final String result = algorithm(info);
+ final var hashFunction = toHashFunction(algorithm);
+ return new EntityCaps2Hash(
+ algorithm, hashFunction.hashString(result, StandardCharsets.UTF_8).asBytes());
+ }
+
+ private static HashFunction toHashFunction(final Hash.Algorithm algorithm) {
+ switch (algorithm) {
+ case SHA_1:
+ return Hashing.sha1();
+ case SHA_256:
+ return Hashing.sha256();
+ case SHA_512:
+ return Hashing.sha512();
+ default:
+ throw new IllegalArgumentException("Unknown hash algorithm");
+ }
+ }
+
+ private static String asHex(final String message) {
+ return Joiner.on(' ')
+ .join(
+ Collections2.transform(
+ Bytes.asList(message.getBytes(StandardCharsets.UTF_8)),
+ b -> String.format("%02x", b)));
+ }
+
+ private static String algorithm(final InfoQuery infoQuery) {
+ return features(infoQuery.getFeatures())
+ + identities(infoQuery.getIdentities())
+ + extensions(infoQuery.getExtensions(Data.class));
+ }
+
+ private static String identities(final Collection<Identity> identities) {
+ return Joiner.on("")
+ .join(
+ Ordering.natural()
+ .sortedCopy(
+ Collections2.transform(
+ identities, EntityCapabilities2::identity)))
+ + FILE_SEPARATOR;
+ }
+
+ private static String identity(final Identity identity) {
+ return Strings.nullToEmpty(identity.getCategory())
+ + UNIT_SEPARATOR
+ + Strings.nullToEmpty(identity.getType())
+ + UNIT_SEPARATOR
+ + Strings.nullToEmpty(identity.getLang())
+ + UNIT_SEPARATOR
+ + Strings.nullToEmpty(identity.getIdentityName())
+ + UNIT_SEPARATOR
+ + RECORD_SEPARATOR;
+ }
+
+ private static String features(Collection<Feature> features) {
+ return Joiner.on("")
+ .join(
+ Ordering.natural()
+ .sortedCopy(
+ Collections2.transform(
+ features, EntityCapabilities2::feature)))
+ + FILE_SEPARATOR;
+ }
+
+ private static String feature(final Feature feature) {
+ return Strings.nullToEmpty(feature.getVar()) + UNIT_SEPARATOR;
+ }
+
+ private static String value(final Value value) {
+ return Strings.nullToEmpty(value.getContent()) + UNIT_SEPARATOR;
+ }
+
+ private static String values(final Collection<Value> values) {
+ return Joiner.on("")
+ .join(
+ Ordering.natural()
+ .sortedCopy(
+ Collections2.transform(
+ values, EntityCapabilities2::value)));
+ }
+
+ private static String field(final Field field) {
+ return Strings.nullToEmpty(field.getFieldName())
+ + UNIT_SEPARATOR
+ + values(field.getExtensions(Value.class))
+ + RECORD_SEPARATOR;
+ }
+
+ private static String fields(final Collection<Field> fields) {
+ return Joiner.on("")
+ .join(
+ Ordering.natural()
+ .sortedCopy(
+ Collections2.transform(
+ fields, EntityCapabilities2::field)))
+ + GROUP_SEPARATOR;
+ }
+
+ private static String extension(final Data data) {
+ return fields(data.getExtensions(Field.class));
+ }
+
+ private static String extensions(final Collection<Data> extensions) {
+ return Joiner.on("")
+ .join(
+ Ordering.natural()
+ .sortedCopy(
+ Collections2.transform(
+ extensions,
+ EntityCapabilities2::extension)))
+ + FILE_SEPARATOR;
+ }
+
+ public static class EntityCaps2Hash extends EntityCapabilities.Hash {
+
+ public final Hash.Algorithm algorithm;
+
+ protected EntityCaps2Hash(final Hash.Algorithm algorithm, byte[] hash) {
+ super(hash);
+ this.algorithm = algorithm;
+ }
+
+ public static EntityCaps2Hash of(final Hash.Algorithm algorithm, final String encoded) {
+ return new EntityCaps2Hash(algorithm, BaseEncoding.base64().decode(encoded));
+ }
+
+ @Override
+ public String capabilityNode(String node) {
+ return String.format(
+ "%s#%s.%s", Namespace.ENTITY_CAPABILITIES_2, algorithm.toString(), encoded());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ EntityCaps2Hash that = (EntityCaps2Hash) o;
+ return algorithm == that.algorithm;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), algorithm);
+ }
+ }
+}
@@ -0,0 +1,78 @@
+package im.conversations.android.xmpp;
+
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import eu.siacs.conversations.xml.Element;
+
+import im.conversations.android.xmpp.model.Extension;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+public final class ExtensionFactory {
+
+ public static Element create(final String name, final String namespace) {
+ final Class<? extends Extension> clazz = of(name, namespace);
+ if (clazz == null) {
+ return new Element(name, namespace);
+ }
+ final Constructor<? extends Element> constructor;
+ try {
+ constructor = clazz.getDeclaredConstructor();
+ } catch (final NoSuchMethodException e) {
+ throw new IllegalStateException(
+ String.format("%s has no default constructor", clazz.getName()),e);
+ }
+ try {
+ return constructor.newInstance();
+ } catch (final IllegalAccessException
+ | InstantiationException
+ | InvocationTargetException e) {
+ throw new IllegalStateException(
+ String.format("%s has inaccessible default constructor", clazz.getName()),e);
+ }
+ }
+
+ private static Class<? extends Extension> of(final String name, final String namespace) {
+ return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace));
+ }
+
+ public static Id id(final Class<? extends Extension> clazz) {
+ return Extensions.EXTENSION_CLASS_MAP.inverse().get(clazz);
+ }
+
+ private ExtensionFactory() {}
+
+ public static class Id {
+ public final String name;
+ public final String namespace;
+
+ public Id(String name, String namespace) {
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Id id = (Id) o;
+ return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name, namespace);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name)
+ .add("namespace", namespace)
+ .toString();
+ }
+ }
+}
@@ -0,0 +1,112 @@
+package im.conversations.android.xmpp;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public class NodeConfiguration implements Map<String, Object> {
+
+ private static final String PERSIST_ITEMS = "pubsub#persist_items";
+ private static final String ACCESS_MODEL = "pubsub#access_model";
+ private static final String SEND_LAST_PUBLISHED_ITEM = "pubsub#send_last_published_item";
+ private static final String MAX_ITEMS = "pubsub#max_items";
+ private static final String NOTIFY_DELETE = "pubsub#notify_delete";
+ private static final String NOTIFY_RETRACT = "pubsub#notify_retract";
+
+ public static final NodeConfiguration OPEN =
+ new NodeConfiguration(
+ new ImmutableMap.Builder<String, Object>()
+ .put(PERSIST_ITEMS, Boolean.TRUE)
+ .put(ACCESS_MODEL, "open")
+ .build());
+ public static final NodeConfiguration PRESENCE =
+ new NodeConfiguration(
+ new ImmutableMap.Builder<String, Object>()
+ .put(PERSIST_ITEMS, Boolean.TRUE)
+ .put(ACCESS_MODEL, "presence")
+ .build());
+ public static final NodeConfiguration WHITELIST_MAX_ITEMS =
+ new NodeConfiguration(
+ new ImmutableMap.Builder<String, Object>()
+ .put(PERSIST_ITEMS, Boolean.TRUE)
+ .put(ACCESS_MODEL, "whitelist")
+ .put(SEND_LAST_PUBLISHED_ITEM, "never")
+ .put(MAX_ITEMS, "max")
+ .put(NOTIFY_DELETE, Boolean.TRUE)
+ .put(NOTIFY_RETRACT, Boolean.TRUE)
+ .build());
+ private final Map<String, Object> delegate;
+
+ private NodeConfiguration(Map<String, Object> map) {
+ this.delegate = map;
+ }
+
+ @Override
+ public int size() {
+ return this.delegate.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.delegate.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(@Nullable Object o) {
+ return this.delegate.containsKey(o);
+ }
+
+ @Override
+ public boolean containsValue(@Nullable Object o) {
+ return this.delegate.containsValue(o);
+ }
+
+ @Nullable
+ @Override
+ public Object get(@Nullable Object o) {
+ return this.delegate.get(o);
+ }
+
+ @Nullable
+ @Override
+ public Object put(String s, Object o) {
+ return this.delegate.put(s, o);
+ }
+
+ @Nullable
+ @Override
+ public Object remove(@Nullable Object o) {
+ return this.delegate.remove(o);
+ }
+
+ @Override
+ public void putAll(@NonNull Map<? extends String, ?> map) {
+ this.delegate.putAll(map);
+ }
+
+ @Override
+ public void clear() {
+ this.delegate.clear();
+ }
+
+ @NonNull
+ @Override
+ public Set<String> keySet() {
+ return this.delegate.keySet();
+ }
+
+ @NonNull
+ @Override
+ public Collection<Object> values() {
+ return this.delegate.values();
+ }
+
+ @NonNull
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ return this.delegate.entrySet();
+ }
+}
@@ -0,0 +1,31 @@
+package im.conversations.android.xmpp;
+
+import androidx.annotation.NonNull;
+import com.google.common.base.MoreObjects;
+
+public class Page {
+
+ public final String first;
+ public final String last;
+ public final Integer count;
+
+ public Page(String first, String last, Integer count) {
+ this.first = first;
+ this.last = last;
+ this.count = count;
+ }
+
+ public static Page emptyWithCount(final String id, final Integer count) {
+ return new Page(id, id, count);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("first", first)
+ .add("last", last)
+ .add("count", count)
+ .toString();
+ }
+}
@@ -0,0 +1,40 @@
+package im.conversations.android.xmpp;
+
+import androidx.annotation.NonNull;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+public class Range {
+
+ public final Order order;
+ public final String id;
+
+ public Range(final Order order, final String id) {
+ this.order = order;
+ this.id = id;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("order", order).add("id", id).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Range range = (Range) o;
+ return order == range.order && Objects.equal(id, range.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(order, id);
+ }
+
+ public enum Order {
+ NORMAL,
+ REVERSE
+ }
+}
@@ -0,0 +1,44 @@
+package im.conversations.android.xmpp;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public final class Timestamps {
+
+ private Timestamps() {
+ throw new IllegalStateException("Do not instantiate me");
+ }
+
+ public static long parse(final String input) throws ParseException {
+ if (input == null) {
+ throw new IllegalArgumentException("timestamp should not be null");
+ }
+ final String timestamp = input.replace("Z", "+0000");
+ final SimpleDateFormat simpleDateFormat =
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
+ final long milliseconds = getMilliseconds(timestamp);
+ final String formatted =
+ timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5);
+ final Date date = simpleDateFormat.parse(formatted);
+ if (date == null) {
+ throw new IllegalArgumentException("Date was null");
+ }
+ return date.getTime() + milliseconds;
+ }
+
+ private static long getMilliseconds(final String timestamp) {
+ if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') {
+ final String millis = timestamp.substring(19, timestamp.length() - 5);
+ try {
+ double fractions = Double.parseDouble("0" + millis);
+ return Math.round(1000 * fractions);
+ } catch (final NumberFormatException e) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model;
+
+import java.util.Collection;
+
+public abstract class AuthenticationStreamFeature extends StreamFeature{
+
+ public AuthenticationStreamFeature(final Class<? extends AuthenticationStreamFeature> clazz) {
+ super(clazz);
+ }
+
+ public abstract Collection<String> getMechanismNames();
+}
@@ -0,0 +1,33 @@
+package im.conversations.android.xmpp.model;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
+
+import eu.siacs.conversations.xml.Element;
+
+public interface ByteContent {
+
+ String getContent();
+
+ default byte[] asBytes() {
+ final var content = this.getContent();
+ if (Strings.isNullOrEmpty(content)) {
+ throw new IllegalStateException(
+ String.format("%s element is lacking content", getClass().getName()));
+ }
+ final var contentCleaned = CharMatcher.whitespace().removeFrom(content);
+ if (BaseEncoding.base64().canDecode(contentCleaned)) {
+ return BaseEncoding.base64().decode(contentCleaned);
+ } else {
+ throw new IllegalStateException(
+ String.format("%s element contains invalid base64", getClass().getName()));
+ }
+ }
+
+ default void setContent(final byte[] bytes) {
+ setContent(BaseEncoding.base64().encode(bytes));
+ }
+
+ Element setContent(final String content);
+}
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model;
+
+public abstract class DeliveryReceipt extends Extension {
+
+ protected DeliveryReceipt(Class<? extends Extension> clazz) {
+ super(clazz);
+ }
+
+ public abstract String getId();
+}
@@ -0,0 +1,8 @@
+package im.conversations.android.xmpp.model;
+
+public abstract class DeliveryReceiptRequest extends Extension {
+
+ protected DeliveryReceiptRequest(Class<? extends Extension> clazz) {
+ super(clazz);
+ }
+}
@@ -0,0 +1,62 @@
+package im.conversations.android.xmpp.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
+
+import eu.siacs.conversations.xml.Element;
+
+import im.conversations.android.xmpp.ExtensionFactory;
+
+import java.util.Collection;
+
+public class Extension extends Element {
+
+ private Extension(final ExtensionFactory.Id id) {
+ super(id.name, id.namespace);
+ }
+
+ public Extension(final Class<? extends Extension> clazz) {
+ this(
+ Preconditions.checkNotNull(
+ ExtensionFactory.id(clazz),
+ String.format(
+ "%s does not seem to be annotated with @XmlElement",
+ clazz.getName())));
+ Preconditions.checkArgument(
+ getClass().equals(clazz), "clazz passed in constructor must match class");
+ }
+
+ public <E extends Extension> boolean hasExtension(final Class<E> clazz) {
+ return Iterables.any(this.children, clazz::isInstance);
+ }
+
+ public <E extends Extension> E getExtension(final Class<E> clazz) {
+ final var extension = Iterables.find(this.children, clazz::isInstance, null);
+ if (extension == null) {
+ return null;
+ }
+ return clazz.cast(extension);
+ }
+
+ public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
+ return Collections2.transform(
+ Collections2.filter(this.children, clazz::isInstance), clazz::cast);
+ }
+
+ public Collection<ExtensionFactory.Id> getExtensionIds() {
+ return Collections2.transform(
+ this.children, c -> new ExtensionFactory.Id(c.getName(), c.getNamespace()));
+ }
+
+ public <T extends Extension> T addExtension(T child) {
+ this.addChild(child);
+ return child;
+ }
+
+ public void addExtensions(final Collection<? extends Extension> extensions) {
+ for (final Extension extension : extensions) {
+ addExtension(extension);
+ }
+ }
+}
@@ -0,0 +1,46 @@
+package im.conversations.android.xmpp.model;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement(namespace = Namespace.HASHES)
+public class Hash extends Extension {
+ public Hash() {
+ super(Hash.class);
+ }
+
+ public Algorithm getAlgorithm() {
+ return Algorithm.tryParse(this.getAttribute("algo"));
+ }
+
+ public void setAlgorithm(final Algorithm algorithm) {
+ this.setAttribute("algo", algorithm.toString());
+ }
+
+ public enum Algorithm {
+ SHA_1,
+ SHA_256,
+ SHA_512;
+
+ public static Algorithm tryParse(@Nullable final String name) {
+ try {
+ return valueOf(
+ CaseFormat.LOWER_HYPHEN.to(
+ CaseFormat.UPPER_UNDERSCORE, Strings.nullToEmpty(name)));
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
+ }
+ }
+}
@@ -0,0 +1,8 @@
+package im.conversations.android.xmpp.model;
+
+public abstract class StreamElement extends Extension {
+
+ protected StreamElement(Class<? extends StreamElement> clazz) {
+ super(clazz);
+ }
+}
@@ -0,0 +1,8 @@
+package im.conversations.android.xmpp.model;
+
+public abstract class StreamFeature extends Extension{
+
+ public StreamFeature(Class<? extends StreamFeature> clazz) {
+ super(clazz);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.addressing;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Address extends Extension {
+ public Address() {
+ super(Address.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.addressing;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Addresses extends Extension {
+ public Addresses() {
+ super(Addresses.class);
+ }
+}
@@ -0,0 +1,6 @@
+@XmlPackage(namespace = Namespace.ADDRESSING)
+package im.conversations.android.xmpp.model.addressing;
+
+import eu.siacs.conversations.xml.Namespace;
+
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,14 @@
+package im.conversations.android.xmpp.model.avatar;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.AVATAR_DATA)
+public class Data extends Extension implements ByteContent {
+
+ public Data() {
+ super(Data.class);
+ }
+}
@@ -0,0 +1,37 @@
+package im.conversations.android.xmpp.model.avatar;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.AVATAR_METADATA)
+public class Info extends Extension {
+
+ public Info() {
+ super(Info.class);
+ }
+
+ public long getHeight() {
+ return this.getLongAttribute("height");
+ }
+
+ public long getWidth() {
+ return this.getLongAttribute("width");
+ }
+
+ public long getBytes() {
+ return this.getLongAttribute("bytes");
+ }
+
+ public String getType() {
+ return this.getAttribute("type");
+ }
+
+ public String getUrl() {
+ return this.getAttribute("url");
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.avatar;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.AVATAR_METADATA)
+public class Metadata extends Extension {
+
+ public Metadata() {
+ super(Metadata.class);
+ }
+}
@@ -0,0 +1,60 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.collect.Iterables;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+import org.whispersystems.libsignal.state.PreKeyRecord;
+
+@XmlElement
+public class Bundle extends Extension {
+
+ public Bundle() {
+ super(Bundle.class);
+ }
+
+ public SignedPreKey getSignedPreKey() {
+ return this.getExtension(SignedPreKey.class);
+ }
+
+ public SignedPreKeySignature getSignedPreKeySignature() {
+ return this.getExtension(SignedPreKeySignature.class);
+ }
+
+ public IdentityKey getIdentityKey() {
+ return this.getExtension(IdentityKey.class);
+ }
+
+ public PreKey getRandomPreKey() {
+ final var preKeys = this.getExtension(PreKeys.class);
+ final Collection<PreKey> preKeyList =
+ preKeys == null ? Collections.emptyList() : preKeys.getExtensions(PreKey.class);
+ return Iterables.get(preKeyList, (int) (preKeyList.size() * Math.random()), null);
+ }
+
+ public void setIdentityKey(final ECPublicKey ecPublicKey) {
+ final var identityKey = this.addExtension(new IdentityKey());
+ identityKey.setContent(ecPublicKey);
+ }
+
+ public void setSignedPreKey(
+ final int id, final ECPublicKey ecPublicKey, final byte[] signature) {
+ final var signedPreKey = this.addExtension(new SignedPreKey());
+ signedPreKey.setId(id);
+ signedPreKey.setContent(ecPublicKey);
+ final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature());
+ signedPreKeySignature.setContent(signature);
+ }
+
+ public void addPreKeys(final List<PreKeyRecord> preKeyRecords) {
+ final var preKeys = this.addExtension(new PreKeys());
+ for (final PreKeyRecord preKeyRecord : preKeyRecords) {
+ final var preKey = preKeys.addExtension(new PreKey());
+ preKey.setId(preKeyRecord.getId());
+ preKey.setContent(preKeyRecord.getKeyPair().getPublicKey());
+ }
+ }
+}
@@ -0,0 +1,22 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.base.Strings;
+import com.google.common.primitives.Ints;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Device extends Extension {
+
+ public Device() {
+ super(Device.class);
+ }
+
+ public Integer getDeviceId() {
+ return Ints.tryParse(Strings.nullToEmpty(this.getAttribute("id")));
+ }
+
+ public void setDeviceId(int deviceId) {
+ this.setAttribute("id", deviceId);
+ }
+}
@@ -0,0 +1,35 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+@XmlElement(name = "list")
+public class DeviceList extends Extension {
+
+ public DeviceList() {
+ super(DeviceList.class);
+ }
+
+ public Collection<Device> getDevices() {
+ return this.getExtensions(Device.class);
+ }
+
+ public Set<Integer> getDeviceIds() {
+ return ImmutableSet.copyOf(
+ Collections2.filter(
+ Collections2.transform(getDevices(), Device::getDeviceId),
+ Objects::nonNull));
+ }
+
+ public void setDeviceIds(Collection<Integer> deviceIds) {
+ for (final Integer deviceId : deviceIds) {
+ final var device = this.addExtension(new Device());
+ device.setDeviceId(deviceId);
+ }
+ }
+}
@@ -0,0 +1,23 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.xmpp.model.ByteContent;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+
+public interface ECPublicKeyContent extends ByteContent {
+
+ default ECPublicKey asECPublicKey() {
+ try {
+ return Curve.decodePoint(asBytes(), 0);
+ } catch (InvalidKeyException e) {
+ throw new IllegalStateException(
+ String.format("%s does not contain a valid ECPublicKey", getClass().getName()),
+ e);
+ }
+ }
+
+ default void setContent(final ECPublicKey ecPublicKey) {
+ setContent(ecPublicKey.serialize());
+ }
+}
@@ -0,0 +1,24 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Encrypted extends Extension {
+
+ public Encrypted() {
+ super(Encrypted.class);
+ }
+
+ public boolean hasPayload() {
+ return hasExtension(Payload.class);
+ }
+
+ public Header getHeader() {
+ return getExtension(Header.class);
+ }
+
+ public Payload getPayload() {
+ return getExtension(Payload.class);
+ }
+}
@@ -0,0 +1,45 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Objects;
+
+@XmlElement
+public class Header extends Extension {
+
+ public Header() {
+ super(Header.class);
+ }
+
+ public void addIv(byte[] iv) {
+ this.addExtension(new IV()).setContent(iv);
+ }
+
+ public void setSourceDevice(long sourceDeviceId) {
+ this.setAttribute("sid", sourceDeviceId);
+ }
+
+ public Optional<Integer> getSourceDevice() {
+ return getOptionalIntAttribute("sid");
+ }
+
+ public Collection<Key> getKeys() {
+ return this.getExtensions(Key.class);
+ }
+
+ public Key getKey(final int deviceId) {
+ return Iterables.find(
+ getKeys(), key -> Objects.equals(key.getRemoteDeviceId(), deviceId), null);
+ }
+
+ public byte[] getIv() {
+ final IV iv = this.getExtension(IV.class);
+ if (iv == null) {
+ throw new IllegalStateException("No IV in header");
+ }
+ return iv.asBytes();
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "iv")
+public class IV extends Extension implements ByteContent {
+
+ public IV() {
+ super(IV.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "identityKey")
+public class IdentityKey extends Extension implements ECPublicKeyContent {
+
+ public IdentityKey() {
+ super(IdentityKey.class);
+ }
+}
@@ -0,0 +1,29 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Key extends Extension implements ByteContent {
+
+ public Key() {
+ super(Key.class);
+ }
+
+ public void setIsPreKey(boolean isPreKey) {
+ this.setAttribute("prekey", isPreKey);
+ }
+
+ public boolean isPreKey() {
+ return this.getAttributeAsBoolean("prekey");
+ }
+
+ public void setRemoteDeviceId(final int remoteDeviceId) {
+ this.setAttribute("rid", remoteDeviceId);
+ }
+
+ public Integer getRemoteDeviceId() {
+ return getOptionalIntAttribute("rid").orNull();
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Payload extends Extension implements ByteContent {
+
+ public Payload() {
+ super(Payload.class);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.primitives.Ints;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "preKeyPublic")
+public class PreKey extends Extension implements ECPublicKeyContent {
+
+ public PreKey() {
+ super(PreKey.class);
+ }
+
+ public int getId() {
+ return Ints.saturatedCast(this.getLongAttribute("preKeyId"));
+ }
+
+ public void setId(int id) {
+ this.setAttribute("preKeyId", id);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "prekeys")
+public class PreKeys extends Extension {
+
+ public PreKeys() {
+ super(PreKeys.class);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import com.google.common.primitives.Ints;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "signedPreKeyPublic")
+public class SignedPreKey extends Extension implements ECPublicKeyContent {
+
+ public SignedPreKey() {
+ super(SignedPreKey.class);
+ }
+
+ public int getId() {
+ return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId"));
+ }
+
+ public void setId(final int id) {
+ this.setAttribute("signedPreKeyId", id);
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "signedPreKeySignature")
+public class SignedPreKeySignature extends Extension implements ByteContent {
+
+ public SignedPreKeySignature() {
+ super(SignedPreKeySignature.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.AXOLOTL)
+package im.conversations.android.xmpp.model.axolotl;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,34 @@
+package im.conversations.android.xmpp.model.bind;
+
+import com.google.common.base.Strings;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Bind extends Extension {
+
+ public Bind() {
+ super(Bind.class);
+ }
+
+ public void setResource(final String resource) {
+ this.addExtension(new Resource(resource));
+ }
+
+ public eu.siacs.conversations.xmpp.Jid getJid() {
+ final var jidExtension = this.getExtension(Jid.class);
+ if (jidExtension == null) {
+ return null;
+ }
+ final var content = jidExtension.getContent();
+ if (Strings.isNullOrEmpty(content)) {
+ return null;
+ }
+ try {
+ return eu.siacs.conversations.xmpp.Jid.ofEscaped(content);
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.bind;
+
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Jid extends Extension {
+
+ public Jid() {
+ super(Jid.class);
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.bind;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Resource extends Extension {
+ public Resource() {
+ super(Resource.class);
+ }
+
+ public Resource(final String resource) {
+ this();
+ this.setContent(resource);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.BIND)
+package im.conversations.android.xmpp.model.bind;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,24 @@
+package im.conversations.android.xmpp.model.bind2;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Bind extends Extension {
+
+ public Bind() {
+ super(Bind.class);
+ }
+
+ public Inline getInline() {
+ return this.getExtension(Inline.class);
+ }
+
+ public Collection<Feature> getInlineFeatures() {
+ final var inline = getInline();
+ return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.bind2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Bound extends Extension {
+ public Bound() {
+ super(Bound.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.bind2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Feature extends Extension {
+
+ public Feature() {
+ super(Feature.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.bind2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Inline extends Extension {
+
+ public Inline() {
+ super(Inline.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.BIND2)
+package im.conversations.android.xmpp.model.bind2;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.blocking;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Block extends Extension {
+
+ public Block() {
+ super(Block.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.blocking;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Blocklist extends Extension {
+ public Blocklist() {
+ super(Blocklist.class);
+ }
+}
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.blocking;
+
+import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Item extends Extension {
+
+ public Item() {
+ super(Item.class);
+ }
+
+ public Jid getJid() {
+ return getAttributeAsJid("jid");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.blocking;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Unblock extends Extension {
+
+ public Unblock() {
+ super(Unblock.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.BLOCKING)
+package im.conversations.android.xmpp.model.blocking;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,32 @@
+package im.conversations.android.xmpp.model.bookmark;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Conference extends Extension {
+
+ public Conference() {
+ super(Conference.class);
+ }
+
+ public boolean isAutoJoin() {
+ return this.getAttributeAsBoolean("autojoin");
+ }
+
+ public String getConferenceName() {
+ return this.getAttribute("name");
+ }
+
+ public void setAutoJoin(boolean autoJoin) {
+ setAttribute("autojoin", autoJoin);
+ }
+
+ public Nick getNick() {
+ return this.getExtension(Nick.class);
+ }
+
+ public Extensions getExtensions() {
+ return this.getExtension(Extensions.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.bookmark;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Extensions extends Extension {
+
+ public Extensions() {
+ super(Extensions.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.bookmark;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Nick extends Extension {
+
+ public Nick() {
+ super(Nick.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.BOOKMARKS2)
+package im.conversations.android.xmpp.model.bookmark;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,43 @@
+package im.conversations.android.xmpp.model.capabilties;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.io.BaseEncoding;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.EntityCapabilities2;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.Hash;
+
+@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES_2)
+public class Capabilities extends Extension {
+
+ public Capabilities() {
+ super(Capabilities.class);
+ }
+
+ public EntityCapabilities2.EntityCaps2Hash getHash() {
+ final Optional<Hash> sha256Hash =
+ Iterables.tryFind(
+ getExtensions(Hash.class), h -> h.getAlgorithm() == Hash.Algorithm.SHA_256);
+ if (sha256Hash.isPresent()) {
+ final String content = sha256Hash.get().getContent();
+ if (Strings.isNullOrEmpty(content)) {
+ return null;
+ }
+ if (BaseEncoding.base64().canDecode(content)) {
+ return EntityCapabilities2.EntityCaps2Hash.of(Hash.Algorithm.SHA_256, content);
+ }
+ }
+ return null;
+ }
+
+ public void setHash(final EntityCapabilities2.EntityCaps2Hash caps2Hash) {
+ final Hash hash = new Hash();
+ hash.setAlgorithm(caps2Hash.algorithm);
+ hash.setContent(caps2Hash.encoded());
+ this.addExtension(hash);
+ }
+}
@@ -0,0 +1,39 @@
+package im.conversations.android.xmpp.model.capabilties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import im.conversations.android.xmpp.model.Extension;
+
+public interface EntityCapabilities {
+
+ <E extends Extension> E getExtension(final Class<E> clazz);
+
+ default NodeHash getCapabilities() {
+ final String node;
+ final im.conversations.android.xmpp.EntityCapabilities.Hash hash;
+ final var capabilities = this.getExtension(Capabilities.class);
+ final var legacyCapabilities = this.getExtension(LegacyCapabilities.class);
+ if (capabilities != null) {
+ node = null;
+ hash = capabilities.getHash();
+ } else if (legacyCapabilities != null) {
+ node = legacyCapabilities.getNode();
+ hash = legacyCapabilities.getHash();
+ } else {
+ return null;
+ }
+ return hash == null ? null : new NodeHash(node, hash);
+ }
+
+ class NodeHash {
+ public final String node;
+ public final im.conversations.android.xmpp.EntityCapabilities.Hash hash;
+
+ private NodeHash(
+ @Nullable String node,
+ @NonNull final im.conversations.android.xmpp.EntityCapabilities.Hash hash) {
+ this.node = node;
+ this.hash = hash;
+ }
+ }
+}
@@ -0,0 +1,45 @@
+package im.conversations.android.xmpp.model.capabilties;
+
+import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.EntityCapabilities;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "c", namespace = Namespace.ENTITY_CAPABILITIES)
+public class LegacyCapabilities extends Extension {
+
+ private static final String HASH_ALGORITHM = "sha-1";
+
+ public LegacyCapabilities() {
+ super(LegacyCapabilities.class);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+
+ public EntityCapabilities.EntityCapsHash getHash() {
+ final String hash = getAttribute("hash");
+ final String ver = getAttribute("ver");
+ if (Strings.isNullOrEmpty(ver) || Strings.isNullOrEmpty(hash)) {
+ return null;
+ }
+ if (HASH_ALGORITHM.equals(hash) && BaseEncoding.base64().canDecode(ver)) {
+ return EntityCapabilities.EntityCapsHash.of(ver);
+ } else {
+ return null;
+ }
+ }
+
+ public void setNode(final String node) {
+ this.setAttribute("node", node);
+ }
+
+ public void setHash(final EntityCapabilities.EntityCapsHash hash) {
+ this.setAttribute("hash", HASH_ALGORITHM);
+ this.setAttribute("ver", hash.encoded());
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.carbons;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Enable extends Extension {
+
+ public Enable() {
+ super(Enable.class);
+ }
+}
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.carbons;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.forward.Forwarded;
+
+@XmlElement
+public class Received extends Extension {
+
+ public Received() {
+ super(Received.class);
+ }
+
+ public Forwarded getForwarded() {
+ return this.getExtension(Forwarded.class);
+ }
+}
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.carbons;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.forward.Forwarded;
+
+@XmlElement
+public class Sent extends Extension {
+
+ public Sent() {
+ super(Sent.class);
+ }
+
+ public Forwarded getForwarded() {
+ return this.getExtension(Forwarded.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.CARBONS)
+package im.conversations.android.xmpp.model.carbons;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,24 @@
+package im.conversations.android.xmpp.model.correction;
+
+import androidx.annotation.NonNull;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.LAST_MESSAGE_CORRECTION)
+public class Replace extends Extension {
+
+ public Replace() {
+ super(Replace.class);
+ }
+
+ public String getId() {
+ return Strings.emptyToNull(this.getAttribute("id"));
+ }
+
+ public void setId(@NonNull final String id) {
+ this.setAttribute("id", id);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.csi;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Active extends StreamElement {
+
+ public Active() {
+ super(Active.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.csi;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamFeature;
+
+@XmlElement(name = "csi")
+public class ClientStateIndication extends StreamFeature {
+
+ public ClientStateIndication() {
+ super(ClientStateIndication.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.csi;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Inactive extends StreamElement {
+
+ public Inactive() {
+ super(Inactive.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.CSI)
+package im.conversations.android.xmpp.model.csi;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,110 @@
+package im.conversations.android.xmpp.model.data;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Map;
+
+@XmlElement(name = "x")
+public class Data extends Extension {
+
+ private static final String FORM_TYPE = "FORM_TYPE";
+ private static final String FIELD_TYPE_HIDDEN = "hidden";
+ private static final String FORM_TYPE_SUBMIT = "submit";
+
+ public Data() {
+ super(Data.class);
+ }
+
+ public String getFormType() {
+ final var fields = this.getExtensions(Field.class);
+ final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName()));
+ return Iterables.getFirst(formTypeField.getValues(), null);
+ }
+
+ public Collection<Field> getFields() {
+ return Collections2.filter(
+ this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
+ }
+
+ private void addField(final String name, final Object value) {
+ addField(name, value, null);
+ }
+
+ private void addField(final String name, final Object value, final String type) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null values are not supported on data fields");
+ }
+ final var field = this.addExtension(new Field());
+ field.setFieldName(name);
+ if (type != null) {
+ field.setType(type);
+ }
+ if (value instanceof Collection) {
+ for (final Object subValue : (Collection<?>) value) {
+ if (subValue instanceof String) {
+ final var valueExtension = field.addExtension(new Value());
+ valueExtension.setContent((String) subValue);
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is not a supported field value",
+ subValue.getClass().getSimpleName()));
+ }
+ }
+ } else {
+ final var valueExtension = field.addExtension(new Value());
+ if (value instanceof String) {
+ valueExtension.setContent((String) value);
+ } else if (value instanceof Integer) {
+ valueExtension.setContent(String.valueOf(value));
+ } else if (value instanceof Boolean) {
+ valueExtension.setContent(Boolean.TRUE.equals(value) ? "1" : "0");
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is not a supported field value",
+ value.getClass().getSimpleName()));
+ }
+ }
+ }
+
+ private void setFormType(final String formType) {
+ this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN);
+ }
+
+ public static Data of(final String formType, final Map<String, Object> values) {
+ final var data = new Data();
+ data.setType(FORM_TYPE_SUBMIT);
+ data.setFormType(formType);
+ for (final Map.Entry<String, Object> entry : values.entrySet()) {
+ data.addField(entry.getKey(), entry.getValue());
+ }
+ return data;
+ }
+
+ public Data submit(final Map<String, Object> values) {
+ final String formType = this.getFormType();
+ final var submit = new Data();
+ submit.setType(FORM_TYPE_SUBMIT);
+ if (formType != null) {
+ submit.setFormType(formType);
+ }
+ for (final Field existingField : this.getFields()) {
+ final var fieldName = existingField.getFieldName();
+ final Object submittedValue = values.get(fieldName);
+ if (submittedValue != null) {
+ submit.addField(fieldName, submittedValue);
+ } else {
+ submit.addField(fieldName, existingField.getValues());
+ }
+ }
+ return submit;
+ }
+
+ private void setType(final String type) {
+ this.setAttribute("type", type);
+ }
+}
@@ -0,0 +1,29 @@
+package im.conversations.android.xmpp.model.data;
+import eu.siacs.conversations.xml.Element;
+import com.google.common.collect.Collections2;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+
+@XmlElement
+public class Field extends Extension {
+ public Field() {
+ super(Field.class);
+ }
+
+ public String getFieldName() {
+ return getAttribute("var");
+ }
+
+ public Collection<String> getValues() {
+ return Collections2.transform(getExtensions(Value.class), Element::getContent);
+ }
+
+ public void setFieldName(String name) {
+ this.setAttribute("var", name);
+ }
+
+ public void setType(String type) {
+ this.setAttribute("type", type);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.data;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Option extends Extension {
+
+ public Option() {
+ super(Option.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.data;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Value extends Extension {
+
+ public Value() {
+ super(Value.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.DATA)
+package im.conversations.android.xmpp.model.data;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,30 @@
+package im.conversations.android.xmpp.model.delay;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.Timestamps;
+import im.conversations.android.xmpp.model.Extension;
+import java.text.ParseException;
+import java.time.Instant;
+
+@XmlElement(namespace = Namespace.DELAY)
+public class Delay extends Extension {
+
+ public Delay() {
+ super(Delay.class);
+ }
+
+ public Instant getStamp() {
+ final var stamp = this.getAttribute("stamp");
+ if (Strings.isNullOrEmpty(stamp)) {
+ return null;
+ }
+ try {
+ return Instant.ofEpochMilli(Timestamps.parse(stamp));
+ } catch (final IllegalArgumentException | ParseException e) {
+ return null;
+ }
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.disco.external;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Service extends Extension {
+
+ public Service() {
+ super(Service.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.disco.external;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Services extends Extension {
+
+ public Services() {
+ super(Services.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.EXTERNAL_SERVICE_DISCOVERY)
+package im.conversations.android.xmpp.model.disco.external;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,19 @@
+package im.conversations.android.xmpp.model.disco.info;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Feature extends Extension {
+ public Feature() {
+ super(Feature.class);
+ }
+
+ public String getVar() {
+ return this.getAttribute("var");
+ }
+
+ public void setVar(final String feature) {
+ this.setAttribute("var", feature);
+ }
+}
@@ -0,0 +1,39 @@
+package im.conversations.android.xmpp.model.disco.info;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Identity extends Extension {
+ public Identity() {
+ super(Identity.class);
+ }
+
+ public String getCategory() {
+ return this.getAttribute("category");
+ }
+
+ public String getType() {
+ return this.getAttribute("type");
+ }
+
+ public String getLang() {
+ return this.getAttribute("xml:lang");
+ }
+
+ public String getIdentityName() {
+ return this.getAttribute("name");
+ }
+
+ public void setIdentityName(final String name) {
+ this.setAttribute("name", name);
+ }
+
+ public void setType(final String type) {
+ this.setAttribute("type", type);
+ }
+
+ public void setCategory(final String category) {
+ this.setAttribute("category", category);
+ }
+}
@@ -0,0 +1,38 @@
+package im.conversations.android.xmpp.model.disco.info;
+
+import com.google.common.collect.Iterables;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+
+@XmlElement(name = "query")
+public class InfoQuery extends Extension {
+
+ public InfoQuery() {
+ super(InfoQuery.class);
+ }
+
+ public void setNode(final String node) {
+ this.setAttribute("node", node);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+
+ public Collection<Feature> getFeatures() {
+ return this.getExtensions(Feature.class);
+ }
+
+ public boolean hasFeature(final String feature) {
+ return Iterables.any(getFeatures(), f -> feature.equals(f.getVar()));
+ }
+
+ public Collection<Identity> getIdentities() {
+ return this.getExtensions(Identity.class);
+ }
+
+ public boolean hasIdentityWithCategory(final String category) {
+ return Iterables.any(getIdentities(), i -> category.equals(i.getCategory()));
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.DISCO_INFO)
+package im.conversations.android.xmpp.model.disco.info;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,22 @@
+package im.conversations.android.xmpp.model.disco.items;
+
+import androidx.annotation.Nullable;
+
+import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Item extends Extension {
+ public Item() {
+ super(Item.class);
+ }
+
+ public Jid getJid() {
+ return getAttributeAsJid("jid");
+ }
+
+ public @Nullable String getNode() {
+ return this.getAttribute("node");
+ }
+}
@@ -0,0 +1,19 @@
+package im.conversations.android.xmpp.model.disco.items;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "query")
+public class ItemsQuery extends Extension {
+ public ItemsQuery() {
+ super(ItemsQuery.class);
+ }
+
+ public void setNode(final String node) {
+ this.setAttribute("node", node);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.DISCO_ITEMS)
+package im.conversations.android.xmpp.model.disco.items;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,188 @@
+package im.conversations.android.xmpp.model.error;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+public abstract class Condition extends Extension {
+
+ private Condition(Class<? extends Condition> clazz) {
+ super(clazz);
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class BadRequest extends Condition {
+
+ public BadRequest() {
+ super(BadRequest.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class Conflict extends Condition {
+
+ public Conflict() {
+ super(Conflict.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class FeatureNotImplemented extends Condition {
+
+ public FeatureNotImplemented() {
+ super(FeatureNotImplemented.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class Forbidden extends Condition {
+
+ public Forbidden() {
+ super(Forbidden.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class Gone extends Condition {
+
+ public Gone() {
+ super(Gone.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class InternalServerError extends Condition {
+
+ public InternalServerError() {
+ super(InternalServerError.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class ItemNotFound extends Condition {
+
+ public ItemNotFound() {
+ super(ItemNotFound.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class JidMalformed extends Condition {
+
+ public JidMalformed() {
+ super(JidMalformed.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class NotAcceptable extends Condition {
+
+ public NotAcceptable() {
+ super(NotAcceptable.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class NotAllowed extends Condition {
+
+ public NotAllowed() {
+ super(NotAllowed.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class NotAuthorized extends Condition {
+
+ public NotAuthorized() {
+ super(NotAuthorized.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class PaymentRequired extends Condition {
+
+ public PaymentRequired() {
+ super(PaymentRequired.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class RecipientUnavailable extends Condition {
+
+ public RecipientUnavailable() {
+ super(RecipientUnavailable.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class Redirect extends Condition {
+
+ public Redirect() {
+ super(Redirect.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class RegistrationRequired extends Condition {
+
+ public RegistrationRequired() {
+ super(RegistrationRequired.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class RemoteServerNotFound extends Condition {
+
+ public RemoteServerNotFound() {
+ super(RemoteServerNotFound.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class RemoteServerTimeout extends Condition {
+
+ public RemoteServerTimeout() {
+ super(RemoteServerTimeout.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class ResourceConstraint extends Condition {
+
+ public ResourceConstraint() {
+ super(ResourceConstraint.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class ServiceUnavailable extends Condition {
+
+ public ServiceUnavailable() {
+ super(ServiceUnavailable.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class SubscriptionRequired extends Condition {
+
+ public SubscriptionRequired() {
+ super(SubscriptionRequired.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class UndefinedCondition extends Condition {
+
+ public UndefinedCondition() {
+ super(UndefinedCondition.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.STANZAS)
+ public static class UnexpectedRequest extends Condition {
+
+ public UnexpectedRequest() {
+ super(UnexpectedRequest.class);
+ }
+ }
+}
@@ -0,0 +1,55 @@
+package im.conversations.android.xmpp.model.error;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Locale;
+import eu.siacs.conversations.xml.Namespace;
+
+@XmlElement(namespace = Namespace.JABBER_CLIENT)
+public class Error extends Extension {
+
+ public Error() {
+ super(Error.class);
+ }
+
+ public Condition getCondition() {
+ return this.getExtension(Condition.class);
+ }
+
+ public void setCondition(final Condition condition) {
+ this.addExtension(condition);
+ }
+
+ public Text getText() {
+ return this.getExtension(Text.class);
+ }
+
+ public String getTextAsString() {
+ final var text = getText();
+ return text == null ? null : text.getContent();
+ }
+
+ public void setType(final Type type) {
+ this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT));
+ }
+
+ public void addExtensions(final Extension[] extensions) {
+ for (final Extension extension : extensions) {
+ this.addExtension(extension);
+ }
+ }
+
+ public enum Type {
+ MODIFY,
+ CANCEL,
+ AUTH,
+ WAIT
+ }
+
+ public static class Extension extends im.conversations.android.xmpp.model.Extension {
+
+ public Extension(Class<? extends im.conversations.android.xmpp.model.Extension> clazz) {
+ super(clazz);
+ }
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.error;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.STANZAS)
+public class Text extends Extension {
+
+ public Text() {
+ super(Text.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.fast;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Fast extends Extension {
+ public Fast() {
+ super(Fast.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.fast;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Mechanism extends Extension {
+ public Mechanism() {
+ super(Mechanism.class);
+ }
+}
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.fast;
+
+import eu.siacs.conversations.crypto.sasl.HashedToken;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class RequestToken extends Extension {
+ public RequestToken() {
+ super(RequestToken.class);
+ }
+
+ public RequestToken(final HashedToken.Mechanism mechanism) {
+ this();
+ this.setAttribute("mechanism", mechanism.name());
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.fast;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Token extends Extension {
+
+ public Token() {
+ super(Token.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.FAST)
+package im.conversations.android.xmpp.model.fast;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,18 @@
+package im.conversations.android.xmpp.model.forward;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.stanza.Message;
+
+@XmlElement(namespace = Namespace.FORWARD)
+public class Forwarded extends Extension {
+
+ public Forwarded() {
+ super(Forwarded.class);
+ }
+
+ public Message getMessage() {
+ return this.getExtension(Message.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.hints;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Store extends Extension {
+
+ public Store() {
+ super(Store.class);
+ }
+}
@@ -0,0 +1,6 @@
+@XmlPackage(namespace = Namespace.HINTS)
+package im.conversations.android.xmpp.model.hints;
+
+import eu.siacs.conversations.xml.Namespace;
+
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Body extends Extension {
+
+ public Body() {
+ super(Body.class);
+ }
+
+ public Body(final String content) {
+ this();
+ setContent(content);
+ }
+
+ public String getLang() {
+ return this.getAttribute("xml:lang");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Priority extends Extension {
+
+ public Priority() {
+ super(Priority.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Show extends Extension {
+ public Show() {
+ super(Show.class);
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Status extends Extension {
+
+
+ public Status() {
+ super(Status.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Subject extends Extension {
+
+ public Subject() {
+ super(Subject.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Thread extends Extension {
+
+ public Thread() {
+ super(Thread.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.JABBER_CLIENT)
+package im.conversations.android.xmpp.model.jabber;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -1,4 +1,4 @@
-package eu.siacs.conversations.xmpp.jingle.stanzas;
+package im.conversations.android.xmpp.model.jingle;
import androidx.annotation.NonNull;
@@ -10,66 +10,38 @@ import com.google.common.collect.ImmutableMap;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
+import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
-import java.util.Map;
-
-public class JinglePacket extends IqPacket {
-
- private JinglePacket() {
- super();
- }
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
- public JinglePacket(final Action action, final String sessionId) {
- super(TYPE.SET);
- final Element jingle = addChild("jingle", Namespace.JINGLE);
- jingle.setAttribute("sid", sessionId);
- jingle.setAttribute("action", action.toString());
- }
+import java.util.Map;
- public static JinglePacket upgrade(final IqPacket iqPacket) {
- Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE));
- Preconditions.checkArgument(iqPacket.getType() == TYPE.SET);
- final JinglePacket jinglePacket = new JinglePacket();
- jinglePacket.setAttributes(iqPacket.getAttributes());
- jinglePacket.setChildren(iqPacket.getChildren());
- return jinglePacket;
- }
+@XmlElement
+public class Jingle extends Extension {
- // TODO deprecate this somehow and make file transfer fail if there are multiple (or something)
- public Content getJingleContent() {
- final Element content = getJingleChild("content");
- return content == null ? null : Content.upgrade(content);
+ public Jingle() {
+ super(Jingle.class);
}
- public Group getGroup() {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- final Element group = jingle.findChild("group", Namespace.JINGLE_APPS_GROUPING);
- return group == null ? null : Group.upgrade(group);
+ public Jingle(final Action action, final String sessionId) {
+ this();
+ this.setAttribute("sid", sessionId);
+ this.setAttribute("action", action.toString());
}
- public void addGroup(final Group group) {
- this.addJingleChild(group);
- }
-
- public Map<String, Content> getJingleContents() {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
- for (final Element child : jingle.getChildren()) {
- if ("content".equals(child.getName())) {
- final Content content = Content.upgrade(child);
- builder.put(content.getContentName(), content);
- }
- }
- return builder.build();
+ public String getSessionId() {
+ return this.getAttribute("sid");
}
- public void addJingleContent(final Content content) { // take content interface
- addJingleChild(content);
+ public Action getAction() {
+ return Action.of(this.getAttribute("action"));
}
public ReasonWrapper getReason() {
- final Element reasonElement = getJingleChild("reason");
+ final Element reasonElement = this.findChild("reason");
if (reasonElement == null) {
return new ReasonWrapper(Reason.UNKNOWN, null);
}
@@ -86,8 +58,7 @@ public class JinglePacket extends IqPacket {
}
public void setReason(final Reason reason, final String text) {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- final Element reasonElement = jingle.addChild("reason");
+ final Element reasonElement = this.addChild("reason");
reasonElement.addChild(reason.toString());
if (!Strings.isNullOrEmpty(text)) {
reasonElement.addChild("text").setContent(text);
@@ -97,31 +68,44 @@ public class JinglePacket extends IqPacket {
// RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise
public void setInitiator(final Jid initiator) {
Preconditions.checkArgument(initiator.isFullJid(), "initiator should be a full JID");
- findChild("jingle", Namespace.JINGLE).setAttribute("initiator", initiator);
+ this.setAttribute("initiator", initiator);
}
// RECOMMENDED for session-accept, NOT RECOMMENDED otherwise
- public void setResponder(Jid responder) {
+ public void setResponder(final Jid responder) {
Preconditions.checkArgument(responder.isFullJid(), "responder should be a full JID");
- findChild("jingle", Namespace.JINGLE).setAttribute("responder", responder);
+ this.setAttribute("responder", responder);
}
- public Element getJingleChild(final String name) {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- return jingle == null ? null : jingle.findChild(name);
+ public Group getGroup() {
+ final Element group = this.findChild("group", Namespace.JINGLE_APPS_GROUPING);
+ return group == null ? null : Group.upgrade(group);
}
- public void addJingleChild(final Element child) {
- final Element jingle = findChild("jingle", Namespace.JINGLE);
- jingle.addChild(child);
+ public void addGroup(final Group group) {
+ this.addChild(group);
}
- public String getSessionId() {
- return findChild("jingle", Namespace.JINGLE).getAttribute("sid");
+ // TODO deprecate this somehow and make file transfer fail if there are multiple (or something)
+ public Content getJingleContent() {
+ final Element content = this.findChild("content");
+ return content == null ? null : Content.upgrade(content);
}
- public Action getAction() {
- return Action.of(findChild("jingle", Namespace.JINGLE).getAttribute("action"));
+ public void addJingleContent(final Content content) { // take content interface
+ this.addChild(content);
+ }
+
+
+ public Map<String, Content> getJingleContents() {
+ ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
+ for (final Element child : this.getChildren()) {
+ if ("content".equals(child.getName())) {
+ final Content content = Content.upgrade(child);
+ builder.put(content.getContentName(), content);
+ }
+ }
+ return builder.build();
}
public enum Action {
@@ -0,0 +1,44 @@
+package im.conversations.android.xmpp.model.jingle.error;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.error.Error;
+
+public abstract class JingleCondition extends Error.Extension {
+
+ private JingleCondition(Class<? extends JingleCondition> clazz) {
+ super(clazz);
+ }
+
+ @XmlElement(namespace = Namespace.JINGLE_ERRORS)
+ public static class OutOfOrder extends JingleCondition {
+
+ public OutOfOrder() {
+ super(OutOfOrder.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.JINGLE_ERRORS)
+ public static class TieBreak extends JingleCondition {
+
+ public TieBreak() {
+ super(TieBreak.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.JINGLE_ERRORS)
+ public static class UnknownSession extends JingleCondition {
+
+ public UnknownSession() {
+ super(UnknownSession.class);
+ }
+ }
+
+ @XmlElement(namespace = Namespace.JINGLE_ERRORS)
+ public static class UnsupportedInfo extends JingleCondition {
+
+ public UnsupportedInfo() {
+ super(UnsupportedInfo.class);
+ }
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.JINGLE)
+package im.conversations.android.xmpp.model.jingle;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Accept extends JingleMessage {
+
+ public Accept() {
+ super(Accept.class);
+ }
+}
@@ -0,0 +1,14 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public abstract class JingleMessage extends Extension {
+
+ public JingleMessage(Class<? extends JingleMessage> clazz) {
+ super(clazz);
+ }
+
+ public String getSessionId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,24 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import com.google.common.primitives.Ints;
+
+import eu.siacs.conversations.xml.Element;
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Proceed extends JingleMessage {
+
+ public Proceed() {
+ super(Proceed.class);
+ }
+
+ public Integer getDeviceId() {
+ // TODO use proper namespace and create extension
+ final Element device = this.findChild("device");
+ final String id = device == null ? null : device.getAttribute("id");
+ if (id == null) {
+ return null;
+ }
+ return Ints.tryParse(id);
+ }
+}
@@ -0,0 +1,38 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import com.google.common.collect.ImmutableList;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription;
+import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
+import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
+import im.conversations.android.annotation.XmlElement;
+
+import java.util.List;
+
+@XmlElement
+public class Propose extends JingleMessage {
+
+ public Propose() {
+ super(Propose.class);
+ }
+
+ public List<GenericDescription> getDescriptions() {
+ final ImmutableList.Builder<GenericDescription> builder = new ImmutableList.Builder<>();
+ // TODO create proper extension for description
+ for (final Element child : this.children) {
+ if ("description".equals(child.getName())) {
+ final String namespace = child.getNamespace();
+ if (Namespace.JINGLE_APPS_FILE_TRANSFER.contains(namespace)) {
+ builder.add(FileTransferDescription.upgrade(child));
+ } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
+ builder.add(RtpDescription.upgrade(child));
+ } else {
+ builder.add(GenericDescription.upgrade(child));
+ }
+ }
+ }
+ return builder.build();
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Reject extends JingleMessage {
+
+ public Reject() {
+ super(Reject.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.jmi;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Retract extends JingleMessage {
+
+ public Retract() {
+ super(Retract.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.JINGLE_MESSAGE)
+package im.conversations.android.xmpp.model.jmi;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,15 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class End extends Extension {
+ public End() {
+ super(End.class);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Fin extends Extension {
+
+ public Fin() {
+ super(Fin.class);
+ }
+
+ public boolean isComplete() {
+ return this.getAttributeAsBoolean("complete");
+ }
+}
@@ -0,0 +1,20 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Metadata extends Extension {
+
+ public Metadata() {
+ super(Metadata.class);
+ }
+
+ public Start getStart() {
+ return this.getExtension(Start.class);
+ }
+
+ public End getEnd() {
+ return this.getExtension(End.class);
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Query extends Extension {
+
+ public Query() {
+ super(Query.class);
+ }
+
+ public void setQueryId(final String id) {
+ this.setAttribute("queryid", id);
+ }
+}
@@ -0,0 +1,25 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.forward.Forwarded;
+
+@XmlElement
+public class Result extends Extension {
+
+ public Result() {
+ super(Result.class);
+ }
+
+ public Forwarded getForwarded() {
+ return this.getExtension(Forwarded.class);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public String getQueryId() {
+ return this.getAttribute("queryid");
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Start extends Extension {
+
+ public Start() {
+ super(Start.class);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.MESSAGE_ARCHIVE_MANAGEMENT)
+package im.conversations.android.xmpp.model.mam;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.markers;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Displayed extends Extension {
+
+ public Displayed() {
+ super(Displayed.class);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.markers;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
+
+@XmlElement
+public class Markable extends DeliveryReceiptRequest {
+
+ public Markable() {
+ super(Markable.class);
+ }
+}
@@ -0,0 +1,20 @@
+package im.conversations.android.xmpp.model.markers;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.DeliveryReceipt;
+
+@XmlElement
+public class Received extends DeliveryReceipt {
+
+ public Received() {
+ super(Received.class);
+ }
+
+ public void setId(String id) {
+ this.setAttribute("id", id);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.CHAT_MARKERS)
+package im.conversations.android.xmpp.model.markers;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.mds;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.MDS_DISPLAYED)
+public class Displayed extends Extension {
+ public Displayed() {
+ super(Displayed.class);
+ }
+}
@@ -0,0 +1,9 @@
+package im.conversations.android.xmpp.model.muc;
+
+public enum Affiliation {
+ OWNER,
+ ADMIN,
+ MEMBER,
+ OUTCAST,
+ NONE;
+}
@@ -0,0 +1,20 @@
+package im.conversations.android.xmpp.model.muc;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class History extends Extension {
+
+ public History() {
+ super(History.class);
+ }
+
+ public void setMaxChars(final int maxChars) {
+ this.setAttribute("maxchars", maxChars);
+ }
+
+ public void setMaxStanzas(final int maxStanzas) {
+ this.setAttribute("maxstanzas", maxStanzas);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.muc;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "x")
+public class MultiUserChat extends Extension {
+
+ public MultiUserChat() {
+ super(MultiUserChat.class);
+ }
+}
@@ -0,0 +1,8 @@
+package im.conversations.android.xmpp.model.muc;
+
+public enum Role {
+ MODERATOR,
+ VISITOR,
+ PARTICIPANT,
+ NONE
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.MUC)
+package im.conversations.android.xmpp.model.muc;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,58 @@
+package im.conversations.android.xmpp.model.muc.user;
+
+import android.util.Log;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.xmpp.Jid;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.muc.Affiliation;
+import im.conversations.android.xmpp.model.muc.Role;
+
+import java.util.Locale;
+
+@XmlElement
+public class Item extends Extension {
+
+
+ public Item() {
+ super(Item.class);
+ }
+
+ public Affiliation getAffiliation() {
+ final var affiliation = this.getAttribute("affiliation");
+ if (Strings.isNullOrEmpty(affiliation)) {
+ return Affiliation.NONE;
+ }
+ try {
+ return Affiliation.valueOf(affiliation.toUpperCase(Locale.ROOT));
+ } catch (final IllegalArgumentException e) {
+ Log.d(Config.LOGTAG,"could not parse affiliation "+affiliation);
+ return Affiliation.NONE;
+ }
+ }
+
+ public Role getRole() {
+ final var role = this.getAttribute("role");
+ if (Strings.isNullOrEmpty(role)) {
+ return Role.NONE;
+ }
+ try {
+ return Role.valueOf(role.toUpperCase(Locale.ROOT));
+ } catch (final IllegalArgumentException e) {
+ Log.d(Config.LOGTAG,"could not parse role "+ role);
+ return Role.NONE;
+ }
+ }
+
+ public String getNick() {
+ return this.getAttribute("nick");
+ }
+
+ public Jid getJid() {
+ return this.getAttributeAsJid("jid");
+ }
+}
@@ -0,0 +1,27 @@
+package im.conversations.android.xmpp.model.muc.user;
+
+import com.google.common.collect.Collections2;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Objects;
+
+@XmlElement(name = "x")
+public class MucUser extends Extension {
+
+ public static final int STATUS_CODE_SELF_PRESENCE = 110;
+
+ public MucUser() {
+ super(MucUser.class);
+ }
+
+ public Item getItem() {
+ return this.getExtension(Item.class);
+ }
+
+ public Collection<Integer> getStatus() {
+ return Collections2.filter(
+ Collections2.transform(getExtensions(Status.class), Status::getCode),
+ Objects::nonNull);
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.muc.user;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Status extends Extension {
+
+ public Status() {
+ super(Status.class);
+ }
+
+ public Integer getCode() {
+ return this.getOptionalIntAttribute("code").orNull();
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.MUC_USER)
+package im.conversations.android.xmpp.model.muc.user;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.nick;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.NICK)
+public class Nick extends Extension {
+
+ public Nick() {
+ super(Nick.class);
+ }
+}
@@ -0,0 +1,19 @@
+package im.conversations.android.xmpp.model.occupant;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.OCCUPANT_ID)
+public class OccupantId extends Extension {
+
+ public OccupantId() {
+ super(OccupantId.class);
+ }
+
+ public String getId() {
+ return Strings.emptyToNull(this.getAttribute("id"));
+ }
+}
@@ -0,0 +1,18 @@
+package im.conversations.android.xmpp.model.oob;
+
+import com.google.common.base.Strings;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "x")
+public class OutOfBandData extends Extension {
+
+ public OutOfBandData() {
+ super(OutOfBandData.class);
+ }
+
+ public String getURL() {
+ final URL url = this.getExtension(URL.class);
+ return url == null ? null : Strings.emptyToNull(url.getContent());
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.oob;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "url")
+public class URL extends Extension {
+
+ public URL() {
+ super(URL.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.OOB)
+package im.conversations.android.xmpp.model.oob;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.pars;
+
+import im.conversations.android.annotation.XmlElement;
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.PARS)
+public class PreAuth extends Extension {
+
+ public PreAuth() {
+ super(PreAuth.class);
+ }
+
+ public void setToken(final String token) {
+ this.setAttribute("token", token);
+ }
+}
@@ -0,0 +1,14 @@
+package im.conversations.android.xmpp.model.pgp;
+
+import eu.siacs.conversations.xml.Namespace;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "x", namespace = Namespace.PGP_ENCRYPTED)
+public class Encrypted extends Extension {
+
+ public Encrypted() {
+ super(Encrypted.class);
+ }
+}
@@ -0,0 +1,15 @@
+package im.conversations.android.xmpp.model.pgp;
+
+import eu.siacs.conversations.xml.Namespace;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "x",namespace = Namespace.PGP_SIGNED)
+public class Signed extends Extension {
+
+
+ public Signed() {
+ super(Signed.class);
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.ping;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(namespace = Namespace.PING)
+public class Ping extends Extension {
+
+ public Ping() {
+ super(Ping.class);
+ }
+}
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public interface Item {
+
+ <T extends Extension> T getExtension(final Class<T> clazz);
+
+ String getId();
+}
@@ -0,0 +1,52 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.pubsub.event.Retract;
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+public interface Items {
+
+ Collection<? extends Item> getItems();
+
+ String getNode();
+
+ Collection<Retract> getRetractions();
+
+ default <T extends Extension> Map<String, T> getItemMap(final Class<T> clazz) {
+ final ImmutableMap.Builder<String, T> builder = ImmutableMap.builder();
+ for (final Item item : getItems()) {
+ final var id = item.getId();
+ final T extension = item.getExtension(clazz);
+ if (extension == null || Strings.isNullOrEmpty(id)) {
+ continue;
+ }
+ builder.put(id, extension);
+ }
+ return builder.buildKeepingLast();
+ }
+
+ default <T extends Extension> T getItemOrThrow(final String id, final Class<T> clazz) {
+ final var map = getItemMap(clazz);
+ final var item = map.get(id);
+ if (item == null) {
+ throw new NoSuchElementException(
+ String.format("An item with id %s does not exist", id));
+ }
+ return item;
+ }
+
+ default <T extends Extension> T getFirstItem(final Class<T> clazz) {
+ final var map = getItemMap(clazz);
+ return Iterables.getFirst(map.values(), null);
+ }
+
+ default <T extends Extension> T getOnlyItem(final Class<T> clazz) {
+ final var map = getItemMap(clazz);
+ return Iterables.getOnlyElement(map.values());
+ }
+}
@@ -0,0 +1,64 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.pubsub.event.Retract;
+import java.util.Collection;
+
+@XmlElement(name = "pubsub")
+public class PubSub extends Extension {
+
+ public PubSub() {
+ super(PubSub.class);
+ }
+
+ public Items getItems() {
+ return this.getExtension(ItemsWrapper.class);
+ }
+
+ @XmlElement(name = "items")
+ public static class ItemsWrapper extends Extension implements Items {
+
+ public ItemsWrapper() {
+ super(ItemsWrapper.class);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+
+ public Collection<? extends im.conversations.android.xmpp.model.pubsub.Item> getItems() {
+ return this.getExtensions(Item.class);
+ }
+
+ public Collection<Retract> getRetractions() {
+ return this.getExtensions(Retract.class);
+ }
+
+ public void setNode(String node) {
+ this.setAttribute("node", node);
+ }
+
+ public void setMaxItems(final int maxItems) {
+ this.setAttribute("max_items", maxItems);
+ }
+ }
+
+ @XmlElement(name = "item")
+ public static class Item extends Extension
+ implements im.conversations.android.xmpp.model.pubsub.Item {
+
+ public Item() {
+ super(Item.class);
+ }
+
+ @Override
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public void setId(String itemId) {
+ this.setAttribute("id", itemId);
+ }
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Publish extends Extension {
+
+ public Publish() {
+ super(Publish.class);
+ }
+
+ public void setNode(String node) {
+ this.setAttribute("node", node);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.data.Data;
+
+@XmlElement
+public class PublishOptions extends Extension {
+
+ public PublishOptions() {
+ super(PublishOptions.class);
+ }
+
+ public static PublishOptions of(NodeConfiguration nodeConfiguration) {
+ final var publishOptions = new PublishOptions();
+ publishOptions.addExtension(Data.of(Namespace.PUBSUB_PUBLISH_OPTIONS, nodeConfiguration));
+ return publishOptions;
+ }
+}
@@ -0,0 +1,20 @@
+package im.conversations.android.xmpp.model.pubsub;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Retract extends Extension {
+
+ public Retract() {
+ super(Retract.class);
+ }
+
+ public void setNode(String node) {
+ this.setAttribute("node", node);
+ }
+
+ public void setNotify(boolean notify) {
+ this.setAttribute("notify", notify ? 1 : 0);
+ }
+}
@@ -0,0 +1,19 @@
+package im.conversations.android.xmpp.model.pubsub.error;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+public abstract class PubSubError extends Extension {
+
+ private PubSubError(Class<? extends PubSubError> clazz) {
+ super(clazz);
+ }
+
+ @XmlElement
+ public static class PreconditionNotMet extends PubSubError {
+
+ public PreconditionNotMet() {
+ super(PreconditionNotMet.class);
+ }
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.PUBSUB_ERROR)
+package im.conversations.android.xmpp.model.pubsub.error;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,56 @@
+package im.conversations.android.xmpp.model.pubsub.event;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.pubsub.Items;
+import java.util.Collection;
+
+@XmlElement
+public class Event extends Extension {
+
+ public Event() {
+ super(Event.class);
+ }
+
+ public Items getItems() {
+ return this.getExtension(ItemsWrapper.class);
+ }
+
+ public Purge getPurge() {
+ return this.getExtension(Purge.class);
+ }
+
+ @XmlElement(name = "items")
+ public static class ItemsWrapper extends Extension implements Items {
+
+ public ItemsWrapper() {
+ super(ItemsWrapper.class);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+
+ public Collection<? extends im.conversations.android.xmpp.model.pubsub.Item> getItems() {
+ return this.getExtensions(Item.class);
+ }
+
+ public Collection<Retract> getRetractions() {
+ return this.getExtensions(Retract.class);
+ }
+ }
+
+ @XmlElement(name = "item")
+ public static class Item extends Extension
+ implements im.conversations.android.xmpp.model.pubsub.Item {
+
+ public Item() {
+ super(Item.class);
+ }
+
+ @Override
+ public String getId() {
+ return this.getAttribute("id");
+ }
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.pubsub.event;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Purge extends Extension {
+
+ public Purge() {
+ super(Purge.class);
+ }
+
+ public String getNode() {
+ return this.getAttribute("node");
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.pubsub.event;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Retract extends Extension {
+
+ public Retract() {
+ super(Retract.class);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.PUBSUB_EVENT)
+package im.conversations.android.xmpp.model.pubsub.event;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.pubsub.owner;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.data.Data;
+
+@XmlElement
+public class Configure extends Extension {
+
+ public Configure() {
+ super(Configure.class);
+ }
+
+ public void setNode(final String node) {
+ this.setAttribute("node", node);
+ }
+
+ public Data getData() {
+ return this.getExtension(Data.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.pubsub.owner;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "pubsub")
+public class PubSubOwner extends Extension {
+
+ public PubSubOwner() {
+ super(PubSubOwner.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.PUBSUB_OWNER)
+package im.conversations.android.xmpp.model.pubsub.owner;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.PUBSUB)
+package im.conversations.android.xmpp.model.pubsub;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.reactions;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Reaction extends Extension {
+
+ public Reaction() {
+ super(Reaction.class);
+ }
+
+ public Reaction(final String reaction) {
+ this();
+ setContent(reaction);
+ }
+}
@@ -0,0 +1,36 @@
+package im.conversations.android.xmpp.model.reactions;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import java.util.Objects;
+
+@XmlElement
+public class Reactions extends Extension {
+
+ public Reactions() {
+ super(Reactions.class);
+ }
+
+ public Collection<String> getReactions() {
+ return Collections2.filter(
+ Collections2.transform(getExtensions(Reaction.class), Reaction::getContent),
+ r -> Objects.nonNull(Strings.nullToEmpty(r)));
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public void setId(String id) {
+ this.setAttribute("id", id);
+ }
+
+ public static Reactions to(final String id) {
+ final var reactions = new Reactions();
+ reactions.setId(id);
+ return reactions;
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.REACTIONS)
+package im.conversations.android.xmpp.model.reactions;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,20 @@
+package im.conversations.android.xmpp.model.receipts;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.DeliveryReceipt;
+
+@XmlElement
+public class Received extends DeliveryReceipt {
+
+ public Received() {
+ super(Received.class);
+ }
+
+ public void setId(String id) {
+ this.setAttribute("id", id);
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.receipts;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
+
+@XmlElement
+public class Request extends DeliveryReceiptRequest {
+
+ public Request() {
+ super(Request.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.DELIVERY_RECEIPTS)
+package im.conversations.android.xmpp.model.receipts;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public class Instructions extends Extension {
+
+ public Instructions() {
+ super(Instructions.class);
+ }
+}
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public class Password extends Extension {
+
+ public Password() {
+ super(Password.class);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import org.jxmpp.jid.parts.Localpart;
+
+@XmlElement(name = "query")
+public class Register extends Extension {
+
+ public Register() {
+ super(Register.class);
+ }
+
+ public void addUsername(final Localpart username) {
+ this.addExtension(new Username()).setContent(username.toString());
+ }
+
+ public void addPassword(final String password) {
+ this.addExtension(new Password()).setContent(password);
+ }
+}
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public class Remove extends Extension {
+
+ public Remove() {
+ super(Remove.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Username extends Extension {
+
+ public Username() {
+ super(Username.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.REGISTER)
+package im.conversations.android.xmpp.model.register;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.roster;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Group extends Extension {
+
+ public Group() {
+ super(Group.class);
+ }
+}
@@ -0,0 +1,61 @@
+package im.conversations.android.xmpp.model.roster;
+
+import com.google.common.collect.Collections2;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.Jid;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+@XmlElement
+public class Item extends Extension {
+
+ public static final List<Subscription> RESULT_SUBSCRIPTIONS =
+ Arrays.asList(Subscription.NONE, Subscription.TO, Subscription.FROM, Subscription.BOTH);
+
+ public Item() {
+ super(Item.class);
+ }
+
+ public Jid getJid() {
+ return getAttributeAsJid("jid");
+ }
+
+ public String getItemName() {
+ return this.getAttribute("name");
+ }
+
+ public boolean isPendingOut() {
+ return "subscribe".equalsIgnoreCase(this.getAttribute("ask"));
+ }
+
+ public Subscription getSubscription() {
+ final String value = this.getAttribute("subscription");
+ try {
+ return value == null ? null : Subscription.valueOf(value.toUpperCase(Locale.ROOT));
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ public Collection<String> getGroups() {
+ return Collections2.filter(
+ Collections2.transform(getExtensions(Group.class), Element::getContent),
+ Objects::nonNull);
+ }
+
+ public enum Subscription {
+ NONE,
+ TO,
+ FROM,
+ BOTH,
+ REMOVE
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.roster;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "query", namespace = Namespace.ROSTER)
+public class Query extends Extension {
+
+ public Query() {
+ super(Query.class);
+ }
+
+ public void setVersion(final String rosterVersion) {
+ this.setAttribute("ver", rosterVersion);
+ }
+
+ public String getVersion() {
+ return this.getAttribute("ver");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.ROSTER)
+package im.conversations.android.xmpp.model.roster;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class After extends Extension {
+
+ public After() {
+ super(After.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Before extends Extension {
+
+ public Before() {
+ super(Before.class);
+ }
+}
@@ -0,0 +1,23 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import com.google.common.base.Strings;
+import com.google.common.primitives.Ints;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Count extends Extension {
+
+ public Count() {
+ super(Count.class);
+ }
+
+ public Integer getCount() {
+ final var content = getContent();
+ if (Strings.isNullOrEmpty(content)) {
+ return null;
+ } else {
+ return Ints.tryParse(content);
+ }
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class First extends Extension {
+
+ public First() {
+ super(First.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Last extends Extension {
+
+ public Last() {
+ super(Last.class);
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Max extends Extension {
+
+ public Max() {
+ super(Max.class);
+ }
+
+ public void setMax(final int max) {
+ this.setContent(String.valueOf(max));
+ }
+}
@@ -0,0 +1,55 @@
+package im.conversations.android.xmpp.model.rsm;
+
+import com.google.common.base.Strings;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.Page;
+import im.conversations.android.xmpp.Range;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Set extends Extension {
+
+ public Set() {
+ super(Set.class);
+ }
+
+ public static Set of(final Range range, final Integer max) {
+ final var set = new Set();
+ if (range.order == Range.Order.NORMAL) {
+ final var after = set.addExtension(new After());
+ after.setContent(range.id);
+ } else if (range.order == Range.Order.REVERSE) {
+ final var before = set.addExtension(new Before());
+ before.setContent(range.id);
+ } else {
+ throw new IllegalArgumentException("Invalid order");
+ }
+ if (max != null) {
+ set.addExtension(new Max()).setMax(max);
+ }
+ return set;
+ }
+
+ public Page asPage() {
+ final var first = this.getExtension(First.class);
+ final var last = this.getExtension(Last.class);
+
+ final var firstId = first == null ? null : first.getContent();
+ final var lastId = last == null ? null : last.getContent();
+ if (Strings.isNullOrEmpty(firstId) || Strings.isNullOrEmpty(lastId)) {
+ throw new IllegalStateException("Invalid page. Missing first or last");
+ }
+ return new Page(firstId, lastId, this.getCount());
+ }
+
+ public boolean isEmpty() {
+ final var first = this.getExtension(First.class);
+ final var last = this.getExtension(Last.class);
+ return first == null && last == null;
+ }
+
+ public Integer getCount() {
+ final var count = this.getExtension(Count.class);
+ return count == null ? null : count.getCount();
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.RESULT_SET_MANAGEMENT)
+package im.conversations.android.xmpp.model.rsm;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Auth extends StreamElement {
+
+ public Auth() {
+ super(Auth.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Mechanism extends Extension {
+
+ public Mechanism() {
+ super(Mechanism.class);
+ }
+}
@@ -0,0 +1,29 @@
+package im.conversations.android.xmpp.model.sasl;
+
+import com.google.common.collect.Collections2;
+
+import eu.siacs.conversations.xml.Element;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.AuthenticationStreamFeature;
+import im.conversations.android.xmpp.model.StreamFeature;
+
+import java.util.Collection;
+import java.util.Objects;
+
+@XmlElement
+public class Mechanisms extends AuthenticationStreamFeature {
+
+
+ public Mechanisms() {
+ super(Mechanisms.class);
+ }
+
+ public Collection<Mechanism> getMechanisms() {
+ return getExtensions(Mechanism.class);
+ }
+
+ public Collection<String> getMechanismNames() {
+ return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Response extends StreamElement {
+
+ public Response() {
+ super(Response.class);
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.sasl;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Success extends StreamElement {
+
+
+ public Success() {
+ super(Success.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.SASL)
+package im.conversations.android.xmpp.model.sasl;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Authenticate extends StreamElement {
+
+ public Authenticate() {
+ super(Authenticate.class);
+ }
+}
@@ -0,0 +1,30 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import com.google.common.collect.Collections2;
+
+import eu.siacs.conversations.xml.Element;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.AuthenticationStreamFeature;
+import im.conversations.android.xmpp.model.StreamFeature;
+
+import java.util.Collection;
+import java.util.Objects;
+
+@XmlElement
+public class Authentication extends AuthenticationStreamFeature {
+ public Authentication() {
+ super(Authentication.class);
+ }
+
+ public Collection<Mechanism> getMechanisms() {
+ return getExtensions(Mechanism.class);
+ }
+
+ public Collection<String> getMechanismNames() {
+ return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull);
+ }
+
+ public Inline getInline() {
+ return this.getExtension(Inline.class);
+ }
+}
@@ -0,0 +1,28 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class AuthorizationIdentifier extends Extension {
+
+
+ public AuthorizationIdentifier() {
+ super(AuthorizationIdentifier.class);
+ }
+
+ public Jid get() {
+ final var content = getContent();
+ if ( Strings.isNullOrEmpty(content)) {
+ return null;
+ }
+ try {
+ return Jid.ofEscaped(content);
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
@@ -0,0 +1,34 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import com.google.common.collect.Collections2;
+
+import eu.siacs.conversations.xml.Element;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.fast.Fast;
+import im.conversations.android.xmpp.model.fast.Mechanism;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+@XmlElement
+public class Inline extends Extension {
+
+ public Inline() {
+ super(Inline.class);
+ }
+
+ public Fast getFast() {
+ return this.getExtension(Fast.class);
+ }
+
+ public Collection<String> getFastMechanisms() {
+ final var fast = getFast();
+ final Collection<Mechanism> mechanisms =
+ fast == null ? Collections.emptyList() : fast.getExtensions(Mechanism.class);
+ return Collections2.filter(
+ Collections2.transform(mechanisms, Element::getContent), Objects::nonNull);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Mechanism extends Extension {
+
+ public Mechanism() {
+ super(Mechanism.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Response extends StreamElement {
+
+ public Response() {
+ super(Response.class);
+ }
+}
@@ -0,0 +1,23 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import eu.siacs.conversations.xmpp.Jid;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Success extends StreamElement {
+
+
+ public Success() {
+ super(Success.class);
+ }
+
+ public Jid getAuthorizationIdentifier() {
+ final var id = this.getExtension(AuthorizationIdentifier.class);
+ if (id == null) {
+ return null;
+ }
+ return id.get();
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.SASL_2)
+package im.conversations.android.xmpp.model.sasl2;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,23 @@
+package im.conversations.android.xmpp.model.sm;
+
+import com.google.common.base.Optional;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement(name = "a")
+public class Ack extends StreamElement {
+
+ public Ack() {
+ super(Ack.class);
+ }
+
+ public Ack(final int sequence) {
+ super(Ack.class);
+ this.setAttribute("h", sequence);
+ }
+
+ public Optional<Integer> getHandled() {
+ return this.getOptionalIntAttribute("h");
+ }
+}
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.sm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Enable extends StreamElement {
+
+ public Enable() {
+ super(Enable.class);
+ this.setAttribute("resume", "true");
+ }
+}
@@ -0,0 +1,35 @@
+package im.conversations.android.xmpp.model.sm;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Strings;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Enabled extends StreamElement {
+
+ public Enabled() {
+ super(Enabled.class);
+ }
+
+ public boolean isResume() {
+ return this.getAttributeAsBoolean("resume");
+ }
+
+ public String getLocation() {
+ return this.getAttribute("location");
+ }
+
+ public Optional<String> getResumeId() {
+ final var id = this.getAttribute("id");
+ if (Strings.isNullOrEmpty(id)) {
+ return Optional.absent();
+ }
+ if (isResume()) {
+ return Optional.of(id);
+ } else {
+ return Optional.absent();
+ }
+ }
+}
@@ -0,0 +1,17 @@
+package im.conversations.android.xmpp.model.sm;
+
+import com.google.common.base.Optional;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Failed extends StreamElement {
+ public Failed() {
+ super(Failed.class);
+ }
+
+ public Optional<Integer> getHandled() {
+ return this.getOptionalIntAttribute("h");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement(name = "r")
+public class Request extends StreamElement {
+
+ public Request() {
+ super(Request.class);
+ }
+}
@@ -0,0 +1,18 @@
+package im.conversations.android.xmpp.model.sm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Resume extends StreamElement {
+
+ public Resume() {
+ super(Resume.class);
+ }
+
+ public Resume(final String id, final int sequence) {
+ super(Resume.class);
+ this.setAttribute("previd", id);
+ this.setAttribute("h", sequence);
+ }
+}
@@ -0,0 +1,18 @@
+package im.conversations.android.xmpp.model.sm;
+
+import com.google.common.base.Optional;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Resumed extends StreamElement {
+
+ public Resumed() {
+ super(Resumed.class);
+ }
+
+ public Optional<Integer> getHandled() {
+ return this.getOptionalIntAttribute("h");
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sm;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamFeature;
+
+@XmlElement(name = "sm")
+public class StreamManagement extends StreamFeature {
+
+ public StreamManagement() {
+ super(StreamManagement.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.STREAM_MANAGEMENT)
+package im.conversations.android.xmpp.model.sm;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,77 @@
+package im.conversations.android.xmpp.model.stanza;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.xml.Element;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.error.Error;
+
+import java.util.Locale;
+
+@XmlElement
+public class Iq extends Stanza {
+
+ public static Iq TIMEOUT = new Iq(Type.TIMEOUT);
+
+ public Iq() {
+ super(Iq.class);
+ }
+
+ public Iq(final Type type) {
+ super(Iq.class);
+ this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT));
+ }
+
+ // TODO get rid of timeout
+ public enum Type {
+ SET,
+ GET,
+ ERROR,
+ RESULT,
+ TIMEOUT
+ }
+
+ public Type getType() {
+ return Type.valueOf(
+ Strings.nullToEmpty(this.getAttribute("type")).toUpperCase(Locale.ROOT));
+ }
+
+ @Override
+ public boolean isInvalid() {
+ final var id = getId();
+ if (Strings.isNullOrEmpty(id)) {
+ return true;
+ }
+ return super.isInvalid();
+ }
+
+ // Legacy methods that need to be refactored:
+
+ public Element query() {
+ final Element query = findChild("query");
+ if (query != null) {
+ return query;
+ }
+ return addChild("query");
+ }
+
+ public Element query(final String xmlns) {
+ final Element query = query();
+ query.setAttribute("xmlns", xmlns);
+ return query();
+ }
+
+ public Iq generateResponse(final Iq.Type type) {
+ final var packet = new Iq(type);
+ packet.setTo(this.getFrom());
+ packet.setId(this.getId());
+ return packet;
+ }
+
+ public String getErrorCondition() {
+ final Error error = getError();
+ final var condition = error == null ? null : error.getCondition();
+ return condition == null ? null : condition.getName();
+ }
+}
@@ -0,0 +1,64 @@
+package im.conversations.android.xmpp.model.stanza;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xml.LocalizedContent;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.jabber.Body;
+
+import java.util.Locale;
+
+@XmlElement
+public class Message extends Stanza {
+
+ public Message() {
+ super(Message.class);
+ }
+
+ public Message(Type type) {
+ this();
+ this.setType(type);
+ }
+
+ public LocalizedContent getBody() {
+ return findInternationalizedChildContentInDefaultNamespace("body");
+ }
+
+ public Type getType() {
+ final var value = this.getAttribute("type");
+ if (value == null) {
+ return Type.NORMAL;
+ } else {
+ try {
+ return Type.valueOf(value.toUpperCase(Locale.ROOT));
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+ }
+
+ public void setType(final Type type) {
+ if (type == null || type == Type.NORMAL) {
+ this.removeAttribute("type");
+ } else {
+ this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT));
+ }
+ }
+
+ public void setBody(final String text) {
+ this.addExtension(new Body(text));
+ }
+
+ public void setAxolotlMessage(Element axolotlMessage) {
+ this.children.remove(findChild("body"));
+ this.children.add(0, axolotlMessage);
+ }
+
+ public enum Type {
+ ERROR,
+ NORMAL,
+ GROUPCHAT,
+ HEADLINE,
+ CHAT
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.stanza;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.capabilties.EntityCapabilities;
+
+@XmlElement
+public class Presence extends Stanza implements EntityCapabilities {
+
+ public Presence() {
+ super(Presence.class);
+ }
+}
@@ -0,0 +1,74 @@
+package im.conversations.android.xmpp.model.stanza;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xmpp.InvalidJid;
+import eu.siacs.conversations.xmpp.Jid;
+
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.StreamElement;
+import im.conversations.android.xmpp.model.error.Error;
+
+public abstract class Stanza extends StreamElement {
+
+ protected Stanza(final Class<? extends Stanza> clazz) {
+ super(clazz);
+ }
+
+ public Jid getTo() {
+ return this.getAttributeAsJid("to");
+ }
+
+ public Jid getFrom() {
+ return this.getAttributeAsJid("from");
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+
+ public void setId(final String id) {
+ this.setAttribute("id", id);
+ }
+
+ public void setFrom(final Jid from) {
+ this.setAttribute("from", from);
+ }
+
+ public void setTo(final Jid to) {
+ this.setAttribute("to", to);
+ }
+
+ public Error getError() {
+ return this.getExtension(Error.class);
+ }
+
+ public boolean isInvalid() {
+ final var to = getTo();
+ final var from = getFrom();
+ if (to instanceof InvalidJid || from instanceof InvalidJid) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean fromServer(final Account account) {
+ final Jid from = getFrom();
+ return from == null
+ || from.equals(account.getDomain())
+ || from.equals(account.getJid().asBareJid())
+ || from.equals(account.getJid());
+ }
+
+ public boolean toServer(final Account account) {
+ final Jid to = getTo();
+ return to == null
+ || to.equals(account.getDomain())
+ || to.equals(account.getJid().asBareJid())
+ || to.equals(account.getJid());
+ }
+
+ public boolean fromAccount(final Account account) {
+ final Jid from = getFrom();
+ return from != null && from.asBareJid().equals(account.getJid().asBareJid());
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.JABBER_CLIENT)
+package im.conversations.android.xmpp.model.stanza;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Active extends ChatStateNotification {
+
+ public Active() {
+ super(Active.class);
+ }
+}
@@ -0,0 +1,10 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.xmpp.model.Extension;
+
+public abstract class ChatStateNotification extends Extension {
+
+ protected ChatStateNotification(Class<? extends ChatStateNotification> clazz) {
+ super(clazz);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Composing extends ChatStateNotification {
+
+ public Composing() {
+ super(Composing.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Gone extends ChatStateNotification {
+
+ public Gone() {
+ super(Gone.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Inactive extends ChatStateNotification {
+
+ public Inactive() {
+ super(Inactive.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlElement;
+
+@XmlElement
+public class Paused extends ChatStateNotification {
+
+ public Paused() {
+ super(Paused.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.CHAT_STATES)
+package im.conversations.android.xmpp.model.state;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,33 @@
+package im.conversations.android.xmpp.model.streams;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.StreamElement;
+import im.conversations.android.xmpp.model.StreamFeature;
+import im.conversations.android.xmpp.model.capabilties.EntityCapabilities;
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.xmpp.model.sm.StreamManagement;
+
+@XmlElement
+public class Features extends StreamElement implements EntityCapabilities {
+ public Features() {
+ super(Features.class);
+ }
+
+ public boolean streamManagement() {
+ return hasStreamFeature(StreamManagement.class);
+ }
+
+ public boolean invite() {
+ return this.hasChild("register", Namespace.INVITE);
+ }
+
+ public boolean clientStateIndication() {
+ return this.hasChild("csi", Namespace.CSI);
+ }
+
+
+ public boolean hasStreamFeature(final Class<? extends StreamFeature> clazz) {
+ return hasExtension(clazz);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.STREAMS)
+package im.conversations.android.xmpp.model.streams;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.tls;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement
+public class Proceed extends StreamElement {
+
+ public Proceed() {
+ super(Proceed.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.tls;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Required extends Extension {
+ public Required() {
+ super(Required.class);
+ }
+}
@@ -0,0 +1,15 @@
+package im.conversations.android.xmpp.model.tls;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.StreamElement;
+
+@XmlElement(name = "starttls")
+public class StartTls extends StreamElement {
+ public StartTls() {
+ super(StartTls.class);
+ }
+
+ public boolean isRequired() {
+ return hasExtension(Required.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.TLS)
+package im.conversations.android.xmpp.model.tls;
+
+import eu.siacs.conversations.xml.Namespace;
+import im.conversations.android.annotation.XmlPackage;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.unique;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class OriginId extends Extension {
+
+ public OriginId() {
+ super(OriginId.class);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.unique;
+
+import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class StanzaId extends Extension {
+
+ public StanzaId() {
+ super(StanzaId.class);
+ }
+
+ public Jid getBy() {
+ return this.getAttributeAsJid("by");
+ }
+
+ public String getId() {
+ return this.getAttribute("id");
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.STANZA_IDS)
+package im.conversations.android.xmpp.model.unique;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,22 @@
+package im.conversations.android.xmpp.model.upload;
+
+import com.google.common.base.Strings;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import okhttp3.HttpUrl;
+
+@XmlElement
+public class Get extends Extension {
+
+ public Get() {
+ super(Get.class);
+ }
+
+ public HttpUrl getUrl() {
+ final var url = this.getAttribute("url");
+ if (Strings.isNullOrEmpty(url)) {
+ return null;
+ }
+ return HttpUrl.parse(url);
+ }
+}
@@ -0,0 +1,16 @@
+package im.conversations.android.xmpp.model.upload;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Header extends Extension {
+
+ public Header() {
+ super(Header.class);
+ }
+
+ public String getHeaderName() {
+ return this.getAttribute("name");
+ }
+}
@@ -0,0 +1,27 @@
+package im.conversations.android.xmpp.model.upload;
+
+import com.google.common.base.Strings;
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import java.util.Collection;
+import okhttp3.HttpUrl;
+
+@XmlElement
+public class Put extends Extension {
+
+ public Put() {
+ super(Put.class);
+ }
+
+ public HttpUrl getUrl() {
+ final var url = this.getAttribute("url");
+ if (Strings.isNullOrEmpty(url)) {
+ return null;
+ }
+ return HttpUrl.parse(url);
+ }
+
+ public Collection<Header> getHeaders() {
+ return this.getExtensions(Header.class);
+ }
+}
@@ -0,0 +1,24 @@
+package im.conversations.android.xmpp.model.upload;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Request extends Extension {
+
+ public Request() {
+ super(Request.class);
+ }
+
+ public void setFilename(String filename) {
+ this.setAttribute("filename", filename);
+ }
+
+ public void setSize(long size) {
+ this.setAttribute("size", size);
+ }
+
+ public void setContentType(String type) {
+ this.setAttribute("content-ype", type);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.upload;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Slot extends Extension {
+
+ public Slot() {
+ super(Slot.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.HTTP_UPLOAD)
+package im.conversations.android.xmpp.model.upload;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,13 @@
+package im.conversations.android.xmpp.model.vcard;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.ByteContent;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "BINVAL")
+public class BinaryValue extends Extension implements ByteContent {
+
+ public BinaryValue() {
+ super(BinaryValue.class);
+ }
+}
@@ -0,0 +1,11 @@
+package im.conversations.android.xmpp.model.vcard;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "PHOTO")
+public class Photo extends Extension {
+ public Photo() {
+ super(Photo.class);
+ }
+}
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.vcard;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "vCard")
+public class VCard extends Extension {
+
+ public VCard() {
+ super(VCard.class);
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.VCARD_TEMP)
+package im.conversations.android.xmpp.model.vcard;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.vcard.update;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Photo extends Extension {
+
+ public Photo() {
+ super(Photo.class);
+ }
+}
@@ -0,0 +1,21 @@
+package im.conversations.android.xmpp.model.vcard.update;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement(name = "x")
+public class VCardUpdate extends Extension {
+
+ public VCardUpdate() {
+ super(VCardUpdate.class);
+ }
+
+ public Photo getPhoto() {
+ return this.getExtension(Photo.class);
+ }
+
+ public String getHash() {
+ final var photo = getPhoto();
+ return photo == null ? null : photo.getContent();
+ }
+}
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.VCARD_TEMP_UPDATE)
+package im.conversations.android.xmpp.model.vcard.update;
+
+import im.conversations.android.annotation.XmlPackage;
+import eu.siacs.conversations.xml.Namespace;
@@ -0,0 +1,25 @@
+package im.conversations.android.xmpp.model.version;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+import eu.siacs.conversations.xml.Namespace;
+
+@XmlElement(name = "query", namespace = Namespace.VERSION)
+public class Version extends Extension {
+
+ public Version() {
+ super(Version.class);
+ }
+
+ public void setSoftwareName(final String name) {
+ this.addChild("name").setContent(name);
+ }
+
+ public void setVersion(final String version) {
+ this.addChild("version").setContent(version);
+ }
+
+ public void setOs(final String os) {
+ this.addChild("os").setContent(os);
+ }
+}
@@ -0,0 +1,90 @@
+package im.conversations.android.xmpp.processor;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.generator.IqGenerator;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.XmppConnection;
+
+import im.conversations.android.xmpp.model.stanza.Iq;
+
+public class BindProcessor implements Runnable {
+
+
+ private final XmppConnectionService service;
+ private final Account account;
+
+ public BindProcessor(XmppConnectionService service, Account account) {
+ this.service = service;
+ this.account = account;
+ }
+
+ @Override
+ public void run() {
+ final XmppConnection connection = account.getXmppConnection();
+ service.cancelAvatarFetches(account);
+ final boolean loggedInSuccessfully = account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, true);
+ final boolean gainedFeature = account.setOption(Account.OPTION_HTTP_UPLOAD_AVAILABLE, connection.getFeatures().httpUpload(0));
+ if (loggedInSuccessfully || gainedFeature) {
+ service.databaseBackend.updateAccount(account);
+ }
+
+ if (loggedInSuccessfully) {
+ if (!TextUtils.isEmpty(account.getDisplayName())) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": display name wasn't empty on first log in. publishing");
+ service.publishDisplayName(account);
+ }
+ }
+
+ account.getRoster().clearPresences();
+ synchronized (account.inProgressConferenceJoins) {
+ account.inProgressConferenceJoins.clear();
+ }
+ synchronized (account.inProgressConferencePings) {
+ account.inProgressConferencePings.clear();
+ }
+ service.getJingleConnectionManager().notifyRebound(account);
+ service.getQuickConversationsService().considerSyncBackground(false);
+
+
+ connection.fetchRoster();
+
+ if (connection.getFeatures().bookmarks2()) {
+ service.fetchBookmarks2(account);
+ } else if (!connection.getFeatures().bookmarksConversion()) {
+ service.fetchBookmarks(account);
+ }
+
+ if (connection.getFeatures().mds()) {
+ service.fetchMessageDisplayedSynchronization(account);
+ } else {
+ Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds");
+ }
+ final boolean flexible = connection.getFeatures().flexibleOfflineMessageRetrieval();
+ final boolean catchup = service.getMessageArchiveService().inCatchup(account);
+ final boolean trackOfflineMessageRetrieval;
+ if (flexible && catchup && connection.isMamPreferenceAlways()) {
+ trackOfflineMessageRetrieval = false;
+ connection.sendIqPacket(IqGenerator.purgeOfflineMessages(), (packet) -> {
+ if (packet.getType() == Iq.Type.RESULT) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully purged offline messages");
+ }
+ });
+ } else {
+ trackOfflineMessageRetrieval = true;
+ }
+ service.sendPresence(account);
+ connection.trackOfflineMessageRetrieval(trackOfflineMessageRetrieval);
+ if (service.getPushManagementService().available(account)) {
+ service.getPushManagementService().registerPushTokenOnServer(account);
+ }
+ service.connectMultiModeConversations(account);
+ service.syncDirtyContacts(account);
+
+ service.getUnifiedPushBroker().renewUnifiedPushEndpointsOnBind(account);
+
+ }
+}
@@ -15,7 +15,8 @@ import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.forms.Data;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+import im.conversations.android.xmpp.model.stanza.Iq;
public class PushManagementService {
@@ -25,7 +26,7 @@ public class PushManagementService {
this.mXmppConnectionService = service;
}
- private static Data findResponseData(IqPacket response) {
+ private static Data findResponseData(Iq response) {
final Element command = response.findChild("command", Namespace.COMMANDS);
final Element x = command == null ? null : command.findChild("x", Namespace.DATA);
return x == null ? null : Data.parse(x);
@@ -35,43 +36,70 @@ public class PushManagementService {
return Jid.of(mXmppConnectionService.getString(R.string.app_server));
}
- void registerPushTokenOnServer(final Account account) {
+ public void registerPushTokenOnServer(final Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
- retrieveFcmInstanceToken(token -> {
- final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
- final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId);
- mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
- final Data data = findResponseData(response);
- if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
- try {
- String node = data.getValue("node");
- String secret = data.getValue("secret");
- Jid jid = Jid.of(data.getValue("jid"));
- if (node != null && secret != null) {
- enablePushOnServer(a, jid, node, secret);
- }
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- }
- } else {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": failed to enable push. invalid response from app server " + response);
- }
- });
- });
+ retrieveFcmInstanceToken(
+ token -> {
+ final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
+ final var packet =
+ mXmppConnectionService
+ .getIqGenerator()
+ .pushTokenToAppServer(getAppServer(), token, androidId);
+ mXmppConnectionService.sendIqPacket(
+ account,
+ packet,
+ (response) -> {
+ final Data data = findResponseData(response);
+ if (response.getType() == Iq.Type.RESULT && data != null) {
+ final Jid jid;
+ try {
+ jid = Jid.ofEscaped(data.getValue("jid"));
+ } catch (final IllegalArgumentException e) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": failed to enable push. invalid jid");
+ return;
+ }
+ final String node = data.getValue("node");
+ final String secret = data.getValue("secret");
+ if (node != null && secret != null) {
+ enablePushOnServer(account, jid, node, secret);
+ }
+ } else {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": failed to enable push. invalid response from app server "
+ + response);
+ }
+ });
+ });
}
- private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) {
- final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
- mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
- if (p.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
- } else if (p.getType() == IqPacket.TYPE.ERROR) {
- Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed");
- }
- });
+ private void enablePushOnServer(
+ final Account account, final Jid appServer, final String node, final String secret) {
+ final Iq enable =
+ mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
+ mXmppConnectionService.sendIqPacket(
+ account,
+ enable,
+ (p) -> {
+ if (p.getType() == Iq.Type.RESULT) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": successfully enabled push on server");
+ } else if (p.getType() == Iq.Type.ERROR) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": enabling push on server failed");
+ }
+ });
}
- private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
+ private void retrieveFcmInstanceToken(
+ final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
final FirebaseMessaging firebaseMessaging;
try {
firebaseMessaging = FirebaseMessaging.getInstance();
@@ -79,26 +107,33 @@ public class PushManagementService {
Log.d(Config.LOGTAG, "unable to get firebase instance token ", e);
return;
}
- firebaseMessaging.getToken().addOnCompleteListener(task -> {
- if (!task.isSuccessful()) {
- Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
- }
- final String result;
- try {
- result = task.getResult();
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e);
- return;
- }
- if (result != null) {
- instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result);
- }
- });
-
+ firebaseMessaging
+ .getToken()
+ .addOnCompleteListener(
+ task -> {
+ if (!task.isSuccessful()) {
+ Log.d(
+ Config.LOGTAG,
+ "unable to get Firebase instance token",
+ task.getException());
+ }
+ final String result;
+ try {
+ result = task.getResult();
+ } catch (Exception e) {
+ Log.d(
+ Config.LOGTAG,
+ "unable to get Firebase instance token due to bug in library ",
+ e);
+ return;
+ }
+ if (result != null) {
+ instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result);
+ }
+ });
}
-
- public boolean available(Account account) {
+ public boolean available(final Account account) {
final XmppConnection connection = account.getXmppConnection();
return connection != null
&& connection.getFeatures().sm()
@@ -107,7 +142,9 @@ public class PushManagementService {
}
private boolean playServicesAvailable() {
- return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
+ return GoogleApiAvailabilityLight.getInstance()
+ .isGooglePlayServicesAvailable(mXmppConnectionService)
+ == ConnectionResult.SUCCESS;
}
public boolean isStub() {
@@ -69,7 +69,7 @@ import eu.siacs.conversations.utils.TLSSocketFactory;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import im.conversations.android.xmpp.model.stanza.Iq;
import io.michaelrocks.libphonenumber.android.Phonenumber;
public class QuickConversationsService extends AbstractQuickConversationsService {
@@ -463,15 +463,15 @@ public class QuickConversationsService extends AbstractQuickConversationsService
for (final PhoneNumberContact c : contacts.values()) {
entries.add(new Element("entry").setAttribute("number", c.getPhoneNumber()));
}
- final IqPacket query = new IqPacket(IqPacket.TYPE.GET);
+ final Iq query = new Iq(Iq.Type.GET);
query.setTo(syncServer);
final Element book = new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries);
final String statusQuo = Entry.statusQuo(contacts.values(), account.getRoster().getWithSystemAccounts(PhoneNumberContact.class));
book.setAttribute("ver", statusQuo);
query.addChild(book);
mLastSyncAttempt = Attempt.create(hash);
- service.sendIqPacket(account, query, (a, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
+ service.sendIqPacket(account, query, (response) -> {
+ if (response.getType() == Iq.Type.RESULT) {
final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION);
if (phoneBook != null) {
final List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class);
@@ -498,7 +498,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": phone number contact list remains unchanged");
}
- } else if (response.getType() == IqPacket.TYPE.TIMEOUT) {
+ } else if (response.getType() == Iq.Type.TIMEOUT) {
mLastSyncAttempt = Attempt.NULL;
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": failed to sync contact list with api server");