/*## (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.util.zip.*;
import javax.tv.service.*;
import org.davic.mpeg.TransportStream;
import org.davic.net.dvb.*;

public abstract class AdbEmmFilter extends MPEGFilter {
 public static class AdbEmmData {
  int part_cnt;
  int total_cnt;
  byte[][] parts;

  public AdbEmmData(int cnt) {
   part_cnt=0;
   total_cnt=cnt;
   parts=new byte[cnt][];   
  }

  public void add_part(int idx,byte data[]) {
   if (parts[idx]==null) {
    parts[idx]=data;
    part_cnt++;
   }
  }

  public byte[] get_part(int idx) {
   return parts[idx];
  }

  public boolean complete() {
   return part_cnt==total_cnt;
  }
 }

 private static Hashtable sections;
 private static byte inflateout[];

 private Object syncobj;
 private boolean running;

 private int data_off;
 private int data_cnt;

 private Object appdata;

 static {
  sections=new Hashtable();
  inflateout=new byte[0x8000];
 }

 public AdbEmmFilter(String name,int onid,int tsid,int pid,int table_id,int filter_pos,byte posfdef[],byte posfmask[],int data_off,int data_cnt,Object appdata) {
  super(name,onid,tsid,pid,table_id,filter_pos,posfdef,posfmask);
  syncobj=new Object();

  this.data_off=data_off;
  this.data_cnt=data_cnt;
  this.appdata=appdata;
 }

 public boolean start() {
  running=true;

  return super.start();
 }

 public boolean match(byte data[]) {
  if (!running) return false;

  if (new_data(data)) return true;
   else return false;
 }

 public abstract int process_data(byte data[],int data_cnt,Object appdata);

 public abstract int get_key(byte data[]);

 private void process_adbemm_data(byte data[]) throws Throwable {
  int cnt=process_data(data,data_cnt,appdata);

  data_cnt-=cnt;

  if (data_cnt<=0) {
   running=false;
 
   sections=new Hashtable();
   inflateout=new byte[0x8000];

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

 private byte[] collect_data(byte data[]) {
  int sec_num=((int)data[6])&0xff;
  int sec_cnt=((int)data[7])&0xff;
  sec_cnt++;

  int key=get_key(data);

  AdbEmmData id=(AdbEmmData)sections.get(new Integer(key));

  if (id==null) {
   id=new AdbEmmData(sec_cnt);
   sections.put(new Integer(key),id);
  }

  id.add_part(sec_num,data);

  if (id.complete()) {
   sections.remove(new Integer(key));

   int size=0;

   for(int i=0;i<sec_cnt;i++) {
    byte part[]=id.get_part(i); 
    size+=part.length-data_off-4;
   }

   byte adbemm[]=new byte[size];

   int pos=0;

   for(int i=0;i<sec_cnt;i++) {
    byte part[]=id.get_part(i); 
    int part_size=part.length-data_off-4;

    System.arraycopy(part,data_off,adbemm,pos,part_size);
    pos+=part_size;
   }

   Inflater inflater=new Inflater(false);    
   inflater.setInput(adbemm);

   int outsize=0;

   try {
    ByteArrayOutputStream baos=new ByteArrayOutputStream();
    
    for(;!inflater.finished();baos.write(inflateout,0,outsize))
     outsize=inflater.inflate(inflateout);

    inflater.end();

    return baos.toByteArray();
   } catch(Throwable t) {
    ApiMonitor.log("exception "+t);
   }
  }

  return null;
 }

 public void action(byte data[]) {
  byte adbemm[]=collect_data(data);

  if (adbemm!=null) {
   try {
    process_adbemm_data(adbemm);
   } catch(Throwable t) {
    ApiMonitor.log("action "+t);
   }
  }  
 }

 public void wait_for_all() throws Throwable {
  synchronized(syncobj) {
   syncobj.wait();
  }
 }

}
