diff options
Diffstat (limited to 'src/com/isode/stroke/client/CoreClient.java')
-rw-r--r-- | src/com/isode/stroke/client/CoreClient.java | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java new file mode 100644 index 0000000..0ce1503 --- /dev/null +++ b/src/com/isode/stroke/client/CoreClient.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tron¨on. + * All rights reserved. + */ +package com.isode.stroke.client; + +import com.isode.stroke.elements.Message; +import com.isode.stroke.elements.Presence; +import com.isode.stroke.elements.Stanza; +import com.isode.stroke.elements.StreamType; +import com.isode.stroke.eventloop.EventLoop; +import com.isode.stroke.jid.JID; +import com.isode.stroke.network.Connection; +import com.isode.stroke.network.ConnectionFactory; +import com.isode.stroke.network.Connector; +import com.isode.stroke.network.NetworkFactories; +import com.isode.stroke.network.PlatformDomainNameResolver; +import com.isode.stroke.network.TimerFactory; +import com.isode.stroke.parser.payloadparsers.FullPayloadParserFactoryCollection; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.serializer.payloadserializers.FullPayloadSerializerCollection; +import com.isode.stroke.session.BasicSessionStream; +import com.isode.stroke.session.SessionStream; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.tls.CertificateTrustChecker; +import com.isode.stroke.tls.CertificateVerificationError; +import com.isode.stroke.tls.PKCS12Certificate; +import com.isode.stroke.tls.PlatformTLSFactories; +import com.isode.stroke.tls.TLSContextFactory; + +/** + * The central class for communicating with an XMPP server. + * + * This class is responsible for setting up the connection with the XMPP server, authenticating, and + * initializing the session. + * + * This class can be used directly in your application, although the Client subclass provides more + * functionality and interfaces, and is better suited for most needs. + */ +public class CoreClient { + private SignalConnection sessionStreamDataReadConnection_; + private SignalConnection sessionStreamDataWrittenConnection_; + private SignalConnection sessionFinishedConnection_; + private SignalConnection sessionNeedCredentialsConnection_; + private SignalConnection connectorConnectFinishedConnection_; + private final EventLoop eventLoop_; + + public CoreClient(EventLoop eventLoop, JID jid, String password, NetworkFactories networkFactories) { + jid_ = jid; + password_ = password; + disconnectRequested_ = false; + eventLoop_ = eventLoop; + this.networkFactories = networkFactories; + this.certificateTrustChecker = null; + resolver_ = new PlatformDomainNameResolver(eventLoop); + stanzaChannel_ = new ClientSessionStanzaChannel(); + stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() { + + public void call(Message p1) { + onMessageReceived.emit(p1); + } + }); + stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() { + + public void call(Presence p1) { + onPresenceReceived.emit(p1); + } + }); + stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() { + + public void call(Stanza p1) { + onStanzaAcked.emit(p1); + } + }); + stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() { + + public void call(Boolean p1) { + handleStanzaChannelAvailableChanged(p1); + } + }); + + iqRouter_ = new IQRouter(stanzaChannel_); + tlsFactories = new PlatformTLSFactories(); + } + + /*CoreClient::~CoreClient() { + if (session_ || connection_) { + std::cerr << "Warning: Client not disconnected properly" << std::endl; + } + delete tlsLayerFactory_; + delete timerFactory_; + delete connectionFactory_; + delete iqRouter_; + + stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&CoreClient::handleStanzaChannelAvailableChanged, this, _1)); + stanzaChannel_->onMessageReceived.disconnect(boost::ref(onMessageReceived)); + stanzaChannel_->onPresenceReceived.disconnect(boost::ref(onPresenceReceived)); + stanzaChannel_->onStanzaAcked.disconnect(boost::ref(onStanzaAcked)); + delete stanzaChannel_; + }*/ + public void connect(ClientOptions o) { + options = o; + connect(jid_.getDomain()); + } + + public void connect(String host) { + disconnectRequested_ = false; + assert (connector_ == null); + /* FIXME: Port Proxies */ + connector_ = Connector.create(host, networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory()); + connectorConnectFinishedConnection_ = connector_.onConnectFinished.connect(new Slot1<Connection>() { + public void call(Connection p1) { + handleConnectorFinished(p1); + } + }); + connector_.setTimeoutMilliseconds(60 * 1000); + connector_.start(); + } + + void handleConnectorFinished(Connection connection) { + if (connectorConnectFinishedConnection_ != null) { + connectorConnectFinishedConnection_.disconnect(); + } + connector_ = null; + if (connection == null) { + if (!disconnectRequested_) { + onError.emit(new ClientError(ClientError.Type.ConnectionError)); + } + } else { + assert (connection_ == null); + connection_ = connection; + + assert (sessionStream_ == null); + sessionStream_ = new BasicSessionStream(StreamType.ClientStreamType, connection_, payloadParserFactories_, payloadSerializers_, tlsFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), eventLoop_); + if (certificate_ != null && !certificate_.isEmpty()) { + sessionStream_.setTLSCertificate(new PKCS12Certificate(certificate_, password_)); + } + sessionStreamDataReadConnection_ = sessionStream_.onDataRead.connect(new Slot1<String>() { + + public void call(String p1) { + handleDataRead(p1); + } + }); + + sessionStreamDataWrittenConnection_ = sessionStream_.onDataWritten.connect(new Slot1<String>() { + + public void call(String p1) { + handleDataWritten(p1); + } + }); + + session_ = ClientSession.create(jid_, sessionStream_); + session_.setCertificateTrustChecker(certificateTrustChecker); + session_.setUseStreamCompression(options.useStreamCompression); + switch (options.useTLS) { + case UseTLSWhenAvailable: + session_.setUseTLS(ClientSession.UseTLS.UseTLSWhenAvailable); + break; + case NeverUseTLS: + session_.setUseTLS(ClientSession.UseTLS.NeverUseTLS); + break; + } + stanzaChannel_.setSession(session_); + sessionFinishedConnection_ = session_.onFinished.connect(new Slot1<com.isode.stroke.base.Error>() { + + public void call(com.isode.stroke.base.Error p1) { + handleSessionFinished(p1); + } + }); + sessionNeedCredentialsConnection_ = session_.onNeedCredentials.connect(new Slot() { + + public void call() { + handleNeedCredentials(); + } + }); + session_.start(); + } + } + + public void disconnect() { + // FIXME: We should be able to do without this boolean. We just have to make sure we can tell the difference between + // connector finishing without a connection due to an error or because of a disconnect. + disconnectRequested_ = true; + if (session_ != null && !session_.isFinished()) { + session_.finish(); + } else if (connector_ != null) { + connector_.stop(); + } + } + + public void setCertificate(String certificate) { + certificate_ = certificate; + } + + private void handleSessionFinished(com.isode.stroke.base.Error error) { + sessionFinishedConnection_.disconnect(); + sessionNeedCredentialsConnection_.disconnect(); + session_ = null; + + sessionStreamDataReadConnection_.disconnect(); + sessionStreamDataWrittenConnection_.disconnect(); + sessionStream_ = null; + + connection_.disconnect(); + connection_ = null; + + if (error != null) { + ClientError clientError = null; + if (error instanceof ClientSession.Error) { + ClientSession.Error actualError = (ClientSession.Error) error; + switch (actualError.type) { + case AuthenticationFailedError: + clientError = new ClientError(ClientError.Type.AuthenticationFailedError); + break; + case CompressionFailedError: + clientError = new ClientError(ClientError.Type.CompressionFailedError); + break; + case ServerVerificationFailedError: + clientError = new ClientError(ClientError.Type.ServerVerificationFailedError); + break; + case NoSupportedAuthMechanismsError: + clientError = new ClientError(ClientError.Type.NoSupportedAuthMechanismsError); + break; + case UnexpectedElementError: + clientError = new ClientError(ClientError.Type.UnexpectedElementError); + break; + case ResourceBindError: + clientError = new ClientError(ClientError.Type.ResourceBindError); + break; + case SessionStartError: + clientError = new ClientError(ClientError.Type.SessionStartError); + break; + case TLSError: + clientError = new ClientError(ClientError.Type.TLSError); + break; + case TLSClientCertificateError: + clientError = new ClientError(ClientError.Type.ClientCertificateError); + break; + } + } else if (error instanceof SessionStream.Error) { + SessionStream.Error actualError = (SessionStream.Error) error; + switch (actualError.type) { + case ParseError: + clientError = new ClientError(ClientError.Type.XMLError); + break; + case TLSError: + clientError = new ClientError(ClientError.Type.TLSError); + break; + case InvalidTLSCertificateError: + clientError = new ClientError(ClientError.Type.ClientCertificateLoadError); + break; + case ConnectionReadError: + clientError = new ClientError(ClientError.Type.ConnectionReadError); + break; + case ConnectionWriteError: + clientError = new ClientError(ClientError.Type.ConnectionWriteError); + break; + } + } else if (error instanceof CertificateVerificationError) { + CertificateVerificationError verificationError = (CertificateVerificationError)error; + switch (verificationError.type) { + case UnknownError: + clientError = new ClientError(ClientError.Type.UnknownCertificateError); + break; + case Expired: + clientError = new ClientError(ClientError.Type.CertificateExpiredError); + break; + case NotYetValid: + clientError = new ClientError(ClientError.Type.CertificateNotYetValidError); + break; + case SelfSigned: + clientError = new ClientError(ClientError.Type.CertificateSelfSignedError); + break; + case Rejected: + clientError = new ClientError(ClientError.Type.CertificateRejectedError); + break; + case Untrusted: + clientError = new ClientError(ClientError.Type.CertificateUntrustedError); + break; + case InvalidPurpose: + clientError = new ClientError(ClientError.Type.InvalidCertificatePurposeError); + break; + case PathLengthExceeded: + clientError = new ClientError(ClientError.Type.CertificatePathLengthExceededError); + break; + case InvalidSignature: + clientError = new ClientError(ClientError.Type.InvalidCertificateSignatureError); + break; + case InvalidCA: + clientError = new ClientError(ClientError.Type.InvalidCAError); + break; + case InvalidServerIdentity: + clientError = new ClientError(ClientError.Type.InvalidServerIdentityError); + break; + } + } + assert clientError != null; + onError.emit(clientError); + } + } + + private void handleNeedCredentials() { + assert session_ != null; + session_.sendCredentials(password_); + } + + private void handleDataRead(String data) { + onDataRead.emit(data); + } + + private void handleDataWritten(String data) { + onDataWritten.emit(data); + } + + private void handleStanzaChannelAvailableChanged(boolean available) { + if (available) { + onConnected.emit(); + } + } + + public void sendMessage(Message message) { + stanzaChannel_.sendMessage(message); + } + + public void sendPresence(Presence presence) { + stanzaChannel_.sendPresence(presence); + } + + public IQRouter getIQRouter() { + return iqRouter_; + } + + public StanzaChannel getStanzaChannel() { + return stanzaChannel_; + } + + public boolean isAvailable() { + return stanzaChannel_.isAvailable(); + } + + /** + * Returns the JID of the client. + * After the session was initialized, this returns the bound JID. + */ + public JID getJID() { + if (session_ != null) { + return session_.getLocalJID(); + } else { + return jid_; + } + } + public final Signal1<ClientError> onError = new Signal1<ClientError>(); + public final Signal onConnected = new Signal(); + public final Signal1<String> onDataRead = new Signal1<String>(); + public final Signal1<String> onDataWritten = new Signal1<String>(); + public final Signal1<Message> onMessageReceived = new Signal1<Message>(); + public final Signal1<Presence> onPresenceReceived = new Signal1<Presence>(); + public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>(); + private PlatformDomainNameResolver resolver_; + private JID jid_; + private String password_; + private ClientSessionStanzaChannel stanzaChannel_; + private IQRouter iqRouter_; + private Connector connector_; + private ConnectionFactory connectionFactory_; + private FullPayloadParserFactoryCollection payloadParserFactories_ = new FullPayloadParserFactoryCollection(); + private FullPayloadSerializerCollection payloadSerializers_ = new FullPayloadSerializerCollection(); + private Connection connection_; + private BasicSessionStream sessionStream_; + private ClientSession session_; + private String certificate_; + private boolean disconnectRequested_; + private ClientOptions options; + private CertificateTrustChecker certificateTrustChecker; + private NetworkFactories networkFactories; + private PlatformTLSFactories tlsFactories; +} |