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