domingo, 19 de dezembro de 2010

O Modelo de Memória Java Card

Nesse Artigo, veremos como é dividida a estrutura de memória de um Java Card, é muito importante aprender bem este conceito, pois lembre-se Smart Cards são as menores plataformas computacionais e com recursos de processamento e memória por enquanto limitadissimos e embora seja Java os riscos de "memory leaks"(vazamento de memória) são reais, isso em um PC teria pouco impacto pois um simples Reset na máquina e sua memória estaria livre novamente, bem em Java Cards esse reset não existe, por sorte "alguns" cartões implementam o Garbage Collector.
Por conta disso sua aplicação deverá ser escrita de maneira extremamente otimizada  e cuidadosa sempre economizando recursos de memória e de processamento.
Java Cards suportam ambos tipos de Objetos, persistent e transient (temporário) porém a mecânica difere da versão Java para PCs.
Um Java Card possui três tipos de Memórias : ROM (Read Only Memory), RAM (Random Access Memory) e EEPROM (Electrically Erasable Programmable Read Only Memory).
Embora a ROM seja uma memória apenas de Leitura é a de menor custo entre as três e tem o papel de armazenar a API Java Card, API Especiais do Fabricante, dados de configuração do chip e se você tiver muito dinheiro poderá também pedir para incluir uma API só sua nessa área de memória junto ao Fabricante do Chip, ou seja essa memória é gravada no ato da fabricação do Chip e não poderá ser alterada depois.
A RAM e a EEPROM, podem ser escritas e lidas porém possuem diferenças elétricas entre si, no caso de perda de energia todo conteúdo da RAM será perdida enquanto o da EEPROM será preservada.
Ora porque não utilizar somente EEPROM? Você deve se estar perguntando, porque a escrita na EEPROM leva cerca de 1000 vezes mais tempo que a escrita em memória RAM e a escrita nessas memórias possui também um limite de vezes entre 100000 ~ 500000 ciclos de escrita.
Em contra partida o circuito elétrico de uma memória RAM costuma ser 4 vezes maior que o da memória EEPROM. Aqui se faz valer aquele velho ditado "does not exist a free lunch".
A memória pode variar bastante de Fabricante para Fabricante, eu tenho em mãos cartões com (36,72,80)Kb EEPROM / 3,3Kb RAM. Porém acredito que isso deva mudar bastante com os Java Card 3 que vem por ai, basta observar que atualmente uma simples memória SD do tamanho de uma unha alcançam notáveis 16GB ou mais de memória.
Bom após toda essa explicação acredito que tenha ficado claro que Objetos Persistentes ficam na EEPROM, enquanto os Temporários (Transient) ficam na RAM.
E você já deve ter notado o quanto criterioso e cuidadoso você terá que ser enquanto programa para Java Cards.

Objetos Persistentes
Como já foi dito, Objetos Persistentes mantém seus dados integros mesmo após o final de uma sessão ou perda de energia. Esses objetos possuem as seguinte propriedades :
  • Objetos Peristentes são criados pelo operador new,
  • Guarda seus dados e valores entre sessões.
  • Qualquer alteração de dados ocorre de maneira atomic, ou seja no caso de se haver perda de alimentaçãou ou falha durante a atualização, os dados anteriores serão restaurados.
  • Um objeto persistente pode referenciar um campo em um objeto transiente.
  • Um campo em um objeto persistente pode referenciar um objeto transiente.
  • Se um objeto persistente não for referenciado por outros objetos, ele se torna permanentemente perdido e a área de memória só poderá ser recuperada através do Garbage Collector.
Quando um applet é instanciado, como qualquer outro objeto persistente, os dados e recursos alocados persistem indefinidamente através das sessões seguintes.

Objeto Transiente (Temporário)
Objetos Transientes possuem uma natureza temporária, ou seja dependendo de como for configurado esse objeto tem seus dados destruidos quando outra aplicação for selecionada, ou quando a sessão terminar ou quando houver perda de energia. Assim como os objetos persistentes os recursos alocados pelos objetos transientes não podem ser recuperados sem o auxilio do Garbage Collector.
Seu Applet deverá criar objetos transientes apenas uma vez, ou seja uma referencia a esse objeto deverá ser criada e colocada na área persistente e essa mesma referencia deverá ser usada durante as sessões posteriores, a figura abaixo demonstra como objeto transientes são alocadas na memória.


Apenas arrays de tipos primitivos (byte, short e boolean) e objetos (Object) podem ser declarados como transiente. Os objetos transientes possuem as seguintes propriedades :
  • São criados invocando-se a API Java Card.
  • Não guarda seus valores entre sessões, os dados serão limpos para valores padrões (zero, false ou null) dependendo da ocorrencia de certos eventos.
  • Qualquer alteração de dados não ocorre de modo atomic, ou seja no caso de se haver perda de energia ou falha durante a atualização, o campo não será restaurado para valores padrões.
  • Um objeto transiente pode referenciar um campo em um objeto persistente.
  • Um campo em um objeto transiente pode referenciar um objeto persistente.
  • Se um objeto transiente não for refenciado por outro objetos, ele se torna permanentemente perdido e a área de memória só poderá ser recuperada através do Garbage Collector.
  • A escrita em objetos transientes costuma ser 1000x mais rápida pois os dados são escritos na Memória RAM.
Objetos transientes devem ser usados como pequenas áreas temporárias pelo Applet, em que dados são frequentemente modificados e não necessitam ser persistentes.
Utilizando objetos transiente também reduz a escrita na EEPROM aumentando o tempo de vida útil da mesma, a performance e a segurança sobre dados sensíveis como chaves criptográficas de sessão.
Em contra partida use com moderação pois a memória RAM é pequena em capacidade cerca de 20x menos que a EEPROM.
Existem dois tipos de Objetos Transientes CLEAR_ON_RESET e CLEAR_ON_DESELECT, que basicamente associa o evento em que o JCRE irá limpar os campos do objeto.
CLEAR_ON_RESET, esse tipo de Objeto mantém seus dados íntegros durante a seleção de outros Applets, é usado geralmente nos casos em que Applets compartilham ou trabalham em conjunto ou mesmo para aplicações que trabalhem com diversos Applets em uma mesma sessão, ou seja os dados são perdidos em caso de reset ou perda de Energia.
CLEAR_ON_DESELECT, esse tipo de Objeto mantém seus dados integros enquanto um Applet permanecer Selecionado, ou seja os dados são perdidos se outro Applet for selecionado, em caso de reset ou perda de Energia.

Criando Objetos Transientes
Conforme dito acima Objetos Transientes são criados através de uma chamada à API Java Card JCSystem.
/**
 * Cria um Array Transiente boolean
 * @param length - Tamanho do Array Transiente
 * @param event - CLEAR_ON_RESET ou CLEAR_ON_DESELECT
 * @return - Array
 */
public static boolean[]
    makeTransientBooleanArray(short length, byte event)

/**
 * Cria um Array Transiente byte
 * @param length - Tamanho do Array Transiente
 * @param event - CLEAR_ON_RESET ou CLEAR_ON_DESELECT
 * @return - Array
 */
public static byte[]
    makeTransientByteArray(short length, byte event)

/**
 * Cria um Array Transiente short
 * @param length - Tamanho do Array Transiente
 * @param event - CLEAR_ON_RESET ou CLEAR_ON_DESELECT
 * @return - Array
 */
public static short[]
    makeTransientShortArray(short length, byte event)

/**
 * Cria um Array Transiente Object
 * @param length - Tamanho do Array Transiente
 * @param event - CLEAR_ON_RESET ou CLEAR_ON_DESELECT
 * @return - Array
 */
public static Object[]
    makeTransientObjectArray(short length, byte event)
O Seguinte Fragmento de Código demonstra como é criado um objeto transiente:
//Cria um Buffer de 10 Elementos Limpos on reset
byte[] buffer =
    JCSystem.makeTransientByteArray(10,JCSystem.CLEAR_ON_RESET);

Verificando por objetos transientes 
Quando um Applet necessita acessar objetos criados por outros Applets, a classe JCSystem oferece um método para verificar o tipo de objeto através do seguinte método:
public static byte isTransient(Object theObject)
O Método isTransient retorna um dos tipos (CLEAR_ON_RESET ou CLEAR_ON_DESELECT) ou uma constante JCSystem.NOT_A_TRANSIENT_OBJECT para indicar se um Objeto é null ou persistente.

Memory Leak um risco real
Sempre que um Applet é Apagado ou De-Instanciado, todos os recursos alocados serão liberados automaticamente pelo JCRE, porém existe riscos de vazamento de memória, e no Java Card caso não tenha um Garbage Collector implementado a memória é perdida permanentemente.
Considere o seguinte exemplo:
public class MyApplet extends Applet {

  //Buffer de teste
  private byte[] buffer;
 
  //Construtor
  public MyApplet(){

   //Cria um Objeto Persistente de 10 Bytes
   buffer = new byte[10];

   register();
  }

  //Um Metodo que causa Memory Leak
  public void MyMethod() {

   //Realoca buffer com mais 20 bytes.
   buffer = new byte[20];

   ...
  }
}
Na linha 10 criamos um array de 10 bytes persistente no construtor.
Na linha 19 um método aloca 20 bytes, porém nesse momento os 10 bytes alocados no construtor serão permanentemente pedidos caso não exista Garbage Collector implementado.
E vale lembrar que nem todos os cartões possuem o GC implementado, logo muitíssimo cuidado ao trabalhar com a Memória de um Java Card.
No exemplo acima, o correto seria na linha 10 criarmos um array de 20 bytes e eliminar a linha 19, com isso no momento de exclusão ou de-instanciamento do Applet os 20 bytes serão liberados automaticamente pelo JCRE. 

Colocando para funcionar
Ok, após toda essa extensa explicação chegou a hora de testar-mos tudo o que vimos até agora.
import javacard.framework.*;

public class MemoryTest extends Applet {
 /** Instrução Read Memory */
 private static final byte INS_READ_MEMORY = 0x02;
 /** Instrução de Estrita na EEPROM */
 private static final byte INS_WRITE_EEPROM = 0x04;
 /** Instrução de Estrita na RAM */
 private static final byte INS_WRITE_RAM = 0x06;
 /** Tamanho do Objeto */
 private static final short ARRAY_SIZE = 10;
 /** Objeto Persistente */
 byte[] eeprom_ba;
 /** Objeto Transiente */
 byte[] ram_ba;
 
 /**
  * Construtor
  */
 private MemoryTest() {
  
  //Persistent, Objeto e Dados gravados na EEPROM
  this.eeprom_ba = new byte[ARRAY_SIZE];
  
  //Transient, Objeto na EEPROM e Dados na RAM
  this.ram_ba = JCSystem.makeTransientByteArray(
                 ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT);
 }

 /**
  * Installer
  * @param bArray
  * @param bOffset
  * @param bLength
  * @throws ISOException
  */
 public static void install(
  byte bArray[], short bOffset, byte bLength)
   throws ISOException 
 {
  new MemoryTest().register();
 }

 /**
  * Processa APDU's
  */
 public void process(APDU apdu){
  
  //Adquire Referencia do buffer de entrada e saida
  byte buffer[] = apdu.getBuffer();
  
  //Boa Prática: Retorna 9000 no SELECT
  if (selectingApplet())
   ISOException.throwIt(ISO7816.SW_NO_ERROR);
  
  //Verifica Instrução enviada pelo Host
  switch ( buffer[ISO7816.OFFSET_INS] ){
  
  //Instrução de leitura de Memória
  case INS_READ_MEMORY : 
  {
   short i;
   
   //Copia os 10 bytes do Objeto Persistente
   //seguido dos 10 bytes do Objeto Transiente
   for ( i = 0 ; i < ARRAY_SIZE ; i++ ){
    buffer[i] = eeprom_ba[i];
    buffer[i + ARRAY_SIZE] = ram_ba[i];
   }
   
   //Sinaliza JCRE que retornará dados
   apdu.setOutgoingAndSend(
    (short) 0, (short)(ARRAY_SIZE * (short) 2));

   //Rertorna dados e 0x9000
   ISOException.throwIt(ISO7816.SW_NO_ERROR);
  }
  
  //Instrução de Escrita na EEPROM
  case INS_WRITE_EEPROM :
  {
   //Adquire Quantidade de dados Recebidos
   short count = apdu.setIncomingAndReceive();
   
   //Se Quantidade de Dados Recebidos Incorreto
   if ( count != ARRAY_SIZE )
    ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
   
   //Copia dados Recebidos para o objeto Persistente
   Util.arrayCopy(
    buffer, ISO7816.OFFSET_CDATA, eeprom_ba, (short) 0, count);

   //Rertorna 0x9000
   ISOException.throwIt(ISO7816.SW_NO_ERROR);
  }

  //Instrução de Escrita na RAM
  case INS_WRITE_RAM :
  {
   //Adquire Quantidade de dados Recebidos
   short count = apdu.setIncomingAndReceive();
   
   //Se Quantidade de Dados Recebidos Incorreto
   if ( count != ARRAY_SIZE )
    ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
   
   //Copia dados Recebidos para o objeto Transiente
   Util.arrayCopy(
    buffer, ISO7816.OFFSET_CDATA, ram_ba, (short) 0, count);

   //Rertorna 0x9000
   ISOException.throwIt(ISO7816.SW_NO_ERROR);
  }
  
  }//Switch END

  //Boa practica: Se não conhece a Instrução apenas diga:
  ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
 }
}
Infelizmente para testar com eficiência esse código é necessário um cartão Java Card real ou simula-lo no CREF(C Reference Implementation Simulator), porém o CREF é de difícil configuração e seu Applet deve ser instalado da mesma forma como se instala em um cartão físico, e a memória EEPROM deve ser gravada em um arquivo no HD para se simular a persistencia, porém lidar com todas essas variáveis do CREF está fora do Escopo desse Artigo.
Na linha 23 criamos um Objeto Persistente que se trata de um array de 10 bytes que será armazenado na EEPROM tanto a referência como o campo de dados.
Na linha 26 criamos um Objeto Transiente utilizando a API Java Card que se trata de um array de 10 bytes que será armazenado na EEPROM apenas a referencia desse objeto, enquanto o campo de dados será aramazenada na RAM.
Da linha 60 a 77 fica a porção de código responsável pela leitura dos dados dos Objetos.
Da linha 80 a 95 fica a porção de código que irá gravar os dados contidos na área de Dados do APDU no objeto persistente.
E finalmente da linha 98 a 113 fica a porção de código que irá gravar os dados contidos na área de Dados do APDU no objeto transiente.

Para ler a memória basta enviar o seguinte APDU:
0x00 0x02 0x00 0x00 0x00

Para Escrever 10  FF's na EEPROM :
0x00 0x04 0x00 0x00 0x0A 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

Para Escrever 10  AA's na RAM :
0x00 0x06 0x00 0x00 0x0A 0xAA 0xAA 0xAA 0xAA 0xAA 0xAA 0xAA 0xAA 0xAA 0xAA 

Com isso vocês poderão testar a natureza de Objetos Persistent e Transient do Java Card.
Existe ainda 2 tópicos relacionados a Memória a Atomicidade e a Transação, o qual abordarei ainda em artigos futuros aguardem.


Um Grande Abraços a todos!

3 comentários:

  1. meu amigo parabens ,vc manja mesmo....nem nos sites de fora do brasil os caras explicam tão bem

    ResponderExcluir
  2. Ricardo,

    Muito bom o seu blog !!!.


    Gostaria de trocar uma idéias com voce sobre manuseio de smartcards.

    paulo@cardtech.com.br


    Aguardo.

    Paulo Roberto
    82 8877-7916

    ResponderExcluir

Observação: somente um membro deste blog pode postar um comentário.