private May<uint> processV3(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {

            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 May<uint>.NoValue;
            }

            ECKeyPair ourSignedPreKey = signedPreKeyStore.LoadSignedPreKey(message.getSignedPreKeyId()).getKeyPair();

            BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();

            parameters.setTheirBaseKey(message.getBaseKey())
                      .setTheirIdentityKey(message.getIdentityKey())
                      .setOurIdentityKey(identityKeyStore.GetIdentityKeyPair())
                      .setOurSignedPreKey(ourSignedPreKey)
                      .setOurRatchetKey(ourSignedPreKey);

            if (message.getPreKeyId().HasValue)
            {
                parameters.setOurOneTimePreKey(new May<ECKeyPair>(preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair()));
            }
            else
            {
                parameters.setOurOneTimePreKey(May<ECKeyPair>.NoValue);
            }

            if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();

            RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());

            sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId());
            sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
            sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());

            if (message.getPreKeyId().HasValue && message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE)
            {
                return message.getPreKeyId();
            }
            else
            {
                return May<uint>.NoValue;
            }
        }
        private May<uint> processV2(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            if (!message.getPreKeyId().HasValue)
            {
                throw new InvalidKeyIdException("V2 message requires one time prekey id!");
            }

            if (!preKeyStore.ContainsPreKey(message.getPreKeyId().ForceGetValue()) &&
                sessionStore.ContainsSession(remoteAddress))
            {
                //Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through...");
                return May<uint>.NoValue; //May.absent();
            }

            ECKeyPair ourPreKey = preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair();

            BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder();

            parameters.setOurIdentityKey(identityKeyStore.GetIdentityKeyPair())
                          .setOurSignedPreKey(ourPreKey)
                          .setOurRatchetKey(ourPreKey)
                          .setOurOneTimePreKey(May<ECKeyPair>.NoValue) //absent
                          .setTheirIdentityKey(message.getIdentityKey())
                          .setTheirBaseKey(message.getBaseKey());

            if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState();

            RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create());

            sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId());
            sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
            sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize());

            if (message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE)
            {
                return message.getPreKeyId();
            }
            else
            {
                return May<uint>.NoValue; // May.absent();
            }
        }
        /**
         * Build a new session from a received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
         *
         * After a session is constructed in this way, the embedded {@link org.whispersystems.libaxolotl.protocol.WhisperMessage}
         * can be decrypted.
         *
         * @param message The received {@link org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage}.
         * @throws org.whispersystems.libaxolotl.InvalidKeyIdException when there is no local
         *                                                             {@link org.whispersystems.libaxolotl.state.PreKeyRecord}
         *                                                             that corresponds to the PreKey ID in
         *                                                             the message.
         * @throws org.whispersystems.libaxolotl.InvalidKeyException when the message is formatted incorrectly.
         * @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the {@link IdentityKey} of the sender is untrusted.
         */
        /*package*/
        internal May<uint>  process(SessionRecord sessionRecord, PreKeyWhisperMessage message)
        {
            uint messageVersion = message.getMessageVersion();
            IdentityKey theirIdentityKey = message.getIdentityKey();

            May<uint> unsignedPreKeyId;

            if (!identityKeyStore.IsTrustedIdentity(remoteAddress.getName(), theirIdentityKey))
            {
                throw new UntrustedIdentityException(remoteAddress.getName(), theirIdentityKey);
            }

            switch (messageVersion)
            {
                case 2: unsignedPreKeyId = processV2(sessionRecord, message); break;
                case 3: unsignedPreKeyId = processV3(sessionRecord, message); break;
                default: throw new Exception("Unknown version: " + messageVersion);
            }

            identityKeyStore.SaveIdentity(remoteAddress.getName(), theirIdentityKey);
            return unsignedPreKeyId;
        }