add models for Error conditions

This commit is contained in:
Daniel Gultsch 2023-01-25 10:50:05 +01:00
parent ddcab5fb58
commit 9a855a57ac
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
19 changed files with 408 additions and 75 deletions

View file

@ -1,6 +1,7 @@
package im.conversations.android.annotation.processor; package im.conversations.android.annotation.processor;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import com.google.common.base.CaseFormat;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -8,7 +9,6 @@ import im.conversations.android.annotation.XmlElement;
import im.conversations.android.annotation.XmlPackage; import im.conversations.android.annotation.XmlPackage;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
@ -88,8 +88,9 @@ public class XmlElementProcessor extends AbstractProcessor {
private static Id of(final TypeElement typeElement) { private static Id of(final TypeElement typeElement) {
final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class); final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class);
PackageElement packageElement = (PackageElement) typeElement.getEnclosingElement(); PackageElement packageElement = getPackageElement(typeElement);
XmlPackage xmlPackage = packageElement.getAnnotation(XmlPackage.class); XmlPackage xmlPackage =
packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class);
if (xmlElement == null) { if (xmlElement == null) {
throw new IllegalStateException( throw new IllegalStateException(
String.format( String.format(
@ -112,13 +113,29 @@ public class XmlElementProcessor extends AbstractProcessor {
} }
final String name; final String name;
if (Strings.isNullOrEmpty(elementName)) { if (Strings.isNullOrEmpty(elementName)) {
name = typeElement.getSimpleName().toString().toLowerCase(Locale.ROOT); name =
CaseFormat.UPPER_CAMEL.to(
CaseFormat.LOWER_HYPHEN, typeElement.getSimpleName().toString());
} else { } else {
name = elementName; name = elementName;
} }
return new Id(name, namespace); 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 static class Id {
public final String name; public final String name;
public final String namespace; public final String namespace;

View file

@ -9,6 +9,7 @@ import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import im.conversations.android.xmpp.ExtensionFactory;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -89,6 +90,11 @@ public class Element {
Collections2.filter(this.children, clazz::isInstance), clazz::cast); Collections2.filter(this.children, clazz::isInstance), clazz::cast);
} }
public Collection<ExtensionFactory.Id> getExtensionIds() {
return Collections2.transform(
this.children, c -> new ExtensionFactory.Id(c.getName(), c.getNamespace()));
}
public String findChildContent(String name) { public String findChildContent(String name) {
Element element = findChild(name); Element element = findChild(name);
return element == null ? null : element.getContent(); return element == null ? null : element.getContent();

View file

@ -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 UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
public static final String JABBER_CLIENT = "jabber:client"; public static final String JABBER_CLIENT = "jabber:client";
public static final String FORWARD = "urn:xmpp:forward:0"; public static final String FORWARD = "urn:xmpp:forward:0";
public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
} }

View file

@ -1,7 +1,6 @@
package eu.siacs.conversations.xml; package eu.siacs.conversations.xml;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -11,10 +10,10 @@ public class XmlElementReader {
return read(ByteSource.wrap(bytes).openStream()); return read(ByteSource.wrap(bytes).openStream());
} }
public static Element read(InputStream inputStream) throws IOException { public static Element read(final InputStream inputStream) throws IOException {
final XmlReader xmlReader = new XmlReader(); try (final XmlReader xmlReader = new XmlReader()) {
xmlReader.setInputStream(inputStream); xmlReader.setInputStream(inputStream);
return xmlReader.readElement(xmlReader.readTag()); return xmlReader.readElement(xmlReader.readTag());
}
} }
} }

View file

@ -1,5 +1,6 @@
package im.conversations.android.xmpp; package im.conversations.android.xmpp;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
@ -61,5 +62,13 @@ public final class ExtensionFactory {
public int hashCode() { public int hashCode() {
return Objects.hashCode(name, namespace); return Objects.hashCode(name, namespace);
} }
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("namespace", namespace)
.toString();
}
} }
} }

View file

@ -60,12 +60,13 @@ import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.StreamElement;
import im.conversations.android.xmpp.model.csi.Active; import im.conversations.android.xmpp.model.csi.Active;
import im.conversations.android.xmpp.model.csi.Inactive; 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.register.Register;
import im.conversations.android.xmpp.model.sm.Ack; import im.conversations.android.xmpp.model.sm.Ack;
import im.conversations.android.xmpp.model.sm.Enable; import im.conversations.android.xmpp.model.sm.Enable;
import im.conversations.android.xmpp.model.sm.Request; import im.conversations.android.xmpp.model.sm.Request;
import im.conversations.android.xmpp.model.sm.Resume; 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.Message;
import im.conversations.android.xmpp.model.stanza.Presence; import im.conversations.android.xmpp.model.stanza.Presence;
import im.conversations.android.xmpp.model.stanza.Stanza; import im.conversations.android.xmpp.model.stanza.Stanza;
@ -120,7 +121,7 @@ public class XmppConnection implements Runnable {
protected final Account account; protected final Account account;
private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>(); private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
private final Hashtable<String, Pair<IQ, Consumer<IQ>>> packetCallbacks = new Hashtable<>(); private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
private final Context context; private final Context context;
private Socket socket; private Socket socket;
private XmlReader tagReader; private XmlReader tagReader;
@ -147,7 +148,7 @@ public class XmppConnection implements Runnable {
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0); private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
private int attempt = 0; private int attempt = 0;
private final Consumer<Presence> presencePacketConsumer; private final Consumer<Presence> presencePacketConsumer;
private final Consumer<IQ> iqPacketConsumer; private final Consumer<Iq> iqPacketConsumer;
private final Consumer<Message> messagePacketConsumer; private final Consumer<Message> messagePacketConsumer;
private final BiFunction<Jid, String, Boolean> messageAcknowledgeProcessor; private final BiFunction<Jid, String, Boolean> messageAcknowledgeProcessor;
private final Consumer<Jid> bindConsumer; private final Consumer<Jid> bindConsumer;
@ -1088,16 +1089,16 @@ public class XmppConnection implements Runnable {
} }
private void processIq(final Tag currentTag) throws IOException { 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())) { if (InvalidJid.invalid(packet.getTo()) || InvalidJid.invalid(packet.getFrom())) {
Log.e( Log.e(
Config.LOGTAG, Config.LOGTAG,
"encountered invalid IQ from " + packet.getFrom() + " to " + packet.getTo()); "encountered invalid IQ from " + packet.getFrom() + " to " + packet.getTo());
return; return;
} }
final Consumer<IQ> callback; final Consumer<Iq> callback;
synchronized (this.packetCallbacks) { synchronized (this.packetCallbacks) {
final Pair<IQ, Consumer<IQ>> packetCallbackDuple = packetCallbacks.get(packet.getId()); final Pair<Iq, Consumer<Iq>> packetCallbackDuple = packetCallbacks.get(packet.getId());
if (packetCallbackDuple != null) { if (packetCallbackDuple != null) {
// Packets to the server should have responses from the server // Packets to the server should have responses from the server
if (toServer(packetCallbackDuple.first)) { if (toServer(packetCallbackDuple.first)) {
@ -1118,7 +1119,7 @@ public class XmppConnection implements Runnable {
Log.e(Config.LOGTAG, account.address + ": ignoring spoofed iq packet"); 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; callback = this.iqPacketConsumer;
} else { } else {
callback = null; callback = null;
@ -1510,12 +1511,12 @@ public class XmppConnection implements Runnable {
sendRegistryRequest(); sendRegistryRequest();
return; 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); preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
sendUnmodifiedIqPacket( sendUnmodifiedIqPacket(
preAuthRequest, preAuthRequest,
(response) -> { (response) -> {
if (response.getType() == IQ.Type.RESULT) { if (response.getType() == Iq.Type.RESULT) {
sendRegistryRequest(); sendRegistryRequest();
} else { } else {
final String error = ""; // response.getErrorCondition(); final String error = ""; // response.getErrorCondition();
@ -1527,16 +1528,16 @@ public class XmppConnection implements Runnable {
} }
private void sendRegistryRequest() { private void sendRegistryRequest() {
final IQ retrieveRegistration = new IQ(IQ.Type.GET); final Iq retrieveRegistration = new Iq(Iq.Type.GET);
retrieveRegistration.addExtension(new Register()); retrieveRegistration.addExtension(new Register());
retrieveRegistration.setTo(account.address.getDomain()); retrieveRegistration.setTo(account.address.getDomain());
sendUnmodifiedIqPacket( sendUnmodifiedIqPacket(
retrieveRegistration, retrieveRegistration,
(packet) -> { (packet) -> {
if (packet.getType() == IQ.Type.TIMEOUT) { if (packet.getType() == Iq.Type.TIMEOUT) {
return; return;
} }
if (packet.getType() == IQ.Type.ERROR) { if (packet.getType() == Iq.Type.ERROR) {
throw new StateChangingError(ConnectionState.REGISTRATION_FAILED); throw new StateChangingError(ConnectionState.REGISTRATION_FAILED);
} }
final Register query = packet.getExtension(Register.class); final Register query = packet.getExtension(Register.class);
@ -1546,7 +1547,7 @@ public class XmppConnection implements Runnable {
if (query.hasChild("username") && (query.hasChild("password"))) { if (query.hasChild("username") && (query.hasChild("password"))) {
final Credential credential = final Credential credential =
CredentialStore.getInstance(context).get(account); CredentialStore.getInstance(context).get(account);
final IQ registrationRequest = new IQ(IQ.Type.SET); final Iq registrationRequest = new Iq(Iq.Type.SET);
final Element username = final Element username =
new Element("username") new Element("username")
.setContent(account.address.getEscapedLocal()); .setContent(account.address.getEscapedLocal());
@ -1621,8 +1622,8 @@ public class XmppConnection implements Runnable {
true); true);
} }
private void handleRegistrationResponse(final IQ packet) { private void handleRegistrationResponse(final Iq packet) {
if (packet.getType() == IQ.Type.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
ConversationsDatabase.getInstance(context) ConversationsDatabase.getInstance(context)
.accountDao() .accountDao()
.setPendingRegistration(account.id, false); .setPendingRegistration(account.id, false);
@ -1687,16 +1688,16 @@ public class XmppConnection implements Runnable {
} else { } else {
resource = this.createNewResource(IDs.tiny(account.randomSeed)); 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); iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
this.sendUnmodifiedIqPacket( this.sendUnmodifiedIqPacket(
iq, iq,
(packet) -> { (packet) -> {
if (packet.getType() == IQ.Type.TIMEOUT) { if (packet.getType() == Iq.Type.TIMEOUT) {
return; return;
} }
final Element bind = packet.findChild("bind"); final Element bind = packet.findChild("bind");
if (bind != null && packet.getType() == IQ.Type.RESULT) { if (bind != null && packet.getType() == Iq.Type.RESULT) {
isBound = true; isBound = true;
final String jid = bind.findChildContent("jid"); final String jid = bind.findChildContent("jid");
if (Strings.isNullOrEmpty(jid)) { if (Strings.isNullOrEmpty(jid)) {
@ -1740,7 +1741,7 @@ public class XmppConnection implements Runnable {
+ packet); + packet);
} }
final Element error = packet.findChild("error"); final Element error = packet.findChild("error");
if (packet.getType() == IQ.Type.ERROR if (packet.getType() == Iq.Type.ERROR
&& error != null && error != null
&& error.hasChild("conflict")) { && error.hasChild("conflict")) {
final String alternativeResource = createNewResource(IDs.tiny()); final String alternativeResource = createNewResource(IDs.tiny());
@ -1764,8 +1765,8 @@ public class XmppConnection implements Runnable {
} }
private void clearIqCallbacks() { private void clearIqCallbacks() {
final IQ failurePacket = new IQ(IQ.Type.TIMEOUT); final Iq failurePacket = new Iq(Iq.Type.TIMEOUT);
final ArrayList<Consumer<IQ>> callbacks = new ArrayList<>(); final ArrayList<Consumer<Iq>> callbacks = new ArrayList<>();
synchronized (this.packetCallbacks) { synchronized (this.packetCallbacks) {
if (this.packetCallbacks.size() == 0) { if (this.packetCallbacks.size() == 0) {
return; return;
@ -1776,15 +1777,15 @@ public class XmppConnection implements Runnable {
+ ": clearing " + ": clearing "
+ this.packetCallbacks.size() + this.packetCallbacks.size()
+ " iq callbacks"); + " iq callbacks");
final Iterator<Pair<IQ, Consumer<IQ>>> iterator = final Iterator<Pair<Iq, Consumer<Iq>>> iterator =
this.packetCallbacks.values().iterator(); this.packetCallbacks.values().iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Pair<IQ, Consumer<IQ>> entry = iterator.next(); Pair<Iq, Consumer<Iq>> entry = iterator.next();
callbacks.add(entry.second); callbacks.add(entry.second);
iterator.remove(); iterator.remove();
} }
} }
for (final Consumer<IQ> callback : callbacks) { for (final Consumer<Iq> callback : callbacks) {
try { try {
callback.accept(failurePacket); callback.accept(failurePacket);
} catch (StateChangingError error) { } catch (StateChangingError error) {
@ -1807,15 +1808,15 @@ public class XmppConnection implements Runnable {
private void sendStartSession() { private void sendStartSession() {
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server"); 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"); startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
this.sendUnmodifiedIqPacket( this.sendUnmodifiedIqPacket(
startSession, startSession,
(packet) -> { (packet) -> {
if (packet.getType() == IQ.Type.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
enableStreamManagement(); enableStreamManagement();
sendPostBindInitialization(false); sendPostBindInitialization(false);
} else if (packet.getType() != IQ.Type.TIMEOUT) { } else if (packet.getType() != Iq.Type.TIMEOUT) {
throw new StateChangingError(ConnectionState.SESSION_FAILURE); 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); return String.format("%s.%s", context.getString(R.string.app_name), postfixId);
} }
public ListenableFuture<IQ> sendIqPacket(final IQ packet) { public ListenableFuture<Iq> sendIqPacket(final Iq packet) {
final SettableFuture<IQ> future = SettableFuture.create(); final SettableFuture<Iq> future = SettableFuture.create();
sendIqPacket( sendIqPacket(
packet, packet,
result -> { result -> {
final var type = result.getType(); final var type = result.getType();
if (type == IQ.Type.RESULT) { if (type == Iq.Type.RESULT) {
future.set(result); future.set(result);
} else if (type == IQ.Type.TIMEOUT) { } else if (type == Iq.Type.TIMEOUT) {
future.setException(new TimeoutException()); future.setException(new TimeoutException());
} else { } else {
// TODO some sort of IqErrorException // TODO some sort of IqErrorException
@ -2022,13 +2023,13 @@ public class XmppConnection implements Runnable {
return future; return future;
} }
public String sendIqPacket(final IQ packet, final Consumer<IQ> callback) { public String sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
packet.setFrom(account.address); packet.setFrom(account.address);
return this.sendUnmodifiedIqPacket(packet, callback, false); return this.sendUnmodifiedIqPacket(packet, callback, false);
} }
public synchronized String sendUnmodifiedIqPacket( public synchronized String sendUnmodifiedIqPacket(
final IQ packet, final Consumer<IQ> callback, boolean force) { final Iq packet, final Consumer<Iq> callback, boolean force) {
if (Strings.isNullOrEmpty(packet.getId())) { if (Strings.isNullOrEmpty(packet.getId())) {
packet.setId(IDs.medium()); packet.setId(IDs.medium());
} }
@ -2106,9 +2107,9 @@ public class XmppConnection implements Runnable {
public void sendPing() { public void sendPing() {
if (!r()) { if (!r()) {
final IQ iq = new IQ(IQ.Type.GET); final Iq iq = new Iq(Iq.Type.GET);
iq.setFrom(account.address); iq.setFrom(account.address);
iq.addChild("ping", Namespace.PING); iq.addExtension(new Ping());
this.sendIqPacket(iq, null); this.sendIqPacket(iq, null);
} }
this.lastPingSent = SystemClock.elapsedRealtime(); this.lastPingSent = SystemClock.elapsedRealtime();

View file

@ -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.Blocklist;
import im.conversations.android.xmpp.model.blocking.Item; import im.conversations.android.xmpp.model.blocking.Item;
import im.conversations.android.xmpp.model.blocking.Unblock; 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; import java.util.Objects;
public class BlockingManager extends AbstractManager { public class BlockingManager extends AbstractManager {
@ -38,13 +38,13 @@ public class BlockingManager extends AbstractManager {
} }
public void fetch() { public void fetch() {
final IQ iqPacket = new IQ(IQ.Type.GET); final Iq iqPacket = new Iq(Iq.Type.GET);
iqPacket.addChild(new Blocklist()); iqPacket.addChild(new Blocklist());
connection.sendIqPacket(iqPacket, this::handleFetchResult); connection.sendIqPacket(iqPacket, this::handleFetchResult);
} }
private void handleFetchResult(final IQ result) { private void handleFetchResult(final Iq result) {
if (result.getType() != IQ.Type.RESULT) { if (result.getType() != Iq.Type.RESULT) {
return; return;
} }
final var blocklist = result.getExtension(Blocklist.class); final var blocklist = result.getExtension(Blocklist.class);

View file

@ -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.Enable;
import im.conversations.android.xmpp.model.carbons.Received; import im.conversations.android.xmpp.model.carbons.Received;
import im.conversations.android.xmpp.model.carbons.Sent; 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 im.conversations.android.xmpp.processor.MessageProcessor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -24,12 +24,12 @@ public class CarbonsManager extends AbstractManager {
} }
public void enable() { public void enable() {
final var iq = new IQ(IQ.Type.SET); final var iq = new Iq(Iq.Type.SET);
iq.addExtension(new Enable()); iq.addExtension(new Enable());
connection.sendIqPacket( connection.sendIqPacket(
iq, iq,
result -> { result -> {
if (result.getType() == IQ.Type.RESULT) { if (result.getType() == Iq.Type.RESULT) {
LOGGER.info("{}: successfully enabled carbons", getAccount().address); LOGGER.info("{}: successfully enabled carbons", getAccount().address);
this.enabled = true; this.enabled = true;
} else { } else {

View file

@ -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.info.InfoQuery;
import im.conversations.android.xmpp.model.disco.items.Item; import im.conversations.android.xmpp.model.disco.items.Item;
import im.conversations.android.xmpp.model.disco.items.ItemsQuery; 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.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -52,7 +52,7 @@ public class DiscoManager extends AbstractManager {
@Nullable final String node, @Nullable final String node,
@Nullable final EntityCapabilities.Hash hash) { @Nullable final EntityCapabilities.Hash hash) {
final var requestNode = hash != null && node != null ? hash.capabilityNode(node) : node; 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); iqRequest.setTo(entity.address);
final InfoQuery infoQueryRequest = iqRequest.addExtension(new InfoQuery()); final InfoQuery infoQueryRequest = iqRequest.addExtension(new InfoQuery());
if (requestNode != null) { if (requestNode != null) {
@ -114,7 +114,7 @@ public class DiscoManager extends AbstractManager {
public ListenableFuture<Collection<Item>> items( public ListenableFuture<Collection<Item>> items(
@NonNull final Entity.DiscoItem entity, @Nullable final String node) { @NonNull final Entity.DiscoItem entity, @Nullable final String node) {
final var requestNode = Strings.emptyToNull(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); iqPacket.setTo(entity.address);
final ItemsQuery itemsQueryRequest = iqPacket.addExtension(new ItemsQuery()); final ItemsQuery itemsQueryRequest = iqPacket.addExtension(new ItemsQuery());
if (requestNode != null) { if (requestNode != null) {

View file

@ -6,7 +6,7 @@ import com.google.common.collect.Collections2;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.model.roster.Item;
import im.conversations.android.xmpp.model.roster.Query; 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 java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,7 +29,7 @@ public class RosterManager extends AbstractManager {
final var account = getAccount(); final var account = getAccount();
final var database = getDatabase(); final var database = getDatabase();
final String rosterVersion = database.accountDao().getRosterVersion(account.id); 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(); final Query rosterQuery = new Query();
iqPacket.addChild(rosterQuery); iqPacket.addChild(rosterQuery);
if (Strings.isNullOrEmpty(rosterVersion)) { if (Strings.isNullOrEmpty(rosterVersion)) {
@ -41,8 +41,8 @@ public class RosterManager extends AbstractManager {
connection.sendIqPacket(iqPacket, this::handleFetchResult); connection.sendIqPacket(iqPacket, this::handleFetchResult);
} }
private void handleFetchResult(final IQ result) { private void handleFetchResult(final Iq result) {
if (result.getType() != IQ.Type.RESULT) { if (result.getType() != Iq.Type.RESULT) {
return; return;
} }
final var query = result.getExtension(Query.class); final var query = result.getExtension(Query.class);

View file

@ -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<? extends Extension> 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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -5,14 +5,14 @@ import im.conversations.android.annotation.XmlElement;
import java.util.Locale; import java.util.Locale;
@XmlElement @XmlElement
public class IQ extends Stanza { public class Iq extends Stanza {
public IQ() { public Iq() {
super(IQ.class); super(Iq.class);
} }
public IQ(final Type type) { public Iq(final Type type) {
super(IQ.class); super(Iq.class);
this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT));
} }

View file

@ -3,6 +3,7 @@ package im.conversations.android.xmpp.model.stanza;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.StreamElement; import im.conversations.android.xmpp.model.StreamElement;
import im.conversations.android.xmpp.model.error.Error;
public abstract class Stanza extends StreamElement { public abstract class Stanza extends StreamElement {
@ -33,4 +34,8 @@ public abstract class Stanza extends StreamElement {
public void setTo(final Jid to) { public void setTo(final Jid to) {
this.setAttribute("to", to); this.setAttribute("to", to);
} }
public Error getError() {
return this.getExtension(Error.class);
}
} }

View file

@ -9,7 +9,6 @@ import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.manager.BookmarkManager; import im.conversations.android.xmpp.manager.BookmarkManager;
import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.manager.RosterManager; import im.conversations.android.xmpp.manager.RosterManager;
import im.conversations.android.xmpp.model.stanza.Presence;
import java.util.function.Consumer; import java.util.function.Consumer;
public class BindProcessor extends XmppConnection.Delegate implements Consumer<Jid> { public class BindProcessor extends XmppConnection.Delegate implements Consumer<Jid> {
@ -47,7 +46,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
getManager(BookmarkManager.class).fetch(); getManager(BookmarkManager.class).fetch();
connection.sendPresencePacket(new Presence()); // connection.sendPresencePacket(new Presence());
// TODO send initial presence // TODO send initial presence
} }

View file

@ -5,41 +5,83 @@ import com.google.common.base.Preconditions;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.BlockingManager; import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.manager.RosterManager; import im.conversations.android.xmpp.manager.RosterManager;
import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.blocking.Block; import im.conversations.android.xmpp.model.blocking.Block;
import im.conversations.android.xmpp.model.blocking.Unblock; import im.conversations.android.xmpp.model.blocking.Unblock;
import im.conversations.android.xmpp.model.error.Condition;
import im.conversations.android.xmpp.model.error.Error;
import im.conversations.android.xmpp.model.ping.Ping;
import im.conversations.android.xmpp.model.roster.Query; 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.Arrays; import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IqProcessor extends XmppConnection.Delegate implements Consumer<IQ> { public class IqProcessor extends XmppConnection.Delegate implements Consumer<Iq> {
private static final Logger LOGGER = LoggerFactory.getLogger(IqProcessor.class);
public IqProcessor(final Context context, final XmppConnection connection) { public IqProcessor(final Context context, final XmppConnection connection) {
super(context, connection); super(context, connection);
} }
@Override @Override
public void accept(final IQ packet) { public void accept(final Iq packet) {
final IQ.Type type = packet.getType(); final Iq.Type type = packet.getType();
Preconditions.checkArgument(Arrays.asList(IQ.Type.GET, IQ.Type.SET).contains(type)); Preconditions.checkArgument(Arrays.asList(Iq.Type.GET, Iq.Type.SET).contains(type));
if (type == IQ.Type.SET if (type == Iq.Type.SET
&& connection.fromAccount(packet) && connection.fromAccount(packet)
&& packet.hasExtension(Query.class)) { && packet.hasExtension(Query.class)) {
getManager(RosterManager.class).handlePush(packet.getExtension(Query.class)); getManager(RosterManager.class).handlePush(packet.getExtension(Query.class));
sendResultFor(packet);
return; return;
} }
if (type == IQ.Type.SET if (type == Iq.Type.SET
&& connection.fromAccount(packet) && connection.fromAccount(packet)
&& packet.hasExtension(Block.class)) { && packet.hasExtension(Block.class)) {
getManager(BlockingManager.class).handlePush(packet.getExtension(Block.class)); getManager(BlockingManager.class).handlePush(packet.getExtension(Block.class));
sendResultFor(packet);
return; return;
} }
if (type == IQ.Type.SET if (type == Iq.Type.SET
&& connection.fromAccount(packet) && connection.fromAccount(packet)
&& packet.hasExtension(Unblock.class)) { && packet.hasExtension(Unblock.class)) {
getManager(BlockingManager.class).handlePush(packet.getExtension(Unblock.class)); getManager(BlockingManager.class).handlePush(packet.getExtension(Unblock.class));
sendResultFor(packet);
return; 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);
} }
} }

View file

@ -42,5 +42,14 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
if (!Strings.isNullOrEmpty(body)) { if (!Strings.isNullOrEmpty(body)) {
LOGGER.info("'{}' from {}", body, message.getFrom()); 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
} }
} }

View file

@ -6,8 +6,11 @@ import static org.junit.Assert.assertEquals;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.XmlElementReader; 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.Item;
import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.model.roster.Query;
import im.conversations.android.xmpp.model.stanza.Message;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
@ -31,4 +34,23 @@ public class XmlElementReaderTest {
final Collection<Item> items = query.getExtensions(Item.class); final Collection<Item> items = query.getExtensions(Item.class);
assertEquals(2, items.size()); assertEquals(2, items.size());
} }
public void readMessageError() throws IOException {
final String xml =
"<message\n"
+ " to='juliet@capulet.com/balcony'\n"
+ " from='romeo@montague.net/garden'\n"
+ " xmlns='jabber:client'\n"
+ " type='error'>\n"
+ " <body>Wherefore art thou, Romeo?</body>\n"
+ " <error code='404' type='cancel'>\n"
+ " <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>\n"
+ " </error>\n"
+ "</message>";
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));
}
} }