/*## (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.*;

public class SSU {
 public static class UpgradeInfo {
  int pid;
  int oui;
  int cust_id;
  int hw_version;
  int ssu_table_id;
  int ssu_unique_download_id;  
  byte data[];

  public UpgradeInfo(int pid,int oui,int cust_id,int hw_version,int ssu_table_id,int ssu_unique_download_id,byte data[]) {
   this.pid=pid;
   this.oui=oui;
   this.cust_id=cust_id;
   this.hw_version=hw_version;
   this.ssu_table_id=ssu_table_id;
   this.ssu_unique_download_id=ssu_unique_download_id;
   this.data=data;
  }

  public void print() throws Throwable {
   String s="- "+Utils.padded_string("pid",PAD)+"0x"+Utils.hex_value(pid,4);
   Output.text(s);

   s="- "+Utils.padded_string("oui",PAD)+"0x"+Utils.hex_value(oui,6);
   String s2=lookup_oui(oui);

   if (s2!=null) {
    s+="("+s2+")";
   }   

   Output.text(s);

   if (oui==SSUDefs.ADB_OUI) {
    s="- "+Utils.padded_string("customer_id",PAD)+"0x"+Utils.hex_value(cust_id,2);
    Output.text(s);

    s="- "+Utils.padded_string("hardware version",PAD)+"0x"+Utils.hex_value(hw_version,4);
    s2=lookup_hwname(hw_version);

    if (s2!=null) {
     s+="  "+s2;
    }   

    Output.text(s);

    s="- "+Utils.padded_string("ssu_table_id",PAD)+"0x"+Utils.hex_value(ssu_table_id,4);
    Output.text(s);


    s="- "+Utils.padded_string("ssu_unique_download_id",PAD)+"0x"+Utils.hex_value(ssu_unique_download_id,4);
    Output.text(s);
   } else {
    s="- selector data";
    Output.text(s);

    MPEG.print_buf(2,data);
   }
  }

 }

 public static class Info {
  int svid;
  int pid;
  UpgradeInfo upgrades[];

  public Info(int svid,int pid,UpgradeInfo upgrades[]) {
   this.svid=svid;
   this.pid=pid;
   this.upgrades=upgrades;
  }
 }

 public static final int PAD = 25;

 private static Hashtable ouis;
 private static Hashtable hwnames;

 private static Info info;

 static {
  ouis=new Hashtable();
  ouis.put(new Integer(0x000391),"Advanced Digital Broadcast");
  ouis.put(new Integer(0x0008c9),"Technisat Digital GmbH"); 
  ouis.put(new Integer(0x0015e5),"Cheertek Inc.");

  hwnames=new Hashtable();

  hwnames.put(new Integer(0xb2b0),"ITI5800S   (BSKA serial)"); 
  hwnames.put(new Integer(0xb2b1),"ITI5800SX  (BSLA serial)"); 

  hwnames.put(new Integer(0xb2b2),"ITI5800S   (BXZB serial)"); 
  hwnames.put(new Integer(0xb2b3),"ITI5800SX  (BZZB serial)"); 

  hwnames.put(new Integer(0x0107),"ITI5720SX  (CLRA serial)"); 

  hwnames.put(new Integer(0x0110),"ITI2850ST  (CSTA serial)"); 

  hwnames.put(new Integer(0x0133),"ITI-2851S"); 
  hwnames.put(new Integer(0x0136),"ITI-3740SX"); 
  hwnames.put(new Integer(0x0141),"ITI-2130S"); 
  hwnames.put(new Integer(0x014f),"NCP-4740SF"); 
 }

 private static String lookup_oui(int oui) {
  return (String)ouis.get(new Integer(oui));
 }

 private static String lookup_hwname(int hwid) {
  return (String)hwnames.get(new Integer(hwid));
 }

 private static int read_short(byte data[],int pos) {
  return ((((int)data[pos])&0xff)<<8)|(((int)data[pos+1])&0xff);
 }

 private static int read_oui(byte data[],int pos) {
  return ((((int)data[pos])&0xff)<<16)|((((int)data[pos+1])&0xff)<<8)|(((int)data[pos+2])&0xff);
 }

 private static UpgradeInfo parse_descriptor(int pid,byte data[],int pos,int len) throws Throwable {
  int cust_id,hw_version,ssu_table_id,ssu_unique_download_id;

  cust_id=hw_version=ssu_table_id=ssu_unique_download_id=0;

  byte selector[]=new byte[len];

  for(int i=0;i<len;i++) {
   selector[i]=data[pos+i];
  }

  int oui=read_oui(data,pos);

  if (len>=8) {
   cust_id=((int)data[pos+7])&0xff;
  }

  if (len>=10) {
   hw_version=read_short(data,pos+8);
  }

  if (len>=13) {
   ssu_table_id=((int)data[pos+12])&0xff;
  }

  if (len>=15) {
   ssu_unique_download_id=read_short(data,pos+13);
  }

  return new UpgradeInfo(pid,oui,cust_id,hw_version,ssu_table_id,ssu_unique_download_id,selector);
 } 

 private static UpgradeInfo parse_desc_table(int pid,byte data[],int pos,int size) throws Throwable {
  while(pos<size) {
   int tag=(int)data[pos];
   int len=(int)data[pos+1];

   if (tag==SSUDefs.DATA_BROADCAST_ID_DESCRIPTOR) {
    int data_broadcast_id=read_short(data,pos+2);

    if (data_broadcast_id==SSUDefs.system_software_update_service) {
     int tag_len=((int)data[pos+4])&0xff;
     return parse_descriptor(pid,data,pos+5,tag_len);
    }
   }

   pos+=len+2;   
  }

  return null;
 }

 public static Info get_info() { 
  if (info!=null) return info;

  try {
   int ssu_svid=MPEG.get_ssu_svid();
   int ssu_pid=MPEG.get_ssu_pid(ssu_svid);

   if (ssu_pid<0) return null;

   byte pmt[]=MPEGSniffer.get_section(null,ssu_pid);    

   int pos=MPEGDefs.SECTION_DATA_OFF;

   int size=pmt.length-4;

   pos+=2;

   int pinfolen=read_short(pmt,pos)&0xfff; 

   pos+=2+pinfolen;

   Vector v=new Vector();

   while(pos<size) {
    int esinfolen=read_short(pmt,pos+3)&0xfff;

    int stream_type=(int)pmt[pos]&0xff;
    int elementary_pid=read_short(pmt,pos+1)&0x1fff;

    UpgradeInfo ui=parse_desc_table(elementary_pid,pmt,pos+5,pos+5+esinfolen);
 
    if (ui!=null) {
     v.addElement(ui);
    }

    pos+=5+esinfolen;
   }

   if (v.size()>0) {
    UpgradeInfo upgrades[]=new UpgradeInfo[v.size()];

    for(int i=0;i<v.size();i++) {
     upgrades[i]=(UpgradeInfo)v.elementAt(i);
    }

    info=new Info(ssu_svid,ssu_pid,upgrades);
   }
  } catch(Throwable t) {
   ApiMonitor.log("exception "+t);
  }

  return info;
 }

 public static UpgradeInfo lookup_upgrade(int hwid) {
  Info info=get_info();

  UpgradeInfo upgrades[]=info.upgrades;

  if (upgrades!=null) {
   for(int i=0;i<upgrades.length;i++) {
    if (upgrades[i].hw_version==hwid) return upgrades[i];
   }
  }

  return null;
 }

 public static int hermes_download_upgrade(UpgradeInfo ui) {
  int res=SSUDefs.UPGRADE_OK;

  try {
   HermesUpgradeDownloader f=HermesUpgradeDownloader.create(ui.pid,ui.ssu_table_id,ui.ssu_unique_download_id);

   if (f==null) return SSUDefs.CREATE_FILTER;

   Output.integer(res);
   Output.flush();

   f.start();
   f.collect_sections();
   f.stop();
  } catch(Throwable t) {}

  return res;
 }

 public static int carbo_download_upgrade(UpgradeInfo ui) {
  boolean first=true;
  int total_cnt=0;
  int image=0;

  try {
   Loader.Key key=Loader.get_upgrade_key(ui.hw_version);

   if (key!=null) {
    int ksize=key.size()>>2;
    int kdata=key.data();

    Output.integer(ksize);

    for(int i=0;i<ksize;i++){
     int val=Memory.read_dword(kdata+4*i);
     Output.integer(val);
    }

    Output.flush();

    CarboUpgradeDownloader f=CarboUpgradeDownloader.create(ui.pid,ui.ssu_table_id,ui.ssu_unique_download_id);

    if (f==null) return SSUDefs.CREATE_FILTER;

    f.start();
    f.collect_sections();
    f.stop();
   } else return SSUDefs.READ_KEYS;

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

  return SSUDefs.UPGRADE_OK;
 }

 public static int download_upgrade(int hwid) {
  int res=SSUDefs.UPGRADE_OK;

  UpgradeInfo ui=lookup_upgrade(hwid);

  ApiMonitor.log("download_upgrade "+ui);

  if (ui==null) return SSUDefs.UNKNOWN_HWID;
  
  switch(hwid) {
   case SysInfoDefs.ITI5800S:
   case SysInfoDefs.ITI5800SX:
    res=hermes_download_upgrade(ui);
    break;

   case SysInfoDefs.ITI2850ST:
   case SysInfoDefs.ITI2851S:
    res=carbo_download_upgrade(ui);
    break;

   default:
    res=carbo_download_upgrade(ui);
    break;
  }

  ApiMonitor.log("download_upgrade res "+res);

  return res;
 }
}
