Simplify/clarify internal interfaces and introduce optional types.

This commit is contained in:
Moxie Marlinspike 2014-07-13 15:21:41 -07:00
parent 5f5ddd7c26
commit f0c22d593f
13 changed files with 1364 additions and 204 deletions

View File

@ -426,23 +426,23 @@ public class SessionBuilderTest extends AndroidTestCase {
}
public void testBadVerificationTagV3() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, InvalidKeyIdException, DuplicateMessageException, LegacyMessageException {
SessionStore aliceSessionStore = new InMemorySessionStore();
SessionStore aliceSessionStore = new InMemorySessionStore();
SignedPreKeyStore aliceSignedPreKeyStore = new InMemorySignedPreKeyStore();
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
PreKeyStore alicePreKeyStore = new InMemoryPreKeyStore();
IdentityKeyStore aliceIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceSessionStore, alicePreKeyStore,
aliceSignedPreKeyStore,
aliceIdentityKeyStore,
BOB_RECIPIENT_ID, 1);
SessionStore bobSessionStore = new InMemorySessionStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SessionStore bobSessionStore = new InMemorySessionStore();
PreKeyStore bobPreKeyStore = new InMemoryPreKeyStore();
SignedPreKeyStore bobSignedPreKeyStore = new InMemorySignedPreKeyStore();
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
bobSignedPreKeyStore,
bobIdentityKeyStore,
ALICE_RECIPIENT_ID, 1);
IdentityKeyStore bobIdentityKeyStore = new InMemoryIdentityKeyStore();
SessionBuilder bobSessionBuilder = new SessionBuilder(bobSessionStore, bobPreKeyStore,
bobSignedPreKeyStore,
bobIdentityKeyStore,
ALICE_RECIPIENT_ID, 1);
ECKeyPair bobPreKeyPair = Curve.generateKeyPair(true);
ECKeyPair bobSignedPreKeyPair = Curve.generateKeyPair(true);
@ -469,6 +469,7 @@ public class SessionBuilderTest extends AndroidTestCase {
for (int i=0;i<incomingMessage.getVerification().length * 8;i++) {
byte[] modifiedVerification = new byte[incomingMessage.getVerification().length];
System.arraycopy(incomingMessage.getVerification(), 0, modifiedVerification, 0, modifiedVerification.length);
modifiedVerification[i / 8] ^= (0x01 << i % 8);
PreKeyWhisperMessage modifiedMessage = new PreKeyWhisperMessage(incomingMessage.getMessageVersion(),

View File

@ -11,11 +11,13 @@ import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -24,6 +26,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Random;
import static org.whispersystems.libaxolotl.ratchet.RatchetingSession.InitializationParameters;
public class SessionCipherTest extends AndroidTestCase {
public void testBasicSessionV2()
@ -134,16 +138,33 @@ public class SessionCipherTest extends AndroidTestCase {
ECKeyPair bobBaseKey = Curve.generateKeyPair(true);
ECKeyPair bobEphemeralKey = bobBaseKey;
InitializationParameters aliceParameters =
InitializationParameters.newBuilder()
.setOurIdentityKey(aliceIdentityKey)
.setOurBaseKey(aliceBaseKey)
.setOurEphemeralKey(aliceEphemeralKey)
.setOurPreKey(Optional.<ECKeyPair>absent())
.setTheirIdentityKey(bobIdentityKey.getPublicKey())
.setTheirBaseKey(bobBaseKey.getPublicKey())
.setTheirEphemeralKey(bobEphemeralKey.getPublicKey())
.setTheirPreKey(Optional.<ECPublicKey>absent())
.create();
RatchetingSession.initializeSession(aliceSessionState, 2, aliceBaseKey, bobBaseKey.getPublicKey(),
aliceEphemeralKey, bobEphemeralKey.getPublicKey(),
null, null,
aliceIdentityKey, bobIdentityKey.getPublicKey());
InitializationParameters bobParameters =
InitializationParameters.newBuilder()
.setOurIdentityKey(bobIdentityKey)
.setOurBaseKey(bobBaseKey)
.setOurEphemeralKey(bobEphemeralKey)
.setOurPreKey(Optional.<ECKeyPair>absent())
.setTheirIdentityKey(aliceIdentityKey.getPublicKey())
.setTheirBaseKey(aliceBaseKey.getPublicKey())
.setTheirEphemeralKey(aliceEphemeralKey.getPublicKey())
.setTheirPreKey(Optional.<ECPublicKey>absent())
.create();
RatchetingSession.initializeSession(bobSessionState, 2, bobBaseKey, aliceBaseKey.getPublicKey(),
bobEphemeralKey, aliceEphemeralKey.getPublicKey(),
null, null,
bobIdentityKey, aliceIdentityKey.getPublicKey());
RatchetingSession.initializeSession(aliceSessionState, 2, aliceParameters);
RatchetingSession.initializeSession(bobSessionState, 2, bobParameters);
}
private void initializeSessionsV3(SessionState aliceSessionState, SessionState bobSessionState)
@ -165,16 +186,33 @@ public class SessionCipherTest extends AndroidTestCase {
ECKeyPair bobPreKey = Curve.generateKeyPair(true);
InitializationParameters aliceParameters =
InitializationParameters.newBuilder()
.setOurIdentityKey(aliceIdentityKey)
.setOurBaseKey(aliceBaseKey)
.setOurEphemeralKey(aliceEphemeralKey)
.setOurPreKey(Optional.of(alicePreKey))
.setTheirIdentityKey(bobIdentityKey.getPublicKey())
.setTheirBaseKey(bobBaseKey.getPublicKey())
.setTheirEphemeralKey(bobEphemeralKey.getPublicKey())
.setTheirPreKey(Optional.of(bobPreKey.getPublicKey()))
.create();
RatchetingSession.initializeSession(aliceSessionState, 3, aliceBaseKey, bobBaseKey.getPublicKey(),
aliceEphemeralKey, bobEphemeralKey.getPublicKey(),
alicePreKey, bobPreKey.getPublicKey(),
aliceIdentityKey, bobIdentityKey.getPublicKey());
InitializationParameters bobParameters =
InitializationParameters.newBuilder()
.setOurIdentityKey(bobIdentityKey)
.setOurBaseKey(bobBaseKey)
.setOurEphemeralKey(bobEphemeralKey)
.setOurPreKey(Optional.of(bobPreKey))
.setTheirIdentityKey(aliceIdentityKey.getPublicKey())
.setTheirBaseKey(aliceBaseKey.getPublicKey())
.setTheirEphemeralKey(aliceEphemeralKey.getPublicKey())
.setTheirPreKey(Optional.of(alicePreKey.getPublicKey()))
.create();
RatchetingSession.initializeSession(bobSessionState, 3, bobBaseKey, aliceBaseKey.getPublicKey(),
bobEphemeralKey, aliceEphemeralKey.getPublicKey(),
bobPreKey, alicePreKey.getPublicKey(),
bobIdentityKey, aliceIdentityKey.getPublicKey());
RatchetingSession.initializeSession(aliceSessionState, 3, aliceParameters);
RatchetingSession.initializeSession(bobSessionState, 3, bobParameters);
}
}

View File

@ -11,9 +11,12 @@ import org.whispersystems.libaxolotl.ecc.ECPrivateKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.Arrays;
import static org.whispersystems.libaxolotl.ratchet.RatchetingSession.InitializationParameters;
public class RatchetingSessionTest extends AndroidTestCase {
public void testRatchetingSessionAsBob() throws InvalidKeyException {
@ -105,12 +108,21 @@ public class RatchetingSessionTest extends AndroidTestCase {
ECPublicKey aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0);
IdentityKey aliceIdentityPublicKey = new IdentityKey(aliceIdentityPublic, 0);
InitializationParameters parameters = InitializationParameters.newBuilder()
.setOurBaseKey(bobBaseKey)
.setOurEphemeralKey(bobEphemeralKey)
.setOurIdentityKey(bobIdentityKey)
.setOurPreKey(Optional.<ECKeyPair>absent())
.setTheirBaseKey(aliceBasePublicKey)
.setTheirEphemeralKey(aliceEphemeralPublicKey)
.setTheirIdentityKey(aliceIdentityPublicKey)
.setTheirPreKey(Optional.<ECPublicKey>absent())
.create();
SessionState session = new SessionState();
RatchetingSession.initializeSession(session, 2, bobBaseKey, aliceBasePublicKey,
bobEphemeralKey, aliceEphemeralPublicKey,
null, null,
bobIdentityKey, aliceIdentityPublicKey);
RatchetingSession.initializeSession(session, 2, parameters);
assertTrue(session.getLocalIdentityKey().equals(bobIdentityKey.getPublicKey()));
assertTrue(session.getRemoteIdentityKey().equals(aliceIdentityPublicKey));
@ -205,10 +217,18 @@ public class RatchetingSessionTest extends AndroidTestCase {
SessionState session = new SessionState();
RatchetingSession.initializeSession(session, 2, aliceBaseKey, bobBasePublicKey,
aliceEphemeralKey, bobEphemeralPublicKey,
null, null,
aliceIdentityKey, bobIdentityKey);
InitializationParameters parameters = InitializationParameters.newBuilder()
.setOurBaseKey(aliceBaseKey)
.setOurEphemeralKey(aliceEphemeralKey)
.setOurIdentityKey(aliceIdentityKey)
.setOurPreKey(Optional.<ECKeyPair>absent())
.setTheirBaseKey(bobBasePublicKey)
.setTheirEphemeralKey(bobEphemeralPublicKey)
.setTheirIdentityKey(bobIdentityKey)
.setTheirPreKey(Optional.<ECPublicKey>absent())
.create();
RatchetingSession.initializeSession(session, 2, parameters);
assertTrue(session.getLocalIdentityKey().equals(aliceIdentityKey.getPublicKey()));
assertTrue(session.getRemoteIdentityKey().equals(bobIdentityKey));

View File

@ -1,11 +1,14 @@
package org.whispersystems.test.ratchet;
import android.test.AndroidTestCase;
import android.util.Log;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.ratchet.VerifyKey;
import org.whispersystems.libaxolotl.util.Hex;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -62,9 +65,8 @@ public class VerifyKeyTest extends AndroidTestCase {
(byte)0x79, (byte)0x00, (byte)0xef, (byte)0x1b, (byte)0x40,
(byte)0x0f, (byte)0xdc};
byte[] expectedTag = {(byte)0x68, (byte)0x64, (byte)0xbf, (byte)0xbf, (byte)0x82,
(byte)0x34, (byte)0x20, (byte)0xdc};
byte[] expectedTag = {(byte)0x2f, (byte)0x77, (byte)0xaf, (byte)0xad, (byte)0x5b,
(byte)0x96, (byte)0xf5, (byte)0x3c};
ECPublicKey aliceBaseKey = Curve.decodePoint(aliceBaseKeyBytes, 0);
ECPublicKey alicePreKey = aliceBaseKey;
@ -75,8 +77,9 @@ public class VerifyKeyTest extends AndroidTestCase {
ECPublicKey bobIdentityKey = Curve.decodePoint(bobIdentityKeyBytes, 0);
VerifyKey verifyKey = new VerifyKey(key);
byte[] verification = verifyKey.generateVerification(aliceBaseKey, alicePreKey, aliceIdentityKey,
bobBaseKey, bobPreKey, bobIdentityKey);
byte[] verification = verifyKey.generateVerification(aliceBaseKey, Optional.of(alicePreKey),
aliceIdentityKey, bobBaseKey,
Optional.of(bobPreKey), bobIdentityKey);
assertTrue(MessageDigest.isEqual(verification, expectedTag));
}

View File

@ -9,19 +9,22 @@ import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.ratchet.RatchetingSession;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.libaxolotl.util.Hex;
import org.whispersystems.libaxolotl.util.KeyHelper;
import org.whispersystems.libaxolotl.util.Medium;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.security.MessageDigest;
import static org.whispersystems.libaxolotl.ratchet.RatchetingSession.InitializationParameters;
/**
* SessionBuilder is responsible for setting up encrypted sessions.
* Once a session has been established, {@link org.whispersystems.libaxolotl.SessionCipher}
@ -107,40 +110,33 @@ public class SessionBuilder {
private void processV3(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
int preKeyId = message.getPreKeyId();
int signedPreKeyId = message.getSignedPreKeyId();
ECPublicKey theirBaseKey = message.getBaseKey();
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
IdentityKey theirIdentityKey = message.getIdentityKey();
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
if (sessionRecord.hasSessionState(message.getMessageVersion(), theirBaseKey.serialize())) {
if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize())) {
Log.w(TAG, "We've already setup a session for this V3 message, letting bundled message fall through...");
return;
}
if (preKeyId >=0 && !preKeyStore.containsPreKey(preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
if (!signedPreKeyStore.containsSignedPreKey(signedPreKeyId))
throw new InvalidKeyIdException("No such device key: " + signedPreKeyId);
InitializationParameters.Builder parameters = InitializationParameters.newBuilder();
ECKeyPair ourBaseKey = signedPreKeyStore.loadSignedPreKey(signedPreKeyId).getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
ECKeyPair ourPreKey = preKeyId < 0 ? ourBaseKey : preKeyStore.loadPreKey(preKeyId).getKeyPair();
ECPublicKey theirPreKey = theirBaseKey;
IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair();
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
parameters.setTheirBaseKey(message.getBaseKey());
parameters.setTheirEphemeralKey(message.getWhisperMessage().getSenderEphemeral());
parameters.setTheirPreKey(Optional.of(message.getBaseKey()));
parameters.setTheirIdentityKey(message.getIdentityKey());
parameters.setOurBaseKey(signedPreKeyStore.loadSignedPreKey(message.getSignedPreKeyId()).getKeyPair());
parameters.setOurEphemeralKey(parameters.getOurBaseKey());
parameters.setOurIdentityKey(identityKeyStore.getIdentityKeyPair());
if (message.getPreKeyId() >= 0) parameters.setOurPreKey(Optional.of(preKeyStore.loadPreKey(message.getPreKeyId()).getKeyPair()));
else parameters.setOurPreKey(Optional.<ECKeyPair>absent());
if (!simultaneousInitiate) sessionRecord.reset();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
message.getMessageVersion(),
ourBaseKey, theirBaseKey,
ourEphemeralKey, theirEphemeralKey,
ourPreKey, theirPreKey,
ourIdentityKey, theirIdentityKey);
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
if (!MessageDigest.isEqual(sessionRecord.getSessionState().getVerification(), message.getVerification())) {
throw new InvalidKeyException("Verification secret mismatch!");
@ -148,51 +144,47 @@ public class SessionBuilder {
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setAliceBaseKey(theirBaseKey.serialize());
sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
if (preKeyId >= 0 && preKeyId != Medium.MAX_VALUE) {
preKeyStore.removePreKey(preKeyId);
if (message.getPreKeyId() >= 0 && message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}
}
private void processV2(PreKeyWhisperMessage message)
throws UntrustedIdentityException, InvalidKeyIdException, InvalidKeyException
{
int preKeyId = message.getPreKeyId();
ECPublicKey theirBaseKey = message.getBaseKey();
ECPublicKey theirEphemeralKey = message.getWhisperMessage().getSenderEphemeral();
IdentityKey theirIdentityKey = message.getIdentityKey();
if (!preKeyStore.containsPreKey(preKeyId) &&
if (!preKeyStore.containsPreKey(message.getPreKeyId()) &&
sessionStore.containsSession(recipientId, deviceId))
{
Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
return;
}
if (!preKeyStore.containsPreKey(preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
PreKeyRecord preKeyRecord = preKeyStore.loadPreKey(preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair();
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
InitializationParameters.Builder parameters = InitializationParameters.newBuilder();
parameters.setTheirBaseKey(message.getBaseKey());
parameters.setTheirEphemeralKey(message.getWhisperMessage().getSenderEphemeral());
parameters.setTheirPreKey(Optional.<ECPublicKey>absent());
parameters.setTheirIdentityKey(message.getIdentityKey());
parameters.setOurBaseKey(preKeyStore.loadPreKey(message.getPreKeyId()).getKeyPair());
parameters.setOurEphemeralKey(parameters.getOurBaseKey());
parameters.setOurPreKey(Optional.<ECKeyPair>absent());
parameters.setOurIdentityKey(identityKeyStore.getIdentityKeyPair());
if (!simultaneousInitiate) sessionRecord.reset();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
message.getMessageVersion(),
ourBaseKey, theirBaseKey,
ourEphemeralKey, theirEphemeralKey,
null, null,
ourIdentityKey, theirIdentityKey);
RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
@ -201,8 +193,8 @@ public class SessionBuilder {
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
if (preKeyId != Medium.MAX_VALUE) {
preKeyStore.removePreKey(preKeyId);
if (message.getPreKeyId() != Medium.MAX_VALUE) {
preKeyStore.removePreKey(message.getPreKeyId());
}
}
@ -218,6 +210,7 @@ public class SessionBuilder {
* trusted.
*/
public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException {
if (!identityKeyStore.isTrustedIdentity(recipientId, preKey.getIdentityKey())) {
throw new UntrustedIdentityException();
}
@ -230,25 +223,31 @@ public class SessionBuilder {
throw new InvalidKeyException("Invalid signature on device key!");
}
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
IdentityKeyPair ourIdentityKey = identityKeyStore.getIdentityKeyPair();
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
ECKeyPair ourEphemeralKey = Curve.generateKeyPair(true);
ECKeyPair ourPreKey = ourBaseKey;
if (preKey.getSignedPreKey() == null && preKey.getPreKey() == null) {
throw new InvalidKeyException("Both signed and unsigned prekeys are absent!");
}
IdentityKey theirIdentityKey = preKey.getIdentityKey();
ECPublicKey theirPreKey = preKey.getPreKey();
ECPublicKey theirBaseKey = preKey.getSignedPreKey() == null ? preKey.getPreKey() : preKey.getSignedPreKey();
ECPublicKey theirEphemeralKey = theirBaseKey;
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
InitializationParameters.Builder parameters = InitializationParameters.newBuilder();
ECKeyPair ourBaseKey = Curve.generateKeyPair(true);
parameters.setOurIdentityKey(identityKeyStore.getIdentityKeyPair());
parameters.setOurBaseKey(ourBaseKey);
parameters.setOurEphemeralKey(Curve.generateKeyPair(true));
parameters.setOurPreKey(Optional.of(parameters.getOurBaseKey()));
parameters.setTheirIdentityKey(preKey.getIdentityKey());
if (preKey.getSignedPreKey() == null) parameters.setTheirBaseKey(preKey.getPreKey());
else parameters.setTheirBaseKey(preKey.getSignedPreKey());
parameters.setTheirEphemeralKey(parameters.getTheirBaseKey());
parameters.setTheirPreKey(Optional.fromNullable(preKey.getPreKey()));
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
preKey.getSignedPreKey() == null ? 2 : 3,
ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourPreKey, theirPreKey,
ourIdentityKey, theirIdentityKey);
parameters.create());
sessionRecord.getSessionState().setPendingPreKey(preKey.getPreKeyId(), preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey());
sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.getLocalRegistrationId());
@ -283,11 +282,9 @@ public class SessionBuilder {
}
private KeyExchangeMessage processInitiate(KeyExchangeMessage message) throws InvalidKeyException {
ECKeyPair ourBaseKey, ourEphemeralKey;
IdentityKeyPair ourIdentityKey;
int flags = KeyExchangeMessage.RESPONSE_FLAG;
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
InitializationParameters.Builder parameters = InitializationParameters.newBuilder();
int flags = KeyExchangeMessage.RESPONSE_FLAG;
SessionRecord sessionRecord = sessionStore.loadSession(recipientId, deviceId);
if (message.getVersion() >= 3 &&
!Curve.verifySignature(message.getIdentityKey().getPublicKey(),
@ -299,32 +296,38 @@ public class SessionBuilder {
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w(TAG, "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPair(true);
ourEphemeralKey = Curve.generateKeyPair(true);
ourIdentityKey = identityKeyStore.getIdentityKeyPair();
parameters.setOurBaseKey(Curve.generateKeyPair(true));
parameters.setOurEphemeralKey(Curve.generateKeyPair(true));
parameters.setOurIdentityKey(identityKeyStore.getIdentityKeyPair());
parameters.setOurPreKey(Optional.<ECKeyPair>absent());
} else {
Log.w(TAG, "We already have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
parameters.setOurBaseKey(sessionRecord.getSessionState().getPendingKeyExchangeBaseKey());
parameters.setOurEphemeralKey(sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey());
parameters.setOurIdentityKey(sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey());
parameters.setOurPreKey(Optional.<ECKeyPair>absent());
flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG;
}
parameters.setTheirBaseKey(message.getBaseKey());
parameters.setTheirEphemeralKey(message.getEphemeralKey());
parameters.setTheirPreKey(Optional.<ECPublicKey>absent());
parameters.setTheirIdentityKey(message.getIdentityKey());
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourBaseKey, message.getBaseKey(),
ourIdentityKey, message.getIdentityKey());
parameters.create());
sessionStore.storeSession(recipientId, deviceId, sessionRecord);
identityKeyStore.saveIdentity(recipientId, message.getIdentityKey());
return new KeyExchangeMessage(sessionRecord.getSessionState().getSessionVersion(),
message.getSequence(), flags, ourBaseKey.getPublicKey(), null,
ourEphemeralKey.getPublicKey(), ourIdentityKey.getPublicKey(),
message.getSequence(), flags,
parameters.getOurBaseKey().getPublicKey(), null,
parameters.getOurEphemeralKey().getPublicKey(),
parameters.getOurIdentityKey().getPublicKey(),
sessionRecord.getSessionState().getVerification());
}
@ -342,18 +345,23 @@ public class SessionBuilder {
else return;
}
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
InitializationParameters parameters =
InitializationParameters.newBuilder()
.setOurBaseKey(sessionRecord.getSessionState().getPendingKeyExchangeBaseKey())
.setOurEphemeralKey(sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey())
.setOurPreKey(Optional.<ECKeyPair>absent())
.setOurIdentityKey(sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey())
.setTheirBaseKey(message.getBaseKey())
.setTheirEphemeralKey(message.getEphemeralKey())
.setTheirPreKey(Optional.<ECPublicKey>absent())
.setTheirIdentityKey(message.getIdentityKey())
.create();
sessionRecord.reset();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
Math.min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourBaseKey, message.getBaseKey(),
ourIdentityKey, message.getIdentityKey());
parameters);
if (sessionRecord.getSessionState().getSessionVersion() >= 3 &&
!MessageDigest.isEqual(message.getVerificationTag(),

View File

@ -26,6 +26,7 @@ import org.whispersystems.libaxolotl.kdf.HKDF;
import org.whispersystems.libaxolotl.state.SessionState;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.Pair;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -34,97 +35,58 @@ import java.util.Arrays;
public class RatchetingSession {
public static void initializeSession(SessionState sessionState,
int sessionVersion,
ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
ECPublicKey theirEphemeralKey,
ECKeyPair ourPreKey,
ECPublicKey theirPreKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
public static void initializeSession(SessionState sessionState,
int sessionVersion,
InitializationParameters parameters)
throws InvalidKeyException
{
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionState, sessionVersion, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey);
} else {
initializeSessionAsBob(sessionState, sessionVersion, ourBaseKey, theirBaseKey, ourEphemeralKey,
ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey);
}
if (isAlice(parameters)) initializeSessionAsAlice(sessionState, sessionVersion, parameters);
else initializeSessionAsBob(sessionState, sessionVersion, parameters);
sessionState.setSessionVersion(sessionVersion);
}
private static void initializeSessionAsAlice(SessionState sessionState,
int sessionVersion,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey,
ECKeyPair ourPreKey, ECPublicKey theirPreKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
InitializationParameters parameters)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPair(true);
DHEResult result = calculate4DHE(true, sessionVersion, ourBaseKey, theirBaseKey,
ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey);
DHEResult result = calculate4DHE(true, sessionVersion, parameters);
Pair<RootKey, ChainKey> sendingChain = result.getRootKey().createChain(theirEphemeralKey, sendingKey);
Pair<RootKey, ChainKey> sendingChain = result.getRootKey().createChain(parameters.getTheirEphemeralKey(), sendingKey);
sessionState.addReceiverChain(theirEphemeralKey, result.getChainKey());
sessionState.addReceiverChain(parameters.getTheirEphemeralKey(), result.getChainKey());
sessionState.setSenderChain(sendingKey, sendingChain.second());
sessionState.setRootKey(sendingChain.first());
if (sessionVersion >= 3) {
VerifyKey verifyKey = result.getVerifyKey();
byte[] verificationTag = verifyKey.generateVerification(ourBaseKey.getPublicKey(),
ourPreKey.getPublicKey(),
ourIdentityKey.getPublicKey().getPublicKey(),
theirBaseKey, theirPreKey,
theirIdentityKey.getPublicKey());
sessionState.setVerification(verificationTag);
sessionState.setVerification(calculateVerificationTag(true, result.getVerifyKey(), parameters));
}
}
private static void initializeSessionAsBob(SessionState sessionState,
int sessionVersion,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
ECKeyPair ourPreKey, ECPublicKey theirPreKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
InitializationParameters parameters)
throws InvalidKeyException
{
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
DHEResult result = calculate4DHE(false, sessionVersion, ourBaseKey, theirBaseKey,
ourPreKey, theirPreKey, ourIdentityKey, theirIdentityKey);
DHEResult result = calculate4DHE(false, sessionVersion, parameters);
sessionState.setSenderChain(ourEphemeralKey, result.getChainKey());
sessionState.setSenderChain(parameters.getOurEphemeralKey(), result.getChainKey());
sessionState.setRootKey(result.getRootKey());
if (sessionVersion >= 3) {
VerifyKey verifyKey = result.getVerifyKey();
byte[] verificationTag = verifyKey.generateVerification(theirBaseKey, theirPreKey,
theirIdentityKey.getPublicKey(),
ourBaseKey.getPublicKey(),
ourPreKey.getPublicKey(),
ourIdentityKey.getPublicKey().getPublicKey());
sessionState.setVerification(verificationTag);
sessionState.setVerification(calculateVerificationTag(false, result.getVerifyKey(), parameters));
}
}
private static DHEResult calculate4DHE(boolean isAlice, int sessionVersion,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourPreKey, ECPublicKey theirPreKey,
IdentityKeyPair ourIdentity, IdentityKey theirIdentity)
private static DHEResult calculate4DHE(boolean isAlice, int sessionVersion, InitializationParameters parameters)
throws InvalidKeyException
{
try {
@ -138,17 +100,22 @@ public class RatchetingSession {
}
if (isAlice) {
secrets.write(Curve.calculateAgreement(theirBaseKey, ourIdentity.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourBaseKey.getPrivateKey()));
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
parameters.getOurIdentityKey().getPrivateKey()));
secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
parameters.getOurBaseKey().getPrivateKey()));
} else {
secrets.write(Curve.calculateAgreement(theirIdentity.getPublicKey(), ourBaseKey.getPrivateKey()));
secrets.write(Curve.calculateAgreement(theirBaseKey, ourIdentity.getPrivateKey()));
secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
parameters.getOurBaseKey().getPrivateKey()));
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
parameters.getOurIdentityKey().getPrivateKey()));
}
secrets.write(Curve.calculateAgreement(theirBaseKey, ourBaseKey.getPrivateKey()));
secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
parameters.getOurBaseKey().getPrivateKey()));
if (sessionVersion >= 3) {
secrets.write(Curve.calculateAgreement(theirPreKey, ourPreKey.getPrivateKey()));
if (sessionVersion >= 3 && parameters.getTheirPreKey().isPresent() && parameters.getOurPreKey().isPresent()) {
secrets.write(Curve.calculateAgreement(parameters.getTheirPreKey().get(), parameters.getOurPreKey().get().getPrivateKey()));
}
byte[] derivedSecretBytes = kdf.deriveSecrets(secrets.toByteArray(), "WhisperText".getBytes(), 96);
@ -163,24 +130,191 @@ public class RatchetingSession {
}
}
private static boolean isAlice(ECPublicKey ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey ourEphemeralKey, ECPublicKey theirEphemeralKey)
private static byte[] calculateVerificationTag(boolean isAlice, VerifyKey verifyKey,
InitializationParameters parameters)
{
if (ourEphemeralKey.equals(ourBaseKey)) {
if (isAlice) {
return verifyKey.generateVerification(parameters.getOurBaseKey().getPublicKey(),
getPublicKey(parameters.getOurPreKey()),
parameters.getOurIdentityKey().getPublicKey().getPublicKey(),
parameters.getTheirBaseKey(),
parameters.getTheirPreKey(),
parameters.getTheirIdentityKey().getPublicKey());
} else {
return verifyKey.generateVerification(parameters.getTheirBaseKey(),
parameters.getTheirPreKey(),
parameters.getTheirIdentityKey().getPublicKey(),
parameters.getOurBaseKey().getPublicKey(),
getPublicKey(parameters.getOurPreKey()),
parameters.getOurIdentityKey().getPublicKey().getPublicKey());
}
}
private static boolean isAlice(InitializationParameters parameters)
{
if (parameters.getOurEphemeralKey().equals(parameters.getOurBaseKey())) {
return false;
}
if (theirEphemeralKey.equals(theirBaseKey)) {
if (parameters.getTheirEphemeralKey().equals(parameters.getTheirBaseKey())) {
return true;
}
return isLowEnd(ourBaseKey, theirBaseKey);
return isLowEnd(parameters.getOurBaseKey().getPublicKey(), parameters.getTheirBaseKey());
}
private static boolean isLowEnd(ECPublicKey ourKey, ECPublicKey theirKey) {
return ourKey.compareTo(theirKey) < 0;
}
private static Optional<ECPublicKey> getPublicKey(Optional<ECKeyPair> keyPair) {
if (keyPair.isPresent()) return Optional.of(keyPair.get().getPublicKey());
else return Optional.absent();
}
public static class InitializationParameters {
private final ECKeyPair ourBaseKey;
private final ECKeyPair ourEphemeralKey;
private final Optional<ECKeyPair> ourPreKey;
private final IdentityKeyPair ourIdentityKey;
private final ECPublicKey theirBaseKey;
private final ECPublicKey theirEphemeralKey;
private final Optional<ECPublicKey> theirPreKey;
private final IdentityKey theirIdentityKey;
public InitializationParameters(ECKeyPair ourBaseKey, ECKeyPair ourEphemeralKey,
Optional<ECKeyPair> ourPreKey, IdentityKeyPair ourIdentityKey,
ECPublicKey theirBaseKey, ECPublicKey theirEphemeralKey,
Optional<ECPublicKey> theirPreKey, IdentityKey theirIdentityKey)
{
this.ourBaseKey = ourBaseKey;
this.ourEphemeralKey = ourEphemeralKey;
this.ourPreKey = ourPreKey;
this.ourIdentityKey = ourIdentityKey;
this.theirBaseKey = theirBaseKey;
this.theirEphemeralKey = theirEphemeralKey;
this.theirPreKey = theirPreKey;
this.theirIdentityKey = theirIdentityKey;
}
public ECKeyPair getOurBaseKey() {
return ourBaseKey;
}
public ECKeyPair getOurEphemeralKey() {
return ourEphemeralKey;
}
public Optional<ECKeyPair> getOurPreKey() {
return ourPreKey;
}
public IdentityKeyPair getOurIdentityKey() {
return ourIdentityKey;
}
public ECPublicKey getTheirBaseKey() {
return theirBaseKey;
}
public ECPublicKey getTheirEphemeralKey() {
return theirEphemeralKey;
}
public Optional<ECPublicKey> getTheirPreKey() {
return theirPreKey;
}
public IdentityKey getTheirIdentityKey() {
return theirIdentityKey;
}
public static Builder newBuilder() {
return new Builder();
}
public static class Builder {
private ECKeyPair ourBaseKey;
private ECKeyPair ourEphemeralKey;
private Optional<ECKeyPair> ourPreKey;
private IdentityKeyPair ourIdentityKey;
private ECPublicKey theirBaseKey;
private ECPublicKey theirEphemeralKey;
private Optional<ECPublicKey> theirPreKey;
private IdentityKey theirIdentityKey;
public Builder setOurBaseKey(ECKeyPair ourBaseKey) {
this.ourBaseKey = ourBaseKey;
return this;
}
public ECKeyPair getOurBaseKey() {
return ourBaseKey;
}
public Builder setOurEphemeralKey(ECKeyPair ourEphemeralKey) {
this.ourEphemeralKey = ourEphemeralKey;
return this;
}
public ECKeyPair getOurEphemeralKey() {
return ourEphemeralKey;
}
public Builder setOurPreKey(Optional<ECKeyPair> ourPreKey) {
this.ourPreKey = ourPreKey;
return this;
}
public Builder setOurIdentityKey(IdentityKeyPair ourIdentityKey) {
this.ourIdentityKey = ourIdentityKey;
return this;
}
public IdentityKeyPair getOurIdentityKey() {
return ourIdentityKey;
}
public Builder setTheirBaseKey(ECPublicKey theirBaseKey) {
this.theirBaseKey = theirBaseKey;
return this;
}
public ECPublicKey getTheirBaseKey() {
return theirBaseKey;
}
public Builder setTheirEphemeralKey(ECPublicKey theirEphemeralKey) {
this.theirEphemeralKey = theirEphemeralKey;
return this;
}
public Builder setTheirPreKey(Optional<ECPublicKey> theirPreKey) {
this.theirPreKey = theirPreKey;
return this;
}
public Builder setTheirIdentityKey(IdentityKey theirIdentityKey) {
this.theirIdentityKey = theirIdentityKey;
return this;
}
public RatchetingSession.InitializationParameters create() {
if (ourBaseKey == null || ourEphemeralKey == null || ourPreKey == null || ourIdentityKey == null ||
theirBaseKey == null || theirEphemeralKey == null || theirPreKey == null || theirIdentityKey == null)
{
throw new IllegalArgumentException("All parameters not specified!");
}
return new RatchetingSession.InitializationParameters(ourBaseKey, ourEphemeralKey,
ourPreKey, ourIdentityKey,
theirBaseKey, theirEphemeralKey,
theirPreKey, theirIdentityKey);
}
}
}
private static class DHEResult {
private final RootKey rootKey;
private final ChainKey chainKey;

View File

@ -2,6 +2,7 @@ package org.whispersystems.libaxolotl.ratchet;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.util.ByteUtil;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@ -23,8 +24,12 @@ public class VerifyKey {
return key;
}
public byte[] generateVerification(ECPublicKey aliceBaseKey, ECPublicKey alicePreKey, ECPublicKey aliceIdentityKey,
ECPublicKey bobBaseKey, ECPublicKey bobPreKey, ECPublicKey bobIdentityKey)
public byte[] generateVerification(ECPublicKey aliceBaseKey,
Optional<ECPublicKey> alicePreKey,
ECPublicKey aliceIdentityKey,
ECPublicKey bobBaseKey,
Optional<ECPublicKey> bobPreKey,
ECPublicKey bobIdentityKey)
{
try {
Mac mac = Mac.getInstance("HmacSHA256");
@ -32,15 +37,15 @@ public class VerifyKey {
mac.update(VERIFICATION_INFO);
mac.update(aliceBaseKey.serialize());
mac.update(alicePreKey.serialize());
mac.update(aliceIdentityKey.serialize());
mac.update(bobBaseKey.serialize());
mac.update(bobIdentityKey.serialize());
if (bobPreKey != null) {
mac.update(bobPreKey.serialize());
if (alicePreKey.isPresent() && bobPreKey.isPresent()) {
mac.update(alicePreKey.get().serialize());
mac.update(bobPreKey.get().serialize());
}
mac.update(bobIdentityKey.serialize());
return ByteUtil.trim(mac.doFinal(), 8);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
import static org.whispersystems.libaxolotl.util.guava.Preconditions.checkNotNull;
import java.util.Collections;
import java.util.Set;
/**
* Implementation of an {@link Optional} not containing a reference.
*/
final class Absent extends Optional<Object> {
static final Absent INSTANCE = new Absent();
@Override public boolean isPresent() {
return false;
}
@Override public Object get() {
throw new IllegalStateException("value is absent");
}
@Override public Object or(Object defaultValue) {
return checkNotNull(defaultValue, "use orNull() instead of or(null)");
}
@SuppressWarnings("unchecked") // safe covariant cast
@Override public Optional<Object> or(Optional<?> secondChoice) {
return (Optional) checkNotNull(secondChoice);
}
@Override public Object or(Supplier<?> supplier) {
return checkNotNull(supplier.get(),
"use orNull() instead of a Supplier that returns null");
}
@Override public Object orNull() {
return null;
}
@Override public Set<Object> asSet() {
return Collections.emptySet();
}
@Override
public <V> Optional<V> transform(Function<? super Object, V> function) {
checkNotNull(function);
return Optional.absent();
}
@Override public boolean equals(Object object) {
return object == this;
}
@Override public int hashCode() {
return 0x598df91c;
}
@Override public String toString() {
return "Optional.absent()";
}
private Object readResolve() {
return INSTANCE;
}
private static final long serialVersionUID = 0;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
/**
* Determines an output value based on an input value.
*
* <p>See the Guava User Guide article on <a href=
* "http://code.google.com/p/guava-libraries/wiki/FunctionalExplained">the use of {@code
* Function}</a>.
*
* @author Kevin Bourrillion
* @since 2.0 (imported from Google Collections Library)
*/
public interface Function<F, T> {
/**
* Returns the result of applying this function to {@code input}. This method is <i>generally
* expected</i>, but not absolutely required, to have the following properties:
*
* <ul>
* <li>Its execution does not cause any observable side effects.
* <li>The computation is <i>consistent with equals</i>; that is, {@link Objects#equal
* Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a),
* function.apply(b))}.
* </ul>
*
* @throws NullPointerException if {@code input} is null and this function does not accept null
* arguments
*/
T apply(F input);
/**
* Indicates whether another object is equal to this function.
*
* <p>Most implementations will have no reason to override the behavior of {@link Object#equals}.
* However, an implementation may also choose to return {@code true} whenever {@code object} is a
* {@link Function} that it considers <i>interchangeable</i> with this one. "Interchangeable"
* <i>typically</i> means that {@code Objects.equal(this.apply(f), that.apply(f))} is true for all
* {@code f} of type {@code F}. Note that a {@code false} result from this method does not imply
* that the functions are known <i>not</i> to be interchangeable.
*/
@Override
boolean equals(Object object);
}

View File

@ -0,0 +1,232 @@
/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
import static org.whispersystems.libaxolotl.util.guava.Preconditions.checkNotNull;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Set;
/**
* An immutable object that may contain a non-null reference to another object. Each
* instance of this type either contains a non-null reference, or contains nothing (in
* which case we say that the reference is "absent"); it is never said to "contain {@code
* null}".
*
* <p>A non-null {@code Optional<T>} reference can be used as a replacement for a nullable
* {@code T} reference. It allows you to represent "a {@code T} that must be present" and
* a "a {@code T} that might be absent" as two distinct types in your program, which can
* aid clarity.
*
* <p>Some uses of this class include
*
* <ul>
* <li>As a method return type, as an alternative to returning {@code null} to indicate
* that no value was available
* <li>To distinguish between "unknown" (for example, not present in a map) and "known to
* have no value" (present in the map, with value {@code Optional.absent()})
* <li>To wrap nullable references for storage in a collection that does not support
* {@code null} (though there are
* <a href="http://code.google.com/p/guava-libraries/wiki/LivingWithNullHostileCollections">
* several other approaches to this</a> that should be considered first)
* </ul>
*
* <p>A common alternative to using this class is to find or create a suitable
* <a href="http://en.wikipedia.org/wiki/Null_Object_pattern">null object</a> for the
* type in question.
*
* <p>This class is not intended as a direct analogue of any existing "option" or "maybe"
* construct from other programming environments, though it may bear some similarities.
*
* <p>See the Guava User Guide article on <a
* href="http://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained#Optional">
* using {@code Optional}</a>.
*
* @param <T> the type of instance that can be contained. {@code Optional} is naturally
* covariant on this type, so it is safe to cast an {@code Optional<T>} to {@code
* Optional<S>} for any supertype {@code S} of {@code T}.
* @author Kurt Alfred Kluever
* @author Kevin Bourrillion
* @since 10.0
*/
public abstract class Optional<T> implements Serializable {
/**
* Returns an {@code Optional} instance with no contained reference.
*/
@SuppressWarnings("unchecked")
public static <T> Optional<T> absent() {
return (Optional<T>) Absent.INSTANCE;
}
/**
* Returns an {@code Optional} instance containing the given non-null reference.
*/
public static <T> Optional<T> of(T reference) {
return new Present<T>(checkNotNull(reference));
}
/**
* If {@code nullableReference} is non-null, returns an {@code Optional} instance containing that
* reference; otherwise returns {@link Optional#absent}.
*/
public static <T> Optional<T> fromNullable(T nullableReference) {
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
}
Optional() {}
/**
* Returns {@code true} if this holder contains a (non-null) instance.
*/
public abstract boolean isPresent();
/**
* Returns the contained instance, which must be present. If the instance might be
* absent, use {@link #or(Object)} or {@link #orNull} instead.
*
* @throws IllegalStateException if the instance is absent ({@link #isPresent} returns
* {@code false})
*/
public abstract T get();
/**
* Returns the contained instance if it is present; {@code defaultValue} otherwise. If
* no default value should be required because the instance is known to be present, use
* {@link #get()} instead. For a default value of {@code null}, use {@link #orNull}.
*
* <p>Note about generics: The signature {@code public T or(T defaultValue)} is overly
* restrictive. However, the ideal signature, {@code public <S super T> S or(S)}, is not legal
* Java. As a result, some sensible operations involving subtypes are compile errors:
* <pre> {@code
*
* Optional<Integer> optionalInt = getSomeOptionalInt();
* Number value = optionalInt.or(0.5); // error
*
* FluentIterable<? extends Number> numbers = getSomeNumbers();
* Optional<? extends Number> first = numbers.first();
* Number value = first.or(0.5); // error}</pre>
*
* As a workaround, it is always safe to cast an {@code Optional<? extends T>} to {@code
* Optional<T>}. Casting either of the above example {@code Optional} instances to {@code
* Optional<Number>} (where {@code Number} is the desired output type) solves the problem:
* <pre> {@code
*
* Optional<Number> optionalInt = (Optional) getSomeOptionalInt();
* Number value = optionalInt.or(0.5); // fine
*
* FluentIterable<? extends Number> numbers = getSomeNumbers();
* Optional<Number> first = (Optional) numbers.first();
* Number value = first.or(0.5); // fine}</pre>
*/
public abstract T or(T defaultValue);
/**
* Returns this {@code Optional} if it has a value present; {@code secondChoice}
* otherwise.
*/
public abstract Optional<T> or(Optional<? extends T> secondChoice);
/**
* Returns the contained instance if it is present; {@code supplier.get()} otherwise. If the
* supplier returns {@code null}, a {@link NullPointerException} is thrown.
*
* @throws NullPointerException if the supplier returns {@code null}
*/
public abstract T or(Supplier<? extends T> supplier);
/**
* Returns the contained instance if it is present; {@code null} otherwise. If the
* instance is known to be present, use {@link #get()} instead.
*/
public abstract T orNull();
/**
* Returns an immutable singleton {@link Set} whose only element is the contained instance
* if it is present; an empty immutable {@link Set} otherwise.
*
* @since 11.0
*/
public abstract Set<T> asSet();
/**
* If the instance is present, it is transformed with the given {@link Function}; otherwise,
* {@link Optional#absent} is returned. If the function returns {@code null}, a
* {@link NullPointerException} is thrown.
*
* @throws NullPointerException if the function returns {@code null}
*
* @since 12.0
*/
public abstract <V> Optional<V> transform(Function<? super T, V> function);
/**
* Returns {@code true} if {@code object} is an {@code Optional} instance, and either
* the contained references are {@linkplain Object#equals equal} to each other or both
* are absent. Note that {@code Optional} instances of differing parameterized types can
* be equal.
*/
@Override public abstract boolean equals(Object object);
/**
* Returns a hash code for this instance.
*/
@Override public abstract int hashCode();
/**
* Returns a string representation for this instance. The form of this string
* representation is unspecified.
*/
@Override public abstract String toString();
/**
* Returns the value of each present instance from the supplied {@code optionals}, in order,
* skipping over occurrences of {@link Optional#absent}. Iterators are unmodifiable and are
* evaluated lazily.
*
* @since 11.0 (generics widened in 13.0)
*/
// public static <T> Iterable<T> presentInstances(
// final Iterable<? extends Optional<? extends T>> optionals) {
// checkNotNull(optionals);
// return new Iterable<T>() {
// @Override public Iterator<T> iterator() {
// return new AbstractIterator<T>() {
// private final Iterator<? extends Optional<? extends T>> iterator =
// checkNotNull(optionals.iterator());
//
// @Override protected T computeNext() {
// while (iterator.hasNext()) {
// Optional<? extends T> optional = iterator.next();
// if (optional.isPresent()) {
// return optional.get();
// }
// }
// return endOfData();
// }
// };
// };
// };
// }
private static final long serialVersionUID = 0;
}

View File

@ -0,0 +1,447 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
import java.util.NoSuchElementException;
/**
* Simple static methods to be called at the start of your own methods to verify
* correct arguments and state. This allows constructs such as
* <pre>
* if (count <= 0) {
* throw new IllegalArgumentException("must be positive: " + count);
* }</pre>
*
* to be replaced with the more compact
* <pre>
* checkArgument(count > 0, "must be positive: %s", count);</pre>
*
* Note that the sense of the expression is inverted; with {@code Preconditions}
* you declare what you expect to be <i>true</i>, just as you do with an
* <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html">
* {@code assert}</a> or a JUnit {@code assertTrue} call.
*
* <p><b>Warning:</b> only the {@code "%s"} specifier is recognized as a
* placeholder in these messages, not the full range of {@link
* String#format(String, Object[])} specifiers.
*
* <p>Take care not to confuse precondition checking with other similar types
* of checks! Precondition exceptions -- including those provided here, but also
* {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link
* UnsupportedOperationException} and others -- are used to signal that the
* <i>calling method</i> has made an error. This tells the caller that it should
* not have invoked the method when it did, with the arguments it did, or
* perhaps ever. Postcondition or other invariant failures should not throw
* these types of exceptions.
*
* <p>See the Guava User Guide on <a href=
* "http://code.google.com/p/guava-libraries/wiki/PreconditionsExplained">
* using {@code Preconditions}</a>.
*
* @author Kevin Bourrillion
* @since 2.0 (imported from Google Collections Library)
*/
public final class Preconditions {
private Preconditions() {}
/**
* Ensures the truth of an expression involving one or more parameters to the
* calling method.
*
* @param expression a boolean expression
* @throws IllegalArgumentException if {@code expression} is false
*/
public static void checkArgument(boolean expression) {
if (!expression) {
throw new IllegalArgumentException();
}
}
/**
* Ensures the truth of an expression involving one or more parameters to the
* calling method.
*
* @param expression a boolean expression
* @param errorMessage the exception message to use if the check fails; will
* be converted to a string using {@link String#valueOf(Object)}
* @throws IllegalArgumentException if {@code expression} is false
*/
public static void checkArgument(
boolean expression, Object errorMessage) {
if (!expression) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
}
/**
* Ensures the truth of an expression involving one or more parameters to the
* calling method.
*
* @param expression a boolean expression
* @param errorMessageTemplate a template for the exception message should the
* check fail. The message is formed by replacing each {@code %s}
* placeholder in the template with an argument. These are matched by
* position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
* Unmatched arguments will be appended to the formatted message in square
* braces. Unmatched placeholders will be left as-is.
* @param errorMessageArgs the arguments to be substituted into the message
* template. Arguments are converted to strings using
* {@link String#valueOf(Object)}.
* @throws IllegalArgumentException if {@code expression} is false
* @throws NullPointerException if the check fails and either {@code
* errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
* this happen)
*/
public static void checkArgument(boolean expression,
String errorMessageTemplate,
Object... errorMessageArgs) {
if (!expression) {
throw new IllegalArgumentException(
format(errorMessageTemplate, errorMessageArgs));
}
}
/**
* Ensures the truth of an expression involving the state of the calling
* instance, but not involving any parameters to the calling method.
*
* @param expression a boolean expression
* @throws IllegalStateException if {@code expression} is false
*/
public static void checkState(boolean expression) {
if (!expression) {
throw new IllegalStateException();
}
}
/**
* Ensures the truth of an expression involving the state of the calling
* instance, but not involving any parameters to the calling method.
*
* @param expression a boolean expression
* @param errorMessage the exception message to use if the check fails; will
* be converted to a string using {@link String#valueOf(Object)}
* @throws IllegalStateException if {@code expression} is false
*/
public static void checkState(
boolean expression, Object errorMessage) {
if (!expression) {
throw new IllegalStateException(String.valueOf(errorMessage));
}
}
/**
* Ensures the truth of an expression involving the state of the calling
* instance, but not involving any parameters to the calling method.
*
* @param expression a boolean expression
* @param errorMessageTemplate a template for the exception message should the
* check fail. The message is formed by replacing each {@code %s}
* placeholder in the template with an argument. These are matched by
* position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
* Unmatched arguments will be appended to the formatted message in square
* braces. Unmatched placeholders will be left as-is.
* @param errorMessageArgs the arguments to be substituted into the message
* template. Arguments are converted to strings using
* {@link String#valueOf(Object)}.
* @throws IllegalStateException if {@code expression} is false
* @throws NullPointerException if the check fails and either {@code
* errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
* this happen)
*/
public static void checkState(boolean expression,
String errorMessageTemplate,
Object... errorMessageArgs) {
if (!expression) {
throw new IllegalStateException(
format(errorMessageTemplate, errorMessageArgs));
}
}
/**
* Ensures that an object reference passed as a parameter to the calling
* method is not null.
*
* @param reference an object reference
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
*/
public static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
/**
* Ensures that an object reference passed as a parameter to the calling
* method is not null.
*
* @param reference an object reference
* @param errorMessage the exception message to use if the check fails; will
* be converted to a string using {@link String#valueOf(Object)}
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
*/
public static <T> T checkNotNull(T reference, Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
}
return reference;
}
/**
* Ensures that an object reference passed as a parameter to the calling
* method is not null.
*
* @param reference an object reference
* @param errorMessageTemplate a template for the exception message should the
* check fail. The message is formed by replacing each {@code %s}
* placeholder in the template with an argument. These are matched by
* position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
* Unmatched arguments will be appended to the formatted message in square
* braces. Unmatched placeholders will be left as-is.
* @param errorMessageArgs the arguments to be substituted into the message
* template. Arguments are converted to strings using
* {@link String#valueOf(Object)}.
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
*/
public static <T> T checkNotNull(T reference,
String errorMessageTemplate,
Object... errorMessageArgs) {
if (reference == null) {
// If either of these parameters is null, the right thing happens anyway
throw new NullPointerException(
format(errorMessageTemplate, errorMessageArgs));
}
return reference;
}
/*
* All recent hotspots (as of 2009) *really* like to have the natural code
*
* if (guardExpression) {
* throw new BadException(messageExpression);
* }
*
* refactored so that messageExpression is moved to a separate
* String-returning method.
*
* if (guardExpression) {
* throw new BadException(badMsg(...));
* }
*
* The alternative natural refactorings into void or Exception-returning
* methods are much slower. This is a big deal - we're talking factors of
* 2-8 in microbenchmarks, not just 10-20%. (This is a hotspot optimizer
* bug, which should be fixed, but that's a separate, big project).
*
* The coding pattern above is heavily used in java.util, e.g. in ArrayList.
* There is a RangeCheckMicroBenchmark in the JDK that was used to test this.
*
* But the methods in this class want to throw different exceptions,
* depending on the args, so it appears that this pattern is not directly
* applicable. But we can use the ridiculous, devious trick of throwing an
* exception in the middle of the construction of another exception.
* Hotspot is fine with that.
*/
/**
* Ensures that {@code index} specifies a valid <i>element</i> in an array,
* list or string of size {@code size}. An element index may range from zero,
* inclusive, to {@code size}, exclusive.
*
* @param index a user-supplied index identifying an element of an array, list
* or string
* @param size the size of that array, list or string
* @return the value of {@code index}
* @throws IndexOutOfBoundsException if {@code index} is negative or is not
* less than {@code size}
* @throws IllegalArgumentException if {@code size} is negative
*/
public static int checkElementIndex(int index, int size) {
return checkElementIndex(index, size, "index");
}
/**
* Ensures that {@code index} specifies a valid <i>element</i> in an array,
* list or string of size {@code size}. An element index may range from zero,
* inclusive, to {@code size}, exclusive.
*
* @param index a user-supplied index identifying an element of an array, list
* or string
* @param size the size of that array, list or string
* @param desc the text to use to describe this index in an error message
* @return the value of {@code index}
* @throws IndexOutOfBoundsException if {@code index} is negative or is not
* less than {@code size}
* @throws IllegalArgumentException if {@code size} is negative
*/
public static int checkElementIndex(
int index, int size, String desc) {
// Carefully optimized for execution by hotspot (explanatory comment above)
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
}
return index;
}
private static String badElementIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", desc, index);
} else if (size < 0) {
throw new IllegalArgumentException("negative size: " + size);
} else { // index >= size
return format("%s (%s) must be less than size (%s)", desc, index, size);
}
}
/**
* Ensures that {@code index} specifies a valid <i>position</i> in an array,
* list or string of size {@code size}. A position index may range from zero
* to {@code size}, inclusive.
*
* @param index a user-supplied index identifying a position in an array, list
* or string
* @param size the size of that array, list or string
* @return the value of {@code index}
* @throws IndexOutOfBoundsException if {@code index} is negative or is
* greater than {@code size}
* @throws IllegalArgumentException if {@code size} is negative
*/
public static int checkPositionIndex(int index, int size) {
return checkPositionIndex(index, size, "index");
}
/**
* Ensures that {@code index} specifies a valid <i>position</i> in an array,
* list or string of size {@code size}. A position index may range from zero
* to {@code size}, inclusive.
*
* @param index a user-supplied index identifying a position in an array, list
* or string
* @param size the size of that array, list or string
* @param desc the text to use to describe this index in an error message
* @return the value of {@code index}
* @throws IndexOutOfBoundsException if {@code index} is negative or is
* greater than {@code size}
* @throws IllegalArgumentException if {@code size} is negative
*/
public static int checkPositionIndex(
int index, int size, String desc) {
// Carefully optimized for execution by hotspot (explanatory comment above)
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
}
return index;
}
private static String badPositionIndex(int index, int size, String desc) {
if (index < 0) {
return format("%s (%s) must not be negative", desc, index);
} else if (size < 0) {
throw new IllegalArgumentException("negative size: " + size);
} else { // index > size
return format("%s (%s) must not be greater than size (%s)",
desc, index, size);
}
}
/**
* Ensures that {@code start} and {@code end} specify a valid <i>positions</i>
* in an array, list or string of size {@code size}, and are in order. A
* position index may range from zero to {@code size}, inclusive.
*
* @param start a user-supplied index identifying a starting position in an
* array, list or string
* @param end a user-supplied index identifying a ending position in an array,
* list or string
* @param size the size of that array, list or string
* @throws IndexOutOfBoundsException if either index is negative or is
* greater than {@code size}, or if {@code end} is less than {@code start}
* @throws IllegalArgumentException if {@code size} is negative
*/
public static void checkPositionIndexes(int start, int end, int size) {
// Carefully optimized for execution by hotspot (explanatory comment above)
if (start < 0 || end < start || end > size) {
throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
}
}
private static String badPositionIndexes(int start, int end, int size) {
if (start < 0 || start > size) {
return badPositionIndex(start, size, "start index");
}
if (end < 0 || end > size) {
return badPositionIndex(end, size, "end index");
}
// end < start
return format("end index (%s) must not be less than start index (%s)",
end, start);
}
/**
* Substitutes each {@code %s} in {@code template} with an argument. These
* are matched by position - the first {@code %s} gets {@code args[0]}, etc.
* If there are more arguments than placeholders, the unmatched arguments will
* be appended to the end of the formatted message in square braces.
*
* @param template a non-null string containing 0 or more {@code %s}
* placeholders.
* @param args the arguments to be substituted into the message
* template. Arguments are converted to strings using
* {@link String#valueOf(Object)}. Arguments can be null.
*/
static String format(String template,
Object... args) {
template = String.valueOf(template); // null -> "null"
// start substituting the arguments into the '%s' placeholders
StringBuilder builder = new StringBuilder(
template.length() + 16 * args.length);
int templateStart = 0;
int i = 0;
while (i < args.length) {
int placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template.substring(templateStart, placeholderStart));
builder.append(args[i++]);
templateStart = placeholderStart + 2;
}
builder.append(template.substring(templateStart));
// if we run out of placeholders, append the extra args in square braces
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while (i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}
return builder.toString();
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
import static org.whispersystems.libaxolotl.util.guava.Preconditions.checkNotNull;
import java.util.Collections;
import java.util.Set;
/**
* Implementation of an {@link Optional} containing a reference.
*/
final class Present<T> extends Optional<T> {
private final T reference;
Present(T reference) {
this.reference = reference;
}
@Override public boolean isPresent() {
return true;
}
@Override public T get() {
return reference;
}
@Override public T or(T defaultValue) {
checkNotNull(defaultValue, "use orNull() instead of or(null)");
return reference;
}
@Override public Optional<T> or(Optional<? extends T> secondChoice) {
checkNotNull(secondChoice);
return this;
}
@Override public T or(Supplier<? extends T> supplier) {
checkNotNull(supplier);
return reference;
}
@Override public T orNull() {
return reference;
}
@Override public Set<T> asSet() {
return Collections.singleton(reference);
}
@Override public <V> Optional<V> transform(Function<? super T, V> function) {
return new Present<V>(checkNotNull(function.apply(reference),
"Transformation function cannot return null."));
}
@Override public boolean equals(Object object) {
if (object instanceof Present) {
Present<?> other = (Present<?>) object;
return reference.equals(other.reference);
}
return false;
}
@Override public int hashCode() {
return 0x598df91c + reference.hashCode();
}
@Override public String toString() {
return "Optional.of(" + reference + ")";
}
private static final long serialVersionUID = 0;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.whispersystems.libaxolotl.util.guava;
/**
* A class that can supply objects of a single type. Semantically, this could
* be a factory, generator, builder, closure, or something else entirely. No
* guarantees are implied by this interface.
*
* @author Harry Heymann
* @since 2.0 (imported from Google Collections Library)
*/
public interface Supplier<T> {
/**
* Retrieves an instance of the appropriate type. The returned object may or
* may not be a new instance, depending on the implementation.
*
* @return an instance of the appropriate type
*/
T get();
}