/**
* Copyright (c) 2013, Redsolution LTD. All rights reserved.
*
* This file is part of Xabber project; you can redistribute it and/or
* modify it under the terms of the GNU General Public License, Version 3.
*
* Xabber is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License,
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.xabber.android.data.extension.ssn;
import com.xabber.android.data.NetworkException;
import com.xabber.android.data.account.AccountItem;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.account.listeners.OnAccountRemovedListener;
import com.xabber.android.data.connection.ConnectionItem;
import com.xabber.android.data.connection.StanzaSender;
import com.xabber.android.data.connection.TLSMode;
import com.xabber.android.data.connection.listeners.OnPacketListener;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.NestedMap;
import com.xabber.android.data.log.LogManager;
import com.xabber.xmpp.archive.OtrMode;
import com.xabber.xmpp.ssn.DisclosureValue;
import com.xabber.xmpp.ssn.Feature;
import com.xabber.xmpp.ssn.LoggingValue;
import com.xabber.xmpp.ssn.SecurityValue;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.util.Collection;
/**
* Stanza Session Negotiation.
* <p/>
* http://xmpp.org/extensions/xep-0155.html
*
* @author alexander.ivanov
*/
public class SSNManager implements OnPacketListener, OnAccountRemovedListener {
/**
* Session state for the session id in account.
*/
private final NestedMap<SessionState> sessionStates;
/**
* OTR encryption mode for the session id in account.
*/
private final NestedMap<OtrMode> sessionOtrs;
private static SSNManager instance;
public static SSNManager getInstance() {
if (instance == null) {
instance = new SSNManager();
}
return instance;
}
private SSNManager() {
sessionStates = new NestedMap<>();
sessionOtrs = new NestedMap<>();
}
@Override
public void onAccountRemoved(AccountItem accountItem) {
sessionStates.clear(accountItem.getAccount().toString());
sessionOtrs.clear(accountItem.getAccount().toString());
}
@Override
public void onStanza(ConnectionItem connection, Stanza stanza) {
Jid from = stanza.getFrom();
if (from == null) {
return;
}
if (!(connection instanceof AccountItem) || !(stanza instanceof Message)) {
return;
}
AccountJid account = ((AccountItem) connection).getAccount();
Message message = (Message) stanza;
String session = message.getThread();
if (session == null) {
return;
}
for (ExtensionElement packetExtension : stanza.getExtensions()) {
if (packetExtension instanceof Feature) {
Feature feature = (Feature) packetExtension;
if (!feature.isValid()) {
continue;
}
DataForm.Type dataFormType = feature.getDataFormType();
if (dataFormType == DataForm.Type.form) {
onFormReceived(account, from, session, feature);
} else if (dataFormType == DataForm.Type.submit) {
onSubmitReceived(account, from, session, feature);
} else if (dataFormType == DataForm.Type.result) {
onResultReceived(account, session, feature);
}
}
}
}
private void onFormReceived(AccountJid account, Jid from, String session, Feature feature) {
OtrMode otrMode = getOtrMode(account, session);
boolean cancel = false;
Collection<DisclosureValue> disclosureValues = feature
.getDisclosureOptions();
DisclosureValue disclosureValue = DisclosureValue.never;
if (disclosureValues == null)
disclosureValue = null;
else if (!disclosureValues.contains(disclosureValue))
cancel = true;
Collection<SecurityValue> securityValues = feature.getSecurityOptions();
SecurityValue securityValue;
AccountItem accountItem = AccountManager.getInstance().getAccount(account);
if (accountItem != null
&& accountItem.getConnectionSettings().getTlsMode() == TLSMode.required) {
securityValue = SecurityValue.c2s;
} else {
securityValue = SecurityValue.none;
}
if (securityValues == null)
securityValue = null;
else if (!securityValues.contains(securityValue))
cancel = true;
Collection<LoggingValue> loggingValues = feature.getLoggingOptions();
LoggingValue loggingValue;
if (loggingValues == null)
loggingValue = null;
else {
loggingValue = otrMode.selectLoggingValue(loggingValues);
if (loggingValue == null)
cancel = true;
}
if (cancel) {
DataForm dataForm = Feature.createDataForm(DataForm.Type.submit);
if (feature.getAcceptValue() != null) {
Feature.addAcceptField(dataForm, false);
sessionStates.remove(account.toString(), session);
} else {
Feature.addRenegotiateField(dataForm, false);
}
sendFeature(account, from, session, new Feature(dataForm));
return;
}
DataForm dataForm = Feature.createDataForm(DataForm.Type.submit);
if (feature.getAcceptValue() != null)
Feature.addAcceptField(dataForm, true);
else
Feature.addRenegotiateField(dataForm, true);
if (disclosureValue != null)
Feature.addDisclosureField(dataForm, null, disclosureValue);
if (securityValue != null)
Feature.addSecurityField(dataForm, null, securityValue);
if (loggingValue != null) {
Feature.addLoggingField(dataForm, null, loggingValue);
}
sessionStates.put(account.toString(), session, SessionState.active);
sendFeature(account, from, session, new Feature(dataForm));
}
private void onSubmitReceived(AccountJid account, Jid from, String session, Feature feature) {
if (feature.getTerminateValue() != null) {
onTerminateReceived(account, from, session);
return;
}
if (!isAccepted(account, session, feature)) {
return;
}
OtrMode otrMode = getOtrMode(account, session);
LoggingValue loggingValue = feature.getLoggingValue();
if (loggingValue == null || otrMode.acceptLoggingValue(loggingValue)) {
DataForm dataForm = Feature.createDataForm(DataForm.Type.result);
if (feature.getAcceptValue() != null) {
Feature.addAcceptField(dataForm, true);
} else {
Feature.addRenegotiateField(dataForm, true);
}
sendFeature(account, from, session, new Feature(dataForm));
sessionStates.put(account.toString(), session, SessionState.active);
} else {
DataForm dataForm = Feature.createDataForm(DataForm.Type.result);
if (feature.getAcceptValue() != null) {
Feature.addAcceptField(dataForm, false);
sessionStates.remove(account.toString(), session);
} else {
Feature.addRenegotiateField(dataForm, false);
}
sendFeature(account, from, session, new Feature(dataForm));
}
}
private void onTerminateReceived(AccountJid account, Jid from, String session) {
if (sessionStates.get(account.toString(), session) == null) {
return;
}
sessionStates.remove(account.toString(), session);
DataForm dataForm = Feature.createDataForm(DataForm.Type.result);
Feature.addTerminateField(dataForm);
sendFeature(account, from, session, new Feature(dataForm));
}
private OtrMode getOtrMode(AccountJid account, String session) {
OtrMode otrMode = sessionOtrs.get(account.toString(), session);
if (otrMode != null) {
return otrMode;
}
return OtrMode.concede;
}
private boolean isAccepted(AccountJid account, String session, Feature feature) {
Boolean accept = feature.getAcceptValue();
if (accept != null && !accept) {
sessionStates.remove(account.toString(), session);
return false;
}
Boolean renegotiate = feature.getRenegotiateValue();
if (renegotiate != null && !renegotiate) {
if (sessionStates.get(account.toString(), session) == SessionState.renegotiation) {
sessionStates.put(account.toString(), session, SessionState.active);
}
return false;
}
return true;
}
private void onResultReceived(AccountJid account, String session, Feature feature) {
isAccepted(account, session, feature);
}
/**
* Sets OTR mode for the session and starts negotiation / renegotiation.
*/
public void setSessionOtrMode(String account, String user, String session, OtrMode otrMode) {
if (sessionOtrs.get(account, session) == otrMode) {
return;
}
sessionOtrs.put(account, session, otrMode);
SessionState state = sessionStates.get(account, session);
DataForm dataForm = Feature.createDataForm(DataForm.Type.form);
Feature.addLoggingField(dataForm, otrMode.getLoggingValues(),
otrMode.getLoggingValues()[0]);
Feature.addDisclosureField(dataForm, DisclosureValue.values(),
otrMode.getDisclosureValue());
Feature.addSecurityField(dataForm, SecurityValue.values(),
otrMode.getSecurityValue());
if (state == null || state == SessionState.requesting) {
sessionStates.put(account, session, SessionState.requesting);
Feature.addAcceptField(dataForm, true);
} else {
sessionStates.put(account, session, SessionState.renegotiation);
Feature.addRenegotiateField(dataForm, true);
}
try {
sendFeature(AccountJid.from(account), JidCreate.from(user), session, new Feature(dataForm));
} catch (XmppStringprepException e) {
LogManager.exception(this, e);
}
}
private void sendFeature(AccountJid account, Jid user, String session, Feature feature) {
Message message = new Message(user, Message.Type.normal);
message.setThread(session);
message.addExtension(feature);
try {
StanzaSender.sendStanza(account, message);
} catch (NetworkException e) {
LogManager.exception(this, e);
}
}
}