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;
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;

View file

@ -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<ExtensionFactory.Id> 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();

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 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";
}

View file

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

View file

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

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.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<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 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<Presence> presencePacketConsumer;
private final Consumer<IQ> iqPacketConsumer;
private final Consumer<Iq> iqPacketConsumer;
private final Consumer<Message> messagePacketConsumer;
private final BiFunction<Jid, String, Boolean> messageAcknowledgeProcessor;
private final Consumer<Jid> 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<IQ> callback;
final Consumer<Iq> callback;
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) {
// 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<Consumer<IQ>> callbacks = new ArrayList<>();
final Iq failurePacket = new Iq(Iq.Type.TIMEOUT);
final ArrayList<Consumer<Iq>> 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<Pair<IQ, Consumer<IQ>>> iterator =
final Iterator<Pair<Iq, Consumer<Iq>>> iterator =
this.packetCallbacks.values().iterator();
while (iterator.hasNext()) {
Pair<IQ, Consumer<IQ>> entry = iterator.next();
Pair<Iq, Consumer<Iq>> entry = iterator.next();
callbacks.add(entry.second);
iterator.remove();
}
}
for (final Consumer<IQ> callback : callbacks) {
for (final Consumer<Iq> 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<IQ> sendIqPacket(final IQ packet) {
final SettableFuture<IQ> future = SettableFuture.create();
public ListenableFuture<Iq> sendIqPacket(final Iq packet) {
final SettableFuture<Iq> 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<IQ> callback) {
public String sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
packet.setFrom(account.address);
return this.sendUnmodifiedIqPacket(packet, callback, false);
}
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())) {
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();

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.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);

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.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 {

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.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<Collection<Item>> 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) {

View file

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

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

View file

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

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.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<Jid> {
@ -47,7 +46,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
getManager(BookmarkManager.class).fetch();
connection.sendPresencePacket(new Presence());
// connection.sendPresencePacket(new 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.manager.BlockingManager;
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.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.stanza.IQ;
import im.conversations.android.xmpp.model.stanza.Iq;
import java.util.Arrays;
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) {
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);
}
}

View file

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

View file

@ -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<Item> items = query.getExtensions(Item.class);
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));
}
}