Added BaseTestSupport#assertFileContentsEquals
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java
new file mode 100644
index 0000000..50310f0
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.compression;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.ExposedBufferByteArrayOutputStream;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DeflatingInputStream extends InputStream {
+ private ExposedBufferByteArrayOutputStream baos;
+ private int readPos;
+ private InputStream inputStream;
+ private OutputStream compressor;
+ private final byte[] readBuf = new byte[IoUtils.DEFAULT_COPY_SIZE]; // TODO make it configurable
+ private final byte[] oneByte = new byte[1];
+
+ public DeflatingInputStream(InputStream inputStream, ExposedBufferByteArrayOutputStream baos, OutputStream compressor) {
+ this.inputStream = Objects.requireNonNull(inputStream, "No initial input stream");
+ this.baos = Objects.requireNonNull(baos, "No buffering output stream");
+ this.compressor = Objects.requireNonNull(compressor, "No compressor stream");
+ }
+
+ @Override
+ public int read() throws IOException {
+ int readLen = read(oneByte);
+ if (readLen < 0) {
+ return -1;
+ }
+
+ return oneByte[0] & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ if (len == 0) {
+ return 0;
+ }
+
+ int curOffset = off;
+ int maxOffset = off + len;
+ int remainLen = len;
+ while ((remainLen > 0) && (curOffset < maxOffset)) {
+ // see if we can use what we already compressed
+ int count = baos.size();
+ int available = count - readPos;
+ int copyLen = Math.min(available, remainLen);
+ if (copyLen > 0) {
+ byte[] compressedData = baos.getBuffer();
+ System.arraycopy(compressedData, readPos, buf, curOffset, copyLen);
+ readPos += copyLen;
+ curOffset += copyLen;
+ remainLen -= copyLen;
+ } else {
+ int readLen = fillCompressedBuffer();
+ if (readLen < 0) {
+ // if no more data available and we did not fill the buffer yet report EOF
+ if (curOffset <= off) {
+ return -1;
+ }
+
+ break; // report whatever we compressed so far
+ }
+ }
+ }
+
+ return curOffset - off;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ throw new StreamCorruptedException("Not possible to skip compressed stream " + n + " bytes");
+ }
+
+ @Override
+ public int available() throws IOException {
+ // keep trying until we have some data in the compression buffer or no more data available
+ while (true) {
+ int count = baos.size();
+ int available = count - readPos;
+ if (available > 0) {
+ return available;
+ }
+
+ int readLen = fillCompressedBuffer();
+ if (readLen < 0) {
+ return 0;
+ }
+ }
+ }
+
+ protected int fillCompressedBuffer() throws IOException {
+ int readLen = inputStream.read(readBuf);
+ baos.reset();
+ readPos = 0;
+
+ // no more input so done compressing - flush it so it is reflected in the buffer array
+ if (readLen < 0) {
+ compressor.flush();
+ compressor.close();
+
+ // check if anything flushed
+ int count = baos.size();
+ if (count > 0) {
+ return count;
+ }
+
+ return -1;
+ }
+
+ // start compressing some more data
+ compressor.write(readBuf, 0, readLen);
+ return readLen;
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ throw new UnsupportedOperationException("mark(" + readlimit + ") N/A");
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new StreamCorruptedException("Not possible to reset compressed stream");
+ }
+
+ @Override
+ public void close() throws IOException {
+ IOException err = null;
+ try {
+ compressor.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+
+ try {
+ baos.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+
+ try {
+ super.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+
+ if (err != null) {
+ throw err;
+ }
+ }
+
+}
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java
new file mode 100644
index 0000000..72b7917
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.util.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Exposes direct access to the underlying buffer
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ExposedBufferByteArrayOutputStream extends ByteArrayOutputStream {
+ public ExposedBufferByteArrayOutputStream() {
+ super();
+ }
+
+ public ExposedBufferByteArrayOutputStream(int size) {
+ super(size);
+ }
+
+ public byte[] getBuffer() {
+ return this.buf;
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java
index 9c04133..761075b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java
@@ -20,6 +20,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
@@ -50,6 +51,7 @@
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.net.SshdSocketAddress;
import org.apache.sshd.server.SshServer;
import org.junit.Assert;
@@ -319,6 +321,28 @@
return folder;
}
+ public static void assertFileContentsEquals(String prefix, Path expected, Path actual) throws IOException {
+ long cmpSize = Files.size(expected);
+ assertEquals(prefix + ": Mismatched file size", cmpSize, Files.size(expected));
+
+ try (InputStream expStream = Files.newInputStream(expected);
+ InputStream actStream = Files.newInputStream(actual)) {
+ byte[] expData = new byte[IoUtils.DEFAULT_COPY_SIZE];
+ byte[] actData = new byte[expData.length];
+
+ for (long offset = 0L; offset < cmpSize;) {
+ Arrays.fill(expData, (byte) 0);
+ int expLen = expStream.read(expData);
+ Arrays.fill(actData, (byte) 0);
+ int actLen = actStream.read(actData);
+ assertEquals(prefix + ": Mismatched read size at offset=" + offset, expLen, actLen);
+ assertArrayEquals(prefix + ": Mismatched data at offset=" + offset, expData, actData);
+
+ offset += expLen;
+ }
+ }
+ }
+
public static File assertHierarchyTargetFolderExists(File folder) {
if (folder.exists()) {
assertTrue("Target is an existing file instead of a folder: " + folder.getAbsolutePath(), folder.isDirectory());