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