Mudanças entre as edições de "Compilador ASN1"
Linha 551: | Linha 551: | ||
=== Especificações em múltiplos módulos === | === 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: | ||
+ | |||
+ | <syntaxhighlight lang=text> | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | ''Dois módulos chamados '''Ativo''' e '''Modulo''' em um mesmo arquivo'' | ||
+ | |||
+ | Os módulos podem ser fornecidos também em arquivos separados: | ||
+ | |||
+ | {| border=1 | ||
+ | !Ativo.asn1 | ||
+ | !Modulo.asn1 | ||
+ | |- | ||
+ | | <syntaxhighlight lang=text> | ||
+ | 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 | ||
+ | </syntaxhighlight> || <syntaxhighlight lang=text> | ||
+ | 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 | ||
+ | </syntaxhighlight | ||
+ | |} | ||
+ | |||
+ | Em ambos os casos, note-se que 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: | ||
+ | |||
+ | <syntaxhighlight lang=bash> | ||
+ | gen_asn1 Ativo.asn1 Modulo.asn1 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | 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: | ||
+ | |||
+ | <syntaxhighlight lang=text> | ||
+ | # Substitua aqui o nome do seu arquivo com a especificação ASN1 ... pode haver um ou mais arquivos | ||
+ | ASN1SRC:=Ativo.asn1 Modulo.asn1 | ||
+ | </syntaxhighlight> | ||
=== Limitações === | === Limitações === |
Edição das 19h41min de 27 de novembro de 2015
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:
- Obtendo seu código-fonte, e realizando sua compilação e instalação
- 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:
- 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++.
- 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.
O compilador ASN1++, e pode ser obtido em:
Após transferir o arquivo, descompacte-o em algum diretório. 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
- Makefile.build: script de compilação auxiliar
- demo.cc: um programa de demonstração do uso da API
- Ativo.asn1 e Modulo.asn1: arquivos com a especificação ASN.1 do programa de demonstração
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:
- Escrever a especificação da estrutura de dados em ASN.1
- Compilar a especificação com o compilador asn1c usando o script de compilação fornecido na API ASN1++
- Escrever um programa de teste, o qual deve explorar a API ASN1++
- Compilar o programa de teste
- Usar o programa de teste
Passo 1: escrever a especificação da estrutura de dados em ASN.1
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
|
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 Modulo.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
vector<long> vn;
vn.push_back(35123);
pkt.set_codigo(vn);
pkt.set_nome( "PETR3");
pkt.set_data("30092015");
pkt.set_horario("135812");
// este campo "cod" é do tipo enumerated "Teste"
pkt.set_cod(Teste_um);
// "valor" é um campo do tipo sequence of, e pode ser
// definido usando um vector ...
vector<TExtra> v;
TExtra e;
e.set_id(11);
e.set_desc("teste");
v.push_back(e);
pkt.set_valor(v);
// "conteudo" é um choice ... aqui se escolhe seu campo "ot"
// que é do tipo TOutro (um sequence)
Choice_Conteudo & con = pkt.get_conteudo();
TOutro ot = con.get_ot();
ot.set_id(777);
vector<string> vt;
vt.push_back("um");
ot.set_valor(vt);
ot.set_ok(0);
// 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::XerSerializer encoder(out);
// codifica a estrutura de dados
encoder.serialize(pkt);
out.close();
// cria o decodificador
ifstream arq("pkt.data");
TAtivo::XerDeserializer 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 DER):
Ativo ::= {
nome: PETR3
codigo: codigo ::= {
35123
}
valor: valor ::= {
Extra ::= {
id: 11
desc: teste
}
}
data: 30092015
horario: 135812
cod: 0
ind: 0
conteudo: Outro ::= {
id: 777
ok: FALSE
valor: Lista ::= {
um
}
}
}
Estrutura de dados obtida da decodificação DER:
Ativo ::= {
nome: PETR3
codigo: codigo ::= {
35123
}
valor: valor ::= {
Extra ::= {
id: 11
desc: teste
}
}
data: 30092015
horario: 135812
cod: 0
ind: 0
conteudo: Outro ::= {
id: 777
ok: FALSE
valor: Lista ::= {
um
}
}
}
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_DEFAULT_H
#define ASN1_PARSER_DEFAULT_H
#include <asn1++/asn1++.h>
#include<Ativo.h>
#include<Tipos.h>
class TAtivo : public ASN1DataType<Ativo_t> {
public:
private:
ASN1Sequence<long> * m_valor;
ASN1String * m_nome;
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_valor = new ASN1Sequence<long>(&pkt->valor);
m_nome = new ASN1String(pkt->nome);
}
virtual ~TAtivo() {
delete m_valor;
delete m_nome;
}
ASN1Sequence<long> & get_valor() { return *m_valor;}
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(const ASN1Sequence<long> & arg) { *m_valor = arg;}
void set_valor(vector<long> & v) {
m_valor->do_empty();
for (vector<long>::const_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;}
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;}
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() {return (TAtivo*) DERDeserializer<Ativo_t>::deserialize();}
TAtivo * scan() {return (TAtivo*) DERDeserializer<Ativo_t>::scan();}
};
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() {return (TAtivo*) XERDeserializer<Ativo_t>::deserialize();}
TAtivo * scan() {return (TAtivo*) XERDeserializer<Ativo_t>::scan();}
};
};
#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.
A API gerada pode ser usada como exemplificado a seguir:
#include "parser_default.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); |
SET OF tipo ou SEQUENCE OF tipo | ASN1Sequence<tipo> & get_campo(); void get_campo(vector<tipo> & v); |
void set_campo(ASN1Sequence<tipo> & arg); void set_campo(vector<tipo> & v); |
String e variações | string get_campo(); | void set_campo(string & arg); |
um tipo SEQUENCE | Ttipo & get_campo(); | não há |
OBJECT IDENTIFIER | ASN1Oid & get_campo(); string get_campo(); |
void set_campo(ASN1Oid & arg); void set_campo(string & arg); |
RELATIVE-OID | ASN1RelativeOid & get_campo(); string get_campo(); |
void set_campo(ASN1RelativeOid & arg); void set_campo(string & arg); |
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: ... nesse exemplo, o nome da classe será Choice_payload. Ela deve ser referenciada como Mensagem::Choice_payload.
Mensagem ::= SEQUENCE { seq INTEGER, payload CHOICE { codigo INTEGER, dados PrintableString (SIZE(1..64)) } }
- 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: ... nesse exemplo, o nome da classe será Choice_Payload. Ela deve ser referenciada diretamente como Choice_Payload.
Mensagem ::= SEQUENCE { seq INTEGER, payload Payload } Payload ::= CHOICE { codigo INTEGER, dados PrintableString (SIZE(1..64)) }
A identificação de qual membro do tipo CHOICE foi selecionado deve 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>.
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
</syntaxhighlight
|}
Em ambos os casos, note-se que 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:
<syntaxhighlight lang=bash>
gen_asn1 Ativo.asn1 Modulo.asn1
# Substitua aqui o nome do seu arquivo com a especificação ASN1 ... pode haver um ou mais arquivos
ASN1SRC:=Ativo.asn1 Modulo.asn1
LimitaçõesClasses e classes template da API de codificação ASN1++A API ASN1++ contém as seguintes classes e classes template:
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>
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();
ASN1StringA 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>
ASN1DataType<Mensagem_t> pkt(&asn_DEF_Mensagem);
Mensagem_t * msg = pkt.get_data();
ASN1String name(msg->nome);
name = "CONTROL";
cout << "nome: " << name.str() << endl;
ASN1BitStringA 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>
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 ASN1RelativeOidA 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>
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::iteratorO 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.
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>
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>::iteratorA 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.
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>
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>
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
|