/* $Id: rex.h,v 1.40 2001/12/13 05:03:03 kaminsky Exp $ */

/*
 *
 * Copyright (C) 2000-2001 Eric Peterson (ericp@lcs.mit.edu)
 * Copyright (C) 2001 Michael Kaminsky (kaminsky@lcs.mit.edu)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#ifndef _SFSMISC_REX_H_
#define _SFSMISC_REX_H_ 1

#include "sfsmisc.h"
#include "sfsconnect.h"
#include "qhash.h"
#include "crypt.h"
#include "aios.h"
#include "rex_prot.h"
#include "agentconn.h"

class rexchannel;

class rexfd : public virtual refcount
{
 protected:
  rexchannel *pch;
  ptr <aclnt> proxy;
  u_int32_t channo;
  int fd;

 public:
  // these implement null fd behavior, so you'll probably want to override them
  virtual void abort ();
  virtual void data (svccb *sbp);
  virtual void newfd (svccb *sbp) { sbp->replyref (false); }
  virtual void exited () {};	/* called when remote module exits */
  rexfd (rexchannel *pch, int fd);
  virtual ~rexfd ();
};

class unixfd : public rexfd
{
 protected:
  int localfd;
  ptr<aios> paios;

 private:
  bool weof;
  bool reof;
  bool shutrdonexit;
  cbv closecb;

  void update_connstate (int how, int error = 0);
  void datacb (ref<bool> okp, clnt_stat) {
    if (!*okp)
      update_connstate (SHUT_RDWR);
  }

 public:
  virtual void rcb (const str data, int err);
  virtual void newfd (svccb *sbp);
  virtual void data (svccb *sbp);
  virtual void abort () {
    update_connstate (SHUT_RDWR);
  }
  void exited () {
    if (shutrdonexit)
      update_connstate (SHUT_RD);
  }
  
  unixfd (rexchannel *pch, int fd, int localfd, 
	  bool noclose = false, bool shutrdonexit = false,
	  cbv closecb = cbv_null);

  virtual ~unixfd () {
    if (paios)
      paios->flush ();
    closecb ();
  }
};

class rexsession;

class rexchannel {
  vec <ptr<rexfd> >  vfds;
  int fdc;

 protected:
  rexsession *sess;
  ptr <aclnt> proxy;
  u_int32_t channo;
  bool got_exit_cb;

  int initnfds;
  vec <str> command;
  
  friend class rexsession;

  virtual void quit ();
  virtual void abort ();
  virtual void madechannel (int error) {};

  void channelinit (u_int32_t chnumber, ref <aclnt> proxyaclnt, int error);

  virtual void data(svccb *sbp);
  virtual void newfd (svccb *sbp);
  virtual void exited (int status);

 public:
  void insert_fd (int fdn, ref<rexfd> rfd);
  void remove_fd (int fdn);  

  int        get_initnfds () { return initnfds; }
  vec<str>   get_cmd      () { return command; }
  u_int32_t  get_channo   () { return channo; }
  ptr<aclnt> get_proxy    () { return proxy; }
      
  rexchannel (rexsession *sess, int initialfdcount, vec <str> command)
    : fdc (0), sess (sess), got_exit_cb (false), initnfds (initialfdcount),
      command(command) {
/*     warn << "--reached rexchannel: fdc = " << fdc << "\n"; */
  }
  virtual ~rexchannel () {
/*     warn << "--reached ~rexchannel\n"; */
  }
};

class rexsession {
  bool verbose;

  ptr<axprt_crypt> proxyxprt;
  ptr<asrv> rexserv;

  //todo : make this non-refcounted pointer
  qhash<u_int32_t, ref <rexchannel> > channels;
  qhash<u_int32_t, ref <rexchannel> > channels_pending_exit;
  int cchan;

  callback<void>::ptr endcb;
  
 public:
  str schost;
  ptr<aclnt> proxy;

 private:
  void rexcb_dispatch (svccb *sbp);
  void madechannel (ref<rex_mkchannel_res> resp, 
		    ref<rexchannel> newchan, clnt_stat err);
  void seq2sessinfo (u_int64_t seqno, sfs_hash *sidp, sfs_sessinfo *sip,
		     rex_sesskeydat *kcsdat, rex_sesskeydat *kscdat);
  void attached (rexd_attach_res *resp, ptr<axprt_crypt> sessxprt,
		 sfs_sessinfo *sessinfo, cbv sessioncreatedcb, clnt_stat err);
  void connected (rex_sesskeydat *kcsdat, rex_sesskeydat *kscdat,
		  sfs_seqno *rexseqno, cbv sessioncreatedcb, ptr<sfscon> sc,
		  str err);
  void quitcaller (const u_int32_t &chno, ptr<rexchannel> pchan) {
    pchan->quit ();
  }
  void abortcaller (const u_int32_t &chno, ptr<rexchannel> pchan) {
    pchan->abort ();
  }

 public:
  // get's called when all channels close or we get EOF from proxy
  void setendcb (cbv endcb) { rexsession::endcb = endcb; }
  void set_verbose (bool status) { verbose = status; }
  void makechannel (ref<rexchannel> newchan, rex_env env = rex_env ());
  void remove_chan (int channo);
  void remove_chan_pending_exit (int channo);

  // informs all channels that client wants to quit.  default
  // behavior is to kill remote module.
  void quit () { channels.traverse (wrap (this, &rexsession::quitcaller)); }

  // calls the abort member function of every channel, which should blow
  // all the channels away
  void abort () {
    endcb = NULL;
    channels.traverse (wrap (this, &rexsession::abortcaller));
  }

  //use this one if you already have an encrypted transport connected to proxy
  rexsession (str schostname, ptr<axprt_crypt> proxyxprt);
  rexsession (cbv sessioncreatedcb, str schostname, bool forwardagent);
  ~rexsession () {
    /* warn << "--reached ~rexsession\n"; */
  }
};

#endif /* _SFSMISC_REX_H_ */
