001/** 002 * 003 * Copyright 2018 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 java.io.IOException; 020import java.nio.ByteBuffer; 021import java.util.zip.DataFormatException; 022import java.util.zip.Deflater; 023import java.util.zip.Inflater; 024 025import org.jivesoftware.smack.ConnectionConfiguration; 026import org.jivesoftware.smack.XmppInputOutputFilter; 027import org.jivesoftware.smack.compression.XMPPInputOutputStream.FlushMethod; 028 029public final class ZlibXmppCompressionFactory extends XmppCompressionFactory { 030 031 public static final ZlibXmppCompressionFactory INSTANCE = new ZlibXmppCompressionFactory(); 032 033 private ZlibXmppCompressionFactory() { 034 super("zlib", 100); 035 } 036 037 @Override 038 public XmppInputOutputFilter fabricate(ConnectionConfiguration configuration) { 039 return new ZlibXmppInputOutputFilter(); 040 } 041 042 private static final class ZlibXmppInputOutputFilter implements XmppInputOutputFilter { 043 044 private final Deflater compressor; 045 private final Inflater decompressor = new Inflater(); 046 047 private int maxOutputOutput = -1; 048 private int maxInputOutput = -1; 049 050 private int maxBytesWrittenAfterFullFlush = -1; 051 052 private ZlibXmppInputOutputFilter() { 053 this(Deflater.DEFAULT_COMPRESSION); 054 } 055 056 private ZlibXmppInputOutputFilter(int compressionLevel) { 057 compressor = new Deflater(compressionLevel); 058 } 059 060 private ByteBuffer outputBuffer; 061 062 @Override 063 public OutputResult output(ByteBuffer outputData, boolean isFinalDataOfElement, boolean destinationAddressChanged, 064 boolean moreDataAvailable) throws IOException { 065 if (destinationAddressChanged && XMPPInputOutputStream.getFlushMethod() == FlushMethod.FULL_FLUSH) { 066 outputBuffer = ByteBuffer.allocate(1024); 067 int bytesWritten = deflate(Deflater.FULL_FLUSH); 068 maxBytesWrittenAfterFullFlush = Math.max(bytesWritten, maxBytesWrittenAfterFullFlush); 069 } 070 071 if (outputData == null && outputBuffer == null) { 072 return OutputResult.NO_OUTPUT; 073 } 074 075 int bytesRemaining = outputData.remaining(); 076 if (outputBuffer == null) { 077 // We assume that the compressed data will not take more space as the uncompressed. Even if this is not 078 // always true, the automatic buffer resize mechanism of deflate() will take care. 079 outputBuffer = ByteBuffer.allocate(bytesRemaining); 080 } 081 082 // There is an invariant of Deflater/Inflater that input should only be set if needsInput() return true. 083 assert (compressor.needsInput()); 084 compressor.setInput(outputData.array(), outputData.position(), outputData.limit()); 085 086 int flushMode; 087 if (moreDataAvailable) { 088 flushMode = Deflater.NO_FLUSH; 089 } else { 090 flushMode = Deflater.SYNC_FLUSH; 091 } 092 093 /* 094 long bytesReadBefore = compressor.getBytesRead(); 095 */ 096 /* int bytesWritten = */ deflate(flushMode); 097 /* 098 long bytesReadAfter = compressor.getBytesRead(); 099 long bytesConsumed = bytesReadAfter - bytesReadBefore; 100 assert(bytesConsumed <= Integer.MAX_VALUE); 101 102 int newOutputDataPosition = outputData.position() + (int) bytesConsumed; 103 outputData.position(newOutputDataPosition); 104 */ 105 106 maxOutputOutput = Math.max(outputBuffer.position(), maxOutputOutput); 107 108 OutputResult outputResult = new OutputResult(outputBuffer); 109 outputBuffer = null; 110 return outputResult; 111 } 112 113 private int deflate(int flushMode) { 114 int totalBytesWritten = 0; 115 while (true) { 116 int initialOutputBufferPosition = outputBuffer.position(); 117 118 int bytesWritten = compressor.deflate(outputBuffer.array(), initialOutputBufferPosition, 119 outputBuffer.limit(), flushMode); 120 121 int newOutputBufferPosition = initialOutputBufferPosition + bytesWritten; 122 outputBuffer.position(newOutputBufferPosition); 123 124 totalBytesWritten += bytesWritten; 125 126 if (compressor.needsInput()) { 127 break; 128 } 129 130 int increasedBufferSize = outputBuffer.capacity() * 2; 131 ByteBuffer newCurrentOutputBuffer = ByteBuffer.allocate(increasedBufferSize); 132 outputBuffer.put(newCurrentOutputBuffer); 133 outputBuffer = newCurrentOutputBuffer; 134 } 135 136 return totalBytesWritten; 137 } 138 139 @Override 140 public ByteBuffer input(ByteBuffer inputData) throws IOException { 141 int bytesRemaining = inputData.remaining(); 142 143 int offset, length; 144 byte[] inputBytes; 145 if (inputData.isDirect()) { 146 // Copy since we are dealing with a direct buffer. 147 inputBytes = new byte[bytesRemaining]; 148 inputData.get(inputBytes); 149 offset = 0; 150 length = inputBytes.length; 151 } else { 152 inputBytes = inputData.array(); 153 offset = inputData.position(); 154 length = inputData.limit() - inputData.position(); 155 } 156 157 decompressor.setInput(inputBytes, offset, length); 158 159 int bytesInflated; 160 ByteBuffer outputBuffer = ByteBuffer.allocate(bytesRemaining); 161 while (true) { 162 try { 163 bytesInflated = decompressor.inflate(outputBuffer.array()); 164 } 165 catch (DataFormatException e) { 166 throw new IOException(e); 167 } 168 if (decompressor.needsInput()) { 169 break; 170 } 171 172 int increasedBufferSize = outputBuffer.capacity() * 2; 173 ByteBuffer increasedOutputBuffer = ByteBuffer.allocate(increasedBufferSize); 174 increasedOutputBuffer.put(outputBuffer); 175 outputBuffer = increasedOutputBuffer; 176 } 177 178 if (bytesInflated == 0) { 179 return null; 180 } 181 182 maxInputOutput = Math.max(outputBuffer.position(), maxInputOutput); 183 184 return outputBuffer; 185 } 186 187 } 188}