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