/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;
import java.util.*;
import java.io.*;
import java.net.*;

public class SrvConnection {

 public static class InputStreamDemux implements DaemonThread.Code {

  private void discard_message(int size) throws Throwable {
   while(size>0) {
    int val=bis.read();
    size--;
   }   
  }

  private void forward_message(int type,int size) throws Throwable {
   PipedOutputStream os=get_demux_pipe(type);      

   while(size>0) {
    int val=bis.read();
    os.write(val);
    size--;
   }   

   os.flush();
  }

  public void run(Object args[]) {
   try {
    while(connected) {
     int size=in.read_short();
     int type=in.get_byte();

     switch(type) {
      case Proto.CMD_ANNOUNCEMENT:
      case Proto.CMD_DATA:
       forward_message(type,size);
       break;

      default:
       discard_message(size);
       break;
     }
    }

   } catch(Throwable t) {
    ApiMonitor.log("InputStreamDemux exception "+t);
   }

   ApiMonitor.log("InputStreamDemux exited");
  }

 }

 private static SrvConnection conn;
 private static boolean connected;

 static BufferedInputStream bis;

 static Hashtable ismap;
 static Hashtable ispipemap;

 private int type;
 private Socket sck;

 private static StreamInput in;
 private Vector outs;

 public static SrvConnection get(int type) throws Throwable {
  if (!connected) {
   conn=new SrvConnection(type,ConfigDefs.BOXSERVER_ADDR,ConfigDefs.BOXSERVER_PORT);

   ismap=new Hashtable();
   ispipemap=new Hashtable();
  }

  return conn;
 }

 public static void set_input_stream_data_type(WrappedInput wi,int type) {
  ismap.put(new Integer(type),wi);
 }

 public static PipedOutputStream get_demux_pipe(int type) {
  Object o=ismap.get(new Integer(type));

  if (o!=null) {
   return (PipedOutputStream)ispipemap.get(o);
  }

  return null;
 }

 private Socket connect(String host,int port) {
  Socket sck=null;

  while(true) {
   try {
    sck=new Socket(host,port);
    sck.setSoTimeout(0);
    break;
   } catch(Throwable t) {}

   ConsoleUtils.sleep(ConfigDefs.BOXSERVER_RETRY_TIMEOUT*1000);
  }

  return sck;
 }

 private SrvConnection(int type,String host,int port) {
  try {
   this.type=type;

   sck=connect(host,port);

   InputStream is=sck.getInputStream();
   bis=new BufferedInputStream(is);

   in=new StreamInput(bis);
   outs=new Vector();

   connected=true;

   if (type==Proto.CLT_BOX) {
    InputStreamDemux demux=new InputStreamDemux();

    DaemonThread.start(demux,null);
   }
  } catch(Throwable t) {}
 }

 public StreamInput input() throws Throwable {
  StreamInput sin=null;

  if (connected) {
   switch(type) {
    case Proto.CLT_CONSOLE:
     sin=new StreamInput(bis);
     break;

    case Proto.CLT_BOX:
     PipedOutputStream pos=new PipedOutputStream();
     PipedInputStream pis=new PipedInputStream(pos);

     sin=new StreamInput(pis);

     ispipemap.put(sin,pos);
     break;
   }
  }

  return sin;
 }

 public StreamOutput output() throws Throwable {
  StreamOutput out=null;

  if (connected) {
   OutputStream os=sck.getOutputStream();
   BufferedOutputStream bos=new BufferedOutputStream(os);

   out=new StreamOutput(bos);

   outs.addElement(out);
  }

  return out;
 }

 public void close() throws Throwable {
  sck.close();  

  in=null;
  outs=null;

  conn=null;

  connected=false;
 }

}
