/*## (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.io.*;
import java.util.*;

public class EMMStore {
 public static class LazyStore implements DaemonThread.Code {
  private static LinkedList queue;

  public boolean started;

  public LazyStore() {
   queue=new LinkedList();
  }

  public void store(String filename,byte data[]) throws Throwable {
   try {
    if ((filename!=null)&&(data!=null)) {
     FileOutputStream fos=new FileOutputStream(filename,false);
     fos.write(data);
     fos.close();
    }
   } catch(Throwable t) {
    ApiMonitor.log_stacktrace(t);
   }
  }

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

    ApiMonitor.log("lazy store running");

    while(lzrunning) {
     synchronized(queue) {
      while(queue.isEmpty()) queue.wait();
       
      String filename=(String)queue.removeFirst();
      byte data[]=(byte[])queue.removeFirst();

      store(filename,data);
     }
    }
   } catch(Throwable t) {}
  }
  
  public static void submit(String filename,byte data[]) {
   synchronized(queue) {
    queue.addLast(filename);
    queue.addLast(data);
    queue.notify();
   }
  }

  public static void stop() {
   lzrunning=false; 
   queue.notify();
  }

 }

 private static boolean lzrunning;
 private static LazyStore lazystore;

 static {
  try {
   new File(EMMStoreDefs.EMM_DIR).mkdirs();
  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }
 }

 public static void start_lazy_store() {
  try {
   lzrunning=true;

   lazystore=new LazyStore();
   DaemonThread.start(lazystore,null);

   while(!lazystore.started) Utils.sleep(10); 
  } catch(Throwable t) {}
 }

 public static void downloader_stop() {
  LazyStore.stop();
 }

 public static void store(String name,int buf,int size) {
  try {
   ApiMonitor.log("store "+name+" size "+size);

   String filename=EMMStoreDefs.EMM_DIR+"/"+name;
   byte data[]=Utils.buf2barray(buf,size);

   ApiMonitor.log("EMM CHANGING SUBS: "+name);
   ApiMonitor.log_buf(data);

   LazyStore.submit(filename,data);
  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }
 }

 private static int get_size(String name) {
  int size=-1;

  try {
   File f=new File(EMMStoreDefs.EMM_DIR+"/"+name);
   if (f.exists()) {
    size=(int)f.length();
   }
  } catch(Throwable t) {}

  return size;
 }

 private static boolean emm_request(int buf,int size) {
  if (size<5) return false;
  
  int clazz=Memory.read_byte(buf);
  int cmd=Memory.read_byte(buf+1);

  if (clazz!=Conax.CLASS) return false;
  if (cmd!=Conax.CMD_EMM) return false;

  return true;
 }

 public static int load(String name) {
  try {
   int size=get_size(name);

   if (size<0) return EMMStoreDefs.CANNOT_OPEN;

   NativeCode nc=NativeCode.getInstance();
   int req=nc.malloc(size);

   FileInputStream fis=new FileInputStream(EMMStoreDefs.EMM_DIR+"/"+name);

   for(int i=0;i<size;i++) {
    int val=fis.read();
    Memory.write_byte(req+i,val);
   }

   fis.close();

   if (!emm_request(req,size)) {
    nc.free(req);
    return EMMStoreDefs.INVALID_FORMAT;
   }

   Entitlement before[]=Conax.get_entitlements();

   int resp[]=SCInterface.send_request(req,size);

   int rdata=resp[SCInterface.IDX_DATA];
   int rsize=resp[SCInterface.IDX_SIZE];

   if (rsize!=2) {
    nc.free(req);
    SCInterface.free_response(resp);
    return EMMStoreDefs.INVALID_EMM;
   }

   int rcode1=Memory.read_byte(rdata);
   int rcode2=Memory.read_byte(rdata+1);

   nc.free(req);
   SCInterface.free_response(resp);

   if ((rcode1!=Conax.CMD_RET_OK1)||(rcode2!=Conax.CMD_RET_OK2)) {
    return EMMStoreDefs.INVALID_EMM;
   }

   Entitlement after[]=Conax.get_entitlements();

   Entitlement[] diff=Entitlement.diff(before,after);

   Output.integer(EMMStoreDefs.RES_OK);
   Output.flush();

   if (diff.length==0) {
    Output.text("EMM sucessfully loaded, though no entitlement was changed");
   } else {
    Output.text("EMM sucessfully loaded");
   
    for(int i=0;i<diff.length;i++) {
     diff[i].print();
    }
   }
   
   Output.end();
  } catch(Throwable t) {}

  return EMMStoreDefs.RES_OK;
 }

 public static int show(String name) {
  try {
   int size=get_size(name);

   if (size<0) return EMMStoreDefs.CANNOT_OPEN;

   byte data[]=new byte[size];

   FileInputStream fis=new FileInputStream(EMMStoreDefs.EMM_DIR+"/"+name);

   for(int i=0;i<size;i++) {
    int val=fis.read();
    data[i]=(byte)val;
   }

   fis.close();

   Output.integer(EMMStoreDefs.RES_OK);
   Output.flush();

   Output.data(data);
   Output.flush();   
  } catch(Throwable t) {}

  return EMMStoreDefs.RES_OK;
 }

}
