Description
So, apparently there's a bug in Mina implementation when downloading the file, which is smaller than Mina's buffer (meaning it gets read in one iteration), from servers, which send EOF indicator at the end of data (https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#[section-9.3|https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-9.3]).
The bug seems to be in setting up EOF indicator in
org.apache.sshd.sftp.client.impl.SftpInputStreamAsync#pollBuffer
prematurely (one iteration sooner), trace with me this example situation - download of the small file (14 B):
At some point we arrive to mentioned function pollBuffer(), note that this.pendingReads is set to 2. In first call of pollBuffer() we go through this code on receiving SSH_FXP_DATA
if (type == SftpConstants.SSH_FXP_DATA) { int dlen = buf.getInt(); int rpos = buf.rpos(); buf.rpos(rpos + dlen); Boolean b = SftpHelper.getEndOfFileIndicatorValue(buf, client.getVersion()); if ((b != null) && b.booleanValue()) { eofIndicator = true; }
Here, consider this situation, we're downloading file with 14B, remote server adds EOF indicator to the end of the data, so it makes 15B, let's add some necessary overhead (size etc.) which is, according to my experience, 13B, so we receive 15 + 13 = 28B into initial buffer.
Now, if we populate these variables, dlen = 14B (file size), rpos = 13 (overhead), and we set buf.rpos to 27 (14 + 13), but wpos is at the moment equal to 28B, as that's what we received. (We're not taking EOF into account here)
Then the SftpHelper.getEndOfFileIndicatorValue is called, which in it's implementation looks like this:
public static Boolean getEndOfFileIndicatorValue(Buffer buffer, int version) { return (version < SftpConstants.SFTP_V6) || (buffer.available() < 1) ? null : buffer.getBoolean(); }
Pay attention to the buffer.available() function as it's implemented like this:
public int available() { return wpos - rpos; }
therefore returning 1 (28 - 27), condition is then resolved in SftpHelper.getEndOfFileIndicatorValue as a true and true is also returned to the pollBuffer and eofIndicator is set to true.
If you'd keep tracing further, you'd find out that the buffer is then never read and returned from SftpInputStreamAsync class, just because eofIndicator was set sooner than it should have been.