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 index 627b0271e..896cd6c84 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -8,7 +9,6 @@ import im.conversations.android.annotation.XmlElement; import im.conversations.android.annotation.XmlPackage; import java.io.IOException; import java.io.PrintWriter; -import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; @@ -88,8 +88,9 @@ public class XmlElementProcessor extends AbstractProcessor { private static Id of(final TypeElement typeElement) { final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class); - PackageElement packageElement = (PackageElement) typeElement.getEnclosingElement(); - XmlPackage xmlPackage = packageElement.getAnnotation(XmlPackage.class); + PackageElement packageElement = getPackageElement(typeElement); + XmlPackage xmlPackage = + packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class); if (xmlElement == null) { throw new IllegalStateException( String.format( @@ -112,13 +113,29 @@ public class XmlElementProcessor extends AbstractProcessor { } final String name; if (Strings.isNullOrEmpty(elementName)) { - name = typeElement.getSimpleName().toString().toLowerCase(Locale.ROOT); + 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; + } + } + } + public static class Id { public final String name; public final String namespace; diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index e859f380e..2cf6b086e 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -9,6 +9,7 @@ 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.ExtensionFactory; import im.conversations.android.xmpp.model.Extension; import java.util.ArrayList; import java.util.Collection; @@ -89,6 +90,11 @@ public class Element { 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 String findChildContent(String name) { Element element = findChild(name); return element == null ? null : element.getContent(); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 66566e12e..ccfb3dce4 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -80,4 +80,6 @@ public final class Namespace { public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; public static final String JABBER_CLIENT = "jabber:client"; public static final String FORWARD = "urn:xmpp:forward:0"; + + public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"; } diff --git a/src/main/java/eu/siacs/conversations/xml/XmlElementReader.java b/src/main/java/eu/siacs/conversations/xml/XmlElementReader.java index cce6fc163..60baa1fbf 100644 --- a/src/main/java/eu/siacs/conversations/xml/XmlElementReader.java +++ b/src/main/java/eu/siacs/conversations/xml/XmlElementReader.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.xml; import com.google.common.io.ByteSource; - import java.io.IOException; import java.io.InputStream; @@ -11,10 +10,10 @@ public class XmlElementReader { return read(ByteSource.wrap(bytes).openStream()); } - public static Element read(InputStream inputStream) throws IOException { - final XmlReader xmlReader = new XmlReader(); - xmlReader.setInputStream(inputStream); - return xmlReader.readElement(xmlReader.readTag()); + public static Element read(final InputStream inputStream) throws IOException { + try (final XmlReader xmlReader = new XmlReader()) { + xmlReader.setInputStream(inputStream); + return xmlReader.readElement(xmlReader.readTag()); + } } - } diff --git a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java index 6a0c1d697..3e71057eb 100644 --- a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java +++ b/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java @@ -1,5 +1,6 @@ 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; @@ -61,5 +62,13 @@ public final class ExtensionFactory { 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/XmppConnection.java b/src/main/java/im/conversations/android/xmpp/XmppConnection.java index cc056ee2b..10d8cc99e 100644 --- a/src/main/java/im/conversations/android/xmpp/XmppConnection.java +++ b/src/main/java/im/conversations/android/xmpp/XmppConnection.java @@ -60,12 +60,13 @@ import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.csi.Active; import im.conversations.android.xmpp.model.csi.Inactive; +import im.conversations.android.xmpp.model.ping.Ping; import im.conversations.android.xmpp.model.register.Register; import im.conversations.android.xmpp.model.sm.Ack; import im.conversations.android.xmpp.model.sm.Enable; import im.conversations.android.xmpp.model.sm.Request; import im.conversations.android.xmpp.model.sm.Resume; -import im.conversations.android.xmpp.model.stanza.IQ; +import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Message; import im.conversations.android.xmpp.model.stanza.Presence; import im.conversations.android.xmpp.model.stanza.Stanza; @@ -120,7 +121,7 @@ public class XmppConnection implements Runnable { protected final Account account; private final SparseArray mStanzaQueue = new SparseArray<>(); - private final Hashtable>> packetCallbacks = new Hashtable<>(); + private final Hashtable>> packetCallbacks = new Hashtable<>(); private final Context context; private Socket socket; private XmlReader tagReader; @@ -147,7 +148,7 @@ public class XmppConnection implements Runnable { private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0); private int attempt = 0; private final Consumer presencePacketConsumer; - private final Consumer iqPacketConsumer; + private final Consumer iqPacketConsumer; private final Consumer messagePacketConsumer; private final BiFunction messageAcknowledgeProcessor; private final Consumer bindConsumer; @@ -1088,16 +1089,16 @@ public class XmppConnection implements Runnable { } private void processIq(final Tag currentTag) throws IOException { - final IQ packet = processStanza(currentTag, IQ.class); + final Iq packet = processStanza(currentTag, Iq.class); if (InvalidJid.invalid(packet.getTo()) || InvalidJid.invalid(packet.getFrom())) { Log.e( Config.LOGTAG, "encountered invalid IQ from " + packet.getFrom() + " to " + packet.getTo()); return; } - final Consumer callback; + final Consumer callback; synchronized (this.packetCallbacks) { - final Pair> packetCallbackDuple = packetCallbacks.get(packet.getId()); + final Pair> packetCallbackDuple = packetCallbacks.get(packet.getId()); if (packetCallbackDuple != null) { // Packets to the server should have responses from the server if (toServer(packetCallbackDuple.first)) { @@ -1118,7 +1119,7 @@ public class XmppConnection implements Runnable { Log.e(Config.LOGTAG, account.address + ": ignoring spoofed iq packet"); } } - } else if (packet.getType() == IQ.Type.GET || packet.getType() == IQ.Type.SET) { + } else if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) { callback = this.iqPacketConsumer; } else { callback = null; @@ -1510,12 +1511,12 @@ public class XmppConnection implements Runnable { sendRegistryRequest(); return; } - final IQ preAuthRequest = new IQ(IQ.Type.SET); + final Iq preAuthRequest = new Iq(Iq.Type.SET); preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); sendUnmodifiedIqPacket( preAuthRequest, (response) -> { - if (response.getType() == IQ.Type.RESULT) { + if (response.getType() == Iq.Type.RESULT) { sendRegistryRequest(); } else { final String error = ""; // response.getErrorCondition(); @@ -1527,16 +1528,16 @@ public class XmppConnection implements Runnable { } private void sendRegistryRequest() { - final IQ retrieveRegistration = new IQ(IQ.Type.GET); + final Iq retrieveRegistration = new Iq(Iq.Type.GET); retrieveRegistration.addExtension(new Register()); retrieveRegistration.setTo(account.address.getDomain()); sendUnmodifiedIqPacket( retrieveRegistration, (packet) -> { - if (packet.getType() == IQ.Type.TIMEOUT) { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } - if (packet.getType() == IQ.Type.ERROR) { + if (packet.getType() == Iq.Type.ERROR) { throw new StateChangingError(ConnectionState.REGISTRATION_FAILED); } final Register query = packet.getExtension(Register.class); @@ -1546,7 +1547,7 @@ public class XmppConnection implements Runnable { if (query.hasChild("username") && (query.hasChild("password"))) { final Credential credential = CredentialStore.getInstance(context).get(account); - final IQ registrationRequest = new IQ(IQ.Type.SET); + final Iq registrationRequest = new Iq(Iq.Type.SET); final Element username = new Element("username") .setContent(account.address.getEscapedLocal()); @@ -1621,8 +1622,8 @@ public class XmppConnection implements Runnable { true); } - private void handleRegistrationResponse(final IQ packet) { - if (packet.getType() == IQ.Type.RESULT) { + private void handleRegistrationResponse(final Iq packet) { + if (packet.getType() == Iq.Type.RESULT) { ConversationsDatabase.getInstance(context) .accountDao() .setPendingRegistration(account.id, false); @@ -1687,16 +1688,16 @@ public class XmppConnection implements Runnable { } else { resource = this.createNewResource(IDs.tiny(account.randomSeed)); } - final IQ iq = new IQ(IQ.Type.SET); + final Iq iq = new Iq(Iq.Type.SET); iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource); this.sendUnmodifiedIqPacket( iq, (packet) -> { - if (packet.getType() == IQ.Type.TIMEOUT) { + if (packet.getType() == Iq.Type.TIMEOUT) { return; } final Element bind = packet.findChild("bind"); - if (bind != null && packet.getType() == IQ.Type.RESULT) { + if (bind != null && packet.getType() == Iq.Type.RESULT) { isBound = true; final String jid = bind.findChildContent("jid"); if (Strings.isNullOrEmpty(jid)) { @@ -1740,7 +1741,7 @@ public class XmppConnection implements Runnable { + packet); } final Element error = packet.findChild("error"); - if (packet.getType() == IQ.Type.ERROR + if (packet.getType() == Iq.Type.ERROR && error != null && error.hasChild("conflict")) { final String alternativeResource = createNewResource(IDs.tiny()); @@ -1764,8 +1765,8 @@ public class XmppConnection implements Runnable { } private void clearIqCallbacks() { - final IQ failurePacket = new IQ(IQ.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) { return; @@ -1776,15 +1777,15 @@ public class XmppConnection implements Runnable { + ": clearing " + this.packetCallbacks.size() + " iq callbacks"); - final Iterator>> iterator = + final Iterator>> iterator = this.packetCallbacks.values().iterator(); while (iterator.hasNext()) { - Pair> entry = iterator.next(); + Pair> entry = iterator.next(); callbacks.add(entry.second); iterator.remove(); } } - for (final Consumer callback : callbacks) { + for (final Consumer callback : callbacks) { try { callback.accept(failurePacket); } catch (StateChangingError error) { @@ -1807,15 +1808,15 @@ public class XmppConnection implements Runnable { private void sendStartSession() { Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server"); - final IQ startSession = new IQ(IQ.Type.SET); + final Iq startSession = new Iq(Iq.Type.SET); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket( startSession, (packet) -> { - if (packet.getType() == IQ.Type.RESULT) { + if (packet.getType() == Iq.Type.RESULT) { enableStreamManagement(); sendPostBindInitialization(false); - } else if (packet.getType() != IQ.Type.TIMEOUT) { + } else if (packet.getType() != Iq.Type.TIMEOUT) { throw new StateChangingError(ConnectionState.SESSION_FAILURE); } }, @@ -2004,15 +2005,15 @@ public class XmppConnection implements Runnable { return String.format("%s.%s", context.getString(R.string.app_name), postfixId); } - public ListenableFuture sendIqPacket(final IQ packet) { - final SettableFuture future = SettableFuture.create(); + public ListenableFuture sendIqPacket(final Iq packet) { + final SettableFuture future = SettableFuture.create(); sendIqPacket( packet, result -> { final var type = result.getType(); - if (type == IQ.Type.RESULT) { + if (type == Iq.Type.RESULT) { future.set(result); - } else if (type == IQ.Type.TIMEOUT) { + } else if (type == Iq.Type.TIMEOUT) { future.setException(new TimeoutException()); } else { // TODO some sort of IqErrorException @@ -2022,13 +2023,13 @@ public class XmppConnection implements Runnable { return future; } - public String sendIqPacket(final IQ packet, final Consumer callback) { + public String sendIqPacket(final Iq packet, final Consumer callback) { packet.setFrom(account.address); return this.sendUnmodifiedIqPacket(packet, callback, false); } public synchronized String sendUnmodifiedIqPacket( - final IQ packet, final Consumer callback, boolean force) { + final Iq packet, final Consumer callback, boolean force) { if (Strings.isNullOrEmpty(packet.getId())) { packet.setId(IDs.medium()); } @@ -2106,9 +2107,9 @@ public class XmppConnection implements Runnable { public void sendPing() { if (!r()) { - final IQ iq = new IQ(IQ.Type.GET); + final Iq iq = new Iq(Iq.Type.GET); iq.setFrom(account.address); - iq.addChild("ping", Namespace.PING); + iq.addExtension(new Ping()); this.sendIqPacket(iq, null); } this.lastPingSent = SystemClock.elapsedRealtime(); diff --git a/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java b/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java index 8f9d8f5fb..2ecde16d8 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java @@ -7,7 +7,7 @@ import im.conversations.android.xmpp.model.blocking.Block; import im.conversations.android.xmpp.model.blocking.Blocklist; import im.conversations.android.xmpp.model.blocking.Item; import im.conversations.android.xmpp.model.blocking.Unblock; -import im.conversations.android.xmpp.model.stanza.IQ; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.Objects; public class BlockingManager extends AbstractManager { @@ -38,13 +38,13 @@ public class BlockingManager extends AbstractManager { } public void fetch() { - final IQ iqPacket = new IQ(IQ.Type.GET); + final Iq iqPacket = new Iq(Iq.Type.GET); iqPacket.addChild(new Blocklist()); connection.sendIqPacket(iqPacket, this::handleFetchResult); } - private void handleFetchResult(final IQ result) { - if (result.getType() != IQ.Type.RESULT) { + private void handleFetchResult(final Iq result) { + if (result.getType() != Iq.Type.RESULT) { return; } final var blocklist = result.getExtension(Blocklist.class); diff --git a/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java b/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java index 60c0a2349..740266289 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java @@ -5,7 +5,7 @@ import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.model.carbons.Enable; import im.conversations.android.xmpp.model.carbons.Received; import im.conversations.android.xmpp.model.carbons.Sent; -import im.conversations.android.xmpp.model.stanza.IQ; +import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.processor.MessageProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +24,12 @@ public class CarbonsManager extends AbstractManager { } public void enable() { - final var iq = new IQ(IQ.Type.SET); + final var iq = new Iq(Iq.Type.SET); iq.addExtension(new Enable()); connection.sendIqPacket( iq, result -> { - if (result.getType() == IQ.Type.RESULT) { + if (result.getType() == Iq.Type.RESULT) { LOGGER.info("{}: successfully enabled carbons", getAccount().address); this.enabled = true; } else { diff --git a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java index d0510cfd6..12cb4462e 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java @@ -17,7 +17,7 @@ import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.items.Item; import im.conversations.android.xmpp.model.disco.items.ItemsQuery; -import im.conversations.android.xmpp.model.stanza.IQ; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -52,7 +52,7 @@ public class DiscoManager extends AbstractManager { @Nullable final String node, @Nullable final EntityCapabilities.Hash hash) { final var requestNode = hash != null && node != null ? hash.capabilityNode(node) : node; - final var iqRequest = new IQ(IQ.Type.GET); + final var iqRequest = new Iq(Iq.Type.GET); iqRequest.setTo(entity.address); final InfoQuery infoQueryRequest = iqRequest.addExtension(new InfoQuery()); if (requestNode != null) { @@ -114,7 +114,7 @@ public class DiscoManager extends AbstractManager { public ListenableFuture> items( @NonNull final Entity.DiscoItem entity, @Nullable final String node) { final var requestNode = Strings.emptyToNull(node); - final var iqPacket = new IQ(IQ.Type.GET); + final var iqPacket = new Iq(Iq.Type.GET); iqPacket.setTo(entity.address); final ItemsQuery itemsQueryRequest = iqPacket.addExtension(new ItemsQuery()); if (requestNode != null) { diff --git a/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java b/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java index 300c8e317..53cdae320 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java @@ -6,7 +6,7 @@ import com.google.common.collect.Collections2; import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.model.roster.Query; -import im.conversations.android.xmpp.model.stanza.IQ; +import im.conversations.android.xmpp.model.stanza.Iq; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +29,7 @@ public class RosterManager extends AbstractManager { final var account = getAccount(); final var database = getDatabase(); final String rosterVersion = database.accountDao().getRosterVersion(account.id); - final IQ iqPacket = new IQ(IQ.Type.GET); + final Iq iqPacket = new Iq(Iq.Type.GET); final Query rosterQuery = new Query(); iqPacket.addChild(rosterQuery); if (Strings.isNullOrEmpty(rosterVersion)) { @@ -41,8 +41,8 @@ public class RosterManager extends AbstractManager { connection.sendIqPacket(iqPacket, this::handleFetchResult); } - private void handleFetchResult(final IQ result) { - if (result.getType() != IQ.Type.RESULT) { + private void handleFetchResult(final Iq result) { + if (result.getType() != Iq.Type.RESULT) { return; } final var query = result.getExtension(Query.class); 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 000000000..952a87594 --- /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 { + + public 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 000000000..73426c27c --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/error/Error.java @@ -0,0 +1,21 @@ +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.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); + } +} 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 000000000..7f8f1c3a0 --- /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/stanza/IQ.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java similarity index 80% rename from src/main/java/im/conversations/android/xmpp/model/stanza/IQ.java rename to src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java index 18430a19f..acc1c8f82 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/IQ.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java @@ -5,14 +5,14 @@ import im.conversations.android.annotation.XmlElement; import java.util.Locale; @XmlElement -public class IQ extends Stanza { +public class Iq extends Stanza { - public IQ() { - super(IQ.class); + public Iq() { + super(Iq.class); } - public IQ(final Type type) { - super(IQ.class); + public Iq(final Type type) { + super(Iq.class); this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); } 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 index 1e2296bba..0c91ad699 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java @@ -3,6 +3,7 @@ package im.conversations.android.xmpp.model.stanza; 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 { @@ -33,4 +34,8 @@ public abstract class Stanza extends StreamElement { public void setTo(final Jid to) { this.setAttribute("to", to); } + + public Error getError() { + return this.getExtension(Error.class); + } } diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java index a1728c09c..43a0c0168 100644 --- a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -9,7 +9,6 @@ import im.conversations.android.xmpp.manager.BlockingManager; import im.conversations.android.xmpp.manager.BookmarkManager; import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.manager.RosterManager; -import im.conversations.android.xmpp.model.stanza.Presence; import java.util.function.Consumer; public class BindProcessor extends XmppConnection.Delegate implements Consumer { @@ -47,7 +46,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer { +public class IqProcessor extends XmppConnection.Delegate implements Consumer { + + private static final Logger LOGGER = LoggerFactory.getLogger(IqProcessor.class); public IqProcessor(final Context context, final XmppConnection connection) { super(context, connection); } @Override - public void accept(final IQ packet) { - final IQ.Type type = packet.getType(); - Preconditions.checkArgument(Arrays.asList(IQ.Type.GET, IQ.Type.SET).contains(type)); - if (type == IQ.Type.SET + public void accept(final Iq packet) { + final Iq.Type type = packet.getType(); + Preconditions.checkArgument(Arrays.asList(Iq.Type.GET, Iq.Type.SET).contains(type)); + if (type == Iq.Type.SET && connection.fromAccount(packet) && packet.hasExtension(Query.class)) { getManager(RosterManager.class).handlePush(packet.getExtension(Query.class)); + sendResultFor(packet); return; } - if (type == IQ.Type.SET + if (type == Iq.Type.SET && connection.fromAccount(packet) && packet.hasExtension(Block.class)) { getManager(BlockingManager.class).handlePush(packet.getExtension(Block.class)); + sendResultFor(packet); return; } - if (type == IQ.Type.SET + if (type == Iq.Type.SET && connection.fromAccount(packet) && packet.hasExtension(Unblock.class)) { getManager(BlockingManager.class).handlePush(packet.getExtension(Unblock.class)); + sendResultFor(packet); return; } - // TODO return feature not implemented + if (type == Iq.Type.GET && packet.hasExtension(Ping.class)) { + LOGGER.debug("Responding to ping from {}", packet.getFrom()); + sendResultFor(packet); + return; + } + + final var extensionIds = packet.getExtensionIds(); + LOGGER.info("Could not handle {}. Sending feature-not-implemented", extensionIds); + sendErrorFor(packet, new Condition.FeatureNotImplemented()); + } + + public void sendResultFor(final Iq request, final Extension... extensions) { + final var from = request.getFrom(); + final var id = request.getId(); + final var response = new Iq(Iq.Type.RESULT); + response.setTo(from); + response.setId(id); + for (final Extension extension : extensions) { + response.addExtension(extension); + } + connection.sendIqPacket(response); + } + + public void sendErrorFor(final Iq request, final Condition condition) { + final var from = request.getFrom(); + final var id = request.getId(); + final var response = new Iq(Iq.Type.ERROR); + response.setTo(from); + response.setId(id); + final Error error = response.addExtension(new Error()); + error.setCondition(condition); + connection.sendIqPacket(response); } } diff --git a/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java index 6fc4d38bb..38fddecf6 100644 --- a/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java +++ b/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java @@ -42,5 +42,14 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume if (!Strings.isNullOrEmpty(body)) { LOGGER.info("'{}' from {}", body, message.getFrom()); } + + // TODO process receipt requests (184 + 333) + + // TODO collect Extensions that require transformation (everything that will end up in the + // message tables) + + // TODO pass pubsub events to pubsub manager + + // TODO pass JMI to JingleManager } } diff --git a/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java b/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java index 1b4fe8d74..804e6baf9 100644 --- a/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java +++ b/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java @@ -6,8 +6,11 @@ import static org.junit.Assert.assertEquals; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.XmlElementReader; +import im.conversations.android.xmpp.model.error.Condition; +import im.conversations.android.xmpp.model.error.Error; import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.model.roster.Query; +import im.conversations.android.xmpp.model.stanza.Message; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -31,4 +34,23 @@ public class XmlElementReaderTest { final Collection items = query.getExtensions(Item.class); assertEquals(2, items.size()); } + + public void readMessageError() throws IOException { + final String xml = + "\n" + + " Wherefore art thou, Romeo?\n" + + " \n" + + " \n" + + " \n" + + ""; + final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8)); + assertThat(element, instanceOf(Message.class)); + final Message message = (Message) element; + final Error error = message.getError(); + assertThat(error.getCondition(), instanceOf(Condition.ItemNotFound.class)); + } }