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 try {
610 SQLiteDatabase db = this.getReadableDatabase();
611 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";
612 String[] args = {account.getUuid()};
613 Cursor cursor = db.rawQuery(sql, args);
614 if (cursor.getCount() == 0) {
615 return null;
616 } else {
617 cursor.moveToFirst();
618 return new Pair<>(cursor.getLong(0), cursor.getString(1));
619 }
620 } catch (Exception e) {
621 return null;
622 }
623 }
624
625 public Conversation findConversationByUuid(String conversationUuid) {
626 SQLiteDatabase db = this.getReadableDatabase();
627 String[] selectionArgs = {conversationUuid};
628 Cursor cursor = db.query(Conversation.TABLENAME, null,
629 Conversation.UUID + "=?", selectionArgs, null, null, null);
630 if (cursor.getCount() == 0) {
631 return null;
632 }
633 cursor.moveToFirst();
634 Conversation conversation = Conversation.fromCursor(cursor);
635 cursor.close();
636 return conversation;
637 }
638
639 public Message findMessageByUuid(String messageUuid) {
640 SQLiteDatabase db = this.getReadableDatabase();
641 String[] selectionArgs = {messageUuid};
642 Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
643 selectionArgs, null, null, null);
644 if (cursor.getCount() == 0) {
645 return null;
646 }
647 cursor.moveToFirst();
648 Message message = Message.fromCursor(cursor);
649 cursor.close();
650 return message;
651 }
652
653 public Account findAccountByUuid(String accountUuid) {
654 SQLiteDatabase db = this.getReadableDatabase();
655 String[] selectionArgs = {accountUuid};
656 Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
657 selectionArgs, null, null, null);
658 if (cursor.getCount() == 0) {
659 return null;
660 }
661 cursor.moveToFirst();
662 Account account = Account.fromCursor(cursor);
663 cursor.close();
664 return account;
665 }
666
667 public List<Message> getImageMessages(Conversation conversation) {
668 ArrayList<Message> list = new ArrayList<>();
669 SQLiteDatabase db = this.getReadableDatabase();
670 Cursor cursor;
671 String[] selectionArgs = {conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE)};
672 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
673 + "=? AND " + Message.TYPE + "=?", selectionArgs, null, null, null);
674 if (cursor.getCount() > 0) {
675 cursor.moveToLast();
676 do {
677 Message message = Message.fromCursor(cursor);
678 message.setConversation(conversation);
679 list.add(message);
680 } while (cursor.moveToPrevious());
681 }
682 cursor.close();
683 return list;
684 }
685
686 private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
687 final SQLiteDatabase db = this.getReadableDatabase();
688 String[] columns = null;
689 String[] selectionArgs = {account.getUuid(),
690 contact.getName(),
691 Integer.toString(contact.getDeviceId())};
692 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
693 columns,
694 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
695 + SQLiteAxolotlStore.NAME + " = ? AND "
696 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
697 selectionArgs,
698 null, null, null);
699
700 return cursor;
701 }
702
703 public SessionRecord loadSession(Account account, AxolotlAddress contact) {
704 SessionRecord session = null;
705 Cursor cursor = getCursorForSession(account, contact);
706 if (cursor.getCount() != 0) {
707 cursor.moveToFirst();
708 try {
709 session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
710 } catch (IOException e) {
711 cursor.close();
712 throw new AssertionError(e);
713 }
714 }
715 cursor.close();
716 return session;
717 }
718
719 public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
720 final SQLiteDatabase db = this.getReadableDatabase();
721 return getSubDeviceSessions(db, account, contact);
722 }
723
724 private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
725 List<Integer> devices = new ArrayList<>();
726 String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
727 String[] selectionArgs = {account.getUuid(),
728 contact.getName()};
729 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
730 columns,
731 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
732 + SQLiteAxolotlStore.NAME + " = ?",
733 selectionArgs,
734 null, null, null);
735
736 while (cursor.moveToNext()) {
737 devices.add(cursor.getInt(
738 cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
739 }
740
741 cursor.close();
742 return devices;
743 }
744
745 public boolean containsSession(Account account, AxolotlAddress contact) {
746 Cursor cursor = getCursorForSession(account, contact);
747 int count = cursor.getCount();
748 cursor.close();
749 return count != 0;
750 }
751
752 public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
753 SQLiteDatabase db = this.getWritableDatabase();
754 ContentValues values = new ContentValues();
755 values.put(SQLiteAxolotlStore.NAME, contact.getName());
756 values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
757 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
758 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
759 db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
760 }
761
762 public void deleteSession(Account account, AxolotlAddress contact) {
763 SQLiteDatabase db = this.getWritableDatabase();
764 deleteSession(db, account, contact);
765 }
766
767 private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
768 String[] args = {account.getUuid(),
769 contact.getName(),
770 Integer.toString(contact.getDeviceId())};
771 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
772 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
773 + SQLiteAxolotlStore.NAME + " = ? AND "
774 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
775 args);
776 }
777
778 public void deleteAllSessions(Account account, AxolotlAddress contact) {
779 SQLiteDatabase db = this.getWritableDatabase();
780 String[] args = {account.getUuid(), contact.getName()};
781 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
782 SQLiteAxolotlStore.ACCOUNT + "=? AND "
783 + SQLiteAxolotlStore.NAME + " = ?",
784 args);
785 }
786
787 private Cursor getCursorForPreKey(Account account, int preKeyId) {
788 SQLiteDatabase db = this.getReadableDatabase();
789 String[] columns = {SQLiteAxolotlStore.KEY};
790 String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
791 Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
792 columns,
793 SQLiteAxolotlStore.ACCOUNT + "=? AND "
794 + SQLiteAxolotlStore.ID + "=?",
795 selectionArgs,
796 null, null, null);
797
798 return cursor;
799 }
800
801 public PreKeyRecord loadPreKey(Account account, int preKeyId) {
802 PreKeyRecord record = null;
803 Cursor cursor = getCursorForPreKey(account, preKeyId);
804 if (cursor.getCount() != 0) {
805 cursor.moveToFirst();
806 try {
807 record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
808 } catch (IOException e) {
809 throw new AssertionError(e);
810 }
811 }
812 cursor.close();
813 return record;
814 }
815
816 public boolean containsPreKey(Account account, int preKeyId) {
817 Cursor cursor = getCursorForPreKey(account, preKeyId);
818 int count = cursor.getCount();
819 cursor.close();
820 return count != 0;
821 }
822
823 public void storePreKey(Account account, PreKeyRecord record) {
824 SQLiteDatabase db = this.getWritableDatabase();
825 ContentValues values = new ContentValues();
826 values.put(SQLiteAxolotlStore.ID, record.getId());
827 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
828 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
829 db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
830 }
831
832 public void deletePreKey(Account account, int preKeyId) {
833 SQLiteDatabase db = this.getWritableDatabase();
834 String[] args = {account.getUuid(), Integer.toString(preKeyId)};
835 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
836 SQLiteAxolotlStore.ACCOUNT + "=? AND "
837 + SQLiteAxolotlStore.ID + "=?",
838 args);
839 }
840
841 private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
842 SQLiteDatabase db = this.getReadableDatabase();
843 String[] columns = {SQLiteAxolotlStore.KEY};
844 String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
845 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
846 columns,
847 SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
848 selectionArgs,
849 null, null, null);
850
851 return cursor;
852 }
853
854 public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
855 SignedPreKeyRecord record = null;
856 Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
857 if (cursor.getCount() != 0) {
858 cursor.moveToFirst();
859 try {
860 record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
861 } catch (IOException e) {
862 throw new AssertionError(e);
863 }
864 }
865 cursor.close();
866 return record;
867 }
868
869 public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
870 List<SignedPreKeyRecord> prekeys = new ArrayList<>();
871 SQLiteDatabase db = this.getReadableDatabase();
872 String[] columns = {SQLiteAxolotlStore.KEY};
873 String[] selectionArgs = {account.getUuid()};
874 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
875 columns,
876 SQLiteAxolotlStore.ACCOUNT + "=?",
877 selectionArgs,
878 null, null, null);
879
880 while (cursor.moveToNext()) {
881 try {
882 prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
883 } catch (IOException ignored) {
884 }
885 }
886 cursor.close();
887 return prekeys;
888 }
889
890 public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
891 Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
892 int count = cursor.getCount();
893 cursor.close();
894 return count != 0;
895 }
896
897 public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
898 SQLiteDatabase db = this.getWritableDatabase();
899 ContentValues values = new ContentValues();
900 values.put(SQLiteAxolotlStore.ID, record.getId());
901 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
902 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
903 db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
904 }
905
906 public void deleteSignedPreKey(Account account, int signedPreKeyId) {
907 SQLiteDatabase db = this.getWritableDatabase();
908 String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
909 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
910 SQLiteAxolotlStore.ACCOUNT + "=? AND "
911 + SQLiteAxolotlStore.ID + "=?",
912 args);
913 }
914
915 private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
916 final SQLiteDatabase db = this.getReadableDatabase();
917 return getIdentityKeyCursor(db, account, name, own);
918 }
919
920 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
921 return getIdentityKeyCursor(db, account, name, own, null);
922 }
923
924 private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
925 final SQLiteDatabase db = this.getReadableDatabase();
926 return getIdentityKeyCursor(db, account, fingerprint);
927 }
928
929 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
930 return getIdentityKeyCursor(db, account, null, null, fingerprint);
931 }
932
933 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
934 String[] columns = {SQLiteAxolotlStore.TRUSTED,
935 SQLiteAxolotlStore.KEY};
936 ArrayList<String> selectionArgs = new ArrayList<>(4);
937 selectionArgs.add(account.getUuid());
938 String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
939 if (name != null) {
940 selectionArgs.add(name);
941 selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
942 }
943 if (fingerprint != null) {
944 selectionArgs.add(fingerprint);
945 selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
946 }
947 if (own != null) {
948 selectionArgs.add(own ? "1" : "0");
949 selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
950 }
951 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
952 columns,
953 selectionString,
954 selectionArgs.toArray(new String[selectionArgs.size()]),
955 null, null, null);
956
957 return cursor;
958 }
959
960 public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
961 SQLiteDatabase db = getReadableDatabase();
962 return loadOwnIdentityKeyPair(db, account);
963 }
964
965 private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
966 String name = account.getJid().toBareJid().toString();
967 IdentityKeyPair identityKeyPair = null;
968 Cursor cursor = getIdentityKeyCursor(db, account, name, true);
969 if (cursor.getCount() != 0) {
970 cursor.moveToFirst();
971 try {
972 identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
973 } catch (InvalidKeyException e) {
974 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
975 }
976 }
977 cursor.close();
978
979 return identityKeyPair;
980 }
981
982 public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
983 return loadIdentityKeys(account, name, null);
984 }
985
986 public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
987 Set<IdentityKey> identityKeys = new HashSet<>();
988 Cursor cursor = getIdentityKeyCursor(account, name, false);
989
990 while (cursor.moveToNext()) {
991 if (trust != null &&
992 cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
993 != trust.getCode()) {
994 continue;
995 }
996 try {
997 identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
998 } catch (InvalidKeyException e) {
999 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
1000 }
1001 }
1002 cursor.close();
1003
1004 return identityKeys;
1005 }
1006
1007 public long numTrustedKeys(Account account, String name) {
1008 SQLiteDatabase db = getReadableDatabase();
1009 String[] args = {
1010 account.getUuid(),
1011 name,
1012 String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()),
1013 String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode())
1014 };
1015 return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1016 SQLiteAxolotlStore.ACCOUNT + " = ?"
1017 + " AND " + SQLiteAxolotlStore.NAME + " = ?"
1018 + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
1019 args
1020 );
1021 }
1022
1023 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
1024 storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
1025 }
1026
1027 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
1028 SQLiteDatabase db = this.getWritableDatabase();
1029 ContentValues values = new ContentValues();
1030 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
1031 values.put(SQLiteAxolotlStore.NAME, name);
1032 values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
1033 values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
1034 values.put(SQLiteAxolotlStore.KEY, base64Serialized);
1035 values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
1036 db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
1037 }
1038
1039 public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
1040 Cursor cursor = getIdentityKeyCursor(account, fingerprint);
1041 XmppAxolotlSession.Trust trust = null;
1042 if (cursor.getCount() > 0) {
1043 cursor.moveToFirst();
1044 int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
1045 trust = XmppAxolotlSession.Trust.fromCode(trustValue);
1046 }
1047 cursor.close();
1048 return trust;
1049 }
1050
1051 public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1052 SQLiteDatabase db = this.getWritableDatabase();
1053 return setIdentityKeyTrust(db, account, fingerprint, trust);
1054 }
1055
1056 private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1057 String[] selectionArgs = {
1058 account.getUuid(),
1059 fingerprint
1060 };
1061 ContentValues values = new ContentValues();
1062 values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
1063 int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1064 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1065 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1066 selectionArgs);
1067 return rows == 1;
1068 }
1069
1070 public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
1071 SQLiteDatabase db = this.getWritableDatabase();
1072 String[] selectionArgs = {
1073 account.getUuid(),
1074 fingerprint
1075 };
1076 try {
1077 ContentValues values = new ContentValues();
1078 values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
1079 return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1080 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1081 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1082 selectionArgs) == 1;
1083 } catch (CertificateEncodingException e) {
1084 Log.d(Config.LOGTAG, "could not encode certificate");
1085 return false;
1086 }
1087 }
1088
1089 public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
1090 SQLiteDatabase db = this.getReadableDatabase();
1091 String[] selectionArgs = {
1092 account.getUuid(),
1093 fingerprint
1094 };
1095 String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
1096 String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
1097 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
1098 if (cursor.getCount() < 1) {
1099 return null;
1100 } else {
1101 cursor.moveToFirst();
1102 byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
1103 if (certificate == null || certificate.length == 0) {
1104 return null;
1105 }
1106 try {
1107 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1108 return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
1109 } catch (CertificateException e) {
1110 Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
1111 return null;
1112 }
1113 }
1114 }
1115
1116 public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
1117 storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
1118 }
1119
1120 public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
1121 storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
1122 }
1123
1124 public void recreateAxolotlDb() {
1125 recreateAxolotlDb(getWritableDatabase());
1126 }
1127
1128 public void recreateAxolotlDb(SQLiteDatabase db) {
1129 Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
1130 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
1131 db.execSQL(CREATE_SESSIONS_STATEMENT);
1132 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
1133 db.execSQL(CREATE_PREKEYS_STATEMENT);
1134 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
1135 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
1136 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
1137 db.execSQL(CREATE_IDENTITIES_STATEMENT);
1138 }
1139
1140 public void wipeAxolotlDb(Account account) {
1141 String accountName = account.getUuid();
1142 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
1143 SQLiteDatabase db = this.getWritableDatabase();
1144 String[] deleteArgs = {
1145 accountName
1146 };
1147 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
1148 SQLiteAxolotlStore.ACCOUNT + " = ?",
1149 deleteArgs);
1150 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
1151 SQLiteAxolotlStore.ACCOUNT + " = ?",
1152 deleteArgs);
1153 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
1154 SQLiteAxolotlStore.ACCOUNT + " = ?",
1155 deleteArgs);
1156 db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1157 SQLiteAxolotlStore.ACCOUNT + " = ?",
1158 deleteArgs);
1159 }
1160}