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}