Compilador ASN1

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

O compilador ASN1++ gera uma classe C++ para cada tipo de dados de uma especificação ASN.1. Cada classe possui métodos para obter e modificar valores dos campos de sua estrutura de dados (getters e setters), além de codificadores e decodificadores DER e XER sob medida. Campos do tipo "choice" também foram implementados, assim como "enumerated". O compilador ASN1++ baseia-se em um compilador ASN1 para linguagem C. Ele encapsula o código-fonte gerado por esse outro compilador, disponibilizando-o em um conjunto de classes wrapper C++, porém com algumas limitações. Isso possibilita a fácil integração das estruturas de dados ASN.1 em programas C++.

Compilador ASN1 para linguagem C

O compilador ASN1 desenvolvido por Lev Walkin traduz as especificações ASN1 para linguagem C, além de prover uma biblioteca para codificação e decodificação. Ele é compatível com ASN.1 rev. 2002. Há duas formas de utilizá-lo:

  1. Obtendo seu código-fonte, e realizando sua compilação e instalação
  2. Acessando sua versão online

O resultado da compilação ASN1 é um conjunto de arquivos em código-fonte C, contendo a definição dos tipos de dados criados com ASN1 e as funções auxiliares para usar esses tipos de dados e realizar codificação e decodificação. Por exemplo, ao compilar esta especificação ASN1:

Exemplo DEFINITIONS ::=
BEGIN
	Mensagem ::= SEQUENCE {
   		nome	PrintableString,
		cod	INTEGER,
                oid     OBJECT IDENTIFIER,
                flags   BIT STRING,
                ok      BOOLEAN,
                dados   SET OF INTEGER
	}
END


... são gerados estes arquivos:

asn_application.h  +Compiler.Log       Mensagem.h           per_opentype.c
asn_codecs.h       constraints.c       module.asn1          per_opentype.h
asn_codecs_prim.c  constraints.h       NativeEnumerated.c   per_support.c
asn_codecs_prim.h  constr_SEQUENCE.c   NativeEnumerated.h   per_support.h
asn_internal.h     constr_SEQUENCE.h   NativeInteger.c      VisibleString.c
asn_system.h       constr_TYPE.c       NativeInteger.h      VisibleString.h
ber_decoder.c      constr_TYPE.h       OBJECT_IDENTIFIER.c  xer_decoder.c
ber_decoder.h      converter-sample.c  OBJECT_IDENTIFIER.h  xer_decoder.h
ber_tlv_length.c   der_encoder.c       OCTET_STRING.c       xer_encoder.c
ber_tlv_length.h   der_encoder.h       OCTET_STRING.h       xer_encoder.h
ber_tlv_tag.c      INTEGER.c           per_decoder.c        xer_support.c
ber_tlv_tag.h      INTEGER.h           per_decoder.h        xer_support.h
BIT_STRING.c       Makefile.am.sample  per_encoder.c
BIT_STRING.h       Mensagem.c          per_encoder.h

Dentre eles, os arquivos Mensagem.c e Mensagem.h contêm as declarações correspondentes à estrutura de dados Mensagem. Os demais arquivos incluem funções e declarações para o uso, codificação e decodificação de estruturas de dados especificadas via ASN1. Portanto, além de compilar uma especificação, é gerada uma API para usar as estruturas de dados criadas.


A API nativa gerada pelo compilador pode ser usada diretamente, porém ela apresenta limitações quanto ao uso dos tipos de dados básicos ASN1. Alguns tipos de dados, tais como OCTET STRING e suas variações, OBJECT IDENTIFIER e RELATIVE-OID, não estão acompanhados de funções na API para manipulá-los de forma prática. Em alguns casos é necessário criar algoritmos para acessar a estrutura interna desses tipos de dados, o que se torna repetitivo ao usá-los em programas e sujeito a erros. Além disso, as operações de codificação e decodificação também demandam uma quantidade razoável de código-fonte extra a ser escrito para poderem ser usadas. Por isso essa API gerada pelo compilador foi encapsulada em uma outra API C++, que oferece um conjunto de classes para sanar essas limitações.

Compilador ASN1++

O compilador ASN1++ é composto de duas partes:

  1. Um tradutor de ASN.1 para C++: esse tradutor é o compilador ASN1++ de fato, e ele traduz a especificação ASN.1 para um conjunto de classes C++ que representam as estruturas de dados da especificação. Essas classes exploram a API de codificação ASN1++.
  2. Uma biblioteca para codificação: a biblioteca de codificação ASN1++ facilita o acesso e a modificação de valores dos campos das estruturas de dados, além da codificação e decodificação com DER ou XER.

Instalação

O compilador ASN1++ pode ser obtido em:


Após transferir o arquivo, descompacte-o em algum diretório, o qual será referido como DIRETORIO mais adiante. Em seguida execute estes passos:

  1. Em um terminal entre no diretório onde está o compilador ASN1++:
    cd DIRETORIO/asn1pp-master
    
  2. Execute o shell script install.sh. Ele irá pedir sua senha para executar alguns comandos como superusuário (com sudo):
    ./install.sh
    


O compilador e sua API são compostos destes arquivos, que estão no subdiretório asn1++:

  • bin: diretório contendo o programa gen_asn1, que é o compilador ASN1++
  • include: diretório com arquivos contendo declarações das classes que compõem a API.
  • lib: diretório com bibliotecas estáticas contendo a implementação da API em 32 e 64 bits
  • Makefile: script de compilação para ajudar a compilação de um projeto ASN1. Possibilita compilar a especificação ASN1 e também o projeto. NÃO DEVE SER MODIFICADO.
  • Makefile.build: script de compilação auxiliar, que deve ser editado para cada aplicação a ser desenvolvida
  • examples: vários exemplos com especificações ASN.1 e programas de demonstração do uso da API


Para utilizá-la basta copiar todos os arquivos nela contidos para o mesmo diretório onde está sua especificação ASN1. Caso deseje usar os scripts de compilação (recomendável), edite Makefile.build e modifique-o conforme ali indicado.

Exemplo de compilação

Um exemplo de estrutura de dados que descreve um ativo financeiro é usado para demonstrar o uso do compilador ASN.1 e a API ASN1++. Os passos a seguir são:

  1. Escrever a especificação da estrutura de dados em ASN.1
  2. Compilar a especificação com o compilador asn1c usando o script de compilação fornecido na API ASN1++
  3. Escrever um programa de teste, o qual deve explorar a API ASN1++
  4. Compilar o programa de teste
  5. Usar o programa de teste


Passo 1: escrever a especificação da estrutura de dados em ASN.1

Ativo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

Ativo ::= SEQUENCE {
  nome PrintableString(SIZE(1..16)),
  codigo INTEGER,
  valor INTEGER,
  data NumericString(SIZE(8)),
  horario NumericString (SIZE(6))
}
 
END


Passo 2: compilar a especificação

Neste exemplo a especificação ASN1 deve estar em um arquivo chamado ativo.asn1. A compilação é feita usando-se o script de compilação Makefile.build, fornecido na API ASN1++. Esse arquivo Makefile.build deve ser editado de forma a definir estas variáveis:

#########################################################################
## Inclua os nomes dos arquivos do seu projeto na variável MYOBJS, porém usando
## extensões .o ao invés de .c ou .cc.
## Ex: se seu projeto contém o arquivo proto.cc, modifique MYOBJS assim:
## MYOBJS = proto.o
## Ex. 2: no caso de seu projeto conter os arquivos proto.cc e session.cc:
## MYOBJS = proto.o session.o
##
MYOBJS=demo.o

# Modifique a variável PROG para o nome do programa executável a ser gerado.
# Neste exemplo, o programa se chamará "demo"
PROG=demo

# Substitua aqui o nome dos arquivos com a especificação ASN1
ASN1SRC:=Ativo.asn1


Em seguida, deve-se realizar a compilação da especificação com este comando:

make build


Passo 3: escrever um programa de teste, o qual deve explorar a biblioteca de codificação gerada durante a compilação


O programa de teste deve-se chamar demo.cc. Ele cria uma estrutura de de dados Ativo, atribui valores aos membros dessa estrutura e mostra-a na tela. Em seguida, ele a codifica com XER e grava o resultado em um arquivo pkt.data. Ao final, decodifica o conteúdo desse arquivo, recuperando a estrutura de dados criada.

Programa de teste
#include <parser_Ativo.h>
#include <iostream>
#include <fstream>

using namespace std;

int main() {
  TAtivo pkt;

  // definindo os valores de varios campos 
  pkt.set_nome("PETR3");
  pkt.set_codigo(111);
  pkt.set_valor(1298);
  pkt.set_data("30092015");
  pkt.set_horario("135812");

  // verifica se os valores contidos na estrutura de dados respeitam
  // a especificação
  pkt.check_constraints();

  // mostra a estrutura de dados na tela
  cout << "Estrutura de dados em memória (antes de codificação XER):" << endl;
  pkt.show();

  // cria o codificador
  ofstream out("pkt.data");
  TAtivo::DerSerializer encoder(out);

  // codifica a estrutura de dados
  encoder.serialize(pkt);
  out.close();

  // cria o decodificador
  ifstream arq("pkt.data");
  TAtivo::DerDeserializer decoder(arq);

  // tenta decodificar uma estrutura de dados
  TAtivo * other = decoder.deserialize();
  arq.close();

  cout << endl;

  if (other) {
    cout << "Estrutura de dados obtida da decodificação XER:" << endl;
    other->show();
  } else cerr << "Erro: não consegui decodificar a estrutura de dados ..." << endl;

  // devem-se destruir explicitamente as estruturas de dados obtidas 
  // do decodificador 
  delete other; 
}


Passo 4: Compilar o programa de teste

make all


Passo 5: Usar o programa de teste

aluno@M2:~$ build/demo
Estrutura de dados em memória (antes de codificação XER):
Ativo ::= {
    nome: PETR3
    codigo: 111
    valor: 1298
    data: 30092015
    horario: 135812
}

Estrutura de dados obtida da decodificação XER:
Ativo ::= {
    nome: PETR3
    codigo: 111
    valor: 1298
    data: 30092015
    horario: 135812
}

A API gerada pelo compilador

O compilador ASN1++ gera uma API composta por classes que representam as estruturas de dados da especificação ASN1. Por exemplo, a seguinte especificação:


Module-Exemplo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

Tipos ::= ENUMERATED {um, dois, tres}
 
Ativo ::= SEQUENCE {
  nome PrintableString (SIZE(1..16)),
  codigo Tipos,
  valor SEQUENCE OF INTEGER,
  quantidade INTEGER
}

END


... após compilada gera o seguinte código-fonte:

API gerada pelo compilador
#ifndef ASN1_PARSER_MODULE-EXEMPLO_H
#define ASN1_PARSER_MODULE-EXEMPLO_H

#include <asn1++/asn1++.h>

#include<Tipos.h>
#include<Ativo.h>

class TAtivo : public ASN1DataType<Ativo_t> {
 public:
 private:
  ASN1String * m_nome;
  ASN1Sequence<long> * m_valor;
 public:
  TAtivo() : ASN1DataType<Ativo_t>(&asn_DEF_Ativo) {
    init();
  }
  TAtivo(Ativo_t * ptr) : ASN1DataType<Ativo_t>(&asn_DEF_Ativo, ptr) {
    init();
  }
  TAtivo(const TAtivo & o) : ASN1DataType<Ativo_t>(&asn_DEF_Ativo, o.pkt) {
    init();
    destroy = false;
  }
  void init() {
    m_nome = new ASN1String(pkt->nome);
    m_valor = new ASN1Sequence<long>(&pkt->valor);
  }
  virtual ~TAtivo() {
    delete m_nome;
    delete m_valor;
  }
  long get_quantidade() { return pkt->quantidade;}
  void set_quantidade(long arg) { pkt->quantidade = arg;}
  string get_nome() { return m_nome->str();}
  void set_nome(const string & arg) { *m_nome = arg;}
  void get_valor(vector<long> & v) {
    for (ASN1Sequence<long>::iterator it=m_valor->begin(); it != m_valor->end(); it++) {
      v.push_back(*it);
    }
  }
  void set_valor(vector<long> & v) {
    m_valor->do_empty();
    for (vector<long>::iterator it=v.begin(); it != v.end(); it++) m_valor->add(*it);
  }
  Tipos_t get_codigo() { return pkt->codigo;}
  void set_codigo(Tipos_t arg) { pkt->codigo = arg;}
  class DerSerializer : public DERSerializer<Ativo_t> {
  public:
    DerSerializer(ostream & out) : DERSerializer<Ativo_t>(&asn_DEF_Ativo, out) {}
    ~DerSerializer() {}
    ssize_t serialize(TAtivo & pkt) {DERSerializer<Ativo_t>::serialize(pkt);}
  };
  class DerDeserializer : public DERDeserializer<Ativo_t> {
  public:
    DerDeserializer(istream & inp) : DERDeserializer<Ativo_t>(&asn_DEF_Ativo, inp) {}
    ~DerDeserializer() {}
    TAtivo * deserialize() {
      ASN1DataType<Ativo> * p = DERDeserializer<Ativo_t>::deserialize();
      return get_obj(p);
    }
    TAtivo * scan() {
      ASN1DataType<Ativo> * p = DERDeserializer<Ativo_t>::scan();
      return get_obj(p);
    }
 private:
  TAtivo * get_obj(ASN1DataType<Ativo_t> * p) {
      TAtivo * obj = new TAtivo(p->_get_data());
      p->set_destroy(false);
      obj->set_destroy(true);
      delete p;
      return obj;
    }
  };
  class XerSerializer : public XERSerializer<Ativo_t> {
  public:
    XerSerializer(ostream & out) : XERSerializer<Ativo_t>(&asn_DEF_Ativo, out) {}
    ~XerSerializer() {}
    ssize_t serialize(TAtivo & pkt) {XERSerializer<Ativo_t>::serialize(pkt);}
  };
  class XerDeserializer : public XERDeserializer<Ativo_t> {
  public:
    XerDeserializer(istream & inp) : XERDeserializer<Ativo_t>(&asn_DEF_Ativo, inp) {}
    ~XerDeserializer() {}
    TAtivo * deserialize() {
      ASN1DataType<Ativo> * p = XERDeserializer<Ativo_t>::deserialize();
      return get_obj(p);
    }
    TAtivo * scan() {
      ASN1DataType<Ativo> * p = XERDeserializer<Ativo_t>::scan();
      return get_obj(p);
    }
 private:
  TAtivo * get_obj(ASN1DataType<Ativo_t> * p) {
      TAtivo * obj = new TAtivo(p->_get_data());
      p->set_destroy(false);
      obj->set_destroy(true);
      delete p;
      return obj;
    }
  };
};

#endif


O código-fonte gerado contém uma classe para cada tipo de dados SEQUENCE definido na especificação. Essa classe encapsula os campos do tipo de dados, e implementa métodos para acesso (getters) e modificação (setters) de valores desses campos.

  • O nome da classe que representa um tipo de dados é formado pelo nome do tipo de dados precedido pelo prefixo T. No exemplo acima, a classe TAtivo representa o tipo de dados Ativo.
  • Métodos de acesso para cada campo do tipo de dados são formados pelo nome do campo precedido pelo prefixo get_. No exemplo acima, os campos nome e codigo são acessados pelos métodos get_nome e get_codigo.
  • Métodos de modificação para cada campo do tipo de dados são formados pelo nome do campo precedido pelo prefixo set_. No exemplo acima, os campos nome e codigo podem ser modificados através dos métodos set_nome e set_codigo.
  • Cada classe é declarada em um arquivo parser_NOME_da_CLASSE.h. Portanto esses arquivos de declaração devem ser incluídos no código-fonte de implementação (arquivos .cpp) para que as classes geradas possam ser utilizadas.

A API gerada pode ser usada como exemplificado a seguir:


#include "parser_Ativo.h"

int main() {
  TAtivo pkt;

  // modificação usando setters
  pkt.set_nome(nome);
  pkt.set_codigo(Tipos_dois);
  pkt.set_quantidade(100);

  // No caso de um campo SEQUENCE OF, pode-se obter uma referência ao campo como uma lista, e então modificá-la
  ASN1Sequence<long> & valor = pkt.get_valor();
  valor.add(876);
  valor.add(901);
  valor.add(889);

  // Ou pode-se passar um vector<long> como argumento para seu setter ...
  vector<long> v;
  v.push_back(876);
  v.push_back(901);
  v.push_back(889);
  pkt.set_valor(v);

  // Acesso aos campos usando os getters
  string nome = pkt.get_nome();
  Tipos_t codigo = pkt.get_codigo();
  long quantidade = pkt.get_quantidade();
  vector<long> seq;
  pkt.get_valor(seq);
}

Métodos de acesso (getters e setters)

Os métodos de acesso e modificação são definidos de acordo com o tipo de dados do campo em questão. A tabela a seguir apresenta os vários tipos de campos e seus métodos de acesso:


Tipo do campo Método de acesso (getter) Método de modificação (setter)
INTEGER long get_campo(); void set_campo(long arg);
BOOLEAN bool get_campo(); void set_campo(bool arg);
um tipo ENUMERATED tipo_t get_campo(); void set_campo(tipo_t arg);
UTCTIME time_t get_campo(); void set_campo(time_t arg);
SET OF tipo ou SEQUENCE OF tipo void get_campo(vector<tipo> & v); void set_campo(vector<tipo> & v);
String e variações string get_campo(); void set_campo(string & arg);
BIT STRING void get_campo(uint8_t * arg);
ASN1BitString & get_campo();
void set_campo(uint8_t * arg)
um tipo SEQUENCE Ttipo & get_campo(); não há
OBJECT IDENTIFIER ASN1Oid & get_campo_attr();
string get_campo();
void set_campo(string & arg);
RELATIVE-OID ASN1RelativeOid & get_campo_attr();
string get_campo();
void set_campo(string & arg);
NULL não há não há

Campos do tipo CHOICE

Um campo do tipo CHOICE é implementado como objeto de uma classe Choice_campo (se o campo se chama conteudo, a classe é chamada Choice_conteudo). Essa classe oferece métodos de acesso aos campos internos do tipo CHOICE, além de verificar se cada acesso é válido. A classe Choice_campo pode ou não ser declarada aninhada à classe do tipo de dados que a contém, dependendo de como aparece na especificação ASN.1.

  • Classe Choice_campo aninhada: a classe aninhada é gerada se a especificação declarar diretamente um campo do tipo CHOICE dentro de um tipo de dados. Ex:
    Mensagem ::= SEQUENCE {
      seq INTEGER,
      payload CHOICE {
        codigo INTEGER,
        dados PrintableString (SIZE(1..64))
      }
    }
    
    ... nesse exemplo, o nome da classe será Choice_payload. Ela deve ser referenciada como Mensagem::Choice_payload:
      TMensagem msg;
    
      TMensagem::Choice_payload & carga = msg.get_payload();
      carga.set_codigo(12345);
    
  • Classe Choice_Nome não aninhada: uma classe Choice_Nome é gerada de forma não aninhada se na especificação for definido um novo tipo de dados CHOICE. Ex:
    Mensagem ::= SEQUENCE {
      seq INTEGER,
      payload Payload
    }
    
    Payload ::= CHOICE {
        codigo INTEGER,
        dados PrintableString (SIZE(1..64))
    }
    
    ... nesse exemplo, o nome da classe será Choice_Payload. Ela deve ser referenciada diretamente como Choice_Payload:
      TMensagem msg;
    
      Choice_payload & carga = msg.get_payload();
      carga.set_codigo(12345);
    


A identificação de qual membro do tipo CHOICE foi selecionado pode ser feita por meio do método get_choice():

  <campo>_PR get_choice();

... sendo <campo> o nome do campo CHOICE. Por exemplo, se a classe se chama Choice_conteudo, o nome do campo é conteudo. Os valores de resultado possíveis são dados por uma enumeração, e seus nomes são <campo>_PR_NOTHING para o caso de nenhuma seleção ter sido feita, ou <campo>_PR_<membro> para o caso de ter sido escolhido o membro chamada <membro>.

Campos do tipo SEQUENCE

Campos do tipo SEQUENCE devem ser acessados de forma diferente, caso se desejem definir seus valores. Obrigatoriamente deve-se usar um método getter para obter um objeto do tipo em questão, o qual pode então ser modificado. O exemplo a seguir demonstra como se deve modificar o valor de um campo de tipo composto.


A especificação a seguir declara um tipo SEQUENCE AttributePair. Esse tipo é usado na declaração do tipo Mensagem, para o campo atributo contido no CHOICE payload:

AttributePair ::= SEQUENCE {
  nome PrintableString (SIZE(1..16)),
  codigo INTEGER
}

Mensagem ::= SEQUENCE {
  seq INTEGER,
  payload CHOICE {
    atributo AttributePair,
    info PrintableString (SIZE(1..64))
  }
}

O acesso ao campo atributo deve ser feito usando o método get_atributo:

TMensagem msg;

TMensagem::Choice_payload & carga = msg.get_payload();
TAttributePair attr = carga.get_atributo();

attr.set_codigo(567);
attr.set_nome("TESTE");

Codificadores e decodificadores

As classes geradas contêm também codificadores e decodificadores DER e XER sob medida. Os codificadores são implementados como classes aninhadas,

ifstream arq("pkt.data");
TAtivo::DerDeserializer decoder(arq);
TAtivo * pkt = decoder.deserialize();

if (pkt) {
  pkt->show();
  delete pkt;
}

Especificações em múltiplos módulos

Uma especificação ASN.1 pode ser escrita usando múltiplos módulos. Isso facilita o reaproveitamento de código e a própria estruturação da especificação. Para usar múltiplos módulos, podem-se fornecê-los em um mesmo arquivo, como neste exemplo:


Ativo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

IMPORTS Lista,Nome,Tipos FROM Modulo 
       Extra,Outro FROM Modulo;

Teste ::= ENUMERATED {um, dois, tres}

Numero ::= INTEGER

Ativo ::= SEQUENCE {
  nome Nome,
  codigo SEQUENCE OF Numero,
  valor SEQUENCE OF Extra,
  data NumericString(SIZE(8)),
  horario NumericString (SIZE(6)),
  cod Teste,
  ind Numero,
  conteudo Conteudo
}
 
Conteudo ::= CHOICE {
    ex Extra,
    ot Outro,
    mais PrintableString (SIZE(2..8)),
    num Tipos
  }

END

Modulo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

EXPORTS Tipos,Nome,Lista,Extra,Outro;

Tipos ::= ENUMERATED {um, dois, tres}
 
Nome ::= PrintableString (SIZE(2..16))

Lista ::= SEQUENCE OF Nome

Extra ::= SEQUENCE {
  id INTEGER,
  desc PrintableString
}

Outro ::= SEQUENCE {
  id INTEGER,
  ok BOOLEAN,
  valor Lista
}

END

Dois módulos chamados Ativo e Modulo em um mesmo arquivo


Os módulos podem ser fornecidos também em arquivos separados:


Ativo.asn1 Modulo.asn1
Ativo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

IMPORTS Lista,Nome,Tipos FROM Modulo 
       Extra,Outro FROM Modulo;

Teste ::= ENUMERATED {um, dois, tres}

Numero ::= INTEGER

Ativo ::= SEQUENCE {
  nome Nome,
  codigo SEQUENCE OF Numero,
  valor SEQUENCE OF Extra,
  data NumericString(SIZE(8)),
  horario NumericString (SIZE(6)),
  cod Teste,
  ind Numero,
  conteudo Conteudo
}
 
Conteudo ::= CHOICE {
    ex Extra,
    ot Outro,
    mais PrintableString (SIZE(2..8)),
    num Tipos
  }

END
Modulo DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

EXPORTS Tipos,Nome,Lista,Extra,Outro;

Tipos ::= ENUMERATED {um, dois, tres}
 
Nome ::= PrintableString (SIZE(2..16))

Lista ::= SEQUENCE OF Nome

Extra ::= SEQUENCE {
  id INTEGER,
  desc PrintableString
}

Outro ::= SEQUENCE {
  id INTEGER,
  ok BOOLEAN,
  valor Lista
}

END


Em ambos os casos, um módulo pode especificar que tipos são exportados. De forma análoga, um módulo pode listar quais tipos devem ser importados de cada outro módulo. Isso aparece nas declarações IMPORTS e EXPORTS. No exemplo acima, o módulo Modulo exporta os tipos Tipos,Nome,Lista,Extra,Outro, e o módulo Ativo importa esses tipos do módulo Modulo. Assim, um módulo somente pode importar um tipo de dados de outro módulo se o respectivo tipo for exportado.


O compilador ASN1++ aceita tanto múltiplos módulos em um mesmo arquivo quanto em dois ou mais arquivos. Quando se usam dois ou mais arquivos, seus nomes devem ser fornecidos ao compilador. Ex:

gen_asn1 Ativo.asn1 Modulo.asn1


O script de compilação contido em MAkefile.build possibilita indicar todos os arquivos ASN.1 da especificação, conforme comentado no próprio script. Isso aparece na definição da variável ASN1SRC:

# Substitua aqui o nome do seu arquivo com a especificação ASN1 ... pode haver um ou mais arquivos
ASN1SRC:=Ativo.asn1 Modulo.asn1

Limitações

Classes e classes template da API de codificação ASN1++

A API ASN1++ contém as seguintes classes e classes template:

  • ASN1DataType<T>: classe para auxiliar o uso do tipo de dados ASN1 definido pelo usuário. Definida como template para suportar qualquer tipo de dados ASN1
  • ASN1String: classe para auxiliar o acesso, conversão e modificação de strings ASN1
  • ASN1BitString: classe para auxiliar o uso de BIT STRING
  • ASN1Oid: classe para auxiliar o acesso e modificação de OBJECT IDENTIFIER
  • ASN1RelativeOid: classe para auxiliar o acesso e modificação de RELATIVE OID (um caso especial de OID). Tem mesmas operações que ASN1Oid.
  • ASN1Sequence<T>: classe para facilitar o uso de sequências ASN1 (SEQUENCE OF e SET OF). Definida como template pois o tipo do dado contido em sequências é genérico
  • ASN1DERSerializer<T>: codificador DER. Codifica estruturas de dados e grava o resultado em um stream (ex: ofstream ou ostringstream)
  • ASN1XERSerializer<T>: codificador XER. Codifica estruturas de dados e grava o resultado em um stream (ex: ofstream ou ostringstream)
  • ASN1DERDeserializer<T>: decodificador DER. Decodifica octetos lidos de um stream (ex: ifstream ou istringstream) e gera uma estrutura de dados.
  • ASN1XERDeserializer<T>: decodificador XER. Decodifica octetos lidos de um stream (ex: ifstream ou istringstream) e gera uma estrutura de dados.


PTC-Asn1++.png


ASN1DataType<T>

A classe ASN1DataType<T> encapsula uma estrutura de dados do tipo T, que deve ter sido gerada pelo compilador ASN1.


#include <asn1++/datatype.h>


Métodos:

  • ASN1DataType(asn_TYPE_descriptor_t * desc): construtor da classe, o qual deve receber uma referência a um descritor do tipo de dados ASN1 (se o tipo de dados se chamar Mensagem, seu descritor se chama asn_DEF_Mensagem). O descritor é automaticamente declarado no código-fonte gerado pelo compilador ASN1. Este construtor cria automaticamente a estrutura de dados encapsulada de tipo T.
  • ASN1DataType(asn_TYPE_descriptor_t * desc, T * apkt): este construtor difere do anterior apenas na possibilidade de passar como parâmetro uma estrutura de dados de tipo T já existente, para que seja encapsulada.
  • T * get_data(): retorna uma referência para a estrutura de dados encapsulada
  • asn_TYPE_descriptor_t * get_desc(): retorna uma referência ao descritor do tipo de dados ASN1
  • void show(): mostra na saída padrão uma representação textual da estrutura de dados encapsulada
  • void check_constraints(): verifica se os valores dos membros da estrutura de dados encapsulada respeitam as restrições declaradas na especificação ASN1.


  ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
  Mensagem_t * msg = pkt.get_data();

  // modifica valores dos membros da estrutura de dados (não mostrado aqui)

  // verifica se valores contidos na estrutura de dados respeitam as restrições
  // da especificação ASN1
  pkt.check_constraints();

  // Mostra a estrutura de dados na tela
  pkt.show();

ASN1String

A classe ASN1String encapsula uma referência a uma string ASN1 (OCTET_STRING_t *), e implementa operações para modificar e acessar essa string.


#include <asn1++/string.h>


Métodos:

  • ASN1String(OCTET_STRING & astr): construtor da classe. O parâmetro astr deve ser um membro string de uma estrutura de dados ASN1.
  • string str(): retorna uma string C++ com o conteúdo da string ASN1
  • ASN1String & operator=(const char * s): possibilita atribuir uma string C à string ASN1
  • ASN1String & operator=(const string * s): possibilita atribuir uma string C++ à string ASN1
  • ASN1String & operator+=(const char * s): possibilita anexar uma string C à string ASN1
  • ASN1String & operator+=(const string & s): possibilita anexar uma string C++ à string ASN1


Exemplo de uso:

  ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
  Mensagem_t * msg = pkt.get_data();

  ASN1String name(msg->nome);
  name = "CONTROL";
  cout << "nome: " << name.str() << endl;

ASN1BitString

A classe ASN1BitString encapsula uma referência a uma string de bits ASN1 (BIT_STRING_t *), e implementa operações para modificar e acessar diretamente os bits.


#include <asn1++/bitstring.h>


Métodos:

  • ASN1BitString(BIT_STRING_t & bs, int nbits): construtor da classe. O parâmetro bs deve ser um membro string de bits de uma estrutura de dados ASN1, e nbits deve informar qual seu comprimento em bits.
  • void set(int pos): Ativa um bit na posição "pos"
  • void reset(int pos): Desativa um bit na posição "pos"
  • bool get(int pos): retorna o valor do bit na posição "pos"
  • void clear(): desativa todos os bits
  • void enable(): ativa todos os bits


Exemplo de uso:

  ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
  Mensagem_t * msg = pkt.get_data();

  ASN1BitString flags(msg->flags, 12);
  flags.enable();
  flags.reset(2);
  flags.reset(3);

ASN1Oid e ASN1RelativeOid

A classe ASN1Oid encapsula uma referência a um identificador de objeto ASN1 (OBJECT_IDENTIFIER_t *), e implementa operações para modificá-lo e acessá-lo. A classe ASN1RelativeOid estende ASN1Oid para encapsular identificadores relativos de objetos ASN1 (RELATIVE_OID_t *).


#include <asn1++/oid.h>
#include <asn1++/relative-oid.h>


Métodos de ASN1Oid:

  • ASN1Oid(OBJECT_IDENTIFIER_t & oid): construtor da classe. O parâmetro oid deve ser um membro identificador de objeto de uma estrutura de dados ASN1.
  • ASN1Oid(const ASN1Oid & oid): construtor de cópia da classe.
  • ASN1Oid(const string & oid): construtor da classe. O parâmetro oid deve ser uma string com um OID válido.
  • ASN1Oid(): construtor da classe. Cria automaticamente o valor OBJECT_IDENTIFIER_t encapsulado.
  • ASN1Oid & operator=(const string & oid): possibilita a definição do OID por meio de uma string (ex: 1.3.6.4)
  • ASN1Oid & operator=(const ASN1Oid & other): possibilita a definição do OID por meio de um outro ASN1Oid
  • ASN1Oid & operator+=(const ASN1Oid & other): concatena o OID referenciado por other (faz mais sentido que other seja um ASN1RelativeOid)
  • ASN1Oid & operator+=(const string & other): concatena o OID representado pela string other
  • bool operator>(const ASN1Oid & other): retorna true se o outro OID for um prefixo deste OID
  • bool operator<(const ASN1Oid & other): retorna true se este OID for um prefixo do outro OID
  • void get_prefix(ASN1Oid & oid): extrai o prefixo um nível acima e grava-o em oid
  • void get_prefix(ASN1Oid & oid, unsigned int N): extrai o prefixo N níveis acima e grava-o em oid
  • iterator begin(): retorna um iterador para o início do OID
  • iterator end(): retorna um iterador para o final do OID (após o último número do OID)


Métodos de ASN1RelativeOid:

  • ASN1RelativeOid(OBJECT_IDENTIFIER_t & roid): construtor da classe ASN1RelativeOid. O parâmetro roid deve ser um membro identificador relativo de objeto de uma estrutura de dados ASN1.
  • ASN1RelativeOid(): construtor da classe ASN1RelativeOid. Cria automaticamente o valor RELATIVE_OID_t encapsulado.
  • ASN1RelativeOid & operator=(const string & oid): possibilita a definição do OID por meio de uma string (ex: 6.4)


Exemplo de uso:

  ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
  Mensagem_t * msg = pkt.get_data();

  ASN1Oid oid(msg->oid);
  oid = "1.3.6.4";

  ASN1RelativeOid roid;
  roid = "10.200";

  oid += roid;

  cout << "OID: " << oid.str() << endl;

ASN1Oid::iterator

O iterador da classe ASN1Oid possibilita o acesso sequencial dos números que constituem um OID. Ele possibilita também a modificação individual de qualquer um desses números.

A iteração é feita com semântica semelhante a iteradores da STL (ex: vector<T>::iterator, string::iterator, ...).

Para iniciar um iterador, deve-se usar o método ASN1Oid::begin().

O método ASN1Oid::end() retorna um iterador para o fim do OID, o que pode ser usado para testar a condição de fim de iteração.


Métodos de ASN1Oid::iterator

  • iterator& operator++(): avança a iteração para o próximo número do OID
  • iterator& operator++(int): avança a iteração para o próximo número do OID
  • long& operator*(): retorna uma referência ao número atual do OID na iteração
  • bool operator==(const iterator &it): compara se dois iteradores são iguais
  • bool operator!=(const iterator &it): compara se dois iteradores são diferentes


Exemplo de uso:

  ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
  Mensagem_t * msg = pkt.get_data();

  ASN1Oid oid(msg->oid);
  oid = "1.3.6.4";

  // mostra os números do OID na tela, separados por espaços
  for (ASN1Oid::iterator it = oid.begin(); it != oid.end(); it++) {
    cout << *it << " ";
  }

ASN1Sequence<T>

Classe template para facilitar o uso de sequências ASN1 (SEQUENCE OF e SET OF). Possibilita acrescentar, remover e acessar valores de uma sequência.


#include <asn1++/sequence.h>


Métodos:

  • ASN1Sequence(void * a_seq): construtor da classe. O parâmetro a_seq deve ser um ponteiro para uma sequência ASN1.
  • void add(T val): adiciona o valor val à sequência
  • T& get(int pos): obtém o valor na posição pos
  • void do_empty(): esvazia a sequência
  • void del(int pos): remove o dado na posição pos
  • void del(int pos, bool do_free): remove o dado na posição pos e o destrói se do_free for verdadeiro
  • int len(): retorna o comprimento da sequência


Exemplo de uso:

ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);  
Mensagem_t * msg = pkt.get_data();
ASN1Sequence<long> seq(&msg->dados);

seq.add(10);
seq.add(20);

cout << "seq[0]: " << seq.get(0) << endl;

ASN1Sequence<T>::iterator

A classe template ASN1Sequence<T> contém um iterador para facilitar o acesso e modificação dos dados de uma sequência. A interface e semântica desse iterador é idêntica a de ASN1Oid::iterator.


Exemplo de uso:

ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);  
Mensagem_t * msg = pkt.get_data();
ASN1Sequence<long> seq(&msg->dados);

seq.add(10);
seq.add(20);

for (ASN1Sequence<long>::iterator it = seq.begin(); it != seq.end(); it++) {
  cout << "dado: " << *it << endl;
}

ASN1DERSerializer<T> e ASN1XERSerializer<T>

As classes template ASN1DERSerializer<T> e ASN1XERSerializer<T> implementam respectivamente os codificadores DER e XER para uma estrutura de dados ASN1 do tipo T.


#include <asn1++/codec.h>


Métodos:

  • DERSerializer(asn_TYPE_descriptor_t * desc, ostream & arq): construtor da classe DERSerializer. O parâmetro desc corresponde ao descritor do tipo de dados ASN1 a ser codificado, e arq é um stream de saída onde será gravada a estrutura de dados codificada.
  • XERSerializer(asn_TYPE_descriptor_t * desc, ostream & arq): construtor da classe XERSerializer. Segue mesma definição do construtor de DERSerializer. Cria um codificador BASIC-XER.
  • XERSerializer(asn_TYPE_descriptor_t * desc, ostream & arq, xer_encoder_flags_e mode): construtor da classe XERSerializer. Segue mesma definição do construtor de DERSerializer. O parâmetro mode possibilita selecionar o modo de codificação:
    • XER_F_BASIC: cria um codificador BASIC-XER.
    • XER_F_CANONICAL: cria um codificador CXER (Canonical XER).
  • ssize_t serialize(ASN1DataType<T> & pkt): codifica a estrutura de dados dada pelo parâmetro pkt.


Exemplo de uso:

ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
Mensagem_t * msg = pkt.get_data();

ASN1Oid oid(msg->oid);
oid = "1.3.6.4";

ASN1String nome(msg->nome);
nome = "CONTROL";

// cria um codificador DER que grava as estruturas codificadas na saída padrão
DERSerializer<Mensagem_t> encoder(&asn_DEF_Mensagem, cout);
encoder.serialize(pkt);

// cria um codificador XER que grava as estruturas codificadas na saída padrão
// usando BASIC-XER
XERSerializer<Mensagem_t> encoder(&asn_DEF_Mensagem, cout);
encoder.serialize(pkt);

// cria um codificador XER que grava as estruturas codificadas na saída padrão
// usando CXER (Canonical-XER)
XERSerializer<Mensagem_t> encoder(&asn_DEF_Mensagem, cout, XER_F_CANONICAL);
encoder.serialize(pkt);

ASN1DERDeserializer<T> e ASN1XERDeserializer<T>

As classes template ASN1DERDeserializer<T> e ASN1XERDeserializer<T> implementam respectivamente os decodificadores DER e XER para uma estrutura de dados ASN1 do tipo T.


#include <asn1++/codec.h>


Métodos:

  • DERDeserializer(asn_TYPE_descriptor_t * desc, istream & arq): construtor da classe DERDeserializer. O parâmetro desc corresponde ao descritor do tipo de dados ASN1 a ser decodificado, e arq é um stream de entrada de onde serão decodificadas as estruturas de dados.
  • XERDeserializer(asn_TYPE_descriptor_t * desc, ostream & arq): construtor da classe XERDeserializer. Segue mesma definição do construtor de DERDeserializer.
  • ASN1DataType<T>* deserialize(): decodifica uma estrutura de dados, retornando um ponteiro para estrutura extraída. Se não conseguir decodificar, retorna NULL.
  • ASN1DataType<T> * scan(): percorre a stream de entrada até conseguir decodificar uma estrututura de dados. Octetos lidos que não puderem ser decodificados são descartados. Se chegar ao fim da stream e não conseguir decodificar, retorna NULL.


Exemplo de uso:

ifstream arq("pacotes.data");
DERDeserializer<Mensagem_t> decoder(&asn_DEF_Mensagem, arq);

ASN1DataType<Mensagem_t> * pkt = decoder.decode();
if (pkt) pkt.show();

pkt = decoder.scan();
if (pkt) pkt.show();

Guia rápido para a biblioteca de codificação original


  • Acesso a membros do tipo INTEGER e BOOLEAN: basta referenciá-los diretamente, como neste exemplo aplicado à estrutura de dados Ativo:
    Ativo_t * pkt = (Ativo_t*)malloc(sizeof(Ativo_t));
    
    pkt->codigo = 555;
    
  • Conversão de string C para OCTET STRING:
    int OCTET_STRING_fromString(OCTET_STRING_t *s, const char *str);
    
  • Conversão de OCTET STRING para string C:
    // "ptr" é do tipo OCTET_STRING_t *
    // "data" é do tipo char*, e deve ter capacidade >= ptr->size+1
    memcpy(data, ptr->buf, ptr->size);
    data[ptr->size] = 0;
    


Para membros da estrutura de dados que forem SET OF ou SEQUENCE OF (i.e. listas de valores), usa-se esta definição de estrutura de dados como exemplo:

ASN.1 Linguagem C
Ativo ::= SEQUENCE {
  nome PrintableString (SIZE(1..16)),
  codigo INTEGER,
  valor SET OF INTEGER
}
/* Ativo */
typedef struct Ativo {
        PrintableString_t        nome;
        long     codigo;
        struct valor {
                A_SET_OF(long) list;
                
                /* Context for parsing across buffer boundaries */
                asn_struct_ctx_t _asn_ctx;
        } valor;
        
        /* Context for parsing across buffer boundaries */
        asn_struct_ctx_t _asn_ctx;
} Ativo_t;
  • Anexar um valor a uma SEQUENCE OF ou SET OF:
    // "pkt" é do tipo Ativo_t*
    int * p;
    
    p = (int*)malloc(sizeof(int));
    asn_set_add(&pkt->valor, p);
    
  • Remover um valor de uma SEQUENCE OF ou SET OF:
    // remove o valor na posição "0" da sequência
    // "valor" da estrutura de dados Ativo_t apontada por "pkt"
    // o último parâmetro informa se memória do dado removido deve ser liberada (0=não, 1=sim)
    asn_set_del(&pkt->valor, 0, 0);
    
  • Esvazia uma SEQUENCE OF ou SET OF:
    // remove todos os dados da sequência "valor"
    // liberando suas áreas de memória
    asn_set_empty(&pkt->valor);
    
  • Acesso a um dado de uma SEQUENCE OF ou SET OF:
    // n: posição acessada no vetor interno de dados da sequência
    // k: posição lógica do dado acessado (equivalente à usada em asn_set_del)
    int n, k;
    
    for (n=0, k=0; (k < pkt->valor.list.count) && (n < pkt->valor.list.size);) {
      if (pkt->valor.list.array[n] != NULL) {
        long * number = pkt->valor.list.array[n];
    
        printf("valor[%d]=%d\n", k, *number);
        k++;
      }
    }
    
  • Codificar com BER
  • Codificar com DER
  • Codificar com PER
  • Codificar com XER