// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1996
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        tifsocks4.C
// 
// Purpose:     
// 
// Created:     6 Jan 1997   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// $Id: tifsocks4.C,v 1.5 1997/03/03 23:59:31 jfasch Exp $
// 
// $Log: tifsocks4.C,v $
// Revision 1.5  1997/03/03 23:59:31  jfasch
// bug fix in blocking thing
//
// Revision 1.4  1997/02/21 16:23:08  jfasch
// SOCKS4Match --> TCPMatch (in utils)
//
// Revision 1.3  1997/02/13 12:54:42  gorasche
// enum to int cast for Win32
//
// Revision 1.2  1997/02/12 20:28:40  jfasch
// some header file movings
//
// Revision 1.1  1997/02/04 13:48:16  jfasch
// Initial revision
//
// 
// </file> 
#include "tifsocks4.h"

#include "tiobr.h"
#include "tsockio.h"

#include <hyperg/utils/assert.h>
#include <hyperg/utils/data.h>
#include <hyperg/utils/fields.h>
#include <hyperg/utils/inetsocket.h>
#include <hyperg/utils/new.h>
#include <hyperg/utils/socks4.h>
#include <hyperg/utils/verbose.h>

// --------------------------------------------------------------------
class SOCKS4Buffer : public RefCounted {
public:
   SOCKS4Buffer (const SOCKS4ConnectRequest&) ;
   const char* current() const { return buf_.ptr()->raw() + nread_; }
   int navail() const { return buf_.ptr()->size() - nread_; }
   void consume (int n) { nread_ += n; }
   bool done() const { return !navail(); }
private:
   DataPtr buf_ ;
   int nread_ ;
} ;
SmartPtrdeclare (SOCKS4BufferPtr, SOCKS4Buffer) ;
SOCKS4Buffer :: SOCKS4Buffer (const SOCKS4ConnectRequest& r)
: nread_(0) {
   OBuffer buf (r.size()) ; // preallocate exactly what is needed
   r.write (buf) ;
   int len = buf.count() ;
   buf_ = DataPtr (HGNEW (Data (buf.freeze(), len))) ;
}

// --------------------------------------------------------------------
class ConnWaitElement {
public:
   ConnWaitElement() {}
   ConnWaitElement (const TIOINETRequestPtr& orig, 
                    const TIOINETRequestPtr& direct /*to server*/) 
   : orig_(orig), direct_(direct) {}
   ConnWaitElement& operator = (const ConnWaitElement&) ;
   const TIOINETRequestPtr& orig() const { return orig_; }
   const TIOINETRequestPtr& direct() const { return direct_; }
   // these are only dummies since I dont want to sort() the field
   bool operator == (const ConnWaitElement&) const { return false; }
   bool operator != (const ConnWaitElement&) const { return false; }
   bool operator <  (const ConnWaitElement&) const { return false; }
private:
   TIOINETRequestPtr orig_ ;
   TIOINETRequestPtr direct_ ;
} ;
inline ConnWaitElement& ConnWaitElement :: operator = (const ConnWaitElement& e) {
   orig_ = e.orig_ ;
   direct_ = e.direct_ ;
   return *this ;
}
Fieldsdeclare (ConnWaitBase, ConnWaitElement) ;
Fieldsimplement (ConnWaitBase, ConnWaitElement) ;
class ConnWait : public ConnWaitBase {
public:
   bool insert (const TIOINETRequestPtr& orig, const TIOINETRequestPtr& direct) ;
   TIOINETRequestPtr /*==direct*/ removeByOrig (const TIOINETRequest* orig) ;
   TIOINETRequestPtr /*==orig*/   removeByDirect (const TIOINETRequest* direct) ;
} ;
bool ConnWait :: insert (const TIOINETRequestPtr& orig, const TIOINETRequestPtr& direct) {
   ConnWaitBase::append (ConnWaitElement (orig, direct)) ;
   return true ;
}
TIOINETRequestPtr ConnWait :: removeByOrig (const TIOINETRequest* orig) {
   for (int i=0 ; i<ConnWaitBase::count() ; i++) {
      const ConnWaitElement& e = ConnWaitBase::operator[] (i) ;
      if (e.orig().ptr() == orig) {
         TIOINETRequestPtr ret = e.direct() ;
         ConnWaitBase::remove (i) ;
         return ret ;
      }
   }
   return TIOINETRequestPtr() ;
}
TIOINETRequestPtr ConnWait :: removeByDirect (const TIOINETRequest* direct) {
   for (int i=0 ; i<ConnWaitBase::count() ; i++) {
      const ConnWaitElement& e = ConnWaitBase::operator[] (i) ;
      if (e.direct().ptr() == direct) {
         TIOINETRequestPtr ret = e.orig() ;
         ConnWaitBase::remove (i) ;
         return ret ;
      }
   }
   return TIOINETRequestPtr() ;
}

// --------------------------------------------------------------------
class OutWaitElement {
public:
   OutWaitElement() {}
   OutWaitElement (const TIOINETRequestPtr& orig, 
                   const TransparentIOPtr& tio, const SOCKS4BufferPtr& buf)
   : orig_(orig), tio_(tio), buf_(buf) {}
   OutWaitElement& operator = (const OutWaitElement&) ;
   const TIOINETRequestPtr& orig() const { return orig_; }
   const TransparentIOPtr& tio() const { return tio_; }
   const SOCKS4BufferPtr& buffer() const { return buf_; }
   // some dummies to be able to instatiate the Field
   bool operator == (const OutWaitElement&) const { return false ; }
   bool operator != (const OutWaitElement&) const { return false ; }
   bool operator <  (const OutWaitElement&) const { return false ; }
private:
   TIOINETRequestPtr orig_ ;
   TransparentIOPtr tio_ ;
   SOCKS4BufferPtr buf_ ;
} ;
OutWaitElement& OutWaitElement :: operator = (const OutWaitElement& e) {
   orig_ = e.orig_ ;
   tio_ = e.tio_ ;
   buf_ = e.buf_ ;
   return *this ;
}
Fieldsdeclare (OutWaitBase, OutWaitElement) ;
Fieldsimplement (OutWaitBase, OutWaitElement) ;
class OutWait : public OutWaitBase {
public:
   bool insert (const TIOINETRequestPtr& orig, const TransparentIOPtr&, const SOCKS4BufferPtr&) ;
   bool removeByOrig (const TIOINETRequest*, TransparentIOPtr&, SOCKS4BufferPtr&) ;
   int posByTIO (const TransparentIO*) ;
   bool removeByTIO (const TransparentIO*, OutWaitElement&) ;
} ;
bool OutWait :: insert (const TIOINETRequestPtr& orig, 
                        const TransparentIOPtr& io, const SOCKS4BufferPtr& buf) {
   OutWaitBase::append (OutWaitElement (orig, io, buf)) ;
   return true ;
}
bool OutWait :: removeByOrig (const TIOINETRequest* orig, 
                              TransparentIOPtr& tio, SOCKS4BufferPtr& buf) {
   for (int i=0 ; i<OutWaitBase::count() ; i++) {
      const OutWaitElement& e = operator[] (i) ;
      if (e.orig().ptr() == orig) {
         tio = e.tio() ;
         buf = e.buffer() ;
         OutWaitBase::remove (i) ;
         return true ;
      }
   }
   return false ;
}
int OutWait :: posByTIO (const TransparentIO* tio) {
   for (int i=0 ; i<OutWaitBase::count() ; i++) {
      const OutWaitElement& e = operator[] (i) ;
      if (e.tio().ptr() == tio)
         return i ;
   }
   return -1 ;
}
bool OutWait :: removeByTIO (const TransparentIO* tio, 
                             OutWaitElement& elem) {
   for (int i=0 ; i<OutWaitBase::count() ; i++) {
      const OutWaitElement& e = operator[] (i) ;
      if (e.tio().ptr() == tio) {
         elem = e ;
         OutWaitBase::remove (i) ;
         return true ;
      }
   }
   return false ;
}

// --------------------------------------------------------------------
class InWaitElement {
public:
   InWaitElement() {}
   InWaitElement (const TIOINETRequestPtr& orig, const TransparentIOPtr& io)
   : orig_(orig), io_(io), reply_(HGNEW (SOCKS4ConnectReply)) {}
   InWaitElement& operator = (const InWaitElement&) ;
   const TIOINETRequestPtr& orig() const { return orig_; }
   const TransparentIOPtr& io()  const { return io_; }
   const SOCKS4ConnectReplyPtr& reply() const { return reply_; }
   // some dummies again
   bool operator == (const InWaitElement&) const { return false; }
   bool operator != (const InWaitElement&) const { return false; }
   bool operator <  (const InWaitElement&) const { return false; }
private:
   TIOINETRequestPtr orig_ ;
   TransparentIOPtr io_ ;
   SOCKS4ConnectReplyPtr reply_ ;
} ;
InWaitElement& InWaitElement :: operator = (const InWaitElement& e) {
   orig_ = e.orig_ ;
   io_ = e.io_ ;
   reply_ = e.reply_ ;
   return *this ;
}
Fieldsdeclare (InWaitBase, InWaitElement) ;
Fieldsimplement (InWaitBase, InWaitElement) ;
class InWait : public InWaitBase {
public:
   bool insert (const TIOINETRequestPtr& orig, const TransparentIOPtr&) ;
   bool removeByOrig (const TIOINETRequest*, TransparentIOPtr&) ;
   bool removeByIO (const TransparentIO*, TIOINETRequestPtr&) ;
   int posByIO (const TransparentIO*) const ;
} ;
bool InWait :: insert (const TIOINETRequestPtr& orig, const TransparentIOPtr& io) {
   InWaitBase::append (InWaitElement (orig, io)) ;
   return true ;
}
bool InWait :: removeByOrig (const TIOINETRequest* orig, TransparentIOPtr& io) {
   for (int i=0 ; i<InWaitBase::count() ; i++) {
      const InWaitElement& e = operator[] (i) ;
      if (e.orig().ptr() == orig) {
         io = e.io() ;
         InWaitBase::remove (i) ;
         return true ;
      }
   }
   return false ;
}
bool InWait :: removeByIO (const TransparentIO* io, TIOINETRequestPtr& orig) {
   for (int i=0 ; i<InWaitBase::count() ; i++) {
      const InWaitElement& e = operator[] (i) ;
      if  (e.io().ptr() == io) {
         orig = e.orig() ;
         InWaitBase::remove (i) ;
         return true ;
      }
   }
   return false ;
}
int InWait :: posByIO (const TransparentIO* io) const {
   for (int i=0 ; i<InWaitBase::count() ; i++) {
      const InWaitElement& e = operator[] (i) ;
      if  (e.io().ptr() == io) 
         return i ;
   }
   return -1 ;
}

// --------------------------------------------------------------------
Verbose TIOINETFactorySOCKS4 :: verbose ;

const char* TIOINETFactorySOCKS4::version1 = "TIOINETFactorySOCKS4: $Id: tifsocks4.C,v 1.5 1997/03/03 23:59:31 jfasch Exp $" ;

TIOINETFactorySOCKS4 :: TIOINETFactorySOCKS4 (const INETAddress& server, 
                                              const TCPMatch& m, 
                                              const RString& uid)
: server_(server),
  match_(m),
  uid_(uid),
  conn_wait_(HGNEW (ConnWait)),
  out_wait_(HGNEW (OutWait)),
  in_wait_(HGNEW (InWait)) {}

TIOINETFactorySOCKS4 :: ~TIOINETFactorySOCKS4() {
   hgassert (!conn_wait_->count(), 
             "TIOINETFactorySOCKS4::~TIOINETFactorySOCKS4(): "
             "still waiting for server connections to get ready") ;
   hgassert (!out_wait_->count(), "TIOINETFactorySOCKS4::~TIOINETFactorySOCKS4(): "
             "still watching for output on some connections") ;
   hgassert (!in_wait_->count(), "TIOINETFactorySOCKS4::~TIOINETFactorySOCKS4(): "
             "still watching for input on some connections") ;
   HGDELETE (conn_wait_) ;
   HGDELETE (out_wait_) ;
   HGDELETE (in_wait_) ;
}

TIOINETResponsePtr TIOINETFactorySOCKS4 :: connect (const TIOINETRequestPtr& req) {
   hgassert (req, "TIOINETFactorySOCKS4::connect(): nil request") ;
   DEBUGNL ("TIOINETFactorySOCKS4::connect(): address "<<req.ptr()->address().host()<<
            ", port "<<req.ptr()->address().port()<<", user "<<req.ptr()->user()) ;

   if (! match_.match (req.ptr()->address())) {
      DEBUGNL ("TIOINETFactorySOCKS4::connect(): address ("<<
               req.ptr()->address().host()<<','<<req.ptr()->address().port()<<
               ") did not match my requirements") ;
      return TIOINETResponsePtr (
         HGNEW (TIOINETResponse (req.ptr(), TIOINETFactorySOCKS4::TIFNEXT))) ;
   }

   // set the request\'s worker to be me.
   TIOINETFactory::set_worker_(req) ;

   // and do it 
   if (req.ptr()->user())
      return make_nonblocking_(req) ;
   else 
      return make_blocking_(req) ;
}

TIOINETResponsePtr TIOINETFactorySOCKS4 :: make_blocking_(const TIOINETRequestPtr& req) {
   TIOINETResponsePtr res ;

   INETSocketPtr server_conn (HGNEW (INETSocket (server_))) ;

   if (! server_conn.ptr()->ok()) {
      DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): could not connect to server") ;
      return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                         TIOINETFactory::TIFNOCONN))) ;
   }

   SOCKS4ConnectRequest s4rq (req.ptr()->address(), uid_) ;
   buf_.reset() ;
   s4rq.write (buf_) ;
   int n = server_conn.ptr()->write (buf_.data(), buf_.count()) ;
   if (n < 0) {
      DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): error writing request") ;
      return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                         TIOINETFactory::TIFNOCONN))) ;
   }
   if (n != buf_.count()) {
      DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): could not write entire request") ;
      return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                         TIOINETFactory::TIFNOCONN))) ;
   }

   SOCKS4ConnectReply s4rp ;
   int nread = 0 ;
   char tmp[SOCKS4ConnectReply::size] ;
   while (nread < SOCKS4ConnectReply::size) {
      int n = server_conn.ptr()->read (tmp+nread, SOCKS4ConnectReply::size-nread) ;
      if (n < 0) {
         DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): socket read error") ;
         return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                            TIOINETFactory::TIFNOCONN))) ;
      }
      if (n == 0) {
         DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): read 0 bytes blockingly (?)") ;
         return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                            TIOINETFactory::TIFNOCONN))) ;
      }
      s4rp.add (tmp+nread, n) ;
      nread += n ;
   }

   if (! s4rp.ok()) {
      DEBUGNL ("TIOINETFactorySOCKS4::make_blocking_(): SOCKS error: "<<
               s4rp.errorDescription()) ;
      return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(),
                                                         TIOINETFactory::TIFNOCONN))) ;
   }

   // if no error was encountered, the reply must be complete now
   // (SOCKS4 replied must be 8 bytes)
   hgassert (s4rp.complete(), "TIOINETFactorySOCKS4::make_blocking_(): "
             "reply not complete though 8 bytes were read") ;

   return 
      TIOINETResponsePtr (HGNEW (TIOINETResponse (
         req.ptr(),
         TransparentIOPtr (HGNEW (TransparentSocketIO (SocketPtr (server_conn.ptr())))), 
         TIOINETAttributes (INETAddress() /*cannot pass no valid outer
                                            side endpoint at this side
                                            because this is not supported
                                            by SOCKS4*/,
                            req.ptr()->address())))) ;
}

TIOINETResponsePtr TIOINETFactorySOCKS4 :: make_nonblocking_(const TIOINETRequestPtr& req) {
   // set the worker of the request (me).
   TIOINETFactory::set_worker_(req) ;

   // generate a request to issue at my "direct maker". he will then
   // call me back.
   TIOINETRequestPtr directreq (HGNEW (TIOINETRequest (server_, this))) ;

   // issue it.
   TIOINETResponsePtr res = to_server_.connect (directreq) ;

   if (res) {
      if (res.ptr()->error()) {
         DEBUGNL ("TIOINETFactorySOCKS4::make_nonblocking_(): "
                  "error connecting to SOCKS server: "<<(int)res.ptr()->error()) ;
         return TIOINETResponsePtr (HGNEW (TIOINETResponse (req.ptr(), res.ptr()->error()))) ;
      }

      // all is well. start to send out the request (not immediately,
      // rather wait for the system to tell me I may do so).
      DEBUGNL ("TIOINETFactorySOCKS4::make_nonblocking_(): "
               "gotA SOCKS server connection immediately") ;
      hgassert (res.ptr()->io(), 
                "TIOINETFactorySOCKS4::make_nonblocking_(): no valid IO in response") ;
      bool rv = out_wait_->insert (req, res.ptr()->io(), 
                                   SOCKS4BufferPtr (
                                      HGNEW (SOCKS4Buffer (
                                         SOCKS4ConnectRequest (req.ptr()->address(), uid_))))) ;
      hgassert (rv, "TIOINETFactorySOCKS4::make_nonblocking_(): "
                "could not insert waiting for output") ;
      res.ptr()->io().ptr()->registerInput (this) ; // to watch out for eof
      res.ptr()->io().ptr()->registerOutput (this) ; // to write out my buffer
      // tell the user he will have to be patient a little bit.
      return TIOINETResponsePtr() ; 
   }

   // nil response. have to wait for the connection to the SOCKS
   // server (should be the common case (unless the server is on the
   // local system (quite uncommon I think) and the local system is
   // Solaris, for example, where local connections are established
   // instantly). nevertheless, the user will have to wait.
   bool rv = conn_wait_->insert (req /*orig*/, directreq) ;
   hgassert (rv, "TIOINETFactorySOCKS4::make_nonblocking_(): "
             "could not insert waiting for connection to SOCKS server") ;
   return TIOINETResponsePtr() ; 
}

void TIOINETFactorySOCKS4 :: tioInetResponse (const TIOINETResponsePtr& res) {
   DEBUGNL ("TIOINETFactorySOCKS4::tioInetResponse()") ;
   hgassert  (res, "TIOINETFactorySOCKS4::tioInetResponse(): nil response") ;

   // there must be an entry in my first-stage mapping (original
   // request vs. my own request to my "direct maker").
   hgassert (res.ptr()->request(), "TIOINETFactorySOCKS4::tioInetResponse(): "
             "response does not tell by which request it was issued") ;
   TIOINETRequestPtr orig = conn_wait_->removeByDirect (res.ptr()->request()) ;
   hgassert (orig, "TIOINETFactorySOCKS4::tioInetResponse(): could not find original request") ;
   
   if (res.ptr()->error()) {
      DEBUGNL ("TIOINETFactorySOCKS4::tioInetResponse(): "
               "error connecting to SOCKS server: "<<(int)res.ptr()->error()) ;
      hgassert (orig.ptr()->user(), "TIOINETFactorySOCKS4::tioInetResponse(): "
                "original request has no user set") ;
      orig.ptr()->getUser()->tioInetResponse (
         TIOINETResponsePtr (HGNEW (TIOINETResponse (orig.ptr(), res.ptr()->error())))) ;
      return ;
   }

   // all is well. proceed as above (as if the connection had been
   // made immediately).
   DEBUGNL ("TIOINETFactorySOCKS4::tioInetResponse(): connection to SOCKS server established") ;
   hgassert (res.ptr()->io(), "TIOINETFactorySOCKS4::tioInetResponse(): nil connection") ;
   bool rv = out_wait_->insert (orig, res.ptr()->io(), 
                                SOCKS4BufferPtr (
                                   HGNEW (SOCKS4Buffer (
                                      SOCKS4ConnectRequest (orig.ptr()->address(), uid_))))) ;
   hgassert (rv, "TIOINETFactorySOCKS4::tioInetResponse(): "
             "could not insert waiting for output") ;
   res.ptr()->io().ptr()->registerInput (this) ; // to watch for eof (a crashed socks daemon?)
   res.ptr()->io().ptr()->registerOutput (this) ; // to write out my buffer
}

void TIOINETFactorySOCKS4 :: outputReady (const TransparentIO* io) {
   DEBUGNL ("TIOINETFactorySOCKS4::outputReady()") ;
   // there must be a request waiting to get written
   int pos = out_wait_->posByTIO (io) ;
   hgassert (pos>=0, "TIOINETFactorySOCKS4::outputReady(): "
             "no request waiting to get written onto that io") ;

   // write (at least, part of) the connect request of that entity
   OutWaitElement e = out_wait_->operator[] (pos) ;

   // enter paranoia section
   hgassert (e.buffer(), "TIOINETFactorySOCKS4::outputReady(): nil request buffer") ;
   // that entity must still have data available to write (else, it
   // should be in error (and should have been removed already) or
   // already waiting for the incoming reply):
   hgassert (e.buffer().ptr()->navail(), 
             "TIOINETFactorySOCKS4::outputReady(): no request data available anymore") ;
   hgassert (e.tio(), "TIOINETFactorySOCKS4::outputReady(): nil io") ;
   // leave paranoia section
   
   int nwritten = e.tio().ptr()->write (e.buffer().ptr()->current(), 
                                        e.buffer().ptr()->navail()) ;
   hgassert (nwritten!=0, "TIOINETFactorySOCKS4::outputReady(): 0 bytes written") ;
   if (nwritten < 0) {
      // encountered an error. remove these things from the out-wait
      // data structure. stop watching out for input (for
      // eof-recognizing) and output. inform the issuer of the
      // request.
      DEBUGNL ("TIOINETFactorySOCKS4::outputReady(): write error") ;
      hgassert (e.orig(), "TIOINETFactorySOCKS4::outputReady(): nil original request") ;
      TIOINETFactoryUser* u = e.orig().ptr()->getUser() ;
      const TIOINETRequest* orig = e.orig().ptr() ;
      hgassert (u, "TIOINETFactorySOCKS4::outputReady(): "
                "no-one seems to have issued original request") ;
      out_wait_->remove (pos) ;
      e.tio().ptr()->registerInput (nil) ;
      e.tio().ptr()->registerOutput (nil) ;
      // and finally tell the issuer.
      u->tioInetResponse (TIOINETResponsePtr (
         HGNEW (TIOINETResponse (orig /*as a refno*/, TIOINETFactory::TIFNOCONN)))) ;
      return ;
   }

   // all is well. check if we have written all of it, or if we have
   // to wait again.
   e.buffer().ptr()->consume (nwritten) ;
   if (e.buffer().ptr()->done()) {
      DEBUGNL ("TIOINETFactorySOCKS4::outputReady(): done writing request") ;
      // remove the things from the out-wait data structure. stop
      // watching for output. keep being paranoid. move the things
      // over to the in-wait data structure. keep watching for input
      // (I already do so because I have to watch out for eof at any
      // time anyhow).
      bool rv = out_wait_->remove (pos) ;
      hgassert (rv, "TIOINETFactorySOCKS4::outputReady(): could not remove from out-wait") ;
      rv = in_wait_->insert (e.orig(), e.tio()) ;
      hgassert (rv, "TIOINETFactorySOCKS4::outputReady(): "
                "could not insert a new in-waiting request") ;
      e.tio().ptr()->registerOutput (nil) ;
      return ;
   }

   DEBUGNL ("TIOINETFactorySOCKS4::outputReady(): not yet done. have to wait for more input") ;
}

void TIOINETFactorySOCKS4 :: inputReady (const TransparentIO* io) {
   DEBUGNL ("TIOINETFactorySOCKS4::inputReady()") ;

   // check to see what state the corresponding request has. if I can
   // find it in the in-wait structure, it is just waiting for the
   // reply (or the completion of it). if I can find it in the
   // out-waiting structure, this means an error (socks daemon crash
   // or whatever). if I can find it in neither, we have a serious
   // bug.
   
   // begin with the common case (socks server behaves well, I should
   // find it in the in-waiting structure).
   int pos = in_wait_->posByIO (io) ;
   if (pos >= 0) {
      DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): regular input (well, hopefully)") ;
      InWaitElement e = in_wait_->operator[] (pos) ;
      // get the user out of the thing. I need it on several places
      // down there in the code, so here's right the place to do.
      hgassert (e.orig(), 
                "TIOINETFactorySOCKS4::inputReady(): no original request") ;
      TIOINETFactoryUser* u = e.orig().ptr()->getUser() ;
      hgassert (u, "TIOINETFactorySOCKS4::inputReady(): "
                "no-one seems to have issued that request") ;

      // read some characters (not too much because the size is
      // constant and small). add them to the reply.
      hgassert (e.io(), "TIOINETFactorySOCKS4::inputReady(): nil io") ;
      hgassert (e.reply(), "TIOINETFactorySOCKS4::inputReady(): nil reply") ;
      char tmp [SOCKS4ConnectReply::size] ;
      int nread = e.io().ptr()->read (tmp, SOCKS4ConnectReply::size) ;
      if (nread > 0) {
         int nconsumed = e.reply().ptr()->add (tmp, nread) ;
         if (e.reply().ptr()->ok()) {
            // no protocol error encountered so far. check if we have
            // a full (complete()) reply.
            if (e.reply().ptr()->complete()) {
               // done with the reply. remove the entity from the
               // in-wait structure to perform immediate
               // cleanup. (also stop watching for input.) check to
               // see whether the request is granted and build an
               // appropriate response.
               in_wait_->remove (pos) ;
               e.io().ptr()->registerInput (nil) ;

               TIOINETResponsePtr response ;

               if (e.reply().ptr()->errorCode()) { 
                  // not granted. 
                  DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
                           "request denied for the following reason: \""<<
                           e.reply().ptr()->errorDescription()<<'"') ;
                  response = TIOINETResponsePtr (
                     HGNEW (TIOINETResponse (e.orig().ptr(), TIOINETFactory::TIFNOCONN))) ;
               }
               else {
                  DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): request granted") ;
                  // if data is left, we have to put it back into the tio
                  // (this may happen for example if the socks server has
                  // connected and the target sends data immediately, and
                  // the inner side TCP is lazy enough to buffer the
                  // socks reply together with that data).
                  TransparentIOPtr ret_io ;
                  if (nconsumed < nread) {
                     int rest = nread - nconsumed ;
                     DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
                              "have to put back "<<rest<<" bytes") ;
                     TransparentBReadIO* tmpio = HGNEW (TransparentBReadIO (e.io())) ;
                     tmpio->putBack (tmp+nconsumed, rest) ;
                     ret_io = TransparentIOPtr (tmpio) ;
                  }
                  else 
                     ret_io = e.io() ;
               
                  response = TIOINETResponsePtr (
                     HGNEW (TIOINETResponse (e.orig().ptr(), ret_io,
                                             TIOINETAttributes (
                                                // the outer side this
                                                // side address is
                                                // unknown as it is
                                                // not supported by
                                                // the SOCKS4
                                                // protocol.
                                                INETAddress(), 
                                                // whereas the outer
                                                // side that address
                                                // is the one that was
                                                // requested
                                                e.orig().ptr()->address())))) ;
               }

               u->tioInetResponse (response) ;
            }
            else {
               // not complete. wait for new input. should be
               // mentioned altogether since it is not that likely
               // (are there networks that are likely to deliver one
               // byte at a time?).
               DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
                        "funny: response not complete after one read") ;
            }
            return ;
         }
         else {
            // a protocol error has occured (whoever is to
            // blame). stop watching input and tell the issuer.
            DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): SOCKS protocol error") ;
            e.io().ptr()->registerInput (nil) ;
            in_wait_->remove (pos) ;
            u->tioInetResponse (TIOINETResponsePtr (
               HGNEW (TIOINETResponse (e.orig().ptr(), TIOINETFactory::TIFNOCONN)))) ;
            return ;
         }
      }
      else {
         if (nread == 0) {
            DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
                     "eof while watching out for (pieces of) reply") ;
         }
         else {
            DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
                     "read error while watching out for (pieces of) reply") ;
         }
         in_wait_->remove (pos) ;
         e.io().ptr()->registerInput (nil) ;
         u->tioInetResponse (TIOINETResponsePtr (
            HGNEW (TIOINETResponse (e.orig().ptr(), TIOINETFactory::TIFNOCONN)))) ;
         return ;
      }
   }

   else {
      // I must be watching for output events. (means: eof or data
      // input while writing request.) else severe error. remove the
      // thing from the out-wait structure. stop watching for input
      // and output. tell the user about it.
      OutWaitElement e ;
      bool rv = out_wait_->removeByTIO (io, e) ;
      hgassert (rv, "TIOINETFactorySOCKS4::inputReady(): "
                "neither in my in-wait nor in my out-wait structure") ;
      DEBUGNL ("TIOINETFactorySOCKS4::inputReady(): "
               "eof or data input while watching out for output") ;
      e.tio().ptr()->registerInput (nil) ;
      e.tio().ptr()->registerOutput (nil) ;
      
      hgassert (e.orig(), "TIOINETFactorySOCKS4::inputReady(): "
                "no original request") ;
      TIOINETFactoryUser* u = e.orig().ptr()->getUser() ;
      hgassert (u, "TIOINETFactorySOCKS4::inputReady(): "
                "no-one seems to have issued that request") ;

      u->tioInetResponse (TIOINETResponsePtr (
         HGNEW (TIOINETResponse (e.orig().ptr(), TIOINETFactory::TIFNOCONN)))) ;
      return ;
   }

   // well, so much for input. pooh.
}

void TIOINETFactorySOCKS4 :: do_cancel_(const TIOINETRequest* req) {
   // check to see if I have it in any of my states (my maps). if so,
   // take the appropriate action. else, we have serious bug.
   TIOINETRequestPtr direct = conn_wait_->removeByOrig (req) ;
   if (direct) {
      DEBUGNL ("TIOINETFactorySOCKS4::do_cancel_(): caught while connecting to socks server") ;
      direct.ptr()->cancel (this) ;
      return ;
   }
   TransparentIOPtr socksconn ;
   SOCKS4BufferPtr req4buf ;
   if (out_wait_->removeByOrig (req, socksconn, req4buf)) {
      DEBUGNL ("TIOINETFactorySOCKS4::do_cancel_(): "
               "caught while waiting for the request to get written") ;
      // stop watching for both input and output.
      hgassert (socksconn, "TIOINETFactorySOCKS4::do_cancel_(): "
                "got nil connection from the out-wait list") ;
      socksconn.ptr()->registerInput (nil) ;
      socksconn.ptr()->registerOutput (nil) ;
      return ; 
   }
   if (in_wait_->removeByOrig (req, socksconn)) {
      DEBUGNL ("TIOINETFactorySOCKS4::do_cancel_(): "
               "caught while waiting for the reply to get complete") ;
      // I am only watching for input at this point.
      hgassert (socksconn, "TIOINETFactorySOCKS4::do_cancel_(): "
                "got nil connection from the in-wait list") ;
      socksconn.ptr()->registerInput (nil) ;
      return ;
   }
}
