1package eu.siacs.conversations.persistance;
2
3import android.content.ContentValues;
4import android.content.Context;
5import android.database.Cursor;
6import android.database.DatabaseUtils;
7import android.database.sqlite.SQLiteCantOpenDatabaseException;
8import android.database.sqlite.SQLiteDatabase;
9import android.database.sqlite.SQLiteOpenHelper;
10import android.util.Base64;
11import android.util.Log;
12import android.util.Pair;
13
14import org.whispersystems.libaxolotl.AxolotlAddress;
15import org.whispersystems.libaxolotl.IdentityKey;
16import org.whispersystems.libaxolotl.IdentityKeyPair;
17import org.whispersystems.libaxolotl.InvalidKeyException;
18import org.whispersystems.libaxolotl.state.PreKeyRecord;
19import org.whispersystems.libaxolotl.state.SessionRecord;
20import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
21
22import java.io.ByteArrayInputStream;
23import java.io.IOException;
24import java.security.cert.CertificateEncodingException;
25import java.security.cert.CertificateException;
26import java.security.cert.CertificateFactory;
27import java.security.cert.X509Certificate;
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.Iterator;
31import java.util.List;
32import java.util.Set;
33import java.util.concurrent.CopyOnWriteArrayList;
34import org.json.JSONException;
35
36import eu.siacs.conversations.Config;
37import eu.siacs.conversations.crypto.axolotl.AxolotlService;
38import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
39import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
40import eu.siacs.conversations.entities.Account;
41import eu.siacs.conversations.entities.Contact;
42import eu.siacs.conversations.entities.Conversation;
43import eu.siacs.conversations.entities.Message;
44import eu.siacs.conversations.entities.Roster;
45import eu.siacs.conversations.entities.ServiceDiscoveryResult;
46import eu.siacs.conversations.xmpp.jid.InvalidJidException;
47import eu.siacs.conversations.xmpp.jid.Jid;
48
49public class DatabaseBackend extends SQLiteOpenHelper {
50
51 private static DatabaseBackend instance = null;
52
53 private static final String DATABASE_NAME = "history";
54 private static final int DATABASE_VERSION = 25;
55
56 private static String CREATE_CONTATCS_STATEMENT = "create table "
57 + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
58 + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
59 + Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
60 + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
61 + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
62 + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
63 + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
64 + Account.TABLENAME + "(" + Account.UUID
65 + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
66 + Contact.JID + ") ON CONFLICT REPLACE);";
67
68 private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
69 + ServiceDiscoveryResult.TABLENAME + "("
70 + ServiceDiscoveryResult.HASH + " TEXT, "
71 + ServiceDiscoveryResult.VER + " TEXT, "
72 + ServiceDiscoveryResult.RESULT + " TEXT, "
73 + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
74 + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
75
76 private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
77 + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
78 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
79 + SQLiteAxolotlStore.ID + " INTEGER, "
80 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
81 + SQLiteAxolotlStore.ACCOUNT
82 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
83 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
84 + SQLiteAxolotlStore.ID
85 + ") ON CONFLICT REPLACE"
86 + ");";
87
88 private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
89 + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
90 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
91 + SQLiteAxolotlStore.ID + " INTEGER, "
92 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
93 + SQLiteAxolotlStore.ACCOUNT
94 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
95 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
96 + SQLiteAxolotlStore.ID
97 + ") ON CONFLICT REPLACE" +
98 ");";
99
100 private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
101 + SQLiteAxolotlStore.SESSION_TABLENAME + "("
102 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
103 + SQLiteAxolotlStore.NAME + " TEXT, "
104 + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
105 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
106 + SQLiteAxolotlStore.ACCOUNT
107 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
108 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
109 + SQLiteAxolotlStore.NAME + ", "
110 + SQLiteAxolotlStore.DEVICE_ID
111 + ") ON CONFLICT REPLACE"
112 + ");";
113
114 private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
115 + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
116 + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
117 + SQLiteAxolotlStore.NAME + " TEXT, "
118 + SQLiteAxolotlStore.OWN + " INTEGER, "
119 + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
120 + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
121 + SQLiteAxolotlStore.TRUSTED + " INTEGER, "
122 + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
123 + SQLiteAxolotlStore.ACCOUNT
124 + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
125 + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
126 + SQLiteAxolotlStore.NAME + ", "
127 + SQLiteAxolotlStore.FINGERPRINT
128 + ") ON CONFLICT IGNORE"
129 + ");";
130
131 private DatabaseBackend(Context context) {
132 super(context, DATABASE_NAME, null, DATABASE_VERSION);
133 }
134
135 @Override
136 public void onCreate(SQLiteDatabase db) {
137 db.execSQL("PRAGMA foreign_keys=ON;");
138 db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID
139 + " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
140 + Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
141 + Account.DISPLAY_NAME + " TEXT, "
142 + Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
143 + " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
144 + " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)");
145 db.execSQL("create table " + Conversation.TABLENAME + " ("
146 + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
147 + " TEXT, " + Conversation.CONTACT + " TEXT, "
148 + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
149 + " TEXT, " + Conversation.CREATED + " NUMBER, "
150 + Conversation.STATUS + " NUMBER, " + Conversation.MODE
151 + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
152 + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
153 + "(" + Account.UUID + ") ON DELETE CASCADE);");
154 db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
155 + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
156 + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
157 + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
158 + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
159 + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
160 + Message.RELATIVE_FILE_PATH + " TEXT, "
161 + Message.SERVER_MSG_ID + " TEXT, "
162 + Message.FINGERPRINT + " TEXT, "
163 + Message.CARBON + " INTEGER, "
164 + Message.EDITED + " TEXT, "
165 + Message.READ + " NUMBER DEFAULT 1, "
166 + Message.OOB + " INTEGER, "
167 + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
168 + Message.CONVERSATION + ") REFERENCES "
169 + Conversation.TABLENAME + "(" + Conversation.UUID
170 + ") ON DELETE CASCADE);");
171
172 db.execSQL(CREATE_CONTATCS_STATEMENT);
173 db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
174 db.execSQL(CREATE_SESSIONS_STATEMENT);
175 db.execSQL(CREATE_PREKEYS_STATEMENT);
176 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
177 db.execSQL(CREATE_IDENTITIES_STATEMENT);
178 }
179
180 @Override
181 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
182 if (oldVersion < 2 && newVersion >= 2) {
183 db.execSQL("update " + Account.TABLENAME + " set "
184 + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
185 }
186 if (oldVersion < 3 && newVersion >= 3) {
187 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
188 + Message.TYPE + " NUMBER");
189 }
190 if (oldVersion < 5 && newVersion >= 5) {
191 db.execSQL("DROP TABLE " + Contact.TABLENAME);
192 db.execSQL(CREATE_CONTATCS_STATEMENT);
193 db.execSQL("UPDATE " + Account.TABLENAME + " SET "
194 + Account.ROSTERVERSION + " = NULL");
195 }
196 if (oldVersion < 6 && newVersion >= 6) {
197 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
198 + Message.TRUE_COUNTERPART + " TEXT");
199 }
200 if (oldVersion < 7 && newVersion >= 7) {
201 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
202 + Message.REMOTE_MSG_ID + " TEXT");
203 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
204 + Contact.AVATAR + " TEXT");
205 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
206 + Account.AVATAR + " TEXT");
207 }
208 if (oldVersion < 8 && newVersion >= 8) {
209 db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
210 + Conversation.ATTRIBUTES + " TEXT");
211 }
212 if (oldVersion < 9 && newVersion >= 9) {
213 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
214 + Contact.LAST_TIME + " NUMBER");
215 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
216 + Contact.LAST_PRESENCE + " TEXT");
217 }
218 if (oldVersion < 10 && newVersion >= 10) {
219 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
220 + Message.RELATIVE_FILE_PATH + " TEXT");
221 }
222 if (oldVersion < 11 && newVersion >= 11) {
223 db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
224 + Contact.GROUPS + " TEXT");
225 db.execSQL("delete from " + Contact.TABLENAME);
226 db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
227 }
228 if (oldVersion < 12 && newVersion >= 12) {
229 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
230 + Message.SERVER_MSG_ID + " TEXT");
231 }
232 if (oldVersion < 13 && newVersion >= 13) {
233 db.execSQL("delete from " + Contact.TABLENAME);
234 db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
235 }
236 if (oldVersion < 14 && newVersion >= 14) {
237 // migrate db to new, canonicalized JID domainpart representation
238
239 // Conversation table
240 Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
241 while (cursor.moveToNext()) {
242 String newJid;
243 try {
244 newJid = Jid.fromString(
245 cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
246 ).toString();
247 } catch (InvalidJidException ignored) {
248 Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
249 + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
250 + ": " + ignored + ". Skipping...");
251 continue;
252 }
253
254 String updateArgs[] = {
255 newJid,
256 cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
257 };
258 db.execSQL("update " + Conversation.TABLENAME
259 + " set " + Conversation.CONTACTJID + " = ? "
260 + " where " + Conversation.UUID + " = ?", updateArgs);
261 }
262 cursor.close();
263
264 // Contact table
265 cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
266 while (cursor.moveToNext()) {
267 String newJid;
268 try {
269 newJid = Jid.fromString(
270 cursor.getString(cursor.getColumnIndex(Contact.JID))
271 ).toString();
272 } catch (InvalidJidException ignored) {
273 Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
274 + cursor.getString(cursor.getColumnIndex(Contact.JID))
275 + ": " + ignored + ". Skipping...");
276 continue;
277 }
278
279 String updateArgs[] = {
280 newJid,
281 cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
282 cursor.getString(cursor.getColumnIndex(Contact.JID)),
283 };
284 db.execSQL("update " + Contact.TABLENAME
285 + " set " + Contact.JID + " = ? "
286 + " where " + Contact.ACCOUNT + " = ? "
287 + " AND " + Contact.JID + " = ?", updateArgs);
288 }
289 cursor.close();
290
291 // Account table
292 cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
293 while (cursor.moveToNext()) {
294 String newServer;
295 try {
296 newServer = Jid.fromParts(
297 cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
298 cursor.getString(cursor.getColumnIndex(Account.SERVER)),
299 "mobile"
300 ).getDomainpart();
301 } catch (InvalidJidException ignored) {
302 Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
303 + cursor.getString(cursor.getColumnIndex(Account.SERVER))
304 + ": " + ignored + ". Skipping...");
305 continue;
306 }
307
308 String updateArgs[] = {
309 newServer,
310 cursor.getString(cursor.getColumnIndex(Account.UUID)),
311 };
312 db.execSQL("update " + Account.TABLENAME
313 + " set " + Account.SERVER + " = ? "
314 + " where " + Account.UUID + " = ?", updateArgs);
315 }
316 cursor.close();
317 }
318 if (oldVersion < 15 && newVersion >= 15) {
319 recreateAxolotlDb(db);
320 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
321 + Message.FINGERPRINT + " TEXT");
322 }
323 if (oldVersion < 16 && newVersion >= 16) {
324 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
325 + Message.CARBON + " INTEGER");
326 }
327 if (oldVersion < 19 && newVersion >= 19) {
328 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
329 }
330 if (oldVersion < 20 && newVersion >= 20) {
331 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
332 db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
333 }
334 /* Any migrations that alter the Account table need to happen BEFORE this migration, as it
335 * depends on account de-serialization.
336 */
337 if (oldVersion < 17 && newVersion >= 17) {
338 List<Account> accounts = getAccounts(db);
339 for (Account account : accounts) {
340 String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
341 if (ownDeviceIdString == null) {
342 continue;
343 }
344 int ownDeviceId = Integer.valueOf(ownDeviceIdString);
345 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId);
346 deleteSession(db, account, ownAddress);
347 IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
348 if (identityKeyPair != null) {
349 setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED);
350 } else {
351 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
352 }
353 }
354 }
355 if (oldVersion < 18 && newVersion >= 18) {
356 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
357 }
358
359 if (oldVersion < 21 && newVersion >= 21) {
360 List<Account> accounts = getAccounts(db);
361 for (Account account : accounts) {
362 account.unsetPgpSignature();
363 db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
364 + "=?", new String[]{account.getUuid()});
365 }
366 }
367
368 if (oldVersion < 22 && newVersion >= 22) {
369 db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
370 }
371
372 if (oldVersion < 23 && newVersion >= 23) {
373 db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
374 }
375
376 if (oldVersion < 24 && newVersion >= 24) {
377 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
378 }
379
380 if (oldVersion < 25 && newVersion >= 25) {
381 db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
382 }
383 }
384
385 public static synchronized DatabaseBackend getInstance(Context context) {
386 if (instance == null) {
387 instance = new DatabaseBackend(context);
388 }
389 return instance;
390 }
391
392 public void createConversation(Conversation conversation) {
393 SQLiteDatabase db = this.getWritableDatabase();
394 db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
395 }
396
397 public void createMessage(Message message) {
398 SQLiteDatabase db = this.getWritableDatabase();
399 db.insert(Message.TABLENAME, null, message.getContentValues());
400 }
401
402 public void createAccount(Account account) {
403 SQLiteDatabase db = this.getWritableDatabase();
404 db.insert(Account.TABLENAME, null, account.getContentValues());
405 }
406
407 public void insertDiscoveryResult(ServiceDiscoveryResult result) {
408 SQLiteDatabase db = this.getWritableDatabase();
409 db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
410 }
411
412 public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
413 SQLiteDatabase db = this.getReadableDatabase();
414 String[] selectionArgs = {hash, ver};
415 Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
416 ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
417 selectionArgs, null, null, null);
418 if (cursor.getCount() == 0) {
419 cursor.close();
420 return null;
421 }
422 cursor.moveToFirst();
423
424 ServiceDiscoveryResult result = null;
425 try {
426 result = new ServiceDiscoveryResult(cursor);
427 } catch (JSONException e) { /* result is still null */ }
428
429 cursor.close();
430 return result;
431 }
432
433 public CopyOnWriteArrayList<Conversation> getConversations(int status) {
434 CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
435 SQLiteDatabase db = this.getReadableDatabase();
436 String[] selectionArgs = {Integer.toString(status)};
437 Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
438 + " where " + Conversation.STATUS + " = ? order by "
439 + Conversation.CREATED + " desc", selectionArgs);
440 while (cursor.moveToNext()) {
441 list.add(Conversation.fromCursor(cursor));
442 }
443 cursor.close();
444 return list;
445 }
446
447 public ArrayList<Message> getMessages(Conversation conversations, int limit) {
448 return getMessages(conversations, limit, -1);
449 }
450
451 public ArrayList<Message> getMessages(Conversation conversation, int limit,
452 long timestamp) {
453 ArrayList<Message> list = new ArrayList<>();
454 SQLiteDatabase db = this.getReadableDatabase();
455 Cursor cursor;
456 if (timestamp == -1) {
457 String[] selectionArgs = {conversation.getUuid()};
458 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
459 + "=?", selectionArgs, null, null, Message.TIME_SENT
460 + " DESC", String.valueOf(limit));
461 } else {
462 String[] selectionArgs = {conversation.getUuid(),
463 Long.toString(timestamp)};
464 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
465 + "=? and " + Message.TIME_SENT + "<?", selectionArgs,
466 null, null, Message.TIME_SENT + " DESC",
467 String.valueOf(limit));
468 }
469 if (cursor.getCount() > 0) {
470 cursor.moveToLast();
471 do {
472 Message message = Message.fromCursor(cursor);
473 message.setConversation(conversation);
474 list.add(message);
475 } while (cursor.moveToPrevious());
476 }
477 cursor.close();
478 return list;
479 }
480
481 public Iterable<Message> getMessagesIterable(final Conversation conversation) {
482 return new Iterable<Message>() {
483 @Override
484 public Iterator<Message> iterator() {
485 class MessageIterator implements Iterator<Message> {
486 SQLiteDatabase db = getReadableDatabase();
487 String[] selectionArgs = {conversation.getUuid()};
488 Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
489 + "=?", selectionArgs, null, null, Message.TIME_SENT
490 + " ASC", null);
491
492 public MessageIterator() {
493 cursor.moveToFirst();
494 }
495
496 @Override
497 public boolean hasNext() {
498 return !cursor.isAfterLast();
499 }
500
501 @Override
502 public Message next() {
503 Message message = Message.fromCursor(cursor);
504 cursor.moveToNext();
505 return message;
506 }
507
508 @Override
509 public void remove() {
510 throw new UnsupportedOperationException();
511 }
512 }
513 return new MessageIterator();
514 }
515 };
516 }
517
518 public Conversation findConversation(final Account account, final Jid contactJid) {
519 SQLiteDatabase db = this.getReadableDatabase();
520 String[] selectionArgs = {account.getUuid(),
521 contactJid.toBareJid().toString() + "/%",
522 contactJid.toBareJid().toString()
523 };
524 Cursor cursor = db.query(Conversation.TABLENAME, null,
525 Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
526 + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
527 if (cursor.getCount() == 0) {
528 cursor.close();
529 return null;
530 }
531 cursor.moveToFirst();
532 Conversation conversation = Conversation.fromCursor(cursor);
533 cursor.close();
534 return conversation;
535 }
536
537 public void updateConversation(final Conversation conversation) {
538 final SQLiteDatabase db = this.getWritableDatabase();
539 final String[] args = {conversation.getUuid()};
540 db.update(Conversation.TABLENAME, conversation.getContentValues(),
541 Conversation.UUID + "=?", args);
542 }
543
544 public List<Account> getAccounts() {
545 SQLiteDatabase db = this.getReadableDatabase();
546 return getAccounts(db);
547 }
548
549 private List<Account> getAccounts(SQLiteDatabase db) {
550 List<Account> list = new ArrayList<>();
551 Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
552 null, null);
553 while (cursor.moveToNext()) {
554 list.add(Account.fromCursor(cursor));
555 }
556 cursor.close();
557 return list;
558 }
559
560 public void updateAccount(Account account) {
561 SQLiteDatabase db = this.getWritableDatabase();
562 String[] args = {account.getUuid()};
563 db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
564 + "=?", args);
565 }
566
567 public void deleteAccount(Account account) {
568 SQLiteDatabase db = this.getWritableDatabase();
569 String[] args = {account.getUuid()};
570 db.delete(Account.TABLENAME, Account.UUID + "=?", args);
571 }
572
573 public boolean hasEnabledAccounts() {
574 SQLiteDatabase db = this.getReadableDatabase();
575 Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
576 + Account.TABLENAME + " where not options & (1 <<1)", null);
577 try {
578 cursor.moveToFirst();
579 int count = cursor.getInt(0);
580 return (count > 0);
581 } catch (SQLiteCantOpenDatabaseException e) {
582 return true; // better safe than sorry
583 } catch (RuntimeException e) {
584 return true; // better safe than sorry
585 } finally {
586 if (cursor != null) {
587 cursor.close();
588 }
589 }
590 }
591
592 @Override
593 public SQLiteDatabase getWritableDatabase() {
594 SQLiteDatabase db = super.getWritableDatabase();
595 db.execSQL("PRAGMA foreign_keys=ON;");
596 return db;
597 }
598
599 public void updateMessage(Message message) {
600 SQLiteDatabase db = this.getWritableDatabase();
601 String[] args = {message.getUuid()};
602 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
603 + "=?", args);
604 }
605
606 public void updateMessage(Message message, String uuid) {
607 SQLiteDatabase db = this.getWritableDatabase();
608 String[] args = {uuid};
609 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
610 + "=?", args);
611 }
612
613 public void readRoster(Roster roster) {
614 SQLiteDatabase db = this.getReadableDatabase();
615 Cursor cursor;
616 String args[] = {roster.getAccount().getUuid()};
617 cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
618 while (cursor.moveToNext()) {
619 roster.initContact(Contact.fromCursor(cursor));
620 }
621 cursor.close();
622 }
623
624 public void writeRoster(final Roster roster) {
625 final Account account = roster.getAccount();
626 final SQLiteDatabase db = this.getWritableDatabase();
627 db.beginTransaction();
628 for (Contact contact : roster.getContacts()) {
629 if (contact.getOption(Contact.Options.IN_ROSTER)) {
630 db.insert(Contact.TABLENAME, null, contact.getContentValues());
631 } else {
632 String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
633 String[] whereArgs = {account.getUuid(), contact.getJid().toString()};
634 db.delete(Contact.TABLENAME, where, whereArgs);
635 }
636 }
637 db.setTransactionSuccessful();
638 db.endTransaction();
639 account.setRosterVersion(roster.getVersion());
640 updateAccount(account);
641 }
642
643 public void deleteMessagesInConversation(Conversation conversation) {
644 SQLiteDatabase db = this.getWritableDatabase();
645 String[] args = {conversation.getUuid()};
646 db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
647 }
648
649 public Pair<Long, String> getLastMessageReceived(Account account) {
650 Cursor cursor = null;
651 try {
652 SQLiteDatabase db = this.getReadableDatabase();
653 String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
654 String[] args = {account.getUuid()};
655 cursor = db.rawQuery(sql, args);
656 if (cursor.getCount() == 0) {
657 return null;
658 } else {
659 cursor.moveToFirst();
660 return new Pair<>(cursor.getLong(0), cursor.getString(1));
661 }
662 } catch (Exception e) {
663 return null;
664 } finally {
665 if (cursor != null) {
666 cursor.close();
667 }
668 }
669 }
670
671 private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
672 final SQLiteDatabase db = this.getReadableDatabase();
673 String[] columns = null;
674 String[] selectionArgs = {account.getUuid(),
675 contact.getName(),
676 Integer.toString(contact.getDeviceId())};
677 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
678 columns,
679 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
680 + SQLiteAxolotlStore.NAME + " = ? AND "
681 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
682 selectionArgs,
683 null, null, null);
684
685 return cursor;
686 }
687
688 public SessionRecord loadSession(Account account, AxolotlAddress contact) {
689 SessionRecord session = null;
690 Cursor cursor = getCursorForSession(account, contact);
691 if (cursor.getCount() != 0) {
692 cursor.moveToFirst();
693 try {
694 session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
695 } catch (IOException e) {
696 cursor.close();
697 throw new AssertionError(e);
698 }
699 }
700 cursor.close();
701 return session;
702 }
703
704 public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
705 final SQLiteDatabase db = this.getReadableDatabase();
706 return getSubDeviceSessions(db, account, contact);
707 }
708
709 private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
710 List<Integer> devices = new ArrayList<>();
711 String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
712 String[] selectionArgs = {account.getUuid(),
713 contact.getName()};
714 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
715 columns,
716 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
717 + SQLiteAxolotlStore.NAME + " = ?",
718 selectionArgs,
719 null, null, null);
720
721 while (cursor.moveToNext()) {
722 devices.add(cursor.getInt(
723 cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
724 }
725
726 cursor.close();
727 return devices;
728 }
729
730 public boolean containsSession(Account account, AxolotlAddress contact) {
731 Cursor cursor = getCursorForSession(account, contact);
732 int count = cursor.getCount();
733 cursor.close();
734 return count != 0;
735 }
736
737 public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
738 SQLiteDatabase db = this.getWritableDatabase();
739 ContentValues values = new ContentValues();
740 values.put(SQLiteAxolotlStore.NAME, contact.getName());
741 values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
742 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
743 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
744 db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
745 }
746
747 public void deleteSession(Account account, AxolotlAddress contact) {
748 SQLiteDatabase db = this.getWritableDatabase();
749 deleteSession(db, account, contact);
750 }
751
752 private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
753 String[] args = {account.getUuid(),
754 contact.getName(),
755 Integer.toString(contact.getDeviceId())};
756 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
757 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
758 + SQLiteAxolotlStore.NAME + " = ? AND "
759 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
760 args);
761 }
762
763 public void deleteAllSessions(Account account, AxolotlAddress contact) {
764 SQLiteDatabase db = this.getWritableDatabase();
765 String[] args = {account.getUuid(), contact.getName()};
766 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
767 SQLiteAxolotlStore.ACCOUNT + "=? AND "
768 + SQLiteAxolotlStore.NAME + " = ?",
769 args);
770 }
771
772 private Cursor getCursorForPreKey(Account account, int preKeyId) {
773 SQLiteDatabase db = this.getReadableDatabase();
774 String[] columns = {SQLiteAxolotlStore.KEY};
775 String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
776 Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
777 columns,
778 SQLiteAxolotlStore.ACCOUNT + "=? AND "
779 + SQLiteAxolotlStore.ID + "=?",
780 selectionArgs,
781 null, null, null);
782
783 return cursor;
784 }
785
786 public PreKeyRecord loadPreKey(Account account, int preKeyId) {
787 PreKeyRecord record = null;
788 Cursor cursor = getCursorForPreKey(account, preKeyId);
789 if (cursor.getCount() != 0) {
790 cursor.moveToFirst();
791 try {
792 record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
793 } catch (IOException e) {
794 throw new AssertionError(e);
795 }
796 }
797 cursor.close();
798 return record;
799 }
800
801 public boolean containsPreKey(Account account, int preKeyId) {
802 Cursor cursor = getCursorForPreKey(account, preKeyId);
803 int count = cursor.getCount();
804 cursor.close();
805 return count != 0;
806 }
807
808 public void storePreKey(Account account, PreKeyRecord record) {
809 SQLiteDatabase db = this.getWritableDatabase();
810 ContentValues values = new ContentValues();
811 values.put(SQLiteAxolotlStore.ID, record.getId());
812 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
813 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
814 db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
815 }
816
817 public void deletePreKey(Account account, int preKeyId) {
818 SQLiteDatabase db = this.getWritableDatabase();
819 String[] args = {account.getUuid(), Integer.toString(preKeyId)};
820 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
821 SQLiteAxolotlStore.ACCOUNT + "=? AND "
822 + SQLiteAxolotlStore.ID + "=?",
823 args);
824 }
825
826 private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
827 SQLiteDatabase db = this.getReadableDatabase();
828 String[] columns = {SQLiteAxolotlStore.KEY};
829 String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
830 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
831 columns,
832 SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
833 selectionArgs,
834 null, null, null);
835
836 return cursor;
837 }
838
839 public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
840 SignedPreKeyRecord record = null;
841 Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
842 if (cursor.getCount() != 0) {
843 cursor.moveToFirst();
844 try {
845 record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
846 } catch (IOException e) {
847 throw new AssertionError(e);
848 }
849 }
850 cursor.close();
851 return record;
852 }
853
854 public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
855 List<SignedPreKeyRecord> prekeys = new ArrayList<>();
856 SQLiteDatabase db = this.getReadableDatabase();
857 String[] columns = {SQLiteAxolotlStore.KEY};
858 String[] selectionArgs = {account.getUuid()};
859 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
860 columns,
861 SQLiteAxolotlStore.ACCOUNT + "=?",
862 selectionArgs,
863 null, null, null);
864
865 while (cursor.moveToNext()) {
866 try {
867 prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
868 } catch (IOException ignored) {
869 }
870 }
871 cursor.close();
872 return prekeys;
873 }
874
875 public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
876 Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
877 int count = cursor.getCount();
878 cursor.close();
879 return count != 0;
880 }
881
882 public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
883 SQLiteDatabase db = this.getWritableDatabase();
884 ContentValues values = new ContentValues();
885 values.put(SQLiteAxolotlStore.ID, record.getId());
886 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
887 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
888 db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
889 }
890
891 public void deleteSignedPreKey(Account account, int signedPreKeyId) {
892 SQLiteDatabase db = this.getWritableDatabase();
893 String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
894 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
895 SQLiteAxolotlStore.ACCOUNT + "=? AND "
896 + SQLiteAxolotlStore.ID + "=?",
897 args);
898 }
899
900 private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
901 final SQLiteDatabase db = this.getReadableDatabase();
902 return getIdentityKeyCursor(db, account, name, own);
903 }
904
905 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
906 return getIdentityKeyCursor(db, account, name, own, null);
907 }
908
909 private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
910 final SQLiteDatabase db = this.getReadableDatabase();
911 return getIdentityKeyCursor(db, account, fingerprint);
912 }
913
914 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
915 return getIdentityKeyCursor(db, account, null, null, fingerprint);
916 }
917
918 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
919 String[] columns = {SQLiteAxolotlStore.TRUSTED,
920 SQLiteAxolotlStore.KEY};
921 ArrayList<String> selectionArgs = new ArrayList<>(4);
922 selectionArgs.add(account.getUuid());
923 String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
924 if (name != null) {
925 selectionArgs.add(name);
926 selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
927 }
928 if (fingerprint != null) {
929 selectionArgs.add(fingerprint);
930 selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
931 }
932 if (own != null) {
933 selectionArgs.add(own ? "1" : "0");
934 selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
935 }
936 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
937 columns,
938 selectionString,
939 selectionArgs.toArray(new String[selectionArgs.size()]),
940 null, null, null);
941
942 return cursor;
943 }
944
945 public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
946 SQLiteDatabase db = getReadableDatabase();
947 return loadOwnIdentityKeyPair(db, account);
948 }
949
950 private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
951 String name = account.getJid().toBareJid().toString();
952 IdentityKeyPair identityKeyPair = null;
953 Cursor cursor = getIdentityKeyCursor(db, account, name, true);
954 if (cursor.getCount() != 0) {
955 cursor.moveToFirst();
956 try {
957 identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
958 } catch (InvalidKeyException e) {
959 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
960 }
961 }
962 cursor.close();
963
964 return identityKeyPair;
965 }
966
967 public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
968 return loadIdentityKeys(account, name, null);
969 }
970
971 public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
972 Set<IdentityKey> identityKeys = new HashSet<>();
973 Cursor cursor = getIdentityKeyCursor(account, name, false);
974
975 while (cursor.moveToNext()) {
976 if (trust != null &&
977 cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
978 != trust.getCode()) {
979 continue;
980 }
981 try {
982 identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
983 } catch (InvalidKeyException e) {
984 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
985 }
986 }
987 cursor.close();
988
989 return identityKeys;
990 }
991
992 public long numTrustedKeys(Account account, String name) {
993 SQLiteDatabase db = getReadableDatabase();
994 String[] args = {
995 account.getUuid(),
996 name,
997 String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()),
998 String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode())
999 };
1000 return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1001 SQLiteAxolotlStore.ACCOUNT + " = ?"
1002 + " AND " + SQLiteAxolotlStore.NAME + " = ?"
1003 + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
1004 args
1005 );
1006 }
1007
1008 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
1009 storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
1010 }
1011
1012 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
1013 SQLiteDatabase db = this.getWritableDatabase();
1014 ContentValues values = new ContentValues();
1015 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
1016 values.put(SQLiteAxolotlStore.NAME, name);
1017 values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
1018 values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
1019 values.put(SQLiteAxolotlStore.KEY, base64Serialized);
1020 values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
1021 db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
1022 }
1023
1024 public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
1025 Cursor cursor = getIdentityKeyCursor(account, fingerprint);
1026 XmppAxolotlSession.Trust trust = null;
1027 if (cursor.getCount() > 0) {
1028 cursor.moveToFirst();
1029 int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
1030 trust = XmppAxolotlSession.Trust.fromCode(trustValue);
1031 }
1032 cursor.close();
1033 return trust;
1034 }
1035
1036 public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1037 SQLiteDatabase db = this.getWritableDatabase();
1038 return setIdentityKeyTrust(db, account, fingerprint, trust);
1039 }
1040
1041 private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1042 String[] selectionArgs = {
1043 account.getUuid(),
1044 fingerprint
1045 };
1046 ContentValues values = new ContentValues();
1047 values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
1048 int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1049 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1050 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1051 selectionArgs);
1052 return rows == 1;
1053 }
1054
1055 public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
1056 SQLiteDatabase db = this.getWritableDatabase();
1057 String[] selectionArgs = {
1058 account.getUuid(),
1059 fingerprint
1060 };
1061 try {
1062 ContentValues values = new ContentValues();
1063 values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
1064 return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1065 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1066 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1067 selectionArgs) == 1;
1068 } catch (CertificateEncodingException e) {
1069 Log.d(Config.LOGTAG, "could not encode certificate");
1070 return false;
1071 }
1072 }
1073
1074 public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
1075 SQLiteDatabase db = this.getReadableDatabase();
1076 String[] selectionArgs = {
1077 account.getUuid(),
1078 fingerprint
1079 };
1080 String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
1081 String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
1082 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
1083 if (cursor.getCount() < 1) {
1084 return null;
1085 } else {
1086 cursor.moveToFirst();
1087 byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
1088 if (certificate == null || certificate.length == 0) {
1089 return null;
1090 }
1091 try {
1092 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1093 return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
1094 } catch (CertificateException e) {
1095 Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
1096 return null;
1097 }
1098 }
1099 }
1100
1101 public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
1102 storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
1103 }
1104
1105 public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
1106 storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
1107 }
1108
1109 public void recreateAxolotlDb() {
1110 recreateAxolotlDb(getWritableDatabase());
1111 }
1112
1113 public void recreateAxolotlDb(SQLiteDatabase db) {
1114 Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
1115 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
1116 db.execSQL(CREATE_SESSIONS_STATEMENT);
1117 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
1118 db.execSQL(CREATE_PREKEYS_STATEMENT);
1119 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
1120 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
1121 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
1122 db.execSQL(CREATE_IDENTITIES_STATEMENT);
1123 }
1124
1125 public void wipeAxolotlDb(Account account) {
1126 String accountName = account.getUuid();
1127 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
1128 SQLiteDatabase db = this.getWritableDatabase();
1129 String[] deleteArgs = {
1130 accountName
1131 };
1132 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
1133 SQLiteAxolotlStore.ACCOUNT + " = ?",
1134 deleteArgs);
1135 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
1136 SQLiteAxolotlStore.ACCOUNT + " = ?",
1137 deleteArgs);
1138 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
1139 SQLiteAxolotlStore.ACCOUNT + " = ?",
1140 deleteArgs);
1141 db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1142 SQLiteAxolotlStore.ACCOUNT + " = ?",
1143 deleteArgs);
1144 }
1145}