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 return null;
420 cursor.moveToFirst();
421
422 ServiceDiscoveryResult result = null;
423 try {
424 result = new ServiceDiscoveryResult(cursor);
425 } catch (JSONException e) { /* result is still null */ }
426
427 cursor.close();
428 return result;
429 }
430
431 public CopyOnWriteArrayList<Conversation> getConversations(int status) {
432 CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
433 SQLiteDatabase db = this.getReadableDatabase();
434 String[] selectionArgs = {Integer.toString(status)};
435 Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
436 + " where " + Conversation.STATUS + " = ? order by "
437 + Conversation.CREATED + " desc", selectionArgs);
438 while (cursor.moveToNext()) {
439 list.add(Conversation.fromCursor(cursor));
440 }
441 cursor.close();
442 return list;
443 }
444
445 public ArrayList<Message> getMessages(Conversation conversations, int limit) {
446 return getMessages(conversations, limit, -1);
447 }
448
449 public ArrayList<Message> getMessages(Conversation conversation, int limit,
450 long timestamp) {
451 ArrayList<Message> list = new ArrayList<>();
452 SQLiteDatabase db = this.getReadableDatabase();
453 Cursor cursor;
454 if (timestamp == -1) {
455 String[] selectionArgs = {conversation.getUuid()};
456 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
457 + "=?", selectionArgs, null, null, Message.TIME_SENT
458 + " DESC", String.valueOf(limit));
459 } else {
460 String[] selectionArgs = {conversation.getUuid(),
461 Long.toString(timestamp)};
462 cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
463 + "=? and " + Message.TIME_SENT + "<?", selectionArgs,
464 null, null, Message.TIME_SENT + " DESC",
465 String.valueOf(limit));
466 }
467 if (cursor.getCount() > 0) {
468 cursor.moveToLast();
469 do {
470 Message message = Message.fromCursor(cursor);
471 message.setConversation(conversation);
472 list.add(message);
473 } while (cursor.moveToPrevious());
474 }
475 cursor.close();
476 return list;
477 }
478
479 public Iterable<Message> getMessagesIterable(final Conversation conversation) {
480 return new Iterable<Message>() {
481 @Override
482 public Iterator<Message> iterator() {
483 class MessageIterator implements Iterator<Message> {
484 SQLiteDatabase db = getReadableDatabase();
485 String[] selectionArgs = {conversation.getUuid()};
486 Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
487 + "=?", selectionArgs, null, null, Message.TIME_SENT
488 + " ASC", null);
489
490 public MessageIterator() {
491 cursor.moveToFirst();
492 }
493
494 @Override
495 public boolean hasNext() {
496 return !cursor.isAfterLast();
497 }
498
499 @Override
500 public Message next() {
501 Message message = Message.fromCursor(cursor);
502 cursor.moveToNext();
503 return message;
504 }
505
506 @Override
507 public void remove() {
508 throw new UnsupportedOperationException();
509 }
510 }
511 return new MessageIterator();
512 }
513 };
514 }
515
516 public Conversation findConversation(final Account account, final Jid contactJid) {
517 SQLiteDatabase db = this.getReadableDatabase();
518 String[] selectionArgs = {account.getUuid(),
519 contactJid.toBareJid().toString() + "/%",
520 contactJid.toBareJid().toString()
521 };
522 Cursor cursor = db.query(Conversation.TABLENAME, null,
523 Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
524 + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
525 if (cursor.getCount() == 0)
526 return null;
527 cursor.moveToFirst();
528 Conversation conversation = Conversation.fromCursor(cursor);
529 cursor.close();
530 return conversation;
531 }
532
533 public void updateConversation(final Conversation conversation) {
534 final SQLiteDatabase db = this.getWritableDatabase();
535 final String[] args = {conversation.getUuid()};
536 db.update(Conversation.TABLENAME, conversation.getContentValues(),
537 Conversation.UUID + "=?", args);
538 }
539
540 public List<Account> getAccounts() {
541 SQLiteDatabase db = this.getReadableDatabase();
542 return getAccounts(db);
543 }
544
545 private List<Account> getAccounts(SQLiteDatabase db) {
546 List<Account> list = new ArrayList<>();
547 Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
548 null, null);
549 while (cursor.moveToNext()) {
550 list.add(Account.fromCursor(cursor));
551 }
552 cursor.close();
553 return list;
554 }
555
556 public void updateAccount(Account account) {
557 SQLiteDatabase db = this.getWritableDatabase();
558 String[] args = {account.getUuid()};
559 db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
560 + "=?", args);
561 }
562
563 public void deleteAccount(Account account) {
564 SQLiteDatabase db = this.getWritableDatabase();
565 String[] args = {account.getUuid()};
566 db.delete(Account.TABLENAME, Account.UUID + "=?", args);
567 }
568
569 public boolean hasEnabledAccounts() {
570 SQLiteDatabase db = this.getReadableDatabase();
571 Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
572 + Account.TABLENAME + " where not options & (1 <<1)", null);
573 try {
574 cursor.moveToFirst();
575 int count = cursor.getInt(0);
576 cursor.close();
577 return (count > 0);
578 } catch (SQLiteCantOpenDatabaseException e) {
579 return true; // better safe than sorry
580 } catch (RuntimeException e) {
581 return true; // better safe than sorry
582 }
583 }
584
585 @Override
586 public SQLiteDatabase getWritableDatabase() {
587 SQLiteDatabase db = super.getWritableDatabase();
588 db.execSQL("PRAGMA foreign_keys=ON;");
589 return db;
590 }
591
592 public void updateMessage(Message message) {
593 SQLiteDatabase db = this.getWritableDatabase();
594 String[] args = {message.getUuid()};
595 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
596 + "=?", args);
597 }
598
599 public void updateMessage(Message message, String uuid) {
600 SQLiteDatabase db = this.getWritableDatabase();
601 String[] args = {uuid};
602 db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
603 + "=?", args);
604 }
605
606 public void readRoster(Roster roster) {
607 SQLiteDatabase db = this.getReadableDatabase();
608 Cursor cursor;
609 String args[] = {roster.getAccount().getUuid()};
610 cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
611 while (cursor.moveToNext()) {
612 roster.initContact(Contact.fromCursor(cursor));
613 }
614 cursor.close();
615 }
616
617 public void writeRoster(final Roster roster) {
618 final Account account = roster.getAccount();
619 final SQLiteDatabase db = this.getWritableDatabase();
620 db.beginTransaction();
621 for (Contact contact : roster.getContacts()) {
622 if (contact.getOption(Contact.Options.IN_ROSTER)) {
623 db.insert(Contact.TABLENAME, null, contact.getContentValues());
624 } else {
625 String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
626 String[] whereArgs = {account.getUuid(), contact.getJid().toString()};
627 db.delete(Contact.TABLENAME, where, whereArgs);
628 }
629 }
630 db.setTransactionSuccessful();
631 db.endTransaction();
632 account.setRosterVersion(roster.getVersion());
633 updateAccount(account);
634 }
635
636 public void deleteMessagesInConversation(Conversation conversation) {
637 SQLiteDatabase db = this.getWritableDatabase();
638 String[] args = {conversation.getUuid()};
639 db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
640 }
641
642 public Pair<Long, String> getLastMessageReceived(Account account) {
643 try {
644 SQLiteDatabase db = this.getReadableDatabase();
645 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";
646 String[] args = {account.getUuid()};
647 Cursor cursor = db.rawQuery(sql, args);
648 if (cursor.getCount() == 0) {
649 return null;
650 } else {
651 cursor.moveToFirst();
652 return new Pair<>(cursor.getLong(0), cursor.getString(1));
653 }
654 } catch (Exception e) {
655 return null;
656 }
657 }
658
659 private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
660 final SQLiteDatabase db = this.getReadableDatabase();
661 String[] columns = null;
662 String[] selectionArgs = {account.getUuid(),
663 contact.getName(),
664 Integer.toString(contact.getDeviceId())};
665 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
666 columns,
667 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
668 + SQLiteAxolotlStore.NAME + " = ? AND "
669 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
670 selectionArgs,
671 null, null, null);
672
673 return cursor;
674 }
675
676 public SessionRecord loadSession(Account account, AxolotlAddress contact) {
677 SessionRecord session = null;
678 Cursor cursor = getCursorForSession(account, contact);
679 if (cursor.getCount() != 0) {
680 cursor.moveToFirst();
681 try {
682 session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
683 } catch (IOException e) {
684 cursor.close();
685 throw new AssertionError(e);
686 }
687 }
688 cursor.close();
689 return session;
690 }
691
692 public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
693 final SQLiteDatabase db = this.getReadableDatabase();
694 return getSubDeviceSessions(db, account, contact);
695 }
696
697 private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
698 List<Integer> devices = new ArrayList<>();
699 String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
700 String[] selectionArgs = {account.getUuid(),
701 contact.getName()};
702 Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
703 columns,
704 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
705 + SQLiteAxolotlStore.NAME + " = ?",
706 selectionArgs,
707 null, null, null);
708
709 while (cursor.moveToNext()) {
710 devices.add(cursor.getInt(
711 cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
712 }
713
714 cursor.close();
715 return devices;
716 }
717
718 public boolean containsSession(Account account, AxolotlAddress contact) {
719 Cursor cursor = getCursorForSession(account, contact);
720 int count = cursor.getCount();
721 cursor.close();
722 return count != 0;
723 }
724
725 public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
726 SQLiteDatabase db = this.getWritableDatabase();
727 ContentValues values = new ContentValues();
728 values.put(SQLiteAxolotlStore.NAME, contact.getName());
729 values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
730 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
731 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
732 db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
733 }
734
735 public void deleteSession(Account account, AxolotlAddress contact) {
736 SQLiteDatabase db = this.getWritableDatabase();
737 deleteSession(db, account, contact);
738 }
739
740 private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
741 String[] args = {account.getUuid(),
742 contact.getName(),
743 Integer.toString(contact.getDeviceId())};
744 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
745 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
746 + SQLiteAxolotlStore.NAME + " = ? AND "
747 + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
748 args);
749 }
750
751 public void deleteAllSessions(Account account, AxolotlAddress contact) {
752 SQLiteDatabase db = this.getWritableDatabase();
753 String[] args = {account.getUuid(), contact.getName()};
754 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
755 SQLiteAxolotlStore.ACCOUNT + "=? AND "
756 + SQLiteAxolotlStore.NAME + " = ?",
757 args);
758 }
759
760 private Cursor getCursorForPreKey(Account account, int preKeyId) {
761 SQLiteDatabase db = this.getReadableDatabase();
762 String[] columns = {SQLiteAxolotlStore.KEY};
763 String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
764 Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
765 columns,
766 SQLiteAxolotlStore.ACCOUNT + "=? AND "
767 + SQLiteAxolotlStore.ID + "=?",
768 selectionArgs,
769 null, null, null);
770
771 return cursor;
772 }
773
774 public PreKeyRecord loadPreKey(Account account, int preKeyId) {
775 PreKeyRecord record = null;
776 Cursor cursor = getCursorForPreKey(account, preKeyId);
777 if (cursor.getCount() != 0) {
778 cursor.moveToFirst();
779 try {
780 record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
781 } catch (IOException e) {
782 throw new AssertionError(e);
783 }
784 }
785 cursor.close();
786 return record;
787 }
788
789 public boolean containsPreKey(Account account, int preKeyId) {
790 Cursor cursor = getCursorForPreKey(account, preKeyId);
791 int count = cursor.getCount();
792 cursor.close();
793 return count != 0;
794 }
795
796 public void storePreKey(Account account, PreKeyRecord record) {
797 SQLiteDatabase db = this.getWritableDatabase();
798 ContentValues values = new ContentValues();
799 values.put(SQLiteAxolotlStore.ID, record.getId());
800 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
801 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
802 db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
803 }
804
805 public void deletePreKey(Account account, int preKeyId) {
806 SQLiteDatabase db = this.getWritableDatabase();
807 String[] args = {account.getUuid(), Integer.toString(preKeyId)};
808 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
809 SQLiteAxolotlStore.ACCOUNT + "=? AND "
810 + SQLiteAxolotlStore.ID + "=?",
811 args);
812 }
813
814 private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
815 SQLiteDatabase db = this.getReadableDatabase();
816 String[] columns = {SQLiteAxolotlStore.KEY};
817 String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
818 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
819 columns,
820 SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
821 selectionArgs,
822 null, null, null);
823
824 return cursor;
825 }
826
827 public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
828 SignedPreKeyRecord record = null;
829 Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
830 if (cursor.getCount() != 0) {
831 cursor.moveToFirst();
832 try {
833 record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
834 } catch (IOException e) {
835 throw new AssertionError(e);
836 }
837 }
838 cursor.close();
839 return record;
840 }
841
842 public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
843 List<SignedPreKeyRecord> prekeys = new ArrayList<>();
844 SQLiteDatabase db = this.getReadableDatabase();
845 String[] columns = {SQLiteAxolotlStore.KEY};
846 String[] selectionArgs = {account.getUuid()};
847 Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
848 columns,
849 SQLiteAxolotlStore.ACCOUNT + "=?",
850 selectionArgs,
851 null, null, null);
852
853 while (cursor.moveToNext()) {
854 try {
855 prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
856 } catch (IOException ignored) {
857 }
858 }
859 cursor.close();
860 return prekeys;
861 }
862
863 public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
864 Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
865 int count = cursor.getCount();
866 cursor.close();
867 return count != 0;
868 }
869
870 public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
871 SQLiteDatabase db = this.getWritableDatabase();
872 ContentValues values = new ContentValues();
873 values.put(SQLiteAxolotlStore.ID, record.getId());
874 values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
875 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
876 db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
877 }
878
879 public void deleteSignedPreKey(Account account, int signedPreKeyId) {
880 SQLiteDatabase db = this.getWritableDatabase();
881 String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
882 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
883 SQLiteAxolotlStore.ACCOUNT + "=? AND "
884 + SQLiteAxolotlStore.ID + "=?",
885 args);
886 }
887
888 private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
889 final SQLiteDatabase db = this.getReadableDatabase();
890 return getIdentityKeyCursor(db, account, name, own);
891 }
892
893 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
894 return getIdentityKeyCursor(db, account, name, own, null);
895 }
896
897 private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
898 final SQLiteDatabase db = this.getReadableDatabase();
899 return getIdentityKeyCursor(db, account, fingerprint);
900 }
901
902 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
903 return getIdentityKeyCursor(db, account, null, null, fingerprint);
904 }
905
906 private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
907 String[] columns = {SQLiteAxolotlStore.TRUSTED,
908 SQLiteAxolotlStore.KEY};
909 ArrayList<String> selectionArgs = new ArrayList<>(4);
910 selectionArgs.add(account.getUuid());
911 String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
912 if (name != null) {
913 selectionArgs.add(name);
914 selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
915 }
916 if (fingerprint != null) {
917 selectionArgs.add(fingerprint);
918 selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
919 }
920 if (own != null) {
921 selectionArgs.add(own ? "1" : "0");
922 selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
923 }
924 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
925 columns,
926 selectionString,
927 selectionArgs.toArray(new String[selectionArgs.size()]),
928 null, null, null);
929
930 return cursor;
931 }
932
933 public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
934 SQLiteDatabase db = getReadableDatabase();
935 return loadOwnIdentityKeyPair(db, account);
936 }
937
938 private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
939 String name = account.getJid().toBareJid().toString();
940 IdentityKeyPair identityKeyPair = null;
941 Cursor cursor = getIdentityKeyCursor(db, account, name, true);
942 if (cursor.getCount() != 0) {
943 cursor.moveToFirst();
944 try {
945 identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
946 } catch (InvalidKeyException e) {
947 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
948 }
949 }
950 cursor.close();
951
952 return identityKeyPair;
953 }
954
955 public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
956 return loadIdentityKeys(account, name, null);
957 }
958
959 public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
960 Set<IdentityKey> identityKeys = new HashSet<>();
961 Cursor cursor = getIdentityKeyCursor(account, name, false);
962
963 while (cursor.moveToNext()) {
964 if (trust != null &&
965 cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
966 != trust.getCode()) {
967 continue;
968 }
969 try {
970 identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
971 } catch (InvalidKeyException e) {
972 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
973 }
974 }
975 cursor.close();
976
977 return identityKeys;
978 }
979
980 public long numTrustedKeys(Account account, String name) {
981 SQLiteDatabase db = getReadableDatabase();
982 String[] args = {
983 account.getUuid(),
984 name,
985 String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()),
986 String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode())
987 };
988 return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
989 SQLiteAxolotlStore.ACCOUNT + " = ?"
990 + " AND " + SQLiteAxolotlStore.NAME + " = ?"
991 + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)",
992 args
993 );
994 }
995
996 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
997 storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
998 }
999
1000 private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
1001 SQLiteDatabase db = this.getWritableDatabase();
1002 ContentValues values = new ContentValues();
1003 values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
1004 values.put(SQLiteAxolotlStore.NAME, name);
1005 values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
1006 values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
1007 values.put(SQLiteAxolotlStore.KEY, base64Serialized);
1008 values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
1009 db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
1010 }
1011
1012 public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
1013 Cursor cursor = getIdentityKeyCursor(account, fingerprint);
1014 XmppAxolotlSession.Trust trust = null;
1015 if (cursor.getCount() > 0) {
1016 cursor.moveToFirst();
1017 int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
1018 trust = XmppAxolotlSession.Trust.fromCode(trustValue);
1019 }
1020 cursor.close();
1021 return trust;
1022 }
1023
1024 public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1025 SQLiteDatabase db = this.getWritableDatabase();
1026 return setIdentityKeyTrust(db, account, fingerprint, trust);
1027 }
1028
1029 private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
1030 String[] selectionArgs = {
1031 account.getUuid(),
1032 fingerprint
1033 };
1034 ContentValues values = new ContentValues();
1035 values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
1036 int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1037 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1038 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1039 selectionArgs);
1040 return rows == 1;
1041 }
1042
1043 public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
1044 SQLiteDatabase db = this.getWritableDatabase();
1045 String[] selectionArgs = {
1046 account.getUuid(),
1047 fingerprint
1048 };
1049 try {
1050 ContentValues values = new ContentValues();
1051 values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
1052 return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
1053 SQLiteAxolotlStore.ACCOUNT + " = ? AND "
1054 + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
1055 selectionArgs) == 1;
1056 } catch (CertificateEncodingException e) {
1057 Log.d(Config.LOGTAG, "could not encode certificate");
1058 return false;
1059 }
1060 }
1061
1062 public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
1063 SQLiteDatabase db = this.getReadableDatabase();
1064 String[] selectionArgs = {
1065 account.getUuid(),
1066 fingerprint
1067 };
1068 String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
1069 String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
1070 Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
1071 if (cursor.getCount() < 1) {
1072 return null;
1073 } else {
1074 cursor.moveToFirst();
1075 byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
1076 if (certificate == null || certificate.length == 0) {
1077 return null;
1078 }
1079 try {
1080 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
1081 return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
1082 } catch (CertificateException e) {
1083 Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
1084 return null;
1085 }
1086 }
1087 }
1088
1089 public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
1090 storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
1091 }
1092
1093 public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
1094 storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
1095 }
1096
1097 public void recreateAxolotlDb() {
1098 recreateAxolotlDb(getWritableDatabase());
1099 }
1100
1101 public void recreateAxolotlDb(SQLiteDatabase db) {
1102 Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
1103 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
1104 db.execSQL(CREATE_SESSIONS_STATEMENT);
1105 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
1106 db.execSQL(CREATE_PREKEYS_STATEMENT);
1107 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
1108 db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
1109 db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
1110 db.execSQL(CREATE_IDENTITIES_STATEMENT);
1111 }
1112
1113 public void wipeAxolotlDb(Account account) {
1114 String accountName = account.getUuid();
1115 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
1116 SQLiteDatabase db = this.getWritableDatabase();
1117 String[] deleteArgs = {
1118 accountName
1119 };
1120 db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
1121 SQLiteAxolotlStore.ACCOUNT + " = ?",
1122 deleteArgs);
1123 db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
1124 SQLiteAxolotlStore.ACCOUNT + " = ?",
1125 deleteArgs);
1126 db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
1127 SQLiteAxolotlStore.ACCOUNT + " = ?",
1128 deleteArgs);
1129 db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
1130 SQLiteAxolotlStore.ACCOUNT + " = ?",
1131 deleteArgs);
1132 }
1133}