Ssh2: encapsulating "Session" and "Channel"


#1

Hello! I am relatively new to rust - to learn it, I started to rewrite some internal utilities at work previously written in C#, and I needed to rewrite a C# class which was abstracting a device SSH connection.

After a lot of experiments and reading, I finally arrived to something that compiles and appears to work - but would like someone more experienced to look at it and tell me if it is “done right” :slight_smile:

In particular, I am unsure about the “'static” lifetime in the type signature of OwningHandle, and whether I am correctly initialising it from the pointer to Session… Also - it feels like there should not be much “unsafe” about that operation - is there a way to avoid the “unsafe” keyword ? Thanks a lot!

extern crate ssh2;

use ssh2::{Session, Channel, Error};
use std::net::{TcpStream};
extern crate owning_ref;

use owning_ref::OwningHandle;

struct DeviceSSHConnection {
  tcp: TcpStream,
  channel: OwningHandle<Box<Session>, Box<Channel<'static>>>,
}

impl DeviceSSHConnection {
  fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
    use std::net::{TcpStream};
    let mut session = Session::new().unwrap();
    let mut tcp = TcpStream::connect(targ).unwrap();

    session.handshake(&tcp).unwrap();
    session.set_timeout(5000);
    session.userauth_password(c_user, c_pass).unwrap();

    let mut sess = Box::new(session);
    let mut oref = OwningHandle::new_with_fn(sess, unsafe { |x| Box::new((*x).channel_session().unwrap()) } );
    oref.shell().unwrap();
    let ret = DeviceSSHConnection {
      tcp: tcp,
      channel: oref
    };
    ret
  }

  fn write(&mut self, data: &str) -> std::io::Result<usize> {
    use std::io::prelude::*;
    self.channel.write(data.as_bytes())
  }
  fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
    use std::io::prelude::*;
    self.channel.read(buf)
  }

  fn do_io(&mut self) {
    loop {
      let mut buf = [1u8; 16000];
      let res = self.read(&mut buf);
      println!("read res = {:?}", res);
      let s = String::from_utf8_lossy(&buf);
      println!("result: {}", s);
    };
  }

  fn finish(&mut self) {
    self.channel.wait_close();
    println!("{}", self.channel.exit_status().unwrap());
  }
}

fn main() {
  println!("hello!");
  let target = "192.168.127.151:22";

  let mut s2 = DeviceSSHConnection::new(&target, "user", "password");
  s2.write("term len 0\n");
  s2.write("show version\n");
  s2.do_io();
}

The best approach to learning Rust
#2

Found a later post - How to write software without self-referential structs? which I think is talking about the same problem. I will see if I can later rewrite my ssh struct using the rental crate, and if I manage it, will post here.


#3

Here comes the same example, this time using the rental crate.

Writing and reading requires borrowing the channel mutably, so it appears there is no way to access the session afterwards, same as with the OwningHandles:

extern crate ssh2;

use ssh2::{Session, Channel, Error};
use std::net::{TcpStream};

#[macro_use]
extern crate rental;

rental! {
  pub mod rentals {
    use super::*;

    #[rental_mut]
    pub struct DeviceSSHConnection {
      session: Box<Session>,
      channel: Channel<'session>,
    }
  }
}

pub struct DeviceSSHConnection {
  tcp: TcpStream,
  r: rentals::DeviceSSHConnection,
}

impl DeviceSSHConnection {
  fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
    use std::net::{TcpStream};
    let mut session = Session::new().unwrap();
    let mut tcp = TcpStream::connect(targ).unwrap();

    session.handshake(&tcp).unwrap();
    session.set_timeout(5000);
    session.userauth_password(c_user, c_pass).unwrap();

    let mut sess = Box::new(session);
    let ret = DeviceSSHConnection {
      tcp: tcp,
      r: rentals::DeviceSSHConnection::new(
        sess,
        |s| {
          let mut chan = s.channel_session().unwrap();
          chan.shell().unwrap();
          chan
        }
      )
    };
    ret
  }
  fn write(&mut self, data: &str) -> std::io::Result<usize> {
    use std::io::prelude::*;
    self.r.rent_mut(|c| {
      c.write(data.as_bytes())
    })
  }
  fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
    use std::io::prelude::*;
    self.r.rent_mut(|c| {
      c.read(buf)
    })
  }
  fn do_io(&mut self) {
    loop {
      let mut buf = [1u8; 16000];
      let res = self.read(&mut buf);
      println!("read res = {:?}", res);
      let s = String::from_utf8_lossy(&buf);
      println!("result: {}", s);
    };
  }

  fn finish(&mut self) {
    self.r.rent_mut(|c| {
      c.wait_close();
      println!("{}", c.exit_status().unwrap());
    });
  }
}

fn main() {
  use std::io::prelude::*;
  println!("hello!");
  let target = "192.168.127.151:22";

  let mut s2 = DeviceSSHConnection::new(&target, "user", "pass");
  s2.write("term len 0\n");
  s2.write("show version\n");
  s2.do_io();
}