Compare commits

...

149 Commits
v2.0 ... v2.0.6

Author SHA1 Message Date
Moxie Marlinspike
4ea481f9dc Bumping version to 2.0.6
// FREEBIE
2014-04-04 09:27:05 -07:00
Moxie Marlinspike
e94fa0d752 Updated language translations.
// FREEBIE
2014-04-03 16:02:36 -07:00
Moxie Marlinspike
817070e76f Merge pull request #1288 from mcginty/send-icons-redux-redux
remove lock coloring in send icons
2014-04-03 15:11:33 -07:00
Jake McGinty
92b90cd798 remove lock coloring in send icons
// FREEBIE
2014-04-03 14:05:34 -07:00
Moxie Marlinspike
33ecc4d690 Add null check. // FREEBIE 2014-04-03 14:01:15 -07:00
Jake McGinty
7d5e66eb6e fix send icon merge botch, encryption icons logic
// FREEBIE
2014-04-03 12:12:59 -07:00
Moxie Marlinspike
d4ac0c077d Merge pull request #1248 from mcginty/wrong-group-secure
outgoing group MMS should not be encrypted and considered push
2014-04-03 11:22:00 -07:00
Jake McGinty
cbe87aa05c outgoing group MMS should never be encrypted
// FREEBIE
2014-04-03 11:21:13 -07:00
Moxie Marlinspike
9e2f82954f Merge pull request #1260 from mcginty/message-layout-lint
Conversation item tweaks
2014-04-03 10:07:23 -07:00
Jake McGinty
d4d684b670 simplified layouts 2014-04-03 09:59:32 -07:00
Moxie Marlinspike
c43ef8bce0 Merge pull request #1233 from mcginty/send-icons
new send icons, fix logic for disabling/enabling the button
2014-04-03 09:58:10 -07:00
Jake McGinty
54a882a11d new send icons, fix logic for disabling/enabling
// FREEBIE
2014-04-03 09:49:50 -07:00
Moxie Marlinspike
73e2f6ce59 Merge pull request #1272 from mcginty/flag-secure
hide screen security when unsupported
2014-04-02 22:14:08 -07:00
Moxie Marlinspike
54d2184c72 Merge pull request #1205 from mcginty/contact-select
refactor and improve contact selection
2014-04-02 22:11:44 -07:00
Jake McGinty
832763f695 UX for unencrypted fallback case 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
40629a3bcf Fix decryption corner case when passphrase is disabled. 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
5a3daf4846 Curve25519 keys to 1 mod 8 for ephemerals. 2014-04-02 22:10:51 -07:00
Moxie Marlinspike
fd1a18d2d0 Don't display duplicate push messages. 2014-04-02 22:10:50 -07:00
Moxie Marlinspike
ad5d6d5bb7 Add refresh path for PreKey queue. 2014-04-02 22:10:50 -07:00
Moxie Marlinspike
926d3c929f Handle simultaneous initiate protocol case.
1) Modify SessionRecord to store a list of "previous" sessions
   in addition to the current active session.  Previous sessions
   can be used for receiving messages, but not for sending
   messages.

2) When a possible "simultaneous initiate" is detected, push the
   current session onto the "previous session" stack instead of
   clearing it and starting over.

3) Additionally, mark the new session created on a received
   possible "simultaneous initiate" as stale for sending.  The
   next outgoing message would trigger a full prekey refresh.

4) Work to do: outgoing messages on the SMS transport should
   probably not use the existing session if it's marked stale
   for sending.  These messages need to fail and notify the user,
   similar to how we'll handle SMS fallback to push users before
   a prekey session is created.
2014-04-02 22:10:50 -07:00
Moxie Marlinspike
edc20883eb Merge pull request #1281 from thoughtbox/patch-6
Changed MMS Proxy setting for 24201 (Telenor)
2014-04-02 09:14:32 -07:00
thoughtbox
0d7363e36e Changed MMS Proxy setting for 24201 (Telenor)
Going forward, Telenor will be using FQDN.
2014-04-02 12:47:05 +02:00
Jake McGinty
ca6d8a8a0d refactor and improve contact selection
* unify single and multi contact selection activities
* follow android listview design recommendations more closely
* add contact photos to selection
* change indicator for push to be more obvious
* cache circle-cropped bitmaps
* dedupe numbers when contact has multiple of same phone number

// FREEBIE
2014-04-01 14:56:45 -07:00
Moxie Marlinspike
c414334059 Merge pull request #1228 from backspace/support-commas-in-contact-names
Remove commas in names in recipient list string
2014-03-31 08:39:15 -07:00
Moxie Marlinspike
24a38985cf Merge pull request #1249 from mcginty/convo-scroll
scroll to the new message in conversation
2014-03-31 08:14:26 -07:00
Brian Conway
60f2d4d6b6 Add APN for Aio wireless (USA).
Source: http://www.aiowireless.com/support/Apps-and-Services/Bring-Your-Own-Device-BYOD/customer/Bring-Your-Own-Android.html
2014-03-31 08:09:10 -07:00
Jake McGinty
06659fd98f hide screen security when unsupported
// FREEBIE
2014-03-30 02:28:38 -07:00
Jake McGinty
3c9c5213a7 Merge pull request #946 from DorianScholz/groupavatar
Use standard intent to select group avatar.
2014-03-28 12:07:30 -07:00
Dorian Scholz
a183f8d387 Fix #641 by using standard intent to select group avatar.
The ACTION_GET_CONTENT used with cropping is not supported on all devices.
To make this work more reliably I removed the cropping and MediaStore.EXTRA_OUTPUT.
The image is now read via getContentResolver().openInputStream() which should work on all device including KitKat/CM11.
2014-03-27 12:44:56 +01:00
Jake McGinty
216446c55b scroll to the new message in conversation
// FREEBIE
2014-03-26 15:12:01 -07:00
Buck Doyle
bc143059f6 Remove separators from names in recipient list
Fixes #1225 and #1174
2014-03-25 11:11:20 -07:00
Jake McGinty
33000582ed one more try at that one..
// FREEBIE
2014-03-25 03:31:44 -07:00
Jake McGinty
e651f352bb fix NPE in isPushDestination
// FREEBIE
2014-03-24 17:02:39 -07:00
Buck Doyle
cab4a06974 Remove commas in names in recipient list string
Fixes #1225
2014-03-23 15:02:21 -07:00
martinstingl
ccc1f5e9d6 Added the dependency "Android SDK Build-tools".
//FREEBIE
2014-03-20 18:06:58 -07:00
Moxie Marlinspike
b860aeff85 Minor ConversationList scrolling optimization. 2014-03-16 14:36:21 -07:00
Martin Ranta
34c885f08d Fixed a few localization names. 2014-03-16 10:06:26 -07:00
Moxie Marlinspike
71ab6f5c7d Merge pull request #1178 from backspace/extract-input-settings-string
Extract Input Settings preference header string.
2014-03-16 10:02:59 -07:00
Buck Doyle
61fbf382eb Extract Input Settings preference header string.
Fixes #1159

FREEBIE
2014-03-15 22:18:14 -04:00
Moxie Marlinspike
8b21f3f7d6 Bump version to 2.0.5 2014-03-14 10:18:13 -07:00
Moxie Marlinspike
941d0089f4 Add languages to selector 2014-03-14 09:40:58 -07:00
Moxie Marlinspike
8b8c6dd45f Updated translations
//FREEBIE
2014-03-14 09:40:43 -07:00
Jake McGinty
938545444e Merge pull request #984 from mcginty/sms-prefs
more precise sms controls
2014-03-13 21:03:02 -07:00
Jake McGinty
d827ab1b36 more precise sms controls
// FREEBIE
2014-03-13 20:59:20 -07:00
Jake McGinty
4701e59197 Merge pull request #1076 from phenx-de/fix-big-fontsize
Fix conversation list view for larger text sizes.
2014-03-13 13:05:56 -07:00
Moxie Marlinspike
2b2da84918 Merge pull request #1140 from psm14/bugs/group_mms_local_number
Also check cc for duplicates in group MMS
2014-03-12 18:58:08 -07:00
Pat McLaughlin
d229a4274c Also check cc for duplicates 2014-03-12 20:49:55 -04:00
3xo
64711771f0 Fix locale when using country codes. 2014-03-12 16:56:24 -07:00
Moxie Marlinspike
ad54d2a05f Modify string tag.
//FREEBIE
2014-03-12 09:57:32 -07:00
Wikinaut
068c40336c added Google Play Store text 2014-03-11 14:18:23 -07:00
Jake McGinty
11cfc4f1a1 upgrade gradle version
// FREEBIE
2014-03-11 01:05:24 -07:00
phenx-de
f51989b23e Fix conversation list view for larger text sizes. 2014-03-10 10:32:32 +01:00
Moxie Marlinspike
0b4fe84a41 Format-neutral compare numbers in MMS group with local device.
Closes #1018
2014-03-07 13:05:35 -08:00
Moxie Marlinspike
b855a1805f Mark message details on pending messages as such.
Closes #1034
2014-03-07 12:39:16 -08:00
3xo
339193af12 Give user the opportunity to chose no LED-notification. 2014-03-07 12:04:19 -08:00
Jake McGinty
574f33c92d Merge pull request #1069 from phenx-de/color-cleanup
Moved colors to the right place.
2014-03-07 12:00:49 -08:00
Manuel
b6a9eb5bf2 Closes #913: Message ID is -1 when called in aggregate 2014-03-07 11:59:53 -08:00
phenx-de
54b43b7536 Moved colors to the right place. 2014-03-07 11:03:22 +01:00
Moxie Marlinspike
37c9fb7cd1 Merge pull request #1053 from shenki/master
ConversationFragment: enable back button to dismiss message detail dialog
2014-03-06 20:46:43 -08:00
Joel Stanley
667d22bace ConversationFragment: enable back button to dismiss
The ConversationFragment has a AlertDialog for showing the message
details, which sets the cancelable property to be false. This stops the
user from being able to use the back button to dismiss the dialog.
2014-03-06 15:32:51 +10:30
Moxie Marlinspike
3d782449ed Merge pull request #1025 from DorianScholz/sharetextplain
Only receive text/plain.
2014-03-05 09:50:57 -08:00
Moxie Marlinspike
003095b08c Merge pull request #1020 from lxgr/fix-screensec
Use the "screen security" preference for the conversation list
2014-03-05 09:12:33 -08:00
Jake McGinty
d121d9531e Merge pull request #992 from veeti/overdraw
Fix overdraw issues in the conversation view
2014-03-05 00:10:06 -08:00
Veeti Paananen
42aeca26f1 Fix overdraw issues in the conversation view
Remove the redundant window background and row backgrounds to improve
drawing performance.
2014-03-05 08:03:55 +02:00
Dorian Scholz
ea8a1bae46 Only receive text/plain.
At the moment only text/plain is handled in RoutingActivity, so set filter in manifest accordingly.
This prevents #316. But at some point sharing text/calendar or better */* would be nice...

// FREEBIE
2014-03-04 23:57:36 +01:00
Jake McGinty
054fcdca8d Merge pull request #1007 from SamWhited/issue998
s/Verify Recipient/Verify Identity/
2014-03-04 13:49:49 -08:00
Lukas Ribisch
278220cf18 Use the "screen security" preference for the conversation list
It was previously only used for ConversationListActivity. This should be all
the locations (according to a grep for FLAG_SECURE).

FREEBIE
2014-03-04 21:04:04 +01:00
Chris Glass
35eeaa9bd5 Added more build information to BUILDING.md
Added more instructions about setting up a development environment.
2014-03-04 08:44:54 -08:00
Moxie Marlinspike
fbf02603ce Bump version to 2.0.4 2014-03-04 08:39:31 -08:00
Sam Whited
fa423e4432 s/Verify Recipient/Verify Identity/ 2014-03-04 09:55:45 -05:00
Moxie Marlinspike
5caec4a146 Updated language translations. 2014-03-03 19:13:38 -08:00
Moxie Marlinspike
dc77c43435 Merge pull request #796 from mcginty/pending_messages
pending messages more accurately conveyed to user
2014-03-03 18:49:48 -08:00
Jake McGinty
827298d1a2 Merge pull request #975 from tinloaf/reloadcontactlist
Reload contact list after directory refresh // FREEBIE
2014-03-03 18:01:10 -08:00
Lukas Barth
48f5b932f7 Reload contact list after directory refresh // FREEBIE
I forgot that in my PR, it seems.
2014-03-03 21:18:38 +01:00
Jake McGinty
90169e9468 pending messages more accurately conveyed to user 2014-03-03 11:56:54 -08:00
Moxie Marlinspike
b5fe378bc9 Workaround for Android bug when swiped from recent tasks list.
Closes #168

https://code.google.com/p/android/issues/detail?id=53313
2014-03-03 11:45:05 -08:00
Moxie Marlinspike
0f53c9d170 Merge pull request #966 from backspace/add-video-sharing
Support videos from other apps. Fixes #949
2014-03-03 11:01:02 -08:00
Moxie Marlinspike
3474950830 Merge pull request #956 from tinloaf/refreshfromlist
Add possiblity to refresh the push directory directly from contact list
2014-03-03 10:09:32 -08:00
Lukas Barth
39ee363150 Add possiblity to refresh the push directory directly from the contact picking list. Fixes #835. Fixes #859.
Thanks benalbrecht for the icons.
2014-03-03 18:23:14 +01:00
Buck Doyle
411e3ceff6 Support videos from other apps. Fixes #949 2014-03-03 11:55:34 -05:00
Moxie Marlinspike
b7f8c3b3d3 Merge pull request #961 from cloudkicker/notification_preferences
Make notification preferences look normally
2014-03-03 08:53:00 -08:00
Moxie Marlinspike
3efa8e6899 Merge pull request #955 from benalbrecht/nocount
Use new icon from #905 for push message disable confirmation box
2014-03-03 08:33:10 -08:00
Vladislav Meshochkin
948f481530 Make notification preferences look normally 2014-03-03 19:22:51 +04:00
Benjamin Albrecht
716fdefa4c Use icon from #905 for push message disable confirmation box
//FREEBIE
2014-03-03 11:44:14 +01:00
Moxie Marlinspike
2dc893730a Auto-update every 12 hours instead.
// FREEBIE
2014-03-02 16:19:05 -08:00
Moxie Marlinspike
1af99ce155 Fix for periodic directory refresh.
// FREEBIE
2014-03-02 16:14:00 -08:00
Moxie Marlinspike
0850f1b0f1 Add libcurve25519 bins.
// FREEBIE
2014-03-02 16:06:37 -08:00
Florian Walch
1ddc45fd9c Build native code for multiple architectures.
* Include native libs without requiring intermediate .jar.
 * Fix build warnings; use latest build tools.
2014-03-02 16:06:37 -08:00
Moxie Marlinspike
977765c80f Merge pull request #941 from Wikinaut/add-new-string-ViewIdentityActivity_my_identity_fingerprint
add new string ViewIdentityActivity_my_identity_fingerprint
2014-03-02 15:50:18 -08:00
Thomas Gries
4b2d07ab35 add new string ViewIdentityActivity_my_identity_fingerprint 2014-03-03 00:45:42 +01:00
Jens Füllenbach
003ebe6364 Added the color white to the notification colors of the LED. 2014-03-02 14:11:31 -08:00
Benjamin Albrecht
2f7c005c23 Show confirmation box before disabling push messages 2014-03-02 14:09:54 -08:00
Moxie Marlinspike
367b481d07 Merge pull request #905 from benalbrecht/dialog_icons
Use modern icons in info and alert dialogs for ICS+
2014-03-02 14:01:52 -08:00
Moxie Marlinspike
5cd8c922d2 Merge pull request #648 from veeti/tweaks-and-polish
Assorted tweaks and polish
2014-03-02 14:01:20 -08:00
Moxie Marlinspike
a2fe8a9d5c Make default group avatar icon actually work. 2014-03-02 13:22:02 -08:00
Benedict Pregler
ee921a8f49 fixed some hardcoded strings 2014-03-02 13:22:02 -08:00
Benedict Pregler
e0394b4481 a little typo mistake 2014-03-02 13:22:02 -08:00
Veeti Paananen
de100f5be7 Add missing spaces 2014-03-02 09:40:45 +02:00
Veeti Paananen
a020a57be6 End conversation selection action mode if all deselected
This seems to be common convention in most apps.
2014-03-02 09:40:35 +02:00
Veeti Paananen
28f1a0a636 Replace the attachment dialog header icon with something modern 2014-03-02 09:40:35 +02:00
Veeti Paananen
5a807ffc28 Use unified touch highlight in import & export 2014-03-02 09:40:35 +02:00
Veeti Paananen
757cb1c846 Add touch highlight to contact photos 2014-03-02 09:40:35 +02:00
Veeti Paananen
4f066757e7 Use the touch highlight background for the emoji toggle 2014-03-02 09:40:35 +02:00
Veeti Paananen
633aa9b057 Fix the send button's touch highlight color
Introduce a generic touch highlight background drawable, and use the
proper shades of blue (and gray on v19+).
2014-03-02 09:40:35 +02:00
Jake McGinty
a5c26b2e16 Merge pull request #921 from liliakai/master
Add contributing.md // FREEBIE
2014-03-01 15:19:32 -08:00
lilia
ca561d76ff Add contributing.md // FREEBIE
This file is recognized by github and presented to users who are trying
to open issues and submit pull requests.

Hopefully this more prominent placement will help deter people from
opening issues/prs for translation fixes.
2014-03-01 15:11:21 -08:00
Jake McGinty
107d999ee7 make bithub price update // FREEBIE
thanks lilia
2014-03-01 11:57:46 -08:00
Benjamin Albrecht
0f6c7660cb Use modern icons in info and alert dialogs for ICS+ 2014-03-01 14:42:12 +01:00
Moxie Marlinspike
8ea4db03db Better support for local storage with passphrase disabled.
1) Never get into a state where messages aren't displayed
   unencrypted.

2) Fix bug where locked DB required launching twice.
2014-02-28 15:58:30 -08:00
Moxie Marlinspike
81ee9e31c5 Update behavior for incoming SMS path.
1) On KitKat, unencrypted SMS messages are never stored in
   TextSecure unless it is set as the system-wide default.

2) On KitKat, if TextSecure is set as the system-wide default,
   provide an option to change the default to a different app.

3) Don't store the TextSecure challenge on KitKat+ devices.
2014-02-28 13:40:35 -08:00
Moxie Marlinspike
a3e900ecbe Merge pull request #794 from SamWhited/issue788
Add option to disable screen security
2014-02-27 18:02:05 -08:00
Sam Whited
384fb3b2b5 Add option to disable screen security
See WhisperSystems/TextSecure#788
2014-02-27 19:00:39 -05:00
Moxie Marlinspike
d795aa30b3 Merge pull request #795 from benalbrecht/nocount
Don't show letter count when SMS mode is disabled
2014-02-27 14:12:27 -08:00
Jake McGinty
bac4d63312 Merge pull request #805 from wickedshimmy/fix-sender-details
Don't display sender in outgoing message details
2014-02-27 11:18:51 -08:00
Benjamin Albrecht
bf60f90019 Don't show letter count inside a push group or when SMS mode is disabled 2014-02-27 09:32:19 +01:00
Matt Enright
852ca2ac05 Don't display sender in outgoing message details
Because we don't know it. Fixes WhisperSystems#711.
2014-02-26 21:57:17 -05:00
Moxie Marlinspike
038bebfdbb Bump version to 2.0.3 // FREEBIE 2014-02-26 13:08:03 -08:00
Moxie Marlinspike
3b25b87aa8 Fix for NPE 2014-02-26 12:42:16 -08:00
Moxie Marlinspike
5a62856e46 Make SMS delivery reports optional. 2014-02-26 12:38:50 -08:00
Moxie Marlinspike
37a52df4e6 Fix bug that broke notifications for group messages. 2014-02-26 12:31:56 -08:00
Jake McGinty
d3148b6766 Merge pull request #764 from m0jo/master
Fix hpyerlinking recieved/sent date in messages
2014-02-26 09:46:42 -08:00
Tim Bücher
6fb85aff6d Fix hpyerlinking recieved/sent date in messages 2014-02-26 13:43:55 +01:00
Moxie Marlinspike
ed45067227 Bumping version to 2.0.2 // FREEBIE 2014-02-26 00:01:52 -08:00
Jake McGinty
0015711759 migrate logcat submit to preferences
// FREEBIE
2014-02-25 23:11:00 -08:00
Moxie Marlinspike
15390e477e Update translations.
// FREEBIE
2014-02-25 22:47:03 -08:00
Jake McGinty
a8c23413ba Merge pull request #745 from mcginty/logcat_submit
activity to submit logcat to pastebin
2014-02-25 22:20:34 -08:00
Jake McGinty
ce68429a9b activity to submit logcat to pastebin 2014-02-25 22:14:21 -08:00
Jake McGinty
1a9a88a5a1 Merge pull request #744 from backspace/change-sent-link-color
Make outgoing links readable
2014-02-25 21:23:41 -08:00
Moxie Marlinspike
7987362c25 Fix for bug where messages are stuck in pending state after upgrade. 2014-02-25 20:57:18 -08:00
Buck Doyle
5d42110d6c Make outgoing links readable 2014-02-25 22:46:49 -05:00
Moxie Marlinspike
dec7fd4c8a Fix for NPE with audio attachments. 2014-02-25 17:54:49 -08:00
Moxie Marlinspike
82df23dd41 Temporarily bring back end session. 2014-02-25 17:38:55 -08:00
Moxie Marlinspike
ce710b378f Fix for stalled retries. 2014-02-25 17:15:30 -08:00
Moxie Marlinspike
20fd881613 Display error code from server when already registered elsewhere. 2014-02-25 17:00:31 -08:00
Jake McGinty
5fa429b0d5 Merge pull request #676 from mcginty/emoji_density
Fix emoji density scaling issues
2014-02-25 16:28:43 -08:00
Jake McGinty
630dce04fc handle emoji density scaling more correctly 2014-02-25 16:24:33 -08:00
Moxie Marlinspike
0da1d8818e Merge pull request #729 from funk78/feature/use_getDeviceE164Number
FREEBIE reuse code
2014-02-25 15:15:26 -08:00
droidastic
c84285c639 FREEBIE reuse code 2014-02-25 23:56:18 +01:00
Moxie Marlinspike
5a525a2e58 Switch KCS priority to MIN on JB+. Eliminates icon in status bar! 2014-02-25 10:33:11 -08:00
Moxie Marlinspike
dda8a214a4 Merge pull request #668 from m0jo/master
Fix missing localozation strings for custom LED blink pattern.
2014-02-25 08:37:38 -08:00
Tim Bücher
bd167cbb17 Fix missing localozation strings for custom LED blink pattern. 2014-02-25 11:38:31 +01:00
Jake McGinty
a0aaa7d724 Explain why SMS fallback is disabled when it is 2014-02-25 01:23:22 -08:00
Moxie Marlinspike
25e03b3579 Bumping version to 2.0.1 2014-02-24 21:50:33 -08:00
Moxie Marlinspike
52ff4ecfd2 Fix to make emoji work in push groups. 2014-02-24 17:01:28 -08:00
Moxie Marlinspike
43c1576aab Fix for bad database migration. 2014-02-24 17:00:52 -08:00
Jake McGinty
23c607430d fix dark text input on dark background for conversation
// FREEBIE
2014-02-24 16:12:13 -08:00
Jake McGinty
829a92d371 contact selection reactive to dark theme
// FREEBIE
2014-02-24 14:43:38 -08:00
Moxie Marlinspike
559228af5b Fix for bug modifying immutable list. 2014-02-24 14:19:43 -08:00
Moxie Marlinspike
e8a0fac05b Fix for identity key mismatch on devices with V1 identities. 2014-02-24 14:19:17 -08:00
280 changed files with 10839 additions and 3191 deletions

View File

@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
lang_map = fr_CA:fr-rCA,pt_BR:pt-rBR,pt_PT:pt,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW,da_DK:da-rDK,de_DE:de,tr_TR:tr,fr_FR:fr,es_ES:es,hu_HU:hu,sv_SE:sv-rSE,bg_BG:bg,el_GR:el,kn_IN:kn-rIN,cs_CZ:cs
[textsecure-official.master]

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms"
android:versionCode="63"
android:versionName="2.0">
android:versionCode="69"
android:versionName="2.0.6">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/>
@@ -69,7 +69,8 @@
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" />
<data android:mimeType="text/plain" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
@@ -89,7 +90,7 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MmsPreferencesActivity"
<activity android:name=".MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ConversationListActivity"
@@ -134,15 +135,15 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".SingleContactSelectionActivity"
android:label="@string/AndroidManifest__select_contact"
<activity android:name=".NewConversationActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AutoInitiateActivity"
android:theme="@style/TextSecure.Light.Dialog"
@@ -183,10 +184,28 @@
<activity android:name=".RegistrationProgressActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".LogSubmitActivity"
android:label="@string/AndroidManifest__log_submit"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
android:enabled="true"
android:allowTaskReparenting="true"
android:noHistory="true"
android:excludeFromRecents="true"
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.SendReceiveService"/>
<service android:enabled="true" android:name=".service.RegistrationService"/>
<service android:enabled="true" android:name=".service.DirectoryRefreshService"/>
<service android:enabled="true" android:name=".service.PreKeyService"/>
<service android:enabled="true" android:name=".gcm.GcmIntentService"/>
<service android:name=".service.QuickResponseService"

View File

@@ -1,8 +1,67 @@
Building TextSecure
===================
=====================
1. Ensure the 'Android Support Repository' is installed from the Android SDK manager.
Basics
------
Execute Gradle:
TextSecure uses [Gradle](http://gradle.org) to build the project and to maintain
dependencies.
./gradlew build
Building TextSecure
-------------------
The following steps should help you (re)build TextSecure from the command line.
1. Checkout the source somewhere on your filesystem with
git clone https://github.com/WhisperSystems/TextSecure.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed somewhere on your system.
3. Ensure the "Android Support Repository" and "Android SDK Build-tools" are installed from the Android SDK manager.
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it.
sdk.dir=\<path to your sdk installation\>
5. Execute Gradle:
./gradlew build
Re-building native components
-----------------------------
Note: This step is optional; native components are contained as binaries (see [library/libs](library/libs)).
1. Ensure that the Android NDK is installed.
Execute ndk-build:
cd library
ndk-build
Afterwards, execute Gradle as above to re-create the APK.
Setting up a development environment
------------------------------------
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
1. Install Android Studio
2. Make sure the "Android Support Repository" is installed in the Android Studio SDK.
3. Make sure the latest "Android SDK build-tools" is installed in the Android Studio SDK.
4. Create a new Android Studio project. from the Quickstart pannel (use File > Close Project to see it), choose "Checkout from Version Control" then "git".
5. Paste the URL for the TextSecure project when prompted (https://github.com/WhisperSystems/TextSecure.git)
6. Android studio should detect the presence of a project file and ask you wethere to open it. Click "yes".
7. Default config options should be good enough.
8. Project initialisation and build should proceed.
Contributing code
-----------------
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).
Mailing list
------------
Development discussion happens on the whispersystems mailing list.
[To join](https://lists.riseup.net/www/info/whispersystems)
Send emails to whispersystems@lists.riseup.net

View File

@@ -10,8 +10,11 @@ TextSecure is a replacement for the standard text messaging application, allowin
Current BitHub Payment For Commit:
=================
[![Current Price](https://bithub.herokuapp.com/v1/status/payment/commit)](https://whispersystems.org/blog/bithub/)
[![Current Price](https://bithub.herokuapp.com/v1/status/payment/commit/)](https://whispersystems.org/blog/bithub/)
Building and contributing code
==============================
Instructions on how to build TextSecure, as well as on how to setup an IDE to modify it can be found in the "BUILDING.md" file.
Bug tracker
-----------

BIN
artwork/ic_send.psd Normal file

Binary file not shown.

View File

@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
classpath 'com.android.tools.build:gradle:0.9.+'
}
}
@@ -23,13 +23,14 @@ dependencies {
compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
compile 'com.android.support:support-v4:19.0.1'
compile 'com.google.android.gcm:gcm-client:1.0.2'
compile 'se.emilsjolander:stickylistheaders:2.2.0'
compile project(':library')
}
android {
compileSdkVersion 19
buildToolsVersion '19.0.0'
buildToolsVersion '19.0.2'
defaultConfig {
minSdkVersion 9
@@ -42,7 +43,7 @@ android {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aild.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']

3
contributing.md Normal file
View File

@@ -0,0 +1,3 @@
##Translations
Please do not submit issues or pull requests for translation fixes. Anyone can update the translations in [Transifex](https://www.transifex.com/projects/p/textsecure-official/). Please submit your corrections there.

View File

@@ -1,6 +1,6 @@
#Sat Dec 21 23:48:05 PST 2013
#Mon Mar 10 23:44:05 PDT 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip

View File

@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
classpath 'com.android.tools.build:gradle:0.9.+'
}
}
@@ -23,12 +23,11 @@ dependencies {
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
compile 'com.googlecode.libphonenumber:libphonenumber:5.3'
compile 'org.whispersystems:gson:2.2.4'
compile fileTree(dir: 'libs', include: 'armeabi.jar')
}
android {
compileSdkVersion 19
buildToolsVersion '19.0.0'
buildToolsVersion '19.0.2'
android {
sourceSets {
@@ -36,10 +35,11 @@ android {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aild.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
}

View File

@@ -0,0 +1 @@
APP_ABI := armeabi armeabi-v7a x86

View File

@@ -22,11 +22,16 @@
#include "curve25519-donna.h"
JNIEXPORT jbyteArray JNICALL Java_org_whispersystems_textsecure_crypto_ecc_Curve25519_generatePrivateKey
(JNIEnv *env, jclass clazz, jbyteArray random)
(JNIEnv *env, jclass clazz, jbyteArray random, jboolean ephemeral)
{
uint8_t* privateKey = (uint8_t*)(*env)->GetByteArrayElements(env, random, 0);
privateKey[0] &= 248;
if (ephemeral) {
privateKey[0] |= 1;
}
privateKey[31] &= 127;
privateKey[31] |= 64;

View File

@@ -720,9 +720,6 @@ curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) {
int i;
for (i = 0; i < 32; ++i) e[i] = secret[i];
e[0] &= 248;
e[31] &= 127;
e[31] |= 64;
fexpand(bp, basepoint);
cmult(x, z, e, bp);

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
library/libs/x86/libcurve25519.so Executable file

Binary file not shown.

View File

@@ -54,6 +54,13 @@ message SessionStructure {
optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
}
message PreKeyRecordStructure {

View File

@@ -0,0 +1,7 @@
package org.whispersystems.textsecure.crypto;
public class DuplicateMessageException extends Exception {
public DuplicateMessageException(String s) {
super(s);
}
}

View File

@@ -34,7 +34,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
@@ -49,7 +48,7 @@ public class PreKeyUtil {
for (int i=0;i<BATCH_SIZE;i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
ECKeyPair keyPair = Curve25519.generateKeyPair();
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, preKeyId, keyPair);
record.save();
@@ -70,7 +69,7 @@ public class PreKeyUtil {
}
}
ECKeyPair keyPair = Curve25519.generateKeyPair();
ECKeyPair keyPair = Curve25519.generateKeyPair(true);
PreKeyRecord record = new PreKeyRecord(context, masterSecret, Medium.MAX_VALUE, keyPair);
record.save();
@@ -78,38 +77,38 @@ public class PreKeyUtil {
return record;
}
public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
File directory = getPreKeysDirectory(context);
String[] keyRecordIds = directory.list();
Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
for (String keyRecordId : keyRecordIds) {
try {
if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
}
} catch (InvalidKeyIdException e) {
Log.w("PreKeyUtil", e);
new File(getPreKeysDirectory(context), keyRecordId).delete();
} catch (NumberFormatException nfe) {
Log.w("PreKeyUtil", nfe);
new File(getPreKeysDirectory(context), keyRecordId).delete();
}
}
return records;
}
public static void clearPreKeys(Context context) {
File directory = getPreKeysDirectory(context);
String[] keyRecords = directory.list();
for (String keyRecord : keyRecords) {
new File(directory, keyRecord).delete();
}
}
// public static List<PreKeyRecord> getPreKeys(Context context, MasterSecret masterSecret) {
// List<PreKeyRecord> records = new LinkedList<PreKeyRecord>();
// File directory = getPreKeysDirectory(context);
// String[] keyRecordIds = directory.list();
//
// Arrays.sort(keyRecordIds, new PreKeyRecordIdComparator());
//
// for (String keyRecordId : keyRecordIds) {
// try {
// if (!keyRecordId.equals(PreKeyIndex.FILE_NAME) && Integer.parseInt(keyRecordId) != Medium.MAX_VALUE) {
// records.add(new PreKeyRecord(context, masterSecret, Integer.parseInt(keyRecordId)));
// }
// } catch (InvalidKeyIdException e) {
// Log.w("PreKeyUtil", e);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// } catch (NumberFormatException nfe) {
// Log.w("PreKeyUtil", nfe);
// new File(getPreKeysDirectory(context), keyRecordId).delete();
// }
// }
//
// return records;
// }
//
// public static void clearPreKeys(Context context) {
// File directory = getPreKeysDirectory(context);
// String[] keyRecords = directory.list();
//
// for (String keyRecord : keyRecords) {
// new File(directory, keyRecord).delete();
// }
// }
private static void setNextPreKeyId(Context context, int id) {
try {
@@ -126,7 +125,7 @@ public class PreKeyUtil {
try {
File nextFile = new File(getPreKeysDirectory(context), PreKeyIndex.FILE_NAME);
if (nextFile.exists()) {
if (!nextFile.exists()) {
return Util.getSecureRandom().nextInt(Medium.MAX_VALUE);
} else {
InputStreamReader reader = new InputStreamReader(new FileInputStream(nextFile));

View File

@@ -29,7 +29,7 @@ public abstract class SessionCipher {
protected static final Object SESSION_LOCK = new Object();
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException;
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException, DuplicateMessageException;
public abstract int getRemoteRegistrationId();
public static SessionCipher createFor(Context context,

View File

@@ -1,6 +1,7 @@
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve;
@@ -14,10 +15,12 @@ import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -45,27 +48,28 @@ public class SessionCipherV2 extends SessionCipher {
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
ChainKey chainKey = sessionRecord.getSenderChainKey();
SessionState sessionState = sessionRecord.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral();
int previousCounter = sessionRecord.getPreviousCounter();
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionRecord.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionRecord.getPendingPreKey();
int localRegistrationId = sessionRecord.getLocalRegistrationId();
if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second,
sessionRecord.getLocalIdentityKey(),
sessionState.getLocalIdentityKey(),
(WhisperMessageV2) ciphertextMessage);
}
sessionRecord.setSenderChainKey(chainKey.getNextChainKey());
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save();
return ciphertextMessage;
@@ -73,52 +77,88 @@ public class SessionCipherV2 extends SessionCipher {
}
@Override
public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException {
public byte[] decrypt(byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException
{
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral,
chainKey, counter);
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
ciphertextMessage.verifyMac(messageKeys.getMacKey());
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionRecord.save();
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
sessionRecord.clearPendingPreKey();
sessionRecord.save();
for (SessionState previousState : previousStates) {
try {
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionRecord.save();
return plaintext;
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
}
throw new InvalidMessageException("No valid sessions.");
}
}
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
throws InvalidMessageException, DuplicateMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionState.clearPendingPreKey();
return plaintext;
}
@Override
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
return sessionRecord.getRemoteRegistrationId();
return sessionRecord.getSessionState().getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral)
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionRecord.hasReceiverChain(theirEphemeral)) {
return sessionRecord.getReceiverChainKey(theirEphemeral);
if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionState.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionRecord.getRootKey();
ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair();
RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE, true);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionRecord.setRootKey(senderChain.first);
sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second);
sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1);
sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second);
sessionState.setRootKey(senderChain.first);
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.second;
}
@@ -127,30 +167,31 @@ public class SessionCipherV2 extends SessionCipher {
}
}
private MessageKeys getOrCreateMessageKeys(SessionRecordV2 sessionRecord,
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException
throws InvalidMessageException, DuplicateMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) {
return sessionRecord.removeMessageKeys(theirEphemeral, counter);
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionState.removeMessageKeys(theirEphemeral, counter);
} else {
throw new InvalidMessageException("Received message with old counter!");
throw new DuplicateMessageException("Received message with old counter: " +
chainKey.getIndex() + " , " + counter);
}
}
if (chainKey.getIndex() - counter > 500) {
throw new InvalidMessageException("Over 500 messages into the future!");
if (chainKey.getIndex() - counter > 2000) {
throw new InvalidMessageException("Over 2000 messages into the future!");
}
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionRecord.setMessageKeys(theirEphemeral, messageKeys);
sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionRecord.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys();
}

View File

@@ -25,9 +25,9 @@ public class Curve {
private static final int NIST_TYPE2 = 0x03;
public static final int DJB_TYPE = 0x05;
public static ECKeyPair generateKeyPairForType(int keyType) {
public static ECKeyPair generateKeyPairForType(int keyType, boolean ephemeral) {
if (keyType == DJB_TYPE) {
return Curve25519.generateKeyPair();
return Curve25519.generateKeyPair(ephemeral);
} else if (keyType == NIST_TYPE || keyType == NIST_TYPE2) {
return CurveP256.generateKeyPair();
} else {
@@ -35,11 +35,11 @@ public class Curve {
}
}
public static ECKeyPair generateKeyPairForSession(int messageVersion) {
public static ECKeyPair generateKeyPairForSession(int messageVersion, boolean ephemeral) {
if (messageVersion <= CiphertextMessage.LEGACY_VERSION) {
return generateKeyPairForType(NIST_TYPE);
return generateKeyPairForType(NIST_TYPE, ephemeral);
} else {
return generateKeyPairForType(DJB_TYPE);
return generateKeyPairForType(DJB_TYPE, ephemeral);
}
}

View File

@@ -37,10 +37,10 @@ public class Curve25519 {
private static native byte[] calculateAgreement(byte[] ourPrivate, byte[] theirPublic);
private static native byte[] generatePublicKey(byte[] privateKey);
private static native byte[] generatePrivateKey(byte[] random);
private static native byte[] generatePrivateKey(byte[] random, boolean ephemeral);
public static ECKeyPair generateKeyPair() {
byte[] privateKey = generatePrivateKey();
public static ECKeyPair generateKeyPair(boolean ephemeral) {
byte[] privateKey = generatePrivateKey(ephemeral);
byte[] publicKey = generatePublicKey(privateKey);
return new ECKeyPair(new DjbECPublicKey(publicKey), new DjbECPrivateKey(privateKey));
@@ -65,11 +65,11 @@ public class Curve25519 {
return new DjbECPublicKey(keyBytes);
}
private static byte[] generatePrivateKey() {
private static byte[] generatePrivateKey(boolean ephemeral) {
byte[] privateKey = new byte[32];
random.nextBytes(privateKey);
return generatePrivateKey(privateKey);
return generatePrivateKey(privateKey, ephemeral);
}
}

View File

@@ -10,14 +10,14 @@ import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class RatchetingSession {
public static void initializeSession(SessionRecordV2 sessionRecord,
public static void initializeSession(SessionState sessionState,
ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
@@ -27,48 +27,48 @@ public class RatchetingSession {
throws InvalidKeyException
{
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionRecord, ourBaseKey, theirBaseKey, theirEphemeralKey,
initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
} else {
initializeSessionAsBob(sessionRecord, ourBaseKey, theirBaseKey,
initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
ourEphemeralKey, ourIdentityKey, theirIdentityKey);
}
}
private static void initializeSessionAsAlice(SessionRecordV2 sessionRecord,
private static void initializeSessionAsAlice(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionRecord.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType());
ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType(), true);
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);
sessionRecord.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionRecord.setSenderChain(sendingKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first);
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionState.setSenderChain(sendingKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static void initializeSessionAsBob(SessionRecordV2 sessionRecord,
private static void initializeSessionAsBob(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionRecord.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
ourIdentityKey, theirIdentityKey);
sessionRecord.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first);
sessionState.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,

View File

@@ -0,0 +1,6 @@
package org.whispersystems.textsecure.push;
import java.io.IOException;
public class ExpectationFailedException extends IOException {
}

View File

@@ -0,0 +1,12 @@
package org.whispersystems.textsecure.push;
public class PreKeyStatus {
private int count;
public PreKeyStatus() {}
public int getCount() {
return count;
}
}

View File

@@ -40,6 +40,7 @@ public class PushServiceSocket {
private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s";
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
private static final String PREKEY_METADATA_PATH = "/v1/keys/";
private static final String PREKEY_PATH = "/v1/keys/%s";
private static final String PREKEY_DEVICE_PATH = "/v1/keys/%s/%s";
@@ -123,6 +124,13 @@ public class PushServiceSocket {
PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
}
public int getAvailablePreKeys() throws IOException {
String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null);
PreKeyStatus preKeyStatus = new Gson().fromJson(responseText, PreKeyStatus.class);
return preKeyStatus.getCount();
}
public List<PreKeyEntity> getPreKeys(PushAddress destination) throws IOException {
try {
String deviceId = String.valueOf(destination.getDeviceId());
@@ -207,7 +215,7 @@ public class PushServiceSocket {
public List<ContactTokenDetails> retrieveDirectory(Set<String> contactTokens) {
try {
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList(contactTokens));
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<String>(contactTokens));
String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList));
ContactTokenDetailsList activeTokens = new Gson().fromJson(response, ContactTokenDetailsList.class);
@@ -334,6 +342,10 @@ public class PushServiceSocket {
throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
}
if (connection.getResponseCode() == 417) {
throw new ExpectationFailedException();
}
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
}

View File

@@ -70,7 +70,7 @@ public class LocalKeyRecord extends Record {
this.localCurrentKeyPair = this.localNextKeyPair;
this.localNextKeyPair = new KeyPair((this.localNextKeyPair.getId()+1) % Medium.MAX_VALUE,
Curve.generateKeyPairForType(keyType),
Curve.generateKeyPairForType(keyType, true),
masterSecret);
}
}

View File

@@ -5,6 +5,7 @@ import android.util.Log;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
/**
* Helper class for generating key pairs and calculating ECDH agreements.
@@ -34,6 +35,27 @@ public class Session {
return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient);
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
{
RecipientDevice device = new RecipientDevice(recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
return hasEncryptCapableSession(context, masterSecret, recipient, device);
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient,
RecipientDevice device)
{
return
hasV1Session(context, recipient) ||
(hasV2Session(context, masterSecret, recipient) &&
!SessionRecordV2.needsRefresh(context, masterSecret, device));
}
public static boolean hasRemoteIdentityKey(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)
@@ -48,7 +70,6 @@ public class Session {
return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
}
private static boolean hasV1Session(Context context, CanonicalRecipient recipient) {
return SessionRecordV1.hasSession(context, recipient) &&
RemoteKeyRecord.hasRecord(context, recipient) &&
@@ -69,7 +90,8 @@ public class Session {
RecipientDevice.DEFAULT_DEVICE_ID))
{
return new SessionRecordV2(context, masterSecret, recipientId,
RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey();
RecipientDevice.DEFAULT_DEVICE_ID).getSessionState()
.getRemoteIdentityKey();
} else if (SessionRecordV1.hasSession(context, recipientId)) {
return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey();
} else {
@@ -84,8 +106,7 @@ public class Session {
recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID))
{
return new SessionRecordV2(context, masterSecret, recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID).getSessionVersion();
return CiphertextMessage.CURRENT_VERSION;
} else if (SessionRecordV1.hasSession(context, recipient)) {
return new SessionRecordV1(context, masterSecret, recipient).getSessionVersion();
}

View File

@@ -18,26 +18,10 @@ package org.whispersystems.textsecure.storage;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import com.google.protobuf.ByteString;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
import java.io.File;
import java.io.FileInputStream;
@@ -45,11 +29,11 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
/**
* A disk record representing a current session.
@@ -60,11 +44,15 @@ import javax.crypto.spec.SecretKeySpec;
public class SessionRecordV2 extends Record {
private static final Object FILE_LOCK = new Object();
private static final int CURRENT_VERSION = 1;
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int CURRENT_VERSION = 2;
private final MasterSecret masterSecret;
private StorageProtos.SessionStructure sessionStructure =
StorageProtos.SessionStructure.newBuilder().build();
private SessionState sessionState = new SessionState(SessionStructure.newBuilder().build());
private List<SessionState> previousStates = new LinkedList<SessionState>();
public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
@@ -80,6 +68,15 @@ public class SessionRecordV2 extends Record {
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
}
public SessionState getSessionState() {
return sessionState;
}
public List<SessionState> getPreviousSessions() {
return previousStates;
}
public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) {
List<Integer> results = new LinkedList<Integer>();
File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
@@ -129,404 +126,49 @@ public class SessionRecordV2 extends Record {
long recipientId, int deviceId)
{
return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) &&
new SessionRecordV2(context, masterSecret, recipientId, deviceId).hasSenderChain();
new SessionRecordV2(context, masterSecret, recipientId, deviceId).sessionState.hasSenderChain();
}
public static boolean needsRefresh(Context context, MasterSecret masterSecret,
RecipientDevice recipient)
{
return new SessionRecordV2(context, masterSecret,
recipient.getRecipientId(),
recipient.getDeviceId()).getSessionState()
.getNeedsRefresh();
}
public void clear() {
this.sessionStructure = StorageProtos.SessionStructure.newBuilder().build();
this.sessionState = new SessionState(SessionStructure.newBuilder().build());
this.previousStates = new LinkedList<SessionState>();
}
public void setSessionVersion(int version) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setSessionVersion(version)
.build();
}
public int getSessionVersion() {
return this.sessionStructure.getSessionVersion();
}
public void setRemoteIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public void setLocalIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public IdentityKey getRemoteIdentityKey() {
try {
if (!this.sessionStructure.hasRemoteIdentityPublic()) {
return null;
}
return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
return null;
}
}
public IdentityKey getLocalIdentityKey() {
try {
return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public int getPreviousCounter() {
return sessionStructure.getPreviousCounter();
}
public void setPreviousCounter(int previousCounter) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setPreviousCounter(previousCounter)
.build();
}
public RootKey getRootKey() {
return new RootKey(this.sessionStructure.getRootKey().toByteArray());
}
public void setRootKey(RootKey rootKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRootKey(ByteString.copyFrom(rootKey.getKeyBytes()))
.build();
}
public ECPublicKey getSenderEphemeral() {
try {
return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public ECKeyPair getSenderEphemeralPair() {
ECPublicKey publicKey = getSenderEphemeral();
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getSenderChain()
.getSenderEphemeralPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
return getReceiverChain(senderEphemeral) != null;
}
public boolean hasSenderChain() {
return sessionStructure.hasSenderChain();
}
private Pair<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> receiverChains = sessionStructure.getReceiverChainsList();
int index = 0;
for (Chain receiverChain : receiverChains) {
try {
ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0);
if (chainSenderEphemeral.equals(senderEphemeral)) {
return new Pair<Chain,Integer>(receiverChain,index);
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
Chain receiverChain = receiverChainAndIndex.first;
if (receiverChain == null) {
return null;
} else {
return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(),
receiverChain.getChainKey().getIndex());
}
}
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain chain = Chain.newBuilder()
.setChainKey(chainKeyStructure)
.setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build();
if (this.sessionStructure.getReceiverChainsList().size() > 5) {
this.sessionStructure = this.sessionStructure.toBuilder()
.removeReceiverChains(0)
.build();
}
}
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain senderChain = Chain.newBuilder()
.setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize()))
.setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize()))
.setChainKey(chainKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build();
}
public ChainKey getSenderChainKey() {
Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey();
return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex());
}
public void setSenderChainKey(ChainKey nextChainKey) {
Chain.ChainKey chainKey = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(nextChainKey.getKey()))
.setIndex(nextChainKey.getIndex())
.build();
Chain chain = sessionStructure.getSenderChain().toBuilder()
.setChainKey(chainKey).build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build();
}
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
MessageKeys result = null;
while (messageKeyIterator.hasNext()) {
Chain.MessageKey messageKey = messageKeyIterator.next();
if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
messageKey.getIndex());
messageKeyIterator.remove();
break;
}
}
Chain updatedChain = chain.toBuilder().clearMessageKeys()
.addAllMessageKeys(messageKeyList)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
return result;
}
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder()
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter())
.build();
Chain updatedChain = chain.toBuilder()
.addMessageKeys(messageKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setPendingKeyExchange(int sequence,
ECKeyPair ourBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey)
{
PendingKeyExchange structure =
PendingKeyExchange.newBuilder()
.setSequence(sequence)
.setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize()))
.setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize()))
.setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize()))
.setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize()))
.setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize()))
.setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingKeyExchange(structure)
.build();
}
public int getPendingKeyExchangeSequence() {
return sessionStructure.getPendingKeyExchange().getSequence();
}
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalBaseKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalBaseKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange()
.getLocalIdentityKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(),
sessionStructure.getPendingKeyExchange()
.getLocalIdentityKeyPrivate()
.toByteArray());
return new IdentityKeyPair(publicKey, privateKey);
}
public boolean hasPendingKeyExchange() {
return sessionStructure.hasPendingKeyExchange();
}
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
PendingPreKey pending = PendingPreKey.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingPreKey(pending)
.build();
}
public boolean hasPendingPreKey() {
return this.sessionStructure.hasPendingPreKey();
}
public Pair<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(sessionStructure.getPendingPreKey().getPreKeyId(),
Curve.decodePoint(sessionStructure.getPendingPreKey()
.getBaseKey()
.toByteArray(), 0));
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void clearPendingPreKey() {
this.sessionStructure = this.sessionStructure.toBuilder()
.clearPendingPreKey()
.build();
}
public void setRemoteRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteRegistrationId(registrationId)
.build();
}
public int getRemoteRegistrationId() {
return this.sessionStructure.getRemoteRegistrationId();
}
public void setLocalRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalRegistrationId(registrationId)
.build();
}
public int getLocalRegistrationId() {
return this.sessionStructure.getLocalRegistrationId();
public void archiveCurrentState() {
this.previousStates.add(sessionState);
this.sessionState = new SessionState(SessionStructure.newBuilder().build());
}
public void save() {
synchronized (FILE_LOCK) {
try {
List<SessionStructure> previousStructures = new LinkedList<SessionStructure>();
for (SessionState previousState : previousStates) {
previousStructures.add(previousState.getStructure());
}
RecordStructure record = RecordStructure.newBuilder()
.setCurrentSession(sessionState.getStructure())
.addAllPreviousSessions(previousStructures)
.build();
RandomAccessFile file = openRandomAccessFile();
FileChannel out = file.getChannel();
out.position(0);
MasterCipher cipher = new MasterCipher(masterSecret);
writeInteger(CURRENT_VERSION, out);
writeBlob(cipher.encryptBytes(sessionStructure.toByteArray()), out);
writeBlob(cipher.encryptBytes(record.toByteArray()), out);
out.truncate(out.position());
file.close();
@@ -549,11 +191,26 @@ public class SessionRecordV2 extends Record {
MasterCipher cipher = new MasterCipher(masterSecret);
byte[] encryptedBlob = readBlob(in);
if (versionMarker == SINGLE_STATE_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
SessionStructure sessionStructure = SessionStructure.parseFrom(plaintextBytes);
this.sessionState = new SessionState(sessionStructure);
} else if (versionMarker == ARCHIVE_STATES_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
RecordStructure recordStructure = RecordStructure.parseFrom(plaintextBytes);
this.sessionStructure = StorageProtos.SessionStructure
.parseFrom(cipher.decryptBytes(encryptedBlob));
this.sessionState = new SessionState(recordStructure.getCurrentSession());
this.previousStates = new LinkedList<SessionState>();
for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) {
this.previousStates.add(new SessionState(sessionStructure));
}
} else {
throw new AssertionError("Unknown version: " + versionMarker);
}
in.close();
} catch (FileNotFoundException e) {
Log.w("SessionRecordV2", "No session information found.");
// XXX

View File

@@ -0,0 +1,436 @@
package org.whispersystems.textsecure.storage;
import android.util.Log;
import android.util.Pair;
import com.google.protobuf.ByteString;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
public class SessionState {
private SessionStructure sessionStructure;
public SessionState(SessionStructure sessionStructure) {
this.sessionStructure = sessionStructure;
}
public SessionStructure getStructure() {
return sessionStructure;
}
public void setNeedsRefresh(boolean needsRefresh) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setNeedsRefresh(needsRefresh)
.build();
}
public boolean getNeedsRefresh() {
return this.sessionStructure.getNeedsRefresh();
}
public void setSessionVersion(int version) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setSessionVersion(version)
.build();
}
public int getSessionVersion() {
return this.sessionStructure.getSessionVersion();
}
public void setRemoteIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public void setLocalIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public IdentityKey getRemoteIdentityKey() {
try {
if (!this.sessionStructure.hasRemoteIdentityPublic()) {
return null;
}
return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
return null;
}
}
public IdentityKey getLocalIdentityKey() {
try {
return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public int getPreviousCounter() {
return sessionStructure.getPreviousCounter();
}
public void setPreviousCounter(int previousCounter) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setPreviousCounter(previousCounter)
.build();
}
public RootKey getRootKey() {
return new RootKey(this.sessionStructure.getRootKey().toByteArray());
}
public void setRootKey(RootKey rootKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRootKey(ByteString.copyFrom(rootKey.getKeyBytes()))
.build();
}
public ECPublicKey getSenderEphemeral() {
try {
return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public ECKeyPair getSenderEphemeralPair() {
ECPublicKey publicKey = getSenderEphemeral();
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getSenderChain()
.getSenderEphemeralPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
return getReceiverChain(senderEphemeral) != null;
}
public boolean hasSenderChain() {
return sessionStructure.hasSenderChain();
}
private Pair<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> receiverChains = sessionStructure.getReceiverChainsList();
int index = 0;
for (Chain receiverChain : receiverChains) {
try {
ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0);
if (chainSenderEphemeral.equals(senderEphemeral)) {
return new Pair<Chain,Integer>(receiverChain,index);
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
Chain receiverChain = receiverChainAndIndex.first;
if (receiverChain == null) {
return null;
} else {
return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(),
receiverChain.getChainKey().getIndex());
}
}
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain chain = Chain.newBuilder()
.setChainKey(chainKeyStructure)
.setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build();
if (this.sessionStructure.getReceiverChainsList().size() > 5) {
this.sessionStructure = this.sessionStructure.toBuilder()
.removeReceiverChains(0)
.build();
}
}
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain senderChain = Chain.newBuilder()
.setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize()))
.setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize()))
.setChainKey(chainKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build();
}
public ChainKey getSenderChainKey() {
Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey();
return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex());
}
public void setSenderChainKey(ChainKey nextChainKey) {
Chain.ChainKey chainKey = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(nextChainKey.getKey()))
.setIndex(nextChainKey.getIndex())
.build();
Chain chain = sessionStructure.getSenderChain().toBuilder()
.setChainKey(chainKey).build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build();
}
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
MessageKeys result = null;
while (messageKeyIterator.hasNext()) {
Chain.MessageKey messageKey = messageKeyIterator.next();
if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
messageKey.getIndex());
messageKeyIterator.remove();
break;
}
}
Chain updatedChain = chain.toBuilder().clearMessageKeys()
.addAllMessageKeys(messageKeyList)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
return result;
}
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder()
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter())
.build();
Chain updatedChain = chain.toBuilder()
.addMessageKeys(messageKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setPendingKeyExchange(int sequence,
ECKeyPair ourBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey)
{
PendingKeyExchange structure =
PendingKeyExchange.newBuilder()
.setSequence(sequence)
.setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize()))
.setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize()))
.setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize()))
.setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize()))
.setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize()))
.setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingKeyExchange(structure)
.build();
}
public int getPendingKeyExchangeSequence() {
return sessionStructure.getPendingKeyExchange().getSequence();
}
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalBaseKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalBaseKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange()
.getLocalIdentityKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(),
sessionStructure.getPendingKeyExchange()
.getLocalIdentityKeyPrivate()
.toByteArray());
return new IdentityKeyPair(publicKey, privateKey);
}
public boolean hasPendingKeyExchange() {
return sessionStructure.hasPendingKeyExchange();
}
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
PendingPreKey pending = PendingPreKey.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingPreKey(pending)
.build();
}
public boolean hasPendingPreKey() {
return this.sessionStructure.hasPendingPreKey();
}
public Pair<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(sessionStructure.getPendingPreKey().getPreKeyId(),
Curve.decodePoint(sessionStructure.getPendingPreKey()
.getBaseKey()
.toByteArray(), 0));
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void clearPendingPreKey() {
this.sessionStructure = this.sessionStructure.toBuilder()
.clearPendingPreKey()
.build();
}
public void setRemoteRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteRegistrationId(registrationId)
.build();
}
public int getRemoteRegistrationId() {
return this.sessionStructure.getRemoteRegistrationId();
}
public void setLocalRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalRegistrationId(registrationId)
.build();
}
public int getLocalRegistrationId() {
return this.sessionStructure.getLocalRegistrationId();
}
public byte[] serialize() {
return sessionStructure.toByteArray();
}
}

View File

@@ -63,6 +63,10 @@ public final class StorageProtos {
// optional uint32 localRegistrationId = 11;
boolean hasLocalRegistrationId();
int getLocalRegistrationId();
// optional bool needsRefresh = 12;
boolean hasNeedsRefresh();
boolean getNeedsRefresh();
}
public static final class SessionStructure extends
com.google.protobuf.GeneratedMessage
@@ -2992,6 +2996,16 @@ public final class StorageProtos {
return localRegistrationId_;
}
// optional bool needsRefresh = 12;
public static final int NEEDSREFRESH_FIELD_NUMBER = 12;
private boolean needsRefresh_;
public boolean hasNeedsRefresh() {
return ((bitField0_ & 0x00000400) == 0x00000400);
}
public boolean getNeedsRefresh() {
return needsRefresh_;
}
private void initFields() {
sessionVersion_ = 0;
localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY;
@@ -3004,6 +3018,7 @@ public final class StorageProtos {
pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance();
remoteRegistrationId_ = 0;
localRegistrationId_ = 0;
needsRefresh_ = false;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -3050,6 +3065,9 @@ public final class StorageProtos {
if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeUInt32(11, localRegistrationId_);
}
if (((bitField0_ & 0x00000400) == 0x00000400)) {
output.writeBool(12, needsRefresh_);
}
getUnknownFields().writeTo(output);
}
@@ -3103,6 +3121,10 @@ public final class StorageProtos {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(11, localRegistrationId_);
}
if (((bitField0_ & 0x00000400) == 0x00000400)) {
size += com.google.protobuf.CodedOutputStream
.computeBoolSize(12, needsRefresh_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -3269,6 +3291,8 @@ public final class StorageProtos {
bitField0_ = (bitField0_ & ~0x00000200);
localRegistrationId_ = 0;
bitField0_ = (bitField0_ & ~0x00000400);
needsRefresh_ = false;
bitField0_ = (bitField0_ & ~0x00000800);
return this;
}
@@ -3368,6 +3392,10 @@ public final class StorageProtos {
to_bitField0_ |= 0x00000200;
}
result.localRegistrationId_ = localRegistrationId_;
if (((from_bitField0_ & 0x00000800) == 0x00000800)) {
to_bitField0_ |= 0x00000400;
}
result.needsRefresh_ = needsRefresh_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -3440,6 +3468,9 @@ public final class StorageProtos {
if (other.hasLocalRegistrationId()) {
setLocalRegistrationId(other.getLocalRegistrationId());
}
if (other.hasNeedsRefresh()) {
setNeedsRefresh(other.getNeedsRefresh());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -3539,6 +3570,11 @@ public final class StorageProtos {
localRegistrationId_ = input.readUInt32();
break;
}
case 96: {
bitField0_ |= 0x00000800;
needsRefresh_ = input.readBool();
break;
}
}
}
}
@@ -4157,6 +4193,27 @@ public final class StorageProtos {
return this;
}
// optional bool needsRefresh = 12;
private boolean needsRefresh_ ;
public boolean hasNeedsRefresh() {
return ((bitField0_ & 0x00000800) == 0x00000800);
}
public boolean getNeedsRefresh() {
return needsRefresh_;
}
public Builder setNeedsRefresh(boolean value) {
bitField0_ |= 0x00000800;
needsRefresh_ = value;
onChanged();
return this;
}
public Builder clearNeedsRefresh() {
bitField0_ = (bitField0_ & ~0x00000800);
needsRefresh_ = false;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:textsecure.SessionStructure)
}
@@ -4168,6 +4225,703 @@ public final class StorageProtos {
// @@protoc_insertion_point(class_scope:textsecure.SessionStructure)
}
public interface RecordStructureOrBuilder
extends com.google.protobuf.MessageOrBuilder {
// optional .textsecure.SessionStructure currentSession = 1;
boolean hasCurrentSession();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder();
// repeated .textsecure.SessionStructure previousSessions = 2;
java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>
getPreviousSessionsList();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index);
int getPreviousSessionsCount();
java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index);
}
public static final class RecordStructure extends
com.google.protobuf.GeneratedMessage
implements RecordStructureOrBuilder {
// Use RecordStructure.newBuilder() to construct.
private RecordStructure(Builder builder) {
super(builder);
}
private RecordStructure(boolean noInit) {}
private static final RecordStructure defaultInstance;
public static RecordStructure getDefaultInstance() {
return defaultInstance;
}
public RecordStructure getDefaultInstanceForType() {
return defaultInstance;
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable;
}
private int bitField0_;
// optional .textsecure.SessionStructure currentSession = 1;
public static final int CURRENTSESSION_FIELD_NUMBER = 1;
private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_;
public boolean hasCurrentSession() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() {
return currentSession_;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() {
return currentSession_;
}
// repeated .textsecure.SessionStructure previousSessions = 2;
public static final int PREVIOUSSESSIONS_FIELD_NUMBER = 2;
private java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_;
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> getPreviousSessionsList() {
return previousSessions_;
}
public java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList() {
return previousSessions_;
}
public int getPreviousSessionsCount() {
return previousSessions_.size();
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) {
return previousSessions_.get(index);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index) {
return previousSessions_.get(index);
}
private void initFields() {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
previousSessions_ = java.util.Collections.emptyList();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized != -1) return isInitialized == 1;
memoizedIsInitialized = 1;
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeMessage(1, currentSession_);
}
for (int i = 0; i < previousSessions_.size(); i++) {
output.writeMessage(2, previousSessions_.get(i));
}
getUnknownFields().writeTo(output);
}
private int memoizedSerializedSize = -1;
public int getSerializedSize() {
int size = memoizedSerializedSize;
if (size != -1) return size;
size = 0;
if (((bitField0_ & 0x00000001) == 0x00000001)) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(1, currentSession_);
}
for (int i = 0; i < previousSessions_.size(); i++) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(2, previousSessions_.get(i));
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
}
private static final long serialVersionUID = 0L;
@java.lang.Override
protected java.lang.Object writeReplace()
throws java.io.ObjectStreamException {
return super.writeReplace();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(java.io.InputStream input)
throws java.io.IOException {
return newBuilder().mergeFrom(input).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return newBuilder().mergeFrom(input, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
Builder builder = newBuilder();
if (builder.mergeDelimitedFrom(input)) {
return builder.buildParsed();
} else {
return null;
}
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
Builder builder = newBuilder();
if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
return builder.buildParsed();
} else {
return null;
}
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return newBuilder().mergeFrom(input).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return newBuilder().mergeFrom(input, extensionRegistry)
.buildParsed();
}
public static Builder newBuilder() { return Builder.create(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder>
implements org.whispersystems.textsecure.storage.StorageProtos.RecordStructureOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable;
}
// Construct using org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
getCurrentSessionFieldBuilder();
getPreviousSessionsFieldBuilder();
}
}
private static Builder create() {
return new Builder();
}
public Builder clear() {
super.clear();
if (currentSessionBuilder_ == null) {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
} else {
currentSessionBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000001);
if (previousSessionsBuilder_ == null) {
previousSessions_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00000002);
} else {
previousSessionsBuilder_.clear();
}
return this;
}
public Builder clone() {
return create().mergeFrom(buildPartial());
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDescriptor();
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure getDefaultInstanceForType() {
return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance();
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure build() {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
private org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildParsed()
throws com.google.protobuf.InvalidProtocolBufferException {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(
result).asInvalidProtocolBufferException();
}
return result;
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildPartial() {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = new org.whispersystems.textsecure.storage.StorageProtos.RecordStructure(this);
int from_bitField0_ = bitField0_;
int to_bitField0_ = 0;
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
to_bitField0_ |= 0x00000001;
}
if (currentSessionBuilder_ == null) {
result.currentSession_ = currentSession_;
} else {
result.currentSession_ = currentSessionBuilder_.build();
}
if (previousSessionsBuilder_ == null) {
if (((bitField0_ & 0x00000002) == 0x00000002)) {
previousSessions_ = java.util.Collections.unmodifiableList(previousSessions_);
bitField0_ = (bitField0_ & ~0x00000002);
}
result.previousSessions_ = previousSessions_;
} else {
result.previousSessions_ = previousSessionsBuilder_.build();
}
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
}
public Builder mergeFrom(com.google.protobuf.Message other) {
if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.RecordStructure) {
return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.RecordStructure)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure other) {
if (other == org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance()) return this;
if (other.hasCurrentSession()) {
mergeCurrentSession(other.getCurrentSession());
}
if (previousSessionsBuilder_ == null) {
if (!other.previousSessions_.isEmpty()) {
if (previousSessions_.isEmpty()) {
previousSessions_ = other.previousSessions_;
bitField0_ = (bitField0_ & ~0x00000002);
} else {
ensurePreviousSessionsIsMutable();
previousSessions_.addAll(other.previousSessions_);
}
onChanged();
}
} else {
if (!other.previousSessions_.isEmpty()) {
if (previousSessionsBuilder_.isEmpty()) {
previousSessionsBuilder_.dispose();
previousSessionsBuilder_ = null;
previousSessions_ = other.previousSessions_;
bitField0_ = (bitField0_ & ~0x00000002);
previousSessionsBuilder_ =
com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ?
getPreviousSessionsFieldBuilder() : null;
} else {
previousSessionsBuilder_.addAllMessages(other.previousSessions_);
}
}
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
public final boolean isInitialized() {
return true;
}
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
com.google.protobuf.UnknownFieldSet.newBuilder(
this.getUnknownFields());
while (true) {
int tag = input.readTag();
switch (tag) {
case 0:
this.setUnknownFields(unknownFields.build());
onChanged();
return this;
default: {
if (!parseUnknownField(input, unknownFields,
extensionRegistry, tag)) {
this.setUnknownFields(unknownFields.build());
onChanged();
return this;
}
break;
}
case 10: {
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder();
if (hasCurrentSession()) {
subBuilder.mergeFrom(getCurrentSession());
}
input.readMessage(subBuilder, extensionRegistry);
setCurrentSession(subBuilder.buildPartial());
break;
}
case 18: {
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder();
input.readMessage(subBuilder, extensionRegistry);
addPreviousSessions(subBuilder.buildPartial());
break;
}
}
}
}
private int bitField0_;
// optional .textsecure.SessionStructure currentSession = 1;
private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> currentSessionBuilder_;
public boolean hasCurrentSession() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() {
if (currentSessionBuilder_ == null) {
return currentSession_;
} else {
return currentSessionBuilder_.getMessage();
}
}
public Builder setCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (currentSessionBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
currentSession_ = value;
onChanged();
} else {
currentSessionBuilder_.setMessage(value);
}
bitField0_ |= 0x00000001;
return this;
}
public Builder setCurrentSession(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (currentSessionBuilder_ == null) {
currentSession_ = builderForValue.build();
onChanged();
} else {
currentSessionBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000001;
return this;
}
public Builder mergeCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (currentSessionBuilder_ == null) {
if (((bitField0_ & 0x00000001) == 0x00000001) &&
currentSession_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()) {
currentSession_ =
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder(currentSession_).mergeFrom(value).buildPartial();
} else {
currentSession_ = value;
}
onChanged();
} else {
currentSessionBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000001;
return this;
}
public Builder clearCurrentSession() {
if (currentSessionBuilder_ == null) {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
onChanged();
} else {
currentSessionBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000001);
return this;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getCurrentSessionBuilder() {
bitField0_ |= 0x00000001;
onChanged();
return getCurrentSessionFieldBuilder().getBuilder();
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() {
if (currentSessionBuilder_ != null) {
return currentSessionBuilder_.getMessageOrBuilder();
} else {
return currentSession_;
}
}
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getCurrentSessionFieldBuilder() {
if (currentSessionBuilder_ == null) {
currentSessionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>(
currentSession_,
getParentForChildren(),
isClean());
currentSession_ = null;
}
return currentSessionBuilder_;
}
// repeated .textsecure.SessionStructure previousSessions = 2;
private java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_ =
java.util.Collections.emptyList();
private void ensurePreviousSessionsIsMutable() {
if (!((bitField0_ & 0x00000002) == 0x00000002)) {
previousSessions_ = new java.util.ArrayList<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>(previousSessions_);
bitField0_ |= 0x00000002;
}
}
private com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> previousSessionsBuilder_;
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> getPreviousSessionsList() {
if (previousSessionsBuilder_ == null) {
return java.util.Collections.unmodifiableList(previousSessions_);
} else {
return previousSessionsBuilder_.getMessageList();
}
}
public int getPreviousSessionsCount() {
if (previousSessionsBuilder_ == null) {
return previousSessions_.size();
} else {
return previousSessionsBuilder_.getCount();
}
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) {
if (previousSessionsBuilder_ == null) {
return previousSessions_.get(index);
} else {
return previousSessionsBuilder_.getMessage(index);
}
}
public Builder setPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.set(index, value);
onChanged();
} else {
previousSessionsBuilder_.setMessage(index, value);
}
return this;
}
public Builder setPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.set(index, builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.setMessage(index, builderForValue.build());
}
return this;
}
public Builder addPreviousSessions(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.add(value);
onChanged();
} else {
previousSessionsBuilder_.addMessage(value);
}
return this;
}
public Builder addPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.add(index, value);
onChanged();
} else {
previousSessionsBuilder_.addMessage(index, value);
}
return this;
}
public Builder addPreviousSessions(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.add(builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.addMessage(builderForValue.build());
}
return this;
}
public Builder addPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.add(index, builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.addMessage(index, builderForValue.build());
}
return this;
}
public Builder addAllPreviousSessions(
java.lang.Iterable<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> values) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
super.addAll(values, previousSessions_);
onChanged();
} else {
previousSessionsBuilder_.addAllMessages(values);
}
return this;
}
public Builder clearPreviousSessions() {
if (previousSessionsBuilder_ == null) {
previousSessions_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00000002);
onChanged();
} else {
previousSessionsBuilder_.clear();
}
return this;
}
public Builder removePreviousSessions(int index) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.remove(index);
onChanged();
} else {
previousSessionsBuilder_.remove(index);
}
return this;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getPreviousSessionsBuilder(
int index) {
return getPreviousSessionsFieldBuilder().getBuilder(index);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index) {
if (previousSessionsBuilder_ == null) {
return previousSessions_.get(index); } else {
return previousSessionsBuilder_.getMessageOrBuilder(index);
}
}
public java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList() {
if (previousSessionsBuilder_ != null) {
return previousSessionsBuilder_.getMessageOrBuilderList();
} else {
return java.util.Collections.unmodifiableList(previousSessions_);
}
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder() {
return getPreviousSessionsFieldBuilder().addBuilder(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance());
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder(
int index) {
return getPreviousSessionsFieldBuilder().addBuilder(
index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance());
}
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder>
getPreviousSessionsBuilderList() {
return getPreviousSessionsFieldBuilder().getBuilderList();
}
private com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsFieldBuilder() {
if (previousSessionsBuilder_ == null) {
previousSessionsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>(
previousSessions_,
((bitField0_ & 0x00000002) == 0x00000002),
getParentForChildren(),
isClean());
previousSessions_ = null;
}
return previousSessionsBuilder_;
}
// @@protoc_insertion_point(builder_scope:textsecure.RecordStructure)
}
static {
defaultInstance = new RecordStructure(true);
defaultInstance.initFields();
}
// @@protoc_insertion_point(class_scope:textsecure.RecordStructure)
}
public interface PreKeyRecordStructureOrBuilder
extends com.google.protobuf.MessageOrBuilder {
@@ -4656,6 +5410,11 @@ public final class StorageProtos {
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_RecordStructure_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_RecordStructure_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_PreKeyRecordStructure_descriptor;
private static
@@ -4671,7 +5430,7 @@ public final class StorageProtos {
static {
java.lang.String[] descriptorData = {
"\n\032LocalStorageProtocol.proto\022\ntextsecure" +
"\"\205\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
"\"\233\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
"\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" +
"moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" +
"\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" +
@@ -4682,25 +5441,28 @@ public final class StorageProtos {
"e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ",
"\001(\0132*.textsecure.SessionStructure.Pendin" +
"gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" +
"\023localRegistrationId\030\013 \001(\r\032\253\002\n\005Chain\022\027\n\017" +
"senderEphemeral\030\001 \001(\014\022\036\n\026senderEphemeral" +
"Private\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.textse" +
"cure.SessionStructure.Chain.ChainKey\022B\n\013" +
"messageKeys\030\004 \003(\0132-.textsecure.SessionSt" +
"ructure.Chain.MessageKey\032&\n\010ChainKey\022\r\n\005" +
"index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageKey\022\r" +
"\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macK",
"ey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010seque" +
"nce\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023local" +
"BaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemeralKe" +
"y\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030\005 \001(" +
"\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localIden" +
"tityKeyPrivate\030\010 \001(\014\0322\n\rPendingPreKey\022\020\n" +
"\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\"J\n\025PreK" +
"eyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicK" +
"ey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014B6\n%org.whis" +
"persystems.textsecure.storageB\rStoragePr",
"otos"
"\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" +
"sh\030\014 \001(\010\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 " +
"\001(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010c" +
"hainKey\030\003 \001(\0132+.textsecure.SessionStruct" +
"ure.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132" +
"-.textsecure.SessionStructure.Chain.Mess" +
"ageKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key" +
"\030\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tc",
"ipherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendi" +
"ngKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014local" +
"BaseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001" +
"(\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEp" +
"hemeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentity" +
"Key\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001" +
"(\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n" +
"\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n\016cur" +
"rentSession\030\001 \001(\0132\034.textsecure.SessionSt" +
"ructure\0226\n\020previousSessions\030\002 \003(\0132\034.text",
"secure.SessionStructure\"J\n\025PreKeyRecordS" +
"tructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022" +
"\022\n\nprivateKey\030\003 \001(\014B6\n%org.whispersystem" +
"s.textsecure.storageB\rStorageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -4712,7 +5474,7 @@ public final class StorageProtos {
internal_static_textsecure_SessionStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_SessionStructure_descriptor,
new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", },
new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", "NeedsRefresh", },
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class);
internal_static_textsecure_SessionStructure_Chain_descriptor =
@@ -4755,8 +5517,16 @@ public final class StorageProtos {
new java.lang.String[] { "PreKeyId", "BaseKey", },
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class);
internal_static_textsecure_PreKeyRecordStructure_descriptor =
internal_static_textsecure_RecordStructure_descriptor =
getDescriptor().getMessageTypes().get(1);
internal_static_textsecure_RecordStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_RecordStructure_descriptor,
new java.lang.String[] { "CurrentSession", "PreviousSessions", },
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.class,
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.Builder.class);
internal_static_textsecure_PreKeyRecordStructure_descriptor =
getDescriptor().getMessageTypes().get(2);
internal_static_textsecure_PreKeyRecordStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PreKeyRecordStructure_descriptor,

View File

@@ -1,6 +1,5 @@
package org.whispersystems.textsecure.util;
import android.app.AlertDialog;
import android.content.Context;
import android.telephony.TelephonyManager;
import android.widget.EditText;
@@ -88,14 +87,6 @@ public class Util {
return value == null || value.length() == 0;
}
public static void showAlertDialog(Context context, String title, String message) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setIcon(android.R.drawable.ic_dialog_alert);
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
}
public static int generateRegistrationId() {
try {

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:duration="150"
android:fromXScale="0.85"
android:fromYScale="0.85"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%" />
<alpha
android:duration="150"
android:fromAlpha="0.6"
android:toAlpha="1.0" />
</set>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:duration="150"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="0.85"
android:toYScale="0.85"
android:pivotX="50%"
android:pivotY="50%" />
<alpha
android:duration="150"
android:fromAlpha="1.0"
android:toAlpha="0.6" />
</set>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:duration="150"
android:fromXDelta="100%"
android:toXDelta="0%" />
</set>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
android:duration="150"
android:fromXDelta="0%"
android:toXDelta="100%" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

View File

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

View File

Before

Width:  |  Height:  |  Size: 293 B

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

View File

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -2,7 +2,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/card_background_active" />
<solid android:color="@color/touch_highlight" />
</shape>
<bitmap android:src="@drawable/card" />
</item>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="@color/touch_highlight" />
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>

View File

@@ -3,14 +3,14 @@
<item>
<shape android:shape="rectangle">
<solid android:color="#09000000" />
<solid android:color="@color/conversation_item_received_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffeeeeee" />
<solid android:color="@color/conversation_item_received_background_light" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>

Some files were not shown because too many files have changed in this diff Show More