001/**
002 *
003 * Copyright 2018-2020 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.compression;
018
019import org.jivesoftware.smack.ConnectionConfiguration;
020import org.jivesoftware.smack.SmackException;
021import org.jivesoftware.smack.XMPPException;
022import org.jivesoftware.smack.XmppInputOutputFilter;
023import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
024import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
025import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionModule;
026import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
027import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
028import org.jivesoftware.smack.compress.packet.Compress;
029import org.jivesoftware.smack.compress.packet.Compressed;
030import org.jivesoftware.smack.compress.packet.Failure;
031import org.jivesoftware.smack.fsm.State;
032import org.jivesoftware.smack.fsm.StateDescriptor;
033import org.jivesoftware.smack.fsm.StateTransitionResult;
034
035public class CompressionModule extends ModularXmppClientToServerConnectionModule<CompressionModuleDescriptor> {
036
037    protected CompressionModule(CompressionModuleDescriptor moduleDescriptor,
038                    ModularXmppClientToServerConnectionInternal connectionInternal) {
039        super(moduleDescriptor, connectionInternal);
040    }
041
042    public static final class CompressionStateDescriptor extends StateDescriptor {
043        private CompressionStateDescriptor() {
044            super(CompressionModule.CompressionState.class, 138);
045            addPredeccessor(AuthenticatedButUnboundStateDescriptor.class);
046            addSuccessor(AuthenticatedButUnboundStateDescriptor.class);
047            declarePrecedenceOver(ResourceBindingStateDescriptor.class);
048        }
049
050        @Override
051        protected CompressionModule.CompressionState constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
052            CompressionModule compressionModule = connectionInternal.connection.getConnectionModuleFor(CompressionModuleDescriptor.class);
053            return compressionModule.constructCompressionState(this, connectionInternal);
054        }
055    }
056
057    private static final class CompressionState extends State {
058        private XmppCompressionFactory selectedCompressionFactory;
059        private XmppInputOutputFilter usedXmppInputOutputCompressionFitler;
060
061        private CompressionState(StateDescriptor stateDescriptor, ModularXmppClientToServerConnectionInternal connectionInternal) {
062            super(stateDescriptor, connectionInternal);
063        }
064
065        @Override
066        public StateTransitionResult.TransitionImpossible isTransitionToPossible(
067                        WalkStateGraphContext walkStateGraphContext) {
068            final ConnectionConfiguration config = connectionInternal.connection.getConfiguration();
069            if (!config.isCompressionEnabled()) {
070                return new StateTransitionResult.TransitionImpossibleReason("Stream compression disabled by connection configuration");
071            }
072
073            Compress.Feature compressFeature = connectionInternal.connection.getFeature(Compress.Feature.class);
074            if (compressFeature == null) {
075                return new StateTransitionResult.TransitionImpossibleReason("Stream compression not supported or enabled by service");
076            }
077
078            selectedCompressionFactory = XmppCompressionManager.getBestFactory(compressFeature);
079            if (selectedCompressionFactory == null) {
080                return new StateTransitionResult.TransitionImpossibleReason(
081                                "No matching compression factory for " + compressFeature.getMethods());
082            }
083
084            usedXmppInputOutputCompressionFitler = selectedCompressionFactory.fabricate(config);
085
086            return null;
087        }
088
089        @Override
090        public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
091                        throws InterruptedException, SmackException, XMPPException {
092            final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
093            connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
094
095            connectionInternal.addXmppInputOutputFilter(usedXmppInputOutputCompressionFitler);
096
097            connectionInternal.newStreamOpenWaitForFeaturesSequence("server stream features after compression enabled");
098
099            connectionInternal.setCompressionEnabled(true);
100
101            return new CompressionTransitionSuccessResult(compressionMethod);
102        }
103
104        @Override
105        public void resetState() {
106            selectedCompressionFactory = null;
107            usedXmppInputOutputCompressionFitler = null;
108            connectionInternal.setCompressionEnabled(false);
109        }
110    }
111
112    public static final class CompressionTransitionSuccessResult extends StateTransitionResult.Success {
113        private final String compressionMethod;
114
115        private CompressionTransitionSuccessResult(String compressionMethod) {
116            super(compressionMethod + " compression enabled");
117            this.compressionMethod = compressionMethod;
118        }
119
120        public String getCompressionMethod() {
121            return compressionMethod;
122        }
123    }
124
125    public CompressionState constructCompressionState(CompressionStateDescriptor compressionStateDescriptor,
126                    ModularXmppClientToServerConnectionInternal connectionInternal) {
127        return new CompressionState(compressionStateDescriptor, connectionInternal);
128    }
129}