diff --git a/build.gradle b/build.gradle index beb73f787e4ed82d9b51b84522faafd60ac8d266..7381247022b8a3125ac0d51b99aa3cb77440bcef 100644 --- a/build.gradle +++ b/build.gradle @@ -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') { diff --git a/libs/annotation-processor/build.gradle b/libs/annotation-processor/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..6232f33c6e557d384e4e63db790d24e9ea0864c6 --- /dev/null +++ b/libs/annotation-processor/build.gradle @@ -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' + +} \ No newline at end of file diff --git a/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..c42cc5340537f328541761290dcb04eebae5d819 --- /dev/null +++ b/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java @@ -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 set, RoundEnvironment roundEnvironment) { + final Set elements = + roundEnvironment.getElementsAnnotatedWith(XmlElement.class); + final ImmutableMap.Builder 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 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>" + + " EXTENSION_CLASS_MAP;"); + out.println("static {"); + out.println( + "final var builder = new ImmutableBiMap.Builder>();"); + for (final Map.Entry 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 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); + } + } +} diff --git a/libs/annotation/build.gradle b/libs/annotation/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..13a27e90c8f86b1aa7a00a15005dba57406dc570 --- /dev/null +++ b/libs/annotation/build.gradle @@ -0,0 +1,6 @@ +apply plugin: "java-library" + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} \ No newline at end of file diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java new file mode 100644 index 0000000000000000000000000000000000000000..68ff736352d3396aef81c6b6d9d57f5e72e547bb --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java @@ -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 ""; +} diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java new file mode 100644 index 0000000000000000000000000000000000000000..462fc6965b7a885c17d55a99262662169dde346e --- /dev/null +++ b/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java @@ -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(); +} diff --git a/proguard-rules.pro b/proguard-rules.pro index 389e75904be67688b2650bbc60adc2b486c27593..c23fd72ae9e0740cf9826edbedf5784c2a836e2b 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -1,6 +1,7 @@ -dontobfuscate -keep class eu.siacs.conversations.** +-keep class im.conversations.** -keep class org.whispersystems.** diff --git a/settings.gradle b/settings.gradle index 4193570fa762a8d407f5d06beec954146d2254ea..3cecbc88944cf4f85d51fa2a2c363dd3b327c062 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ +include ':libs:annotation', ':libs:annotation-processor:' + rootProject.name = 'Conversations' diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java index f436da434e14d0e90f2ff14233cc1f5a83d0d811..c6c5d232466c9861062f2a9600c3d9fb0a4fbf65 100644 --- a/src/free/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java @@ -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; - } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 3721f4cfeed67e57914529130bae30f63025823b..b1ecda3c23f20a8ce309e8b29c64299531d9d132 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -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 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 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 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 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 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 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 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 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 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 future = SettableFuture.create(); - final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - Pair verification = mXmppConnectionService.getIqParser().verification(response); + final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, (response) -> { + Pair 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 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 deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Element item = IqParser.getItem(response); + final Set deviceIds = IqParser.deviceIds(item); registerDevices(jid, deviceIds); final List 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 preKeyBundleList = parser.preKeys(packet); - final PreKeyBundle bundle = parser.bundle(packet); + final List 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); diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 8c63a2670a6dbe174e9ab4674a7d93d6e07136ed..3f2d2a5adf7f907a58c5866a29ead6d9e7a71585 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -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 features; protected final List forms; private final List 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 { 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; - } - } } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index df87932e5565bc5b47bfa540ce519599b1eb639f..85e3a0d7c762e0f4b9bb69be132761d6ae488408 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -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 ids, final Bundle publishOptions) { + public Iq publishDeviceIds(final Set 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 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 jids = new ArrayList<>(); jids.add(jid); return changeAffiliation(conference, jids, affiliation); } - public IqPacket changeAffiliation(Conversation conference, List jids, String affiliation) { - IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + public Iq changeAffiliation(Conversation conference, List 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; diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index e217f7f1d41af5f3e46c4e5583abf787f60cf35e..f3823dee04c2848d6280e58e9b4a453f5386c790 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -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 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 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); diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 1485385bcac3f9618f4eec0cbc34aa59c987738c..7bb7341842997906b131073e91a010cad3777b0b 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/http/SlotRequester.java b/src/main/java/eu/siacs/conversations/http/SlotRequester.java index 5a3558855c9f6c2746586e31056fb49699f2ce43..d76a99fda85eabf39864583d3c2fbc95f105da47 100644 --- a/src/main/java/eu/siacs/conversations/http/SlotRequester.java +++ b/src/main/java/eu/siacs/conversations/http/SlotRequester.java @@ -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 requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture 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 requestHttpUpload(Account account, Jid host, DownloadableFile file, String mime) { final SettableFuture 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 { diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 5de637399fb571bfaff80675cee50a2275cc8d60..2e2cb26843ff7ddc2402b958fe656155cc1fed37 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 0c08c557ebba9d8a052fad0cd33bb7079dcd8758..339c025c9ac160d6e2ee3e689d0f2cf62a582452 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -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 { - public IqParser(final XmppConnectionService service) { - super(service); + public IqParser(final XmppConnectionService service, final Account account) { + super(service, account); } - public static List items(IqPacket packet) { + public static List items(final Iq packet) { ArrayList 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 deviceIds(final Element item) { + public static Set deviceIds(final Element item) { Set 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 preKeyPublics(final IqPacket packet) { + public static Map preKeyPublics(final Iq packet) { Map 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 verification(final IqPacket packet) { + public static Pair 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 preKeys(final IqPacket preKeys) { + public static List preKeys(final Iq preKeys) { List bundles = new ArrayList<>(); Map 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 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"); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 011942c5037e6f2b1e6cafcca2c9bd70b904f189..c979ae1b6eb78b5d4a13eee3d600694d3865d938 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -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 { private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); private static final List 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 deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + final Set 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 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 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 f; - f = original.getForwardedMessagePacket("received", Namespace.CARBONS); - f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f; + Pair 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 getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class 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 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, diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 584b8e704fe35417d26725e31bf24aeb2ac008a7..fc439ae0830d8c3555c08c60d35c6b4d7a5173cd 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -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 { - 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")) { diff --git a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java index 3ca6bfde4cfecd9f76eebb2e6d13f055c7b7f418..b9d43fa51523e3c8fd74aec716a6325b8063a170 100644 --- a/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java +++ b/src/main/java/eu/siacs/conversations/services/ChannelDiscoveryService.java @@ -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 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 rooms = new ArrayList<>(); - for (Map.Entry entry : localMucService.entrySet()) { - IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); + for (final Map.Entry 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 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(); } }); } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 126993501271a8ac63ab8e9a6843f0820a04667f..2c342e6728a985b12a1bb7d28b8a41ec63e132cc 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -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) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 4aab05ceeb00ddefc6d366c1dfd3e94ac4e7036b..92ae9d9ec668533d097680e333ac58bb4bc33248 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 80b4d6ddde600f4e4bc3b2a757921a77567ca0c5..0cf6fbe248756554170a15b4ad7d37facf659ebe 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -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 mInProgressAvatarFetches = new HashSet<>(); private final Set mOmittedPepAvatarFetches = new HashSet<>(); private final HashSet mLowPingTimeoutMode = new HashSet<>(); - private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> { - if (packet.getType() != IqPacket.TYPE.RESULT) { - Element error = packet.findChild("error"); + private final Consumer 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 = 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 FILENAMES_TO_IGNORE_DELETION = new HashSet<>(); - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - synchronized (mInProgressAvatarFetches) { - for (Iterator 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, 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 callback = (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query1 = response.query(); final Element storage = query1.findChild("storage", "storage:bookmarks"); Map 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 bookmarks = Bookmark.parseFromPubsub(pubsub, a); - processBookmarksInitial(a, bookmarks, true); + final Map 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 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 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 callback = new Consumer() { 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() { - 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 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 callback) { - IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar); - sendIqPacket(account, packet, (a, result) -> { + private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback 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); @@ -4134,7 +4028,7 @@ public class XmppConnectionService extends Service { if (callback != null) { callback.success(avatar); } - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner); return; } } else { @@ -4157,57 +4051,54 @@ public class XmppConnectionService extends Service { } private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar); - this.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - final boolean previouslyOmittedPepFetch; - synchronized (mInProgressAvatarFetches) { - final String KEY = generateFetchKey(account, avatar); - mInProgressAvatarFetches.remove(KEY); - previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); - } - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element vCard = packet.findChild("vCard", "vcard-temp"); - Element photo = vCard != null ? vCard.findChild("PHOTO") : null; - String image = photo != null ? photo.findChildContent("BINVAL") : null; - if (image != null) { - avatar.image = image; - if (getFileBackend().save(avatar)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() - + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); - if (avatar.owner.isBareJid()) { - if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); - account.setAvatar(avatar.getFilename()); - databaseBackend.updateAccount(account); - getAvatarService().clear(account); - updateAccountUi(); - } else { - final Contact contact = account.getRoster().getContact(avatar.owner); - contact.setAvatar(avatar, previouslyOmittedPepFetch); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } - updateConversationUi(); + final Iq packet = this.mIqGenerator.retrieveVcardAvatar(avatar); + this.sendIqPacket(account, packet, response -> { + final boolean previouslyOmittedPepFetch; + synchronized (mInProgressAvatarFetches) { + final String KEY = generateFetchKey(account, avatar); + mInProgressAvatarFetches.remove(KEY); + previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY); + } + if (response.getType() == Iq.Type.RESULT) { + Element vCard = response.findChild("vCard", "vcard-temp"); + Element photo = vCard != null ? vCard.findChild("PHOTO") : null; + String image = photo != null ? photo.findChildContent("BINVAL") : null; + if (image != null) { + avatar.image = image; + if (getFileBackend().save(avatar)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch); + if (avatar.owner.isBareJid()) { + if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard"); + account.setAvatar(avatar.getFilename()); + databaseBackend.updateAccount(account); + getAvatarService().clear(account); + updateAccountUi(); } else { - Conversation conversation = find(account, avatar.owner.asBareJid()); - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); - if (user != null) { - if (user.setAvatar(avatar)) { - getAvatarService().clear(user); - updateConversationUi(); - updateMucRosterUi(); - } - if (user.getRealJid() != null) { - Contact contact = account.getRoster().getContact(user.getRealJid()); - contact.setAvatar(avatar); - syncRoster(account); - getAvatarService().clear(contact); - updateRosterUi(); - } + final Contact contact = account.getRoster().getContact(avatar.owner); + contact.setAvatar(avatar, previouslyOmittedPepFetch); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); + } + updateConversationUi(); + } else { + Conversation conversation = find(account, avatar.owner.asBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner); + if (user != null) { + if (user.setAvatar(avatar)) { + getAvatarService().clear(user); + updateConversationUi(); + updateMucRosterUi(); + } + if (user.getRealJid() != null) { + Contact contact = account.getRoster().getContact(user.getRealJid()); + contact.setAvatar(avatar); + syncRoster(account); + getAvatarService().clear(contact); + updateRosterUi(); } } } @@ -4218,36 +4109,32 @@ public class XmppConnectionService extends Service { }); } - public void checkForAvatar(Account account, final UiCallback callback) { - IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null); - this.sendIqPacket(account, packet, 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"); - if (pubsub != null) { - Element items = pubsub.findChild("items"); - if (items != null) { - Avatar avatar = Avatar.parseMetadata(items); - if (avatar != null) { - avatar.owner = account.getJid().asBareJid(); - if (fileBackend.isAvatarCached(avatar)) { - if (account.setAvatar(avatar.getFilename())) { - databaseBackend.updateAccount(account); - } - getAvatarService().clear(account); - callback.success(avatar); - } else { - fetchAvatarPep(account, avatar, callback); + public void checkForAvatar(final Account account, final UiCallback callback) { + final Iq packet = this.mIqGenerator.retrieveAvatarMetaData(null); + this.sendIqPacket(account, packet, response -> { + if (response.getType() == Iq.Type.RESULT) { + Element pubsub = response.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + Avatar avatar = Avatar.parseMetadata(items); + if (avatar != null) { + avatar.owner = account.getJid().asBareJid(); + if (fileBackend.isAvatarCached(avatar)) { + if (account.setAvatar(avatar.getFilename())) { + databaseBackend.updateAccount(account); } - return; + getAvatarService().clear(account); + callback.success(avatar); + } else { + fetchAvatarPep(account, avatar, callback); } + return; } } } - callback.error(0, null); } + callback.error(0, null); }); } @@ -4259,7 +4146,7 @@ public class XmppConnectionService extends Service { if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) { final MucOptions mucOptions = conversation.getMucOptions(); if (mucOptions.online()) { - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); + final var packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); packet.setTo(mucOptions.getSelf().getFullJid()); connection.sendPresencePacket(packet); } @@ -4274,7 +4161,7 @@ public class XmppConnectionService extends Service { contact.setOption(Contact.Options.DIRTY_DELETE); Account account = contact.getAccount(); if (account.getStatus() == Account.State.ONLINE) { - IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); Element item = iq.query(Namespace.ROSTER).addChild("item"); item.setAttribute("jid", contact.getJid()); item.setAttribute("subscription", "remove"); @@ -4334,12 +4221,12 @@ public class XmppConnectionService extends Service { if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) { changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null); } - final MessagePacket packet = mMessageGenerator.invite(conversation, contact); + final var packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } public void directInvite(Conversation conversation, Jid jid) { - MessagePacket packet = mMessageGenerator.directInvite(conversation, jid); + final var packet = mMessageGenerator.directInvite(conversation, jid); sendMessagePacket(conversation.getAccount(), packet); } @@ -4676,7 +4563,7 @@ public class XmppConnectionService extends Service { if (sendDisplayedMarker && serverAssist) { final var mdsDisplayed = mIqGenerator.mdsDisplayed(stanzaId, conversation); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); packet.addChild(mdsDisplayed); if (!last.isPrivateMessage()) { packet.setTo(packet.getTo().asBareJid()); @@ -4692,7 +4579,7 @@ public class XmppConnectionService extends Service { conversation.getAccount().getJid().asBareJid() + ": sending displayed marker to " + last.getCounterpart().toString()); - final MessagePacket packet = mMessageGenerator.confirm(last); + final var packet = mMessageGenerator.confirm(last); this.sendMessagePacket(account, packet); } } @@ -4798,15 +4685,15 @@ public class XmppConnectionService extends Service { return mucServers; } - public void sendMessagePacket(Account account, MessagePacket packet) { + public void sendMessagePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendMessagePacket(packet); } } - public void sendPresencePacket(Account account, PresencePacket packet) { - XmppConnection connection = account.getXmppConnection(); + public void sendPresencePacket(final Account account, final im.conversations.android.xmpp.model.stanza.Presence packet) { + final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendPresencePacket(packet); } @@ -4814,18 +4701,18 @@ public class XmppConnectionService extends Service { public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - IqPacket request = mIqGenerator.generateCreateAccountWithCaptcha(account, id, data); - connection.sendUnmodifiedIqPacket(request, connection.registrationResponseListener, true); + if (connection == null) { + return; } + connection.sendCreateAccountWithCaptchaPacket(id, data); } - public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { + public void sendIqPacket(final Account account, final Iq packet, final Consumer callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.sendIqPacket(packet, callback); } else if (callback != null) { - callback.onIqPacketReceived(account, new IqPacket(IqPacket.TYPE.TIMEOUT)); + callback.accept(Iq.TIMEOUT); } } @@ -4840,7 +4727,7 @@ public class XmppConnectionService extends Service { } else { status = getTargetPresence(); } - final PresencePacket packet = mPresenceGenerator.selfPresence(account, status); + final var packet = mPresenceGenerator.selfPresence(account, status); if (mLastActivity > 0 && includeIdleTimestamp) { long since = Math.min(mLastActivity, System.currentTimeMillis()); //don't send future dates packet.addChild("idle", Namespace.IDLE).setAttribute("since", AbstractGenerator.getTimestamp(since)); @@ -4890,10 +4777,6 @@ public class XmppConnectionService extends Service { return this.mIqGenerator; } - public IqParser getIqParser() { - return this.mIqParser; - } - public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } @@ -4986,10 +4869,11 @@ public class XmppConnectionService extends Service { public boolean sendBlockRequest(final Blockable blockable, final boolean reportSpam, final String serverMsgId) { if (blockable != null && blockable.getBlockedJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - a.getBlocklist().add(jid); + this.sendIqPacket(account, getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), (response) -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().add(jid); updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } }); @@ -5030,31 +4914,29 @@ public class XmppConnectionService extends Service { public void sendUnblockRequest(final Blockable blockable) { if (blockable != null && blockable.getJid() != null) { + final var account = blockable.getAccount(); final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket(blockable.getAccount(), getIqGenerator().generateSetUnblockRequest(jid), new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - } + this.sendIqPacket(account, getIqGenerator().generateSetUnblockRequest(jid), response -> { + if (response.getType() == Iq.Type.RESULT) { + account.getBlocklist().remove(jid); + updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } }); } } - public void publishDisplayName(Account account) { + public void publishDisplayName(final Account account) { String displayName = account.getDisplayName(); - final IqPacket request; + final Iq request; if (TextUtils.isEmpty(displayName)) { request = mIqGenerator.deleteNode(Namespace.NICK); } else { request = mIqGenerator.publishNick(displayName); } mAvatarService.clear(account); - sendIqPacket(account, request, (account1, packet) -> { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": unable to modify nick name " + packet); + sendIqPacket(account, request, (packet) -> { + if (packet.getType() == Iq.Type.ERROR) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to modify nick name " + packet); } }); } @@ -5072,7 +4954,7 @@ public class XmppConnectionService extends Service { } } - public void fetchCaps(Account account, final Jid jid, final Presence presence) { + public void fetchCaps(final Account account, final Jid jid, final Presence presence) { final Pair key = new Pair<>(presence.getHash(), presence.getVer()); final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); if (disco != null) { @@ -5082,7 +4964,7 @@ public class XmppConnectionService extends Service { syncRoster(account); } } else { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(jid); final String node = presence.getNode(); final String ver = presence.getVer(); @@ -5091,14 +4973,14 @@ public class XmppConnectionService extends Service { query.setAttribute("node", node + "#" + ver); } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + sendIqPacket(account, request, (response) -> { + if (response.getType() == Iq.Type.RESULT) { final ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response); if (presence.getVer().equals(discoveryResult.getVer())) { databaseBackend.insertDiscoveryResult(discoveryResult); - injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); + injectServiceDiscoveryResult(account.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); } } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to fetch caps from " + jid); @@ -5126,13 +5008,13 @@ public class XmppConnectionService extends Service { } } - public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { + public void fetchMamPreferences(final Account account, final OnMamPreferencesFetched callback) { final MessageArchiveService.Version version = MessageArchiveService.Version.get(account); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", version.namespace); - sendIqPacket(account, request, (account1, packet) -> { - Element prefs = packet.findChild("prefs", version.namespace); - if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { + sendIqPacket(account, request, (packet) -> { + final Element prefs = packet.findChild("prefs", version.namespace); + if (packet.getType() == Iq.Type.RESULT && prefs != null) { callback.onPreferencesFetched(prefs); } else { callback.onPreferencesFetchFailed(); @@ -5231,7 +5113,7 @@ public class XmppConnectionService extends Service { } public void pushMamPreferences(Account account, Element prefs) { - IqPacket set = new IqPacket(IqPacket.TYPE.SET); + final Iq set = new Iq(Iq.Type.SET); set.addChild(prefs); sendIqPacket(account, set, null); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 2ce3eb52aca08ae900176314482634833ae3ea02..45c2259f4fd8f38848858a2b4faab90ad3e8c088 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -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) { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 98a2bfb46aececf2acea02d9a46fd573c9df56b5..20db7f8782e9ea96d8583b68996a1542b00629b0 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -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 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); diff --git a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java index 635afd1452d096738017c042456db7e8af7d36bd..d730bd34f7b4a4bef5c5fdd08bd63598d00c1e88 100644 --- a/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java +++ b/src/main/java/eu/siacs/conversations/xml/LocalizedContent.java @@ -37,7 +37,7 @@ public class LocalizedContent { } } } - if (contents.size() == 0) { + if (contents.isEmpty()) { return null; } final String userLanguage = Locale.getDefault().getLanguage(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 2e5eb1c5a69bbaa288d334bb6321fe76c8cfe934..cc08154f9b4fa0a510765b13619b8d1c16dce493 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -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"; } diff --git a/src/main/java/eu/siacs/conversations/xml/TagWriter.java b/src/main/java/eu/siacs/conversations/xml/TagWriter.java index 5a9f3317c4bfae527ca498f915466bcd947fad97..2401c612ca8c7a57882425148b0c0c06e3bdea31 100644 --- a/src/main/java/eu/siacs/conversations/xml/TagWriter.java +++ b/src/main/java/eu/siacs/conversations/xml/TagWriter.java @@ -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 writeQueue = new LinkedBlockingQueue(); + + private final LinkedBlockingQueue 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 { diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java index 240b92b7ae9ae11ab7cbbdfbca51403f8231d356..090a6e47dfcdfaf64ab9dd14309611bebfa8b7c8 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java @@ -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 readElement(final Tag current, final Class 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) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java index f3a21c36f54bc055ccc73dd87e48a5ee66a47adf..4e3092821d4c241a004e5ce77f7e8e2940dea0ea 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/InvalidJid.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java deleted file mode 100644 index 6db24ef94845d95d363869b7de3225a24ee04013..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnIqPacketReceived.java +++ /dev/null @@ -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); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java index 24acf16e27a59950c61357e1f5d9aa02686d9119..6ff26884bee375e74ddef4c5919f23ef05e4a35b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnMessagePacketReceived.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java deleted file mode 100644 index e1bf839f43e26809091dc5bdf8c22626ce98e0f5..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/OnPresencePacketReceived.java +++ /dev/null @@ -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); -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java deleted file mode 100644 index 05ddc392f822b1c55d0457551b823d5b590db129..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/PacketReceived.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.siacs.conversations.xmpp; - -public interface PacketReceived { - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 31e150ac7447734b48112377aad6bf50aa927a09..a3826626678d9a2061dfe33ce7acad9d686a9274 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -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 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 disco = new HashMap<>(); private final HashMap commands = new HashMap<>(); - private final SparseArray mStanzaQueue = new SparseArray<>(); - private final Hashtable> packetCallbacks = - new Hashtable<>(); + private final SparseArray mStanzaQueue = new SparseArray<>(); + private final Hashtable>> packetCallbacks = new Hashtable<>(); private final Set 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 presenceListener; + private final Consumer unregisteredIqListener; + private final Consumer 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 serverSequence = ack.getOptionalIntAttribute("h"); + final Optional 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 intermediateStanzasBuilder = + final ImmutableList.Builder 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 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 h = resumed.getOptionalIntAttribute("h"); + final Optional 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 failedStanzas = new ArrayList<>(); + final ArrayList 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 serverCount = failed.getOptionalIntAttribute("h"); + private void processFailed(final Failed failed, final boolean sendBindRequest) { + final Optional 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 @NonNull S processPacket(final Tag currentTag, final Class 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 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 mechanisms = SaslMechanism.mechanisms(authElement); + final Collection mechanisms = authElement.getMechanismNames(); final Element cbElement = this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING); final Collection 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 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 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 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 bindFeatures) { + private Bind generateBindRequest(final Collection 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 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( @@ -1967,23 +1948,24 @@ public class XmppConnection implements Runnable { account.getJid() + ": disconnecting because of bind failure (" + packet); + final var error = packet.getError(); + // TODO error.is(Condition) + if (packet.getType() == Iq.Type.ERROR + && error != null + && error.hasChild("conflict")) { + account.setResource(createNewResource()); + } + throw new StateChangingError(Account.State.BIND_FAILURE); } - final Element error = packet.findChild("error"); - if (packet.getType() == IqPacket.TYPE.ERROR - && error != null - && error.hasChild("conflict")) { - account.setResource(createNewResource()); - } - throw new StateChangingError(Account.State.BIND_FAILURE); }, true); } private void clearIqCallbacks() { - final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); - final ArrayList callbacks = new ArrayList<>(); + final Iq failurePacket = new Iq(Iq.Type.TIMEOUT); + final ArrayList> callbacks = new ArrayList<>(); synchronized (this.packetCallbacks) { - if (this.packetCallbacks.size() == 0) { + if (this.packetCallbacks.isEmpty()) { return; } Log.d( @@ -1992,17 +1974,16 @@ public class XmppConnection implements Runnable { + ": clearing " + this.packetCallbacks.size() + " iq callbacks"); - final Iterator> iterator = - this.packetCallbacks.values().iterator(); + final var iterator = this.packetCallbacks.values().iterator(); while (iterator.hasNext()) { - Pair entry = iterator.next(); + final var entry = iterator.next(); callbacks.add(entry.second); iterator.remove(); } } - for (OnIqPacketReceived callback : callbacks) { + for (final var callback : callbacks) { try { - callback.onIqPacketReceived(account, failurePacket); + callback.accept(failurePacket); } catch (StateChangingError error) { Log.d( Config.LOGTAG, @@ -2034,15 +2015,15 @@ public class XmppConnection implements Runnable { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": sending legacy session to outdated server"); - final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); + final Iq startSession = new Iq(Iq.Type.SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket( startSession, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final boolean waitForDisco = enableStreamManagement(); sendPostBindInitialization(waitForDisco, false); - } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + } else if (packet.getType() != Iq.Type.TIMEOUT) { throw new StateChangingError(Account.State.SESSION_FAILURE); } }, @@ -2050,11 +2031,10 @@ public class XmppConnection implements Runnable { } private boolean enableStreamManagement() { - final boolean streamManagement = - this.streamFeatures.hasChild("sm", Namespace.STREAM_MANAGEMENT); + final boolean streamManagement = this.streamFeatures.streamManagement(); if (streamManagement) { synchronized (this.mStanzaQueue) { - final EnablePacket enable = new EnablePacket(); + final var enable = new Enable(); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; mStanzaQueue.clear(); @@ -2111,13 +2091,13 @@ public class XmppConnection implements Runnable { private void sendServiceDiscoveryInfo(final Jid jid) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(jid); iq.query("http://jabber.org/protocol/disco#info"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { boolean advancedStreamFeaturesLoaded; synchronized (XmppConnection.this.disco) { ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); @@ -2135,7 +2115,7 @@ public class XmppConnection implements Runnable { || jid.equals(account.getJid().asBareJid()))) { enableAdvancedStreamFeatures(); } - } else if (packet.getType() == IqPacket.TYPE.ERROR) { + } else if (packet.getType() == Iq.Type.ERROR) { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2159,7 +2139,7 @@ public class XmppConnection implements Runnable { enableAdvancedStreamFeatures(); } } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2169,12 +2149,12 @@ public class XmppConnection implements Runnable { } private void discoverMamPreferences() { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.addChild("prefs", MessageArchiveService.Version.MAM_2.namespace); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { Element prefs = response.findChild( "prefs", MessageArchiveService.Version.MAM_2.namespace); @@ -2189,13 +2169,13 @@ public class XmppConnection implements Runnable { } private void discoverCommands() { - final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + final Iq request = new Iq(Iq.Type.GET); request.setTo(account.getDomain()); request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS); sendIqPacket( request, - (account, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { + (response) -> { + if (response.getType() == Iq.Type.RESULT) { final Element query = response.findChild("query", Namespace.DISCO_ITEMS); if (query == null) { return; @@ -2224,17 +2204,14 @@ public class XmppConnection implements Runnable { private void finalizeBind() { this.offlineMessagesRetrieved = false; - if (bindListener != null) { - bindListener.onBind(account); - } - changeStatusToOnline(); + this.bindListener.run(); + this.changeStatusToOnline(); } private void enableAdvancedStreamFeatures() { if (getFeatures().blocking() && !features.blockListRequested) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Requesting block list"); - this.sendIqPacket( - getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), unregisteredIqListener); } for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { @@ -2250,13 +2227,13 @@ public class XmppConnection implements Runnable { private void sendServiceDiscoveryItems(final Jid server) { mPendingServiceDiscoveries.incrementAndGet(); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setTo(server.getDomain()); iq.query("http://jabber.org/protocol/disco#items"); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { final HashSet items = new HashSet<>(); final List elements = packet.query().getChildren(); for (final Element element : elements) { @@ -2279,7 +2256,7 @@ public class XmppConnection implements Runnable { + ": could not query disco items of " + server); } - if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + if (packet.getType() != Iq.Type.TIMEOUT) { if (mPendingServiceDiscoveries.decrementAndGet() == 0 && mWaitForDisco.compareAndSet(true, false)) { finalizeBind(); @@ -2289,12 +2266,12 @@ public class XmppConnection implements Runnable { } private void sendEnableCarbons() { - final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.addChild("enable", Namespace.CARBONS); this.sendIqPacket( iq, - (account, packet) -> { - if (packet.getType() == IqPacket.TYPE.RESULT) { + (packet) -> { + if (packet.getType() == Iq.Type.RESULT) { Log.d( Config.LOGTAG, account.getJid().asBareJid() + ": successfully enabled carbons"); @@ -2363,8 +2340,8 @@ public class XmppConnection implements Runnable { private void failPendingMessages(final String error) { synchronized (this.mStanzaQueue) { for (int i = 0; i < mStanzaQueue.size(); ++i) { - final AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); - if (stanza instanceof MessagePacket packet) { + final Stanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message packet) { final String id = packet.getId(); final Jid to = packet.getTo(); mXmppConnectionService.markMessage( @@ -2398,7 +2375,7 @@ public class XmppConnection implements Runnable { SaslMechanism.Version.SASL_2, Bind2.QUICKSTART_FEATURES); final boolean usingFast = quickStartMechanism instanceof HashedToken; - final Element authenticate = + final StreamElement authenticate = generateAuthenticationRequest( quickStartMechanism.getClientFirstMessage(sslSocketOrNull(this.socket)), usingFast); @@ -2445,13 +2422,14 @@ public class XmppConnection implements Runnable { return CryptoHelper.random(s ? 3 : 9); } - public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + public String sendIqPacket(final Iq packet, final Consumer callback) { packet.setFrom(account.getJid()); return this.sendUnmodifiedIqPacket(packet, callback, false); } public synchronized String sendUnmodifiedIqPacket( - final IqPacket packet, final OnIqPacketReceived callback, boolean force) { + final Iq packet, final Consumer callback, boolean force) { + // TODO if callback != null verify that type is get or set if (packet.getId() == null) { packet.setAttribute("id", nextRandomId()); } @@ -2464,19 +2442,19 @@ public class XmppConnection implements Runnable { return packet.getId(); } - public void sendMessagePacket(final MessagePacket packet) { + public void sendMessagePacket(final im.conversations.android.xmpp.model.stanza.Message packet) { this.sendPacket(packet); } - public void sendPresencePacket(final PresencePacket packet) { + public void sendPresencePacket(final Presence packet) { this.sendPacket(packet); } - private synchronized void sendPacket(final AbstractStanza packet) { + private synchronized void sendPacket(final StreamElement packet) { sendPacket(packet, false); } - private synchronized void sendPacket(final AbstractStanza packet, final boolean force) { + private synchronized void sendPacket(final StreamElement packet, final boolean force) { if (stanzasSent == Integer.MAX_VALUE) { resetStreamId(); disconnect(true); @@ -2492,7 +2470,7 @@ public class XmppConnection implements Runnable { + " do not write stanza to unbound stream " + packet.toString()); } - if (packet instanceof AbstractAcknowledgeableStanza stanza) { + if (packet instanceof Stanza stanza) { if (this.mStanzaQueue.size() != 0) { int currentHighestKey = this.mStanzaQueue.keyAt(this.mStanzaQueue.size() - 1); if (currentHighestKey != stanzasSent) { @@ -2511,7 +2489,9 @@ public class XmppConnection implements Runnable { + stanzasSent); } this.mStanzaQueue.append(stanzasSent, stanza); - if (stanza instanceof MessagePacket && stanza.getId() != null && inSmacksSession) { + if (stanza instanceof im.conversations.android.xmpp.model.stanza.Message + && stanza.getId() != null + && inSmacksSession) { if (Config.EXTENDED_SM_LOGGING) { Log.d( Config.LOGTAG, @@ -2519,7 +2499,7 @@ public class XmppConnection implements Runnable { + ": requesting ack for message stanza #" + stanzasSent); } - tagWriter.writeStanzaAsync(new RequestPacket()); + tagWriter.writeStanzaAsync(new Request()); } } } @@ -2527,7 +2507,7 @@ public class XmppConnection implements Runnable { public void sendPing() { if (!r()) { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setFrom(account.getJid()); iq.addChild("ping", Namespace.PING); this.sendIqPacket(iq, null); @@ -2535,18 +2515,6 @@ public class XmppConnection implements Runnable { this.lastPingSent = SystemClock.elapsedRealtime(); } - public void setOnMessagePacketReceivedListener(final OnMessagePacketReceived listener) { - this.messageListener = listener; - } - - public void setOnUnregisteredIqPacketReceivedListener(final OnIqPacketReceived listener) { - this.unregisteredIqListener = listener; - } - - public void setOnPresencePacketReceivedListener(final OnPresencePacketReceived listener) { - this.presenceListener = listener; - } - public void setOnJinglePacketReceivedListener(final OnJinglePacketReceived listener) { this.jingleListener = listener; } @@ -2555,10 +2523,6 @@ public class XmppConnection implements Runnable { this.statusListener = listener; } - public void setOnBindListener(final OnBindListener listener) { - this.bindListener = listener; - } - public void setOnMessageAcknowledgeListener(final OnMessageAcknowledged listener) { this.acknowledgedListener = listener; } @@ -2654,7 +2618,7 @@ public class XmppConnection implements Runnable { public boolean r() { if (getFeatures().sm()) { - this.tagWriter.writeStanzaAsync(new RequestPacket()); + this.tagWriter.writeStanzaAsync(new Request()); return true; } else { return false; @@ -2732,11 +2696,11 @@ public class XmppConnection implements Runnable { } public void sendActive() { - this.sendPacket(new ActivePacket()); + this.sendPacket(new Active()); } public void sendInactive() { - this.sendPacket(new InactivePacket()); + this.sendPacket(new Inactive()); } public void resetAttemptCount(boolean resetConnectTime) { @@ -2756,11 +2720,11 @@ public class XmppConnection implements Runnable { public void trackOfflineMessageRetrieval(boolean trackOfflineMessageRetrieval) { if (trackOfflineMessageRetrieval) { - final IqPacket iqPing = new IqPacket(IqPacket.TYPE.GET); + final Iq iqPing = new Iq(Iq.Type.GET); iqPing.addChild("ping", Namespace.PING); this.sendIqPacket( iqPing, - (a, response) -> { + (response) -> { Log.d( Config.LOGTAG, account.getJid().asBareJid() @@ -2776,6 +2740,20 @@ public class XmppConnection implements Runnable { return this.offlineMessagesRetrieved; } + public void fetchRoster() { + final Iq iqPacket = new Iq(Iq.Type.GET); + final var version = account.getRosterVersion(); + if (Strings.isNullOrEmpty(account.getRosterVersion())) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching roster"); + } else { + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": fetching roster version " + version); + } + iqPacket.query(Namespace.ROSTER).setAttribute("ver", version); + sendIqPacket(iqPacket, unregisteredIqListener); + } + private class MyKeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { @@ -2957,13 +2935,12 @@ public class XmppConnection implements Runnable { public boolean sm() { return streamId != null || (connection.streamFeatures != null - && connection.streamFeatures.hasChild( - "sm", Namespace.STREAM_MANAGEMENT)); + && connection.streamFeatures.streamManagement()); } public boolean csi() { return connection.streamFeatures != null - && connection.streamFeatures.hasChild("csi", Namespace.CSI); + && connection.streamFeatures.clientStateIndication(); } public boolean pep() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java index 847678a05753255f1bd600ef928403d0e289038e..2c3c3f762695e712f4f0a3011018ffb4949e1e3e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractContentMap.java @@ -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> entry : this.contents.entrySet()) { final DescriptionTransport 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() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java index 6aeb348c11b126d35ab803809594a385dac027c5..7716792885eb154a8cb0d8a75b23ccad04f51f98 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -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) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java index c678c91cb8b1de6fba4994a69ec3408620202954..d0f39747eae75b72c815bb5c00c9d99d41296418 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/FileTransferContentMap.java @@ -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> contents = of(jinglePacket.getJingleContents()); return new FileTransferContentMap(jinglePacket.getGroup(), contents); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java index 7b2f884578f55ed00e28db715b23db1ac534ba7a..5149470f09bca5fe0ef6eb9c494a407fac5a30cc 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/IceServers.java @@ -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 parse(final IqPacket response) { + public static List parse(final Iq response) { ImmutableList.Builder 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 children = diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index c3080d692a4d336df858f502d9c2bb6be5aa59c7..95047919ca69c19380681eeffe59c9d304e40493 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 68564ec3c4e08569a6abec58c9fa31c867200c94..cb8e1d48da2eea7e4184252f101e8026156ba1a0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -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 { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 26e62373c4f3df402363b8b35e91a4dbdc656331..4471cfc62bf15e430b978384d9593139f3147eff 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -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>> 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 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 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 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 future = receiveRtpContentMap(jinglePacket, false); + final ListenableFuture 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 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( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java index 9a60b3924273c14a4acc235dc5a06f9ff425db75..263905051990a159aba342f173d58aa6a07b65cf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OnJinglePacketReceived.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 94f8ca3002dc8490c2f4cf46dde1dea697d8848b..5036b2b858038e6064751e49e1b264c4b230ed92 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -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> contents = of(jinglePacket.getJingleContents()); if (isOmemoVerified(contents)) { @@ -52,7 +52,7 @@ public class RtpContentMap extends AbstractContentMap> contents) { final Collection> values = contents.values(); - if (values.size() == 0) { + if (values.isEmpty()) { return false; } for (final DescriptionTransport descriptionTransport : diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java index 3878d98d94554a539280bc402cff9b2079df60ba..0553e203e55a2f56b5ea64f57c3c8ce90a5fae68 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/FileTransferDescription.java @@ -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); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java index ce2d4b31f2a61298ca733bcb1fd1abff523fc73e..849b2a404ec13bd75330c5fed894bc3ec6b0ea6c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/InbandBytestreamsTransport.java @@ -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); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java index bbda1c62271125a9d0af2753c724c903aa05d5ec..2925592ea498772ccf62ab48d7b183562f21bf17 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/SocksByteStreamsTransport.java @@ -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 activateProxy(final Candidate candidate) { Log.d(Config.LOGTAG, "trying to activate our proxy " + candidate); final SettableFuture 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 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( diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java index e4dd730d518cd8c3b34ba2134b298b7e65b7daff..904c4aba89568f863bef293db89959cf58e187c7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java @@ -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> 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() diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index ef1da85615c5c6c2db62590981dee5e312b52617..ee3770ead9f3a56322ba8fd62c4660b7f954b701 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java deleted file mode 100644 index 2291a9896110d594f99c5fda4f15210b03975cb6..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractAcknowledgeableStanza.java +++ /dev/null @@ -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()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java deleted file mode 100644 index c0b3d07bdd46afd9d939303c2a142a43894bd56a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/AbstractStanza.java +++ /dev/null @@ -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()); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java deleted file mode 100644 index ba8588e1f552d5ab58387eed08929dd8a300d561..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ /dev/null @@ -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(); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java deleted file mode 100644 index 86068bf774efe9ed255c1def48812d0286ec8827..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/MessagePacket.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java deleted file mode 100644 index c321498d86cd8bd3a7d38d8f76df7090c9212d85..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/PresencePacket.java +++ /dev/null @@ -1,8 +0,0 @@ -package eu.siacs.conversations.xmpp.stanzas; - -public class PresencePacket extends AbstractAcknowledgeableStanza { - - public PresencePacket() { - super("presence"); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java deleted file mode 100644 index e1c465f726b44c02fc28648ed893fc1b45a50ae2..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/ActivePacket.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java deleted file mode 100644 index 1b74de066d17eb5a806506a65c3fff8addcd49e7..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/csi/InactivePacket.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java deleted file mode 100644 index 9e7b991a4b1c29e6399f6771398c635680cc016a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/AckPacket.java +++ /dev/null @@ -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)); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java deleted file mode 100644 index 95558b143230ba3e02e87544089c52c2b030581a..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/EnablePacket.java +++ /dev/null @@ -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"); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java deleted file mode 100644 index 4e0e0f11aa192f97bd461fffcb7d762477d90e9d..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/RequestPacket.java +++ /dev/null @@ -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); - } - -} diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java deleted file mode 100644 index 38681d7c1a370957ad4a251d310279c71b387ac8..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/streammgmt/ResumePacket.java +++ /dev/null @@ -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)); - } - -} diff --git a/src/main/java/im/conversations/android/xmpp/Entity.java b/src/main/java/im/conversations/android/xmpp/Entity.java new file mode 100644 index 0000000000000000000000000000000000000000..a578d250780e40c8b5a588a74f1ea43496f2c515 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Entity.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..b282c07910706508ee0ff0a8b1ee70aa6699e400 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java @@ -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 orderedIdentities = + Ordering.from( + (Comparator) + (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 features = + Ordering.natural() + .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar)); + for (final String feature : features) { + s.append(clean(feature)).append("<"); + } + + final List 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 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 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)); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java new file mode 100644 index 0000000000000000000000000000000000000000..1d8a35a68d2d5822c2d0ada756b91e473bb698e9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java @@ -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 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 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 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 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 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); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..04352b559312c5930456d4729783f49e691d4bf9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java @@ -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 clazz = of(name, namespace); + if (clazz == null) { + return new Element(name, namespace); + } + final Constructor 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 of(final String name, final String namespace) { + return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace)); + } + + public static Id id(final Class 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(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..81a55f18c5a80b7acf2a90ccda9661169ff0e05f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java @@ -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 { + + 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() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "open") + .build()); + public static final NodeConfiguration PRESENCE = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "presence") + .build()); + public static final NodeConfiguration WHITELIST_MAX_ITEMS = + new NodeConfiguration( + new ImmutableMap.Builder() + .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 delegate; + + private NodeConfiguration(Map 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 map) { + this.delegate.putAll(map); + } + + @Override + public void clear() { + this.delegate.clear(); + } + + @NonNull + @Override + public Set keySet() { + return this.delegate.keySet(); + } + + @NonNull + @Override + public Collection values() { + return this.delegate.values(); + } + + @NonNull + @Override + public Set> entrySet() { + return this.delegate.entrySet(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Page.java b/src/main/java/im/conversations/android/xmpp/Page.java new file mode 100644 index 0000000000000000000000000000000000000000..21aa219a44731d091955bf4791fbdffa70fee2f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Page.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Range.java b/src/main/java/im/conversations/android/xmpp/Range.java new file mode 100644 index 0000000000000000000000000000000000000000..8aff5094e91d54fbde845cefc481c0156fbca3b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Range.java @@ -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 + } +} diff --git a/src/main/java/im/conversations/android/xmpp/Timestamps.java b/src/main/java/im/conversations/android/xmpp/Timestamps.java new file mode 100644 index 0000000000000000000000000000000000000000..0135901abf1dca3b2a8292c9bb8b0d538bb5ab13 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/Timestamps.java @@ -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; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java new file mode 100644 index 0000000000000000000000000000000000000000..3b9a03761125dd451ea7ce4a96d66a979f2440d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/AuthenticationStreamFeature.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model; + +import java.util.Collection; + +public abstract class AuthenticationStreamFeature extends StreamFeature{ + + public AuthenticationStreamFeature(final Class clazz) { + super(clazz); + } + + public abstract Collection getMechanismNames(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ByteContent.java b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java new file mode 100644 index 0000000000000000000000000000000000000000..0ca6212ff75a2deadcb807c049c4c7917bbc5abe --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ByteContent.java @@ -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); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java new file mode 100644 index 0000000000000000000000000000000000000000..00e2b652aa9e1970191a9f1e053c9ed6f2e4b8a4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceipt extends Extension { + + protected DeliveryReceipt(Class clazz) { + super(clazz); + } + + public abstract String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..a5a7533bbc23902494dc345f9b0146bb29e2f4f1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class DeliveryReceiptRequest extends Extension { + + protected DeliveryReceiptRequest(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java new file mode 100644 index 0000000000000000000000000000000000000000..094ed6ae5146b7f5abac8f41dcd6af389cc87e61 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Extension.java @@ -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 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 boolean hasExtension(final Class clazz) { + return Iterables.any(this.children, clazz::isInstance); + } + + public E getExtension(final Class clazz) { + final var extension = Iterables.find(this.children, clazz::isInstance, null); + if (extension == null) { + return null; + } + return clazz.cast(extension); + } + + public Collection getExtensions(final Class clazz) { + return Collections2.transform( + Collections2.filter(this.children, clazz::isInstance), clazz::cast); + } + + public Collection getExtensionIds() { + return Collections2.transform( + this.children, c -> new ExtensionFactory.Id(c.getName(), c.getNamespace())); + } + + public T addExtension(T child) { + this.addChild(child); + return child; + } + + public void addExtensions(final Collection extensions) { + for (final Extension extension : extensions) { + addExtension(extension); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/Hash.java b/src/main/java/im/conversations/android/xmpp/model/Hash.java new file mode 100644 index 0000000000000000000000000000000000000000..8c41add8d9eefd69ddf246534e66b7bd74256769 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/Hash.java @@ -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()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamElement.java b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java new file mode 100644 index 0000000000000000000000000000000000000000..ca5fd0053b0839a479d5fddedc999b64b09f764f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamElement.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamElement extends Extension { + + protected StreamElement(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java new file mode 100644 index 0000000000000000000000000000000000000000..eadd8d8c4407e9b175ab8e5359e64cd5d505dbd5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/StreamFeature.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model; + +public abstract class StreamFeature extends Extension{ + + public StreamFeature(Class clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..f812ec53beac1689a78daf6fd10f5127c2d1f9e1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Address.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java new file mode 100644 index 0000000000000000000000000000000000000000..3ecafc53095f671f5b4de839d2e58b4074c0e3e9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/Addresses.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..6c504489e2943155c7d2a23cb19354beefea692f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/addressing/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java new file mode 100644 index 0000000000000000000000000000000000000000..b661bca3a329475738cf5167871380828e6f7825 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java new file mode 100644 index 0000000000000000000000000000000000000000..f544af72fc784e0b935bbac0c767bd3504194807 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java new file mode 100644 index 0000000000000000000000000000000000000000..400f989572c2813aaeacdce5863e012ba128af3e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java new file mode 100644 index 0000000000000000000000000000000000000000..2321c2e49b6ebed7b8bb2b3a4bce3f9e41fb459a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java @@ -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 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 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()); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java new file mode 100644 index 0000000000000000000000000000000000000000..0ad10d7025e912b3b9d88952e64000ce7db4c46f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java new file mode 100644 index 0000000000000000000000000000000000000000..ec4fce469437793b0a7d63d129d12fb3f45cd1a1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java @@ -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 getDevices() { + return this.getExtensions(Device.class); + } + + public Set getDeviceIds() { + return ImmutableSet.copyOf( + Collections2.filter( + Collections2.transform(getDevices(), Device::getDeviceId), + Objects::nonNull)); + } + + public void setDeviceIds(Collection deviceIds) { + for (final Integer deviceId : deviceIds) { + final var device = this.addExtension(new Device()); + device.setDeviceId(deviceId); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java new file mode 100644 index 0000000000000000000000000000000000000000..2008fb017d2a9cdba76c7756ea6187a2c15fd1ec --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java @@ -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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java new file mode 100644 index 0000000000000000000000000000000000000000..1a98068ab73cedbef8f385cae450cfaf37ad6f64 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..91e2bd87ba6d44f152d8ee90d3eaafc639b0a369 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java @@ -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 getSourceDevice() { + return getOptionalIntAttribute("sid"); + } + + public Collection 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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java new file mode 100644 index 0000000000000000000000000000000000000000..22164976a92c84dfbcd9a2a12c9161ce5c5f998a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IV.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java new file mode 100644 index 0000000000000000000000000000000000000000..f48fcbd7cb3bd3c0aa6a7992c2c6b7ebe0ba959f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java new file mode 100644 index 0000000000000000000000000000000000000000..3ad7357b8dd934303b42d71e5b37f941e95abbca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Key.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java new file mode 100644 index 0000000000000000000000000000000000000000..9c58701100005988ebd2684d5e0475fdb1d0dcf0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java new file mode 100644 index 0000000000000000000000000000000000000000..a7d39c1daf82b541387ee867ffb29c57e0cdcaf6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java new file mode 100644 index 0000000000000000000000000000000000000000..3613b8aa8a9ade6129bbeaa9462314246d282e5d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java new file mode 100644 index 0000000000000000000000000000000000000000..0e0ca728231e71a4f4dd9df3227a6fed537a59db --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java new file mode 100644 index 0000000000000000000000000000000000000000..5051cb1b14fc3c9e996cf7bd6cbe966eba130fee --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..ad3d21c16c79dfbbcf989c77f28cd94cd91de124 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java new file mode 100644 index 0000000000000000000000000000000000000000..27264f7545e079f2c3cb6415f4763ab3c334dc3b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Bind.java @@ -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; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java new file mode 100644 index 0000000000000000000000000000000000000000..04633a009929cce3b7776f87f5cd28c3f767cd49 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Jid.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java new file mode 100644 index 0000000000000000000000000000000000000000..b3fd1e5c17d070ba994754f499e74a0dd0d7cf31 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/Resource.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..aebaeeb729420e22431f57a7834d23c9578f6be0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java new file mode 100644 index 0000000000000000000000000000000000000000..bff72a06b5f595aa0f3d184c0b3cabad96317c46 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bind.java @@ -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 getInlineFeatures() { + final var inline = getInline(); + return inline == null ? Collections.emptyList() : inline.getExtensions(Feature.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java new file mode 100644 index 0000000000000000000000000000000000000000..0144edb9147ae909c1e11626fcd38c8a20c6cde7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Bound.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java new file mode 100644 index 0000000000000000000000000000000000000000..66720abbc631da53c7308095d87138db0d561156 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Feature.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java new file mode 100644 index 0000000000000000000000000000000000000000..641a9d4f4556673493d9648c4d8e80e0e73123f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/Inline.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..2d8c5e92cb122bfb76f8dada143389791de0295d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bind2/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java new file mode 100644 index 0000000000000000000000000000000000000000..6f5d00b3e566d1651059d75fb350c2a233959439 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java new file mode 100644 index 0000000000000000000000000000000000000000..a56662d77152bf2d22bc11d76f8d01cb90f30c52 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..647b0ae9912de215bffb4f5057bbc95dae3e6bfd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java new file mode 100644 index 0000000000000000000000000000000000000000..90cec110c050788661af7afbae475d68a7906d26 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..22d8f0e1f0b7bdb1d92d58403e5fdf3c6a3d459f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java new file mode 100644 index 0000000000000000000000000000000000000000..0f924e8883b40157d0aa96676ab5f6412e971138 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java new file mode 100644 index 0000000000000000000000000000000000000000..b9385cf5473de88d5d43d771a57fe88995bc2536 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Extensions.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java new file mode 100644 index 0000000000000000000000000000000000000000..ee5efa3864baebb0c848c65769b6b0a8d4a69022 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/Nick.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..1bb963be849a57cdfeae7a2eb1c8930db49c799a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..d0f23b2833e9d1e0694d168e5fe0d65769887d4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java @@ -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 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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..f8ed4ef66196030c51ad5bdac612ffc417e0c2c7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java @@ -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 getExtension(final Class 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; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java new file mode 100644 index 0000000000000000000000000000000000000000..797d627cf10ce40f3c7c54841092d4ae012e65c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java @@ -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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java new file mode 100644 index 0000000000000000000000000000000000000000..38b740e8c0c04ac1b59ce4edf5e906bba9e0ed3d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..507869a60c5967760f42c916d5d5544a29b5ff71 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java new file mode 100644 index 0000000000000000000000000000000000000000..0201c53c636cf8e70a88de457c7a354ce435cc10 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f4c76376a384778c84484ee269d9e0135f69ce18 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java new file mode 100644 index 0000000000000000000000000000000000000000..dbd7395572c7b614f9c7caf83996fbe94e8aca55 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Active.java b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java new file mode 100644 index 0000000000000000000000000000000000000000..21fb65bb4a7d22e05c6ac8f6ba8cc15dd75f9551 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Active.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java new file mode 100644 index 0000000000000000000000000000000000000000..60bd59edb145ce99cee42e4d8aa0e4f67e759307 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/ClientStateIndication.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java new file mode 100644 index 0000000000000000000000000000000000000000..7c36b593d544f29a7c2859a3ebd6ccee39c3a05b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..58d26b1f13186513a6338807cca6eacb2102c11f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Data.java b/src/main/java/im/conversations/android/xmpp/model/data/Data.java new file mode 100644 index 0000000000000000000000000000000000000000..c754ee48de9364084628b26fc596d2645741ead3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Data.java @@ -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 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 values) { + final var data = new Data(); + data.setType(FORM_TYPE_SUBMIT); + data.setFormType(formType); + for (final Map.Entry entry : values.entrySet()) { + data.addField(entry.getKey(), entry.getValue()); + } + return data; + } + + public Data submit(final Map 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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Field.java b/src/main/java/im/conversations/android/xmpp/model/data/Field.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f72fab86e7924e107674d02de4707030ebbf13 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Field.java @@ -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 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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Option.java b/src/main/java/im/conversations/android/xmpp/model/data/Option.java new file mode 100644 index 0000000000000000000000000000000000000000..b9c3e9aae4f1d7285dcfa07ed4f5a20f984f32d4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Option.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Value.java b/src/main/java/im/conversations/android/xmpp/model/data/Value.java new file mode 100644 index 0000000000000000000000000000000000000000..8e9eccc4d715f6151e7cff6c9b9065f4cb3c6adc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/Value.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/data/package-info.java b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..fcc0e1f790ba4f9a77e79115244ff4528f507abd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/data/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java new file mode 100644 index 0000000000000000000000000000000000000000..b294f83d456fb0fa17779f187eb7838200e34cce --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java @@ -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; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java new file mode 100644 index 0000000000000000000000000000000000000000..86a93af0dcfd00eb77688ca5f3da5074a190b27c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Service.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java new file mode 100644 index 0000000000000000000000000000000000000000..36338083da5d03530ee42027f618225e8c248632 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/Services.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..868a5a17531c7b5fedb1764f24afcc15fcb3cb82 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/external/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java new file mode 100644 index 0000000000000000000000000000000000000000..dd288918cba6541a123dea1544edee5aa8689591 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java new file mode 100644 index 0000000000000000000000000000000000000000..6da0a4aa22f1770095dbf0bae98de2a6344a453b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..55f104e25bb547419e1e74ffa077ec563a354ea3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java @@ -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 getFeatures() { + return this.getExtensions(Feature.class); + } + + public boolean hasFeature(final String feature) { + return Iterables.any(getFeatures(), f -> feature.equals(f.getVar())); + } + + public Collection getIdentities() { + return this.getExtensions(Identity.class); + } + + public boolean hasIdentityWithCategory(final String category) { + return Iterables.any(getIdentities(), i -> category.equals(i.getCategory())); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..60eb24a5983d156b91500f99418ae0b1788e7e02 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..f5bf2b98400d94fcbcc11c815638d15f645170bb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..981132ed6905fc901ca3c5f208d2b6af993de4cd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a170e5cee398c171c36b5fa396362aa836ad8dcb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Condition.java b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java new file mode 100644 index 0000000000000000000000000000000000000000..bd68c2c433cad9bb56a6b70a3c74629cf347f6d0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Condition.java @@ -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 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); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Error.java b/src/main/java/im/conversations/android/xmpp/model/error/Error.java new file mode 100644 index 0000000000000000000000000000000000000000..0a07e73f9d078a9412fedae7c34438567ba077d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Error.java @@ -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 clazz) { + super(clazz); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Text.java b/src/main/java/im/conversations/android/xmpp/model/error/Text.java new file mode 100644 index 0000000000000000000000000000000000000000..478b1f5cd531ea54ff7402c7a5e9c7ec4c23bec7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Text.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java new file mode 100644 index 0000000000000000000000000000000000000000..1291d8ea06a5c566c8672c0d71d7e1e6f1d7490f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Fast.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..240f5de0e13d9b9dbd4cf08c7369469600ab8b4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Mechanism.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java new file mode 100644 index 0000000000000000000000000000000000000000..4ac5a9205f4189d246d95b00aa82d99188e5e148 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/RequestToken.java @@ -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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/Token.java b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java new file mode 100644 index 0000000000000000000000000000000000000000..258cd9abadf650fa97e15ab4d45e1e5aa990d69d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/Token.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..effc9e511cb824aff5454a29e3a300f4e6121e11 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/fast/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java new file mode 100644 index 0000000000000000000000000000000000000000..80a646a41df7901ab9fbf18842f37f19757972c0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/Store.java b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java new file mode 100644 index 0000000000000000000000000000000000000000..fe82612adcc61412c55a664610cac99372e2188d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/Store.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..76c25d6551e88a2647b9afa5dba86676743fa930 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/hints/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java new file mode 100644 index 0000000000000000000000000000000000000000..5857f058518768c5ac26016552de2cad6b291baf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java new file mode 100644 index 0000000000000000000000000000000000000000..7c5b3bdc98fdedf964b766ada9f7f47645e936fd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Priority.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java new file mode 100644 index 0000000000000000000000000000000000000000..44dc512be24e2aa4c32de44cfe4aa0f82eb25f8a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Show.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java new file mode 100644 index 0000000000000000000000000000000000000000..3175230d7191576d7c836350a4ca75cfe5273467 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Status.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java new file mode 100644 index 0000000000000000000000000000000000000000..4ae3b8ed519a234698cc3ec30f12d4f1a25d44f4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Subject.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java new file mode 100644 index 0000000000000000000000000000000000000000..703429ef0ba9fb9d3122af6f222f619cf4bb9705 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..448804d7c8c6d93849f8c151009486937c74c3d8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java similarity index 62% rename from src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java rename to src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java index a24040d3dbe82efcbfc558e0ca56c006cf4d82bd..aeb79ffd2f328e340348a472f86289004cd90f72 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/Jingle.java @@ -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 getJingleContents() { - final Element jingle = findChild("jingle", Namespace.JINGLE); - ImmutableMap.Builder 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 getJingleContents() { + ImmutableMap.Builder 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 { diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..a0c6dfbbd76a9b48d975ea8a63fdac2c39957858 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/error/JingleCondition.java @@ -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 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); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..6af2511e8da94ec172d8f1276422988db9812426 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jingle/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java new file mode 100644 index 0000000000000000000000000000000000000000..20ae15aee379933b996ec257fcc49acdc3c0a3f5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Accept.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..0045844f0a47231e302d51db057c862a5a994a3c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/JingleMessage.java @@ -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 clazz) { + super(clazz); + } + + public String getSessionId() { + return this.getAttribute("id"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java new file mode 100644 index 0000000000000000000000000000000000000000..b6be44ee091b85165958c182ebef230dd6c1bbe8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Proceed.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java new file mode 100644 index 0000000000000000000000000000000000000000..d5a48a4033f8bcadda046185e7266655ac4f52df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Propose.java @@ -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 getDescriptions() { + final ImmutableList.Builder 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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java new file mode 100644 index 0000000000000000000000000000000000000000..e71206fd619b09378c35ba0fd450e227a15545eb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Reject.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..7c507156d85d9e7653d9c61b0261187f92ef7beb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/Retract.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9ce640b1f9619957a7127e4e6ce325f3d2c3c65a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/jmi/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/End.java b/src/main/java/im/conversations/android/xmpp/model/mam/End.java new file mode 100644 index 0000000000000000000000000000000000000000..757ed60c69995b98183bee3ccb22324214baa533 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/End.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java new file mode 100644 index 0000000000000000000000000000000000000000..534072647563b9f55b1e9df190a3de8d309fc8af --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Fin.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java new file mode 100644 index 0000000000000000000000000000000000000000..9f05e08fc21fc33bc708294497b09a0549697395 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Metadata.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Query.java b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f701d91de17b8e3f00da00bcb9dcfb8019fee7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Query.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Result.java b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java new file mode 100644 index 0000000000000000000000000000000000000000..253499756ba26bf1387fd4191fa126d4f1ec53b9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Result.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Start.java b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java new file mode 100644 index 0000000000000000000000000000000000000000..9ff84b2564062b368ac48e093f1d2ac7a8b77884 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/Start.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..1aa4982e6af857936aa753c81aa602de71b57050 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java new file mode 100644 index 0000000000000000000000000000000000000000..be31df35d6b0259c9391d96f45c6ad8cadc9a81d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java new file mode 100644 index 0000000000000000000000000000000000000000..08161af709d9747f6c0dc58ece892232dd39e281 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Received.java b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..7007cd176271e4a46ab335e4b7c4e70409caa24e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/Received.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..950963d4f00924052c6b4007cf343b7f52da65df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java new file mode 100644 index 0000000000000000000000000000000000000000..9f5275371c3564ee52d1825bc67fda4327d36514 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/mds/Displayed.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java new file mode 100644 index 0000000000000000000000000000000000000000..6502a16e72a41aea353c8d0467d38c6d22455c2a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java @@ -0,0 +1,9 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Affiliation { + OWNER, + ADMIN, + MEMBER, + OUTCAST, + NONE; +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/History.java b/src/main/java/im/conversations/android/xmpp/model/muc/History.java new file mode 100644 index 0000000000000000000000000000000000000000..e09210e60277c22491ae60940543a77d4e3ab523 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/History.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java new file mode 100644 index 0000000000000000000000000000000000000000..33da7b9af38b4083739aba6f0dd7a004d522b8cf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/MultiUserChat.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Role.java b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..9e9d3d165666b4f310b809adfb2ee1c764623a39 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/Role.java @@ -0,0 +1,8 @@ +package im.conversations.android.xmpp.model.muc; + +public enum Role { + MODERATOR, + VISITOR, + PARTICIPANT, + NONE +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..41d652f204e92fd1e77bd5ff447efa959c4af9f2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..7ff712aeaba976c687928c61c8255b4fb19967b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Item.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java new file mode 100644 index 0000000000000000000000000000000000000000..5496c3ef204cdc2ef51de90f1b60285608408768 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/MucUser.java @@ -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 getStatus() { + return Collections2.filter( + Collections2.transform(getExtensions(Status.class), Status::getCode), + Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java new file mode 100644 index 0000000000000000000000000000000000000000..0706585af418b6f18c561b86e0688a7987815ff6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/Status.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f5bfcaeda0112563ec3a67faff002586518ebe65 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java new file mode 100644 index 0000000000000000000000000000000000000000..e9a98512823681750c9bdd5c79c8507ea2b9ce23 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java new file mode 100644 index 0000000000000000000000000000000000000000..29ffc739f6b419b388929fdb53666aa5ec39e6e6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java @@ -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")); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java new file mode 100644 index 0000000000000000000000000000000000000000..b324332a93828be7faa2aa5e7e8806e076e29e2f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java @@ -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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/URL.java b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java new file mode 100644 index 0000000000000000000000000000000000000000..008b084800de7fb8fc65d2ef102154cdfb823bd3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/URL.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..aec4dee2466834735e150cce4569a1aac5fe4e33 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..d3d4b391034608fbcc7b3e52757f0b69403c5962 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java new file mode 100644 index 0000000000000000000000000000000000000000..43e4e2354b5e6be25564deb883dabab1287b7808 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Encrypted.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java new file mode 100644 index 0000000000000000000000000000000000000000..c75413972f521366f46f542d222f0d851cd7219c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pgp/Signed.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java new file mode 100644 index 0000000000000000000000000000000000000000..7f8f1c3a0505d65182c3e7ea05ecef51c8f20f19 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..dbf2c3c239a6a9f2009c25fb2e28bb8e96e4e56f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java @@ -0,0 +1,10 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.xmpp.model.Extension; + +public interface Item { + + T getExtension(final Class clazz); + + String getId(); +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java new file mode 100644 index 0000000000000000000000000000000000000000..ceb1931ca35cbb1048e5c66f14cfe5ea9eff2303 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java @@ -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 getItems(); + + String getNode(); + + Collection getRetractions(); + + default Map getItemMap(final Class clazz) { + final ImmutableMap.Builder 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 getItemOrThrow(final String id, final Class 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 getFirstItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getFirst(map.values(), null); + } + + default T getOnlyItem(final Class clazz) { + final var map = getItemMap(clazz); + return Iterables.getOnlyElement(map.values()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java new file mode 100644 index 0000000000000000000000000000000000000000..a4fc1ee8ecc2f36ad5cc087373594fc4a6db3cb7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java @@ -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 getItems() { + return this.getExtensions(Item.class); + } + + public Collection 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); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java new file mode 100644 index 0000000000000000000000000000000000000000..7a384f5489745dad052c06c0d52806c2acc8a4e5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..ec94f0604df8e042030d3ff4d7dbeaceebe08a75 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java @@ -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; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..309381197e0a5f226bba63fd80b7b3077ed03e60 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..a1c81a659d8c397d0fa977544fd2f89a2f0cfa59 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java @@ -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 clazz) { + super(clazz); + } + + @XmlElement + public static class PreconditionNotMet extends PubSubError { + + public PreconditionNotMet() { + super(PreconditionNotMet.class); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..49d45f8c59e55b0022880a4e6609bf093fffe966 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..1e180c460053ee0bb74cbefa753c86b63f175e2f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java @@ -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 getItems() { + return this.getExtensions(Item.class); + } + + public Collection 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"); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java new file mode 100644 index 0000000000000000000000000000000000000000..64550e0b77f1ca33d57e2b501204995545bfcac6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java new file mode 100644 index 0000000000000000000000000000000000000000..139a49522c48cb74aa9b4469768baf43305fb446 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..223345c68b18021198477ec4168c2ef4cc0ddd9b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java new file mode 100644 index 0000000000000000000000000000000000000000..53b987f53c9c7fddbb2c3766e3731bd104722a67 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java new file mode 100644 index 0000000000000000000000000000000000000000..c3a61e6195750b2c42524d3e5309aa004918b083 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..d3ecb89aa94dbbaba381de987db16a19d03c3747 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a68a021fd5cbc339dd345d8e0121d44fe0f71541 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java new file mode 100644 index 0000000000000000000000000000000000000000..1d854a83a4def96a087612a5bf66621a22e20961 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java new file mode 100644 index 0000000000000000000000000000000000000000..ec3ae989176bc25d1cef26cafa8137c8ef707d46 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java @@ -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 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; + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..bdb8a8dca23d7ba4a17dba9c09089793959780b2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java new file mode 100644 index 0000000000000000000000000000000000000000..71fe922c158adeb678b30e6181047329e5dc0784 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..684477af354890b8cc9ef7487e4850926925b8f6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..8e3de2cad52e6ee3300ac4ea0e284cce1b8c40c0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java new file mode 100644 index 0000000000000000000000000000000000000000..cd22f2a3a8427cd0d58ff511a6daa2d5525215c3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Password.java b/src/main/java/im/conversations/android/xmpp/model/register/Password.java new file mode 100644 index 0000000000000000000000000000000000000000..9da687c213e2cc26dfbc6eaec8710a84a8ff71f7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Password.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Register.java b/src/main/java/im/conversations/android/xmpp/model/register/Register.java new file mode 100644 index 0000000000000000000000000000000000000000..4a48bd8d15283ab5d2f6cda5f0a782735d84cbf2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Register.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Remove.java b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java new file mode 100644 index 0000000000000000000000000000000000000000..bbd327bfd66130779102cadb4817602e1979c40a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Remove.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Username.java b/src/main/java/im/conversations/android/xmpp/model/register/Username.java new file mode 100644 index 0000000000000000000000000000000000000000..bc93581b66937b358324193a00b9e4e85907aa8b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/Username.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/register/package-info.java b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9e7a3e8f33d5fc7afe2eef429b23023e94e755c5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/register/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java new file mode 100644 index 0000000000000000000000000000000000000000..9f36efae7c7d6defa57a3b142a55725e2ae386df --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2e0ef54add741e8141d333dc9d21e510e309ec --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java @@ -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 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 getGroups() { + return Collections2.filter( + Collections2.transform(getExtensions(Group.class), Element::getContent), + Objects::nonNull); + } + + public enum Subscription { + NONE, + TO, + FROM, + BOTH, + REMOVE + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Query.java b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..616f6ae0b68cd6a13fdc9096d18df47f5852a8fb --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Query.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..eea0703fd6f0ea886d484914a512398c1b6713d5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/After.java b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java new file mode 100644 index 0000000000000000000000000000000000000000..90179bff0a1af8f678196858701e4df7f1a7916f --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/After.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java new file mode 100644 index 0000000000000000000000000000000000000000..c3c6ac1a89a3c07b1b10e4d77d25d39b06602ab4 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Before.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java new file mode 100644 index 0000000000000000000000000000000000000000..c54f9d5e08fc4f8980712bfbebaed0a4d0e5f49e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Count.java @@ -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); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/First.java b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java new file mode 100644 index 0000000000000000000000000000000000000000..b976632e43eb993c3b06bf8e4231748f1c1190c1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/First.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java new file mode 100644 index 0000000000000000000000000000000000000000..01d53e07304c62e7846fc70414f146e1bceb2cbd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Last.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java new file mode 100644 index 0000000000000000000000000000000000000000..06908be8b8c761284c65762541794ef7f1e83757 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Max.java @@ -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)); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java new file mode 100644 index 0000000000000000000000000000000000000000..6f428565c47e7aef7fe9d013d64ef524b963e87d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/Set.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..c00fd37c9bd4f6a8544e25651780b4365c6b75cc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/rsm/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java new file mode 100644 index 0000000000000000000000000000000000000000..668d10d80f8bcb56971968d41d500615d129a987 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Auth.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..e23087d8924afb6de4747b58c012527da85bbcfc --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanism.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java new file mode 100644 index 0000000000000000000000000000000000000000..7612ba3583e0e7a3576198f682d556f083366b65 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Mechanisms.java @@ -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 getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..5e2ab626e1ca067204e9bdb25fd19b1a15c72d9a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Response.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java new file mode 100644 index 0000000000000000000000000000000000000000..d7323e4789e7afebd17ed31b95070e4e6a710397 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/Success.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..3b0de4f4a8ba4a30ce6852b0b2de9c983113b0ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java new file mode 100644 index 0000000000000000000000000000000000000000..7869bf010600d2525add357171a4f57601977db7 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authenticate.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java new file mode 100644 index 0000000000000000000000000000000000000000..ad26d37e6ea9d06fb8f3363fa3a39d1e5de435d2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Authentication.java @@ -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 getMechanisms() { + return getExtensions(Mechanism.class); + } + + public Collection getMechanismNames() { + return Collections2.filter(Collections2.transform(getMechanisms(), Element::getContent), Objects::nonNull); + } + + public Inline getInline() { + return this.getExtension(Inline.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java new file mode 100644 index 0000000000000000000000000000000000000000..e29ae7dea3ced429d4e25d17f1fe1954017b5d98 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/AuthorizationIdentifier.java @@ -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; + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java new file mode 100644 index 0000000000000000000000000000000000000000..6a6ad0dd8b1fa526a5015eb0dcaca4d475586504 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java @@ -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 getFastMechanisms() { + final var fast = getFast(); + final Collection mechanisms = + fast == null ? Collections.emptyList() : fast.getExtensions(Mechanism.class); + return Collections2.filter( + Collections2.transform(mechanisms, Element::getContent), Objects::nonNull); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java new file mode 100644 index 0000000000000000000000000000000000000000..d0a615777be37c9ba979e4db03879954ebe95a1e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Mechanism.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..91f1b7dab6a6e2dc48c40d8e9638085b9ed4404b --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Response.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java new file mode 100644 index 0000000000000000000000000000000000000000..17673b35a7035d6229e1d808d336839b05b43aa3 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/Success.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..10a61d1098dcaded964807e1610ec2d1f3b46336 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java new file mode 100644 index 0000000000000000000000000000000000000000..5cafc8c1d41a53281c2e27cde0503ced3c0d880a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java @@ -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 getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java new file mode 100644 index 0000000000000000000000000000000000000000..9b80a93baa0f565c05b2cbe7df7146cf396397dd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java new file mode 100644 index 0000000000000000000000000000000000000000..b900d435cd81b47e0e36b2a94c1efc01dc2e03ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Enabled.java @@ -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 getResumeId() { + final var id = this.getAttribute("id"); + if (Strings.isNullOrEmpty(id)) { + return Optional.absent(); + } + if (isResume()) { + return Optional.of(id); + } else { + return Optional.absent(); + } + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java new file mode 100644 index 0000000000000000000000000000000000000000..1e15bfe6c0b46fa97d2ddc4b7bcbe4243ed19217 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Failed.java @@ -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 getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Request.java b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1de61bc827995471cde86b6d437cf81ad3db1c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Request.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java new file mode 100644 index 0000000000000000000000000000000000000000..e47b19966ca7b344296c6580797d639a7996ff83 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java new file mode 100644 index 0000000000000000000000000000000000000000..eb240745fd4b1402e96c838ef71a868f1d7dcf68 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/Resumed.java @@ -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 getHandled() { + return this.getOptionalIntAttribute("h"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java new file mode 100644 index 0000000000000000000000000000000000000000..48103755a14d5fc335808ff76972e57e02d26048 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/StreamManagement.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..dd2e036fcc2527fbca9acd8283189f9852f4b133 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java new file mode 100644 index 0000000000000000000000000000000000000000..9f94400c32692b9a734c5d503e98d3151738d532 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java new file mode 100644 index 0000000000000000000000000000000000000000..a1c981b9d994fee3705efb7a6b4c6bcb86ecea02 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java @@ -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 + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java new file mode 100644 index 0000000000000000000000000000000000000000..129660b000cd1565956f10703afe828599eb8bf8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java new file mode 100644 index 0000000000000000000000000000000000000000..82a8ce3dfbfa1f225e6030ace9f746cdff09c9cd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -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 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()); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..d12fe56dbe53003ddbd8a9d81bc960d1cea69da9 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Active.java b/src/main/java/im/conversations/android/xmpp/model/state/Active.java new file mode 100644 index 0000000000000000000000000000000000000000..15970bc5bf5eb6ea9d7bf21d319d22d2d637ddaf --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Active.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java new file mode 100644 index 0000000000000000000000000000000000000000..642ed519d15a34e6d368086ecf0f49d53303c1a2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java @@ -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 clazz) { + super(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Composing.java b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java new file mode 100644 index 0000000000000000000000000000000000000000..9871952e0de7d631364f53f31e8e289c052e39ba --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Composing.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Gone.java b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a74e788c99fa10810afa00f6d11b70538d52ea --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Gone.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java new file mode 100644 index 0000000000000000000000000000000000000000..4a3670308ac0067d55cb4c6e0009d98514765382 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Paused.java b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java new file mode 100644 index 0000000000000000000000000000000000000000..f97f3e5045b54e4a318cdf5843766f6175bb40da --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/Paused.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/state/package-info.java b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a0cc97debfa49a575c48b8c6f5dd53086a75b52e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/state/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/Features.java b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java new file mode 100644 index 0000000000000000000000000000000000000000..0597c2241cfb809a46d7747ce309fc01a01fe15c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/Features.java @@ -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 clazz) { + return hasExtension(clazz); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..56900532c47a7a502beada11ecb22577a0583a07 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java new file mode 100644 index 0000000000000000000000000000000000000000..3e2cf454c9c3df9ec8b6b6b71e74648daaddc8e8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Proceed.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/Required.java b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java new file mode 100644 index 0000000000000000000000000000000000000000..60f4652ba8b6293b6ecbb610a37e75c5815b0d4a --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/Required.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java new file mode 100644 index 0000000000000000000000000000000000000000..337371c7b6aef6fef4b2d69faa1725091db20bd2 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/StartTls.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..de3ed3ecdbc1274997673d6aef30d8042ab78540 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/tls/package-info.java @@ -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; \ No newline at end of file diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java new file mode 100644 index 0000000000000000000000000000000000000000..31a93962104ef834cae83978ce89d65946fb61b8 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java new file mode 100644 index 0000000000000000000000000000000000000000..23b0fdcac823de630719bf14f8c47a559a2fbcfd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..31209ee24f25dc21e2c57aaad97c54fb38ae6066 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Get.java b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java new file mode 100644 index 0000000000000000000000000000000000000000..5fad9afd409fe0d21d3f8a17cc68c7f7a22d5de1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Get.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Header.java b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..00546d0d984f66542270ac645f9bf094035f74b1 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Header.java @@ -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"); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Put.java b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java new file mode 100644 index 0000000000000000000000000000000000000000..1b52a495c551b4acb34778f09c45aff11999ed12 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Put.java @@ -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
getHeaders() { + return this.getExtensions(Header.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Request.java b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java new file mode 100644 index 0000000000000000000000000000000000000000..bbf8a98c1632aca15b6f39015448b1b3a611777d --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Request.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java new file mode 100644 index 0000000000000000000000000000000000000000..df90157812be59d01d54ceca65ee47b7d9e764ff --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/Slot.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e4ccf3d8dc5087ad0c9eb589ba05a2f5ad9ec1ca --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/upload/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java new file mode 100644 index 0000000000000000000000000000000000000000..273dcfb25f8265d127db82c9d837a175e0d026ce --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/BinaryValue.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java new file mode 100644 index 0000000000000000000000000000000000000000..92adc6831c5346cc15a1d2a5663341e29497e2c5 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java new file mode 100644 index 0000000000000000000000000000000000000000..20a6949775b4348d1c703d3362e570ff98b4f07c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..7ee576ca27cc6113a5e8ab2a3dad72c1e8aa2339 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java new file mode 100644 index 0000000000000000000000000000000000000000..cb1f86d053eeba029c5339922bb63f614ed35f32 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/Photo.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java new file mode 100644 index 0000000000000000000000000000000000000000..0be3f94b9fef7428b4977ebde3cdb728f55b1e7e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/VCardUpdate.java @@ -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(); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..efed1536072f01dbe4279eafffe010740dda8ee6 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/update/package-info.java @@ -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; diff --git a/src/main/java/im/conversations/android/xmpp/model/version/Version.java b/src/main/java/im/conversations/android/xmpp/model/version/Version.java new file mode 100644 index 0000000000000000000000000000000000000000..7cbd5d22a805a46e2763d7425502d83e862790bd --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/version/Version.java @@ -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); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..bc8097fda6620d6f7a1e51cab4e529fb837eab28 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -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); + + } +} diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index 45180b5d5dfb835a5db8df7c40868889bbaaebca..e5f4b9d7c83fc7463b1283c60326441d929eece2 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -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() { diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index 69cef954841e74a6fb668290f19c230d7f936e7b..8cf8fe20296d24b9763d8ee263f3796c9acef97c 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java @@ -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 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");