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