diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index ac4e8c47..9d77efab 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -1,6 +1,7 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; +import android.database.Cursor; import android.util.Base64; import java.io.UnsupportedEncodingException; import java.lang.Comparable; @@ -17,6 +18,10 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class ServiceDiscoveryResult { + public static final String TABLENAME = "discovery_results"; + public static final String HASH = "hash"; + public static final String VER = "ver"; + public static final String RESULT = "result"; protected static String blankNull(String s) { return s == null ? "" : s; @@ -36,10 +41,21 @@ public class ServiceDiscoveryResult { } public Identity(final Element el) { - this.category = el.getAttribute("category"); - this.type = el.getAttribute("type"); - this.lang = el.getAttribute("xml:lang"); - this.name = el.getAttribute("name"); + this( + el.getAttribute("category"), + el.getAttribute("type"), + el.getAttribute("xml:lang"), + el.getAttribute("name") + ); + } + + public Identity(final JSONObject o) { + this( + o.optString("category", null), + o.optString("type", null), + o.optString("lang", null), + o.optString("name", null) + ); } public String getCategory() { @@ -88,17 +104,15 @@ public class ServiceDiscoveryResult { } } + protected final String hash; + protected final byte[] ver; protected final List identities; protected final List features; - public ServiceDiscoveryResult(final List identities, final List features) { - this.identities = identities; - this.features = features; - } - public ServiceDiscoveryResult(final IqPacket packet) { this.identities = new ArrayList<>(); this.features = new ArrayList<>(); + this.hash = "sha-1"; // We only support sha-1 for now final List elements = packet.query().getChildren(); @@ -114,6 +128,33 @@ public class ServiceDiscoveryResult { } } } + + this.ver = this.mkCapHash(); + } + + public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException { + this.identities = new ArrayList<>(); + this.features = new ArrayList<>(); + this.hash = hash; + this.ver = ver; + + JSONArray identities = o.optJSONArray("identities"); + for(int i = 0; i < identities.length(); i++) { + this.identities.add(new Identity(identities.getJSONObject(i))); + } + + JSONArray features = o.optJSONArray("features"); + for(int i = 0; i < features.length(); i++) { + this.features.add(features.getString(i)); + } + } + + public ServiceDiscoveryResult(Cursor cursor) throws JSONException { + this( + cursor.getString(cursor.getColumnIndex(HASH)), + Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT), + new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT))) + ); } public List getIdentities() { @@ -135,7 +176,7 @@ public class ServiceDiscoveryResult { return false; } - public byte[] getCapHash() { + protected byte[] mkCapHash() { StringBuilder s = new StringBuilder(); List identities = this.getIdentities(); @@ -191,4 +232,11 @@ public class ServiceDiscoveryResult { } } + public ContentValues getContentValues() { + final ContentValues values = new ContentValues(); + values.put(HASH, this.hash); + values.put(VER, new String(Base64.encode(this.ver, Base64.DEFAULT)).trim()); + values.put(RESULT, this.toJSON().toString()); + return values; + } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 77c16f25..2f28a30f 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.json.JSONException; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -41,6 +42,7 @@ import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Roster; +import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -49,7 +51,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 22; + private static final int DATABASE_VERSION = 23; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -63,6 +65,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " + Contact.JID + ") ON CONFLICT REPLACE);"; + private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table " + + ServiceDiscoveryResult.TABLENAME + "(" + + ServiceDiscoveryResult.HASH + " TEXT, " + + ServiceDiscoveryResult.VER + " TEXT, " + + ServiceDiscoveryResult.RESULT + " TEXT, " + + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", " + + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);"; + private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + SQLiteAxolotlStore.ACCOUNT + " TEXT, " @@ -158,6 +168,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON DELETE CASCADE);"); db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); db.execSQL(CREATE_SESSIONS_STATEMENT); db.execSQL(CREATE_PREKEYS_STATEMENT); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); @@ -355,6 +366,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 22 && newVersion >= 22) { db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); } + + if (oldVersion < 23 && newVersion >= 23) { + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -379,6 +394,30 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(Account.TABLENAME, null, account.getContentValues()); } + public void insertDiscoveryResult(ServiceDiscoveryResult result) { + SQLiteDatabase db = this.getWritableDatabase(); + db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues()); + } + + public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = {hash, ver}; + Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null, + ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", + selectionArgs, null, null, null); + if (cursor.getCount() == 0) + return null; + cursor.moveToFirst(); + + ServiceDiscoveryResult result = null; + try { + result = new ServiceDiscoveryResult(cursor); + } catch (JSONException e) { /* result is still null */ } + + cursor.close(); + return result; + } + public CopyOnWriteArrayList getConversations(int status) { CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); SQLiteDatabase db = this.getReadableDatabase();