/*## (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 org.davic.net.tuning.*;
import org.davic.mpeg.sections.*;
import org.davic.mpeg.TransportStream;
import org.davic.resources.*;
import java.util.zip.*;

public class MPEGFilter implements SectionFilterListener {
 public static class FltData {
  MPEGFilter flt;
  byte data[]; 

  public FltData(MPEGFilter flt,byte data[]) {
   this.flt=flt;
   this.data=data;
  }
 }

 public static class Worker implements DaemonThread.Code {
  static LinkedList queue;
  private static boolean running;

  public FltData get_data() throws Throwable {
   synchronized(queue) {
    while(queue.isEmpty()) queue.wait();
       
    FltData fd=(FltData)queue.removeFirst();

    return fd;
   }
  }

  public void run(Object args[]) {
   try {
    running=true;

    while(running) {
     FltData fd=get_data();

     if (fd==null) continue;

     fd.flt.action(fd.data);
    }   
   } catch(Throwable t) {}
  }

  public static void start() {
   try {
    queue=new LinkedList();

    Worker w=new Worker();
    DaemonThread.start(w,null);    
   } catch(Throwable t) {}
  }

  public static void stop() {
   running=false;

   synchronized(Worker.queue) {
    Worker.queue.notify();
   }
  }
 }

 public static class ResClient implements ResourceClient {
  public boolean requestRelease(ResourceProxy resourceproxy, Object obj) {
   return false;
  }

  public void release(ResourceProxy resourceproxy) {
  }

  public void notifyRelease(ResourceProxy resourceproxy) {
  }
 }

 public static int SECTIONNUM       = 4096;
 public static int CRC32_CHECK_SIZE = 64;

 private boolean enabled;
 private String name;
 private int onid;
 private int tsid;
 private int pid;

 private int table_id;

 private int pos;
 byte posfdef[];
 byte posfmask[];

 private int sidx;
 
 private RingSectionFilter filter;

 private int last_length;
 private long last_crc32;

 static {
  try {
   Worker.start();
  } catch(Throwable t) {}
 }

 public MPEGFilter(String name,int onid,int tsid,int pid,int table_id,int pos,byte posfdef[],byte posfmask[]) {
  this.name=name;
  this.onid=onid;
  this.tsid=tsid;
  this.pid=pid;

  this.table_id=table_id;
  this.pos=pos;

  this.posfdef=posfdef;
  this.posfmask=posfmask;

  sidx=0;

  last_length=-1;
  last_crc32=-1;
 }

 public MPEGFilter(String name,int onid,int tsid,int pid,int table_id,byte posfdef[],byte posfmask[]) {
  this(name,onid,tsid,pid,table_id,3,posfdef,posfmask);
 }

 public MPEGFilter(String name,int onid,int tsid,int pid) {
  this(name,onid,tsid,pid,-1,3,null,null);
 }

 public int pid() {
  return pid;
 }

 public boolean match(byte data[]) {
  return true;
 }

 public void action(byte data[]) {
 }

 public boolean new_data(byte data[]) {
  boolean res=false;

  int size=data.length;
  if (size>CRC32_CHECK_SIZE) size=CRC32_CHECK_SIZE;

  if (last_length!=data.length) {
   CRC32 crc=new CRC32();
   crc.update(data,0,size);

   last_length=data.length;
   last_crc32=crc.getValue();

   res=true;
  } else {   
   CRC32 crc=new CRC32();
   crc.update(data,0,size);

   if (crc.getValue()!=last_crc32) {
    last_length=data.length;
    last_crc32=crc.getValue();

    res=true;
   }
  }

  return res;
 }

 public void start_filter() throws Throwable {
  if (table_id==-1) {
   filter.startFiltering(null,pid);
  } else

  if (posfdef==null) {
   filter.startFiltering(null,pid,table_id);
  } else {
   ApiMonitor.log("pid: "+Utils.hex_value(pid,8));
   ApiMonitor.log("tid: "+table_id+" pos "+pos);
   ApiMonitor.log("filter def: "+SCInfo.addr2string(posfdef));
   ApiMonitor.log("filter mask: "+SCInfo.addr2string(posfmask));

   filter.startFiltering(null,pid,table_id,pos,posfdef,posfmask);
  }
 }

 public void sectionFilterUpdate(SectionFilterEvent event) {
  Section s=null;

  try {
   if (event instanceof TimeOutEvent) {
    ApiMonitor.log("sectionFilterUpdate timeout!!");
    start_filter();
   } else
 
   if (event instanceof SectionAvailableEvent) {
    Section sections[]=filter.getSections();

    s=sections[sidx%SECTIONNUM];

    sidx++;

    if (s==null) return;

    try {
     byte data[]=s.getData();
     s.setEmpty();
     s=null;

     if (match(data)) {
      synchronized(Worker.queue) {
       Worker.queue.addLast(new FltData(this,data));   
      }
     }

    } catch(Throwable e) {
     if (s!=null) s.setEmpty();
    }

    synchronized(Worker.queue) {
     Worker.queue.notify();
    }
   }
  } catch(Throwable e) {
   ApiMonitor.log("sectionFilterUpdate exception: "+e);
  }
 }

 public boolean start() {
  if (enabled) return false;

  try {
   SectionFilterGroup sfg=new SectionFilterGroup(1);
   filter=sfg.newRingSectionFilter(SECTIONNUM);

   TransportStream ts=MPEGSniffer.current_ts();

   if (ts.getTransportStreamId()==tsid) {
    sfg.attach(ts,new ResClient(),null);
  
    filter.addSectionFilterListener(this);
    start_filter();

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

  return enabled;
 }

 public boolean stop() {
  if (!enabled) return false;

  filter.stopFiltering();
  filter.removeSectionFilterListener(this);

  enabled=false;

  return true;
 }

}
