package org.droidplanner.services.android.impl.utils.connection;
import android.net.Network;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SocketFactory;
import org.droidplanner.services.android.impl.communication.model.DataLink;
import org.droidplanner.services.android.impl.core.MAVLink.connection.MavLinkConnection;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import timber.log.Timber;
/**
* Created by Fredia Huya-Kouadio on 2/24/15.
*/
public class SshConnection {
private static final String TAG = SshConnection.class.getSimpleName();
public interface UploadListener {
void onUploaded(File uploadFile, long uploadBytesCount, long totalBytesCount);
boolean shouldContinueUpload();
}
public interface DownloadListener {
void onFileSizeCalculated(long fileSize);
void onDownloaded(String downloadFile, long downloadBytesCount);
}
private static final int CONNECTION_TIMEOUT = 15000; //ms
private static final String EXEC_CHANNEL_TYPE = "exec";
private final JSch jsch;
private final String host;
private final String username;
private final String password;
private final DataLink.DataLinkProvider linkProvider;
public SshConnection(String host, String username, String password, DataLink.DataLinkProvider linkProvider) {
this.jsch = new JSch();
this.host = host;
this.username = username;
this.password = password;
this.linkProvider = linkProvider;
}
private Session getSession() throws JSchException {
Session session = jsch.getSession(username, host);
Bundle extras = linkProvider.getConnectionExtras();
if (extras != null && !extras.isEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Network network = extras.getParcelable(MavLinkConnection.EXTRA_NETWORK);
if (network != null) {
session.setSocketFactory(new SshSocketFactory(network.getSocketFactory()));
}
}
}
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword(this.password);
session.connect(CONNECTION_TIMEOUT);
return session;
}
public boolean ping() {
Session session = null;
try {
session = getSession();
return true;
} catch (JSchException e) {
return false;
} finally {
if (session != null && session.isConnected())
session.disconnect();
}
}
public String execute(String command) throws IOException {
if (TextUtils.isEmpty(command))
return null;
Session session = null;
Channel execChannel = null;
try {
session = getSession();
execChannel = session.openChannel(EXEC_CHANNEL_TYPE);
((ChannelExec) execChannel).setCommand(command);
execChannel.setInputStream(null);
final InputStream in = execChannel.getInputStream();
execChannel.connect(CONNECTION_TIMEOUT);
final int bufferSize = 1024;
final StringBuilder response = new StringBuilder();
final byte[] buffer = new byte[bufferSize];
while (true) {
while (in.available() > 0) {
int dataSize = in.read(buffer, 0, bufferSize);
if (dataSize < 0)
break;
response.append(new String(buffer, 0, dataSize));
}
if (execChannel.isClosed()) {
if (in.available() > 0) continue;
Timber.d("SSH command exit status: " + execChannel.getExitStatus());
break;
}
}
return response.toString();
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (execChannel != null && execChannel.isConnected())
execChannel.disconnect();
if (session != null && session.isConnected())
session.disconnect();
}
}
public boolean downloadFile(String localFile, String remoteFilePath) throws IOException {
return downloadFile(localFile, remoteFilePath, null);
}
public boolean downloadFile(String localFile, String remoteFilePath, DownloadListener listener) throws IOException {
if (localFile == null || remoteFilePath == null)
return false;
Session session = null;
Channel execChannel = null;
FileOutputStream fos = null;
OutputStream out = null;
InputStream in = null;
try {
String prefix = null;
if (new File(localFile).isDirectory()) {
prefix = localFile + File.separator;
}
session = getSession();
// exec 'scp -f remoteFilePath' remotely
String command = "scp -f " + remoteFilePath;
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
// get I/O streams for remote scp
out = channel.getOutputStream();
in = channel.getInputStream();
channel.connect();
byte[] buf = new byte[1024];
// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
int c = checkAck(in);
if (c != 'C') {
return false;
}
// read '0644 '
in.read(buf, 0, 5);
long fileSize = 0L;
while (true) {
if (in.read(buf, 0, 1) < 0) {
// error
return false;
}
if (buf[0] == ' ') break;
fileSize = fileSize * 10L + (long) (buf[0] - '0');
}
String file = null;
for (int i = 0; ; i++) {
in.read(buf, i, 1);
if (buf[i] == (byte) 0x0a) {
file = new String(buf, 0, i);
break;
}
}
if (listener != null)
listener.onFileSizeCalculated(fileSize);
// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
// read a content of localFile
fos = new FileOutputStream(prefix == null ? localFile : prefix + file);
int bytesToRead;
long progress = 0;
while (true) {
if (buf.length < fileSize) bytesToRead = buf.length;
else bytesToRead = (int) fileSize;
bytesToRead = in.read(buf, 0, bytesToRead);
if (bytesToRead < 0) {
// error
return false;
}
progress += bytesToRead;
fos.write(buf, 0, bytesToRead);
fileSize -= bytesToRead;
if (fileSize == 0L) break;
if (listener != null)
listener.onDownloaded(localFile, progress);
}
fos.close();
fos = null;
if (checkAck(in) != 0) {
return false;
}
// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
session.disconnect();
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (fos != null) {
fos.close();
}
if (out != null)
out.close();
if (in != null)
in.close();
if (execChannel != null && execChannel.isConnected())
execChannel.disconnect();
if (session != null && session.isConnected())
session.disconnect();
}
return true;
}
public boolean uploadFile(File localFile, String remoteFilePath, UploadListener listener) throws IOException {
if (localFile == null || !localFile.isFile() || (listener != null && !listener.shouldContinueUpload()))
return false;
Session session = null;
Channel execChannel = null;
FileInputStream fis = null;
OutputStream out = null;
InputStream in = null;
try {
session = getSession();
String command = "scp -t " + remoteFilePath;
execChannel = session.openChannel(EXEC_CHANNEL_TYPE);
((ChannelExec) execChannel).setCommand(command);
//Get I/O streams for remote scp
out = execChannel.getOutputStream();
in = execChannel.getInputStream();
execChannel.connect(CONNECTION_TIMEOUT);
if (checkAck(in) != 0)
return false;
if (listener != null && !listener.shouldContinueUpload())
return false;
//Send "C0644 fileSize filename"
final long fileSize = localFile.length();
command = "C0644 " + fileSize + " " + localFile.getName() + "\n";
out.write(command.getBytes());
out.flush();
if (checkAck(in) != 0)
return false;
//Send local file content
final int bufferSize = 8192;
fis = new FileInputStream(localFile);
final byte[] buffer = new byte[bufferSize];
long uploadedBytesCount = 0;
while (true) {
int len = fis.read(buffer, 0, bufferSize);
if (len <= 0)
break;
out.write(buffer, 0, len);
uploadedBytesCount += len;
if (listener != null) {
listener.onUploaded(localFile, uploadedBytesCount, fileSize);
if (!listener.shouldContinueUpload())
return false;
}
}
//Send '\0'
out.write(0);
out.flush();
if (checkAck(in) != 0)
return false;
return true;
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (fis != null) {
fis.close();
}
if (out != null)
out.close();
if (in != null)
in.close();
if (execChannel != null && execChannel.isConnected())
execChannel.disconnect();
if (session != null && session.isConnected())
session.disconnect();
}
}
private static int checkAck(InputStream in) throws IOException {
int result = in.read();
// result may be 0 for success,
// 1 for error,
// 2 for fatal error,
// -1
if (result == 1 || result == 2) {
//Log the error
final StringBuilder errorMsg = new StringBuilder();
int character;
do {
character = in.read();
errorMsg.append((char) character);
}
while (character != '\n');
if (errorMsg.length() > 0)
Timber.e( errorMsg.toString());
}
return result;
}
private static class SshSocketFactory implements SocketFactory {
private final javax.net.SocketFactory socketFactory;
private SshSocketFactory(javax.net.SocketFactory socketFactory) {
this.socketFactory = socketFactory;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return socketFactory.createSocket(host, port);
}
@Override
public InputStream getInputStream(Socket socket) throws IOException {
return socket.getInputStream();
}
@Override
public OutputStream getOutputStream(Socket socket) throws IOException {
return socket.getOutputStream();
}
}
}