1 .. vim: set encoding=utf-8 filetype=rst sw=2 sts=2 et tw=74 :
4 ==========================================================================
5 Taller de Programación I (75.42)
6 ==========================================================================
9 --------------------------------------------------------------------------
10 Ejercicio 1 - Mini Protocol Buffers
11 --------------------------------------------------------------------------
16 ==========================================================================
18 Repasar conceptos vistos en materias anteriores (algoritmos, estructuras
19 de datos, manejo de memoria, manipulación de archivos, el lenguaje de
24 ==========================================================================
26 Hay infinidad de `formatos para representación de datos`__. Según
27 para que se use, las técnicas varían drásticamente. No es lo mismo
28 la representación que se utiliza para persistir en disco que para
29 enviar datos por una red. Incluso las formas de representación pueden
30 variar para distintos tipos de redes o para distintos medios físicos
31 (no es lo mismo un disco *flash* que un disco rígido magnético
32 convencional). Tampoco es lo mismo representar datos para que sean
33 interpretados solo por la computadora que si tuviera que poder verlos
34 directamente un usuario. La representación varía también con la
35 estructura de los datos a representar también.
37 __ http://es.wikipedia.org/wiki/Categoría:Protocolos_y_formatos_de_nivel_de_presentación
39 Un desafío importante es construir formatos que sirvan de forma genérica
40 para representar cualquier tipo de dato, definiendo previamente un
41 *esquema*. Un formato extremadamente popular que hace esto es XML__.
42 Existen numerosas implementaciones de `parsers`__ XML que permiten
43 interpretar cualquier tipo de dato dado un `esquema`__ determinado.
45 __ http://es.wikipedia.org/wiki/XML
46 __ http://es.wikipedia.org/wiki/Parser
47 __ http://es.wikipedia.org/wiki/DTD
49 Sin embargo XML tiene varias contras y no siempre es conveniente su uso (a
50 pesar de que en la industria nadie parezca darse cuenta =), por ejemplo:
52 * No es posible representar datos binarios sin *intervención* del usuario
53 (hay que elegir una codificación *externa* para representar datos
55 * Es muy ineficiente en cuanto a espacio.
56 * Es relativamente lento de interpretar.
58 Un formato que resuelve estas desventajas (pero que, por supuesto,
59 tiene otras) es el propuesto por `Protocol Buffers`__, un proyecto que,
60 a pesar de su temprana edad, está teniendo mucha adopción (probablemente
61 por estar auspiciado por Google__). Se trata de un formato binario, muy
62 eficiente en términos de espacio y muy fácil y veloz para interpretar.
64 __ http://code.google.com/p/protobuf/
65 __ http://www.google.com/
67 El formato permite representar pares (clave, valor), donde la clave
68 es un valor numérico siempre y el valor puede ser de varios tipos:
69 enteros variables o fijos, con signo o sin signo, cadenas de bytes. A
70 su vez, puede dársele sentido especial a una cadena de bytes para que
71 represente otro set de pares (clave, valor), formando lo que se conoce
72 como un *mensaje embebido*, pudiendo construirse mensajes (estructuras
73 de datos) de una complejidad considerable.
77 ==========================================================================
79 Se pide desarrollar un intérprete genérico, pero reducido, de Protocol
80 Buffers (PB). El programa debe ser capaz de interpretar un archivo
81 binario con formato de PB (base de datos) y, a partir de otro archivo
82 (*esquema*), imprimir los datos de forma legible para el usuario.
84 .. figure:: esquema.eps
85 :alt: Esquema de flujo de datos
88 Esquema de flujo de datos
92 ==========================================================================
94 El archivo de esquemas solicitado en este trabajo es considerablemente
95 más simple que los archivos de `definiciones`__ que utiliza PB.
97 __ http://code.google.com/apis/protocolbuffers/docs/proto.html
101 --------------------------------------------------------------------------
103 Los siguientes *tokens* (especificados en ABNF_) pueden aparecer en
106 array = identifier "*"
107 identifier = ALPHA *(ALPHA / DIGIT)
111 .. _ABNF: http://en.wikipedia.org/wiki/Augmented_Backus–Naur_Form
115 --------------------------------------------------------------------------
117 La sintaxis del archivo, tomando como base los *tokens* especificados en
118 la sección anterior), es la siguiente (también especificada en ABNF_)::
120 scheme = *eol *message
121 message = sp identifier sp eol *attribute eol *eol
122 attribute = sp (identifier / array) WSP sp identifier sp eol
126 --------------------------------------------------------------------------
128 Un archivo de esquema está compuesto por 0 o más mensajes (regla
129 *message*), separados entre sí por una línea vacía. Un mensaje está
130 representado por un nombre (*identifier*) seguido de 0 o más atributos
131 (regla *attribute*), separados por un fin de línea. Un atributo a su vez,
132 se compone de un tipo (*identifier*) y un nombre (*identifier*). Un tipo
133 a su vez puede ser simple (*identifier*) o un arreglo (*array*), en cuyo
134 caso se representa con el nombre del tipo terminado con "*". Existen
135 2 tipos básicos: *int* y *string*. El resto de los tipos deben estar
136 definidos en el archivo en cualquier orden.
138 Cada atributo tiene asociado un *tag* (en el sentido dado por PB)
139 implícito incremental, comenzando por 1. Dada la definición::
146 El atributo ``nombre`` tiene el *tag* **1**, ``telefono`` el **2** y
149 Todos los campos son *requeridos*, excepto aquellos que tengan como tipo
150 un *array*, que serían *repetidos* (en el sentido dado por PB).
154 --------------------------------------------------------------------------
156 Vemos como separar en estas 3 etapas la interpretación del siguiente
172 El primer paso es el análisis léxico: separar en *tokens*. Siguiendo lo
173 propuesto en la sección Léxico_, podemos ir viendo qué *tokens*
174 encontramos en el archivo (los "_" representan espacios en blanco):
176 ============= ===============
177 Contenido Tipo de *token*
178 ============= ===============
184 "personas" identifier
193 ============= ===============
195 Por ejemplo, si en el archivo apareciera::
200 Esto no sería válido léxicamente, ``1persona`` no es un *token* posible.
202 Luego (en realidad generalmente se hace a medida que se van obteniendo los
203 *tokens*), hay que realizar el análisis sintáctico. Es decir, validar que
204 estos *tokens* encontrados, tengan el orden correcto y formen
205 construcciones sintácticas válidas. Para esto aplicamos las reglas de la
208 Un esquema es una *lista* de mensajes, por lo tanto buscamos un mensaje,
209 que se compone de un identificador, una nueva línea y una lista de
210 atributos (vamos verificando que los *tokens* lleguen en el orden
211 correcto). Un atributo a su vez, es un identificador o array seguido de
214 Con esta información podemos ir armando una representación estructurada
215 del archivo en memoria (conocida como AST__ o árbol abstracto de sintaxis)
216 que nos servirá para el próximo paso y para la decodificación de PB.
218 __ http://en.wikipedia.org/wiki/Abstract_syntax_tree
220 Si en el archivo apareciera::
223 Persona* personas no_valido
225 Esto sería léxicamente válido (todos los *tokens* son conocidos) pero
226 sintácticamente inválido, un atributo espera que luego del segundo
227 identificador solo hayan espacios en blanco o un fin de línea (y
228 encontramos otro identificador: ``no_valido``).
230 Finalmente, una vez construído el AST, queda hacer el análisis semántico.
231 Es decir, el chequeo de tipos. Hay que verificar que todos los atributos
232 de todos los mensajes sean de un tipo conocido.
234 Si en el archivo de esquema tuvieramos la siguiente definición de
235 ``Agenda`` en vez de la original::
240 Esto sería léxicamente válido, sintácticamente válido, pero semánticamente
241 inválido (el tipo NoExisto no está definido).
244 Codificación de Protocol Buffers
245 ==========================================================================
247 A continuación se explica un *subset* de la `codificación usada por
248 Protocol Buffers`__, que es lo que debe poder interpretar la aplicación
249 a desarrollar. La explicación está basada en la documentación de
250 Protocol Buffers, de manera tal de utilizar los mismos ejemplos para
251 que sea sencillo para el alumno ampliar los conceptos visitando la
254 __ http://code.google.com/apis/protocolbuffers/docs/encoding.html
257 *Varints* de base 128
258 --------------------------------------------------------------------------
260 Antes que nada es necesario entender un concepto básico para la
261 codificación de PB, *varints*.
263 *Varints* es un método de serialización de enteros que utiliza uno o más
264 bytes. Números pequeños utilizan pequeñas cantidades de bytes.
266 Cada byte en un *varint*, excepto el último byte, tiene el bit más
267 significativo (msb__) activado; esto indica que hay más bytes. Los 7 bits
268 más *bajos* de cada byte son utilizados para guardar, en representación de
269 complemento a 2, el número en grupos de 7 bits, estando el grupo menos
270 significativo primero.
272 __ http://es.wikipedia.org/wiki/Bit_m%C3%A1s_significativo
274 Por ejemplo, esta es la representación del número **1**::
280 Se puede representar en un solo byte, por lo tanto el msb está
281 desactivado. El número **130**, levemente más complicado, se representa
282 de la siguiente forma::
288 ¿Cómo saber que es el número **130**? Primero se descarta el msb de cada
289 byte, ya que sólo nos indica si se ha encontrado el fin de la
290 representación del número (como se puede ver, está activo solo en el
291 primer byte ya que hay más de un byte en este *varint*)::
296 Luego hay que invertir los dos grupos de 7 bits porque, como recuedan, los
297 *varints* guardan el grupo menos significativo primero::
302 Finalmente, se concatenan para obtener el resultado final::
307 Cuyo valor es **130**.
311 Si bien un *varint* no tiene una limitación de tamaño o longitud,
312 en este trabajo se asumirá un tamaño máximo de 9 bytes (es decir,
313 se podrán representar enteros de 9*7 = 63 bits, es decir, tendrán
314 un valor máximo de 2^63-1 = 9.223.372.036.854.775.807), garantizando
315 de esta manera que cualquier varint entre en un entero de 64 bits
316 (por ejemplo, el tipo ``uint64_t`` de C99).
319 Estructura de un mensaje
320 --------------------------------------------------------------------------
322 Como saben, un Protocol Buffer es una serie de pares (clave, valor). La
323 versión binaria de un mensaje utiliza el *tag* del atributo como clave
324 (el nombre y tipo de cada atributo solo puede conocerse con precisión
325 utilizando el esquema del mensaje).
327 Cuando un mensaje es codificado, las claves y valores son concatenados en
328 un flujo de bytes. Las claves se componen en realidad de 2 valores, el
329 *tag* asociado al atributo y un *wire type*, que provee la información
330 necesaria para saber el tamaño del valor que acompaña a la clave.
332 Protocol Buffers soporta varios *wire types* pero en este ejercicio solo
333 deberán reconocerse los siguientes:
335 ======== ========================== ======================================
336 Tipo Significado Utilizado para
337 ======== ========================== ======================================
339 2 delimitado por longitud *string*, mensajes embebidos
340 ======== ========================== ======================================
342 Cada clave de un mensaje es un *varint* con el valor::
344 (tag << 3) | wire_type
346 En otras palabras, los últimos 3 bits guardan el *wire type*.
348 Ante un *wire type* desconocido deberá terminarse la ejecución del
349 programa con el código de error apropiado (ver `Valores de retorno`_).
353 --------------------------------------------------------------------------
355 Luego de una clave, viene un valor. Dependiendo del *wire type*, la
356 codificación de los valores varía.
361 La representación de un *varint* (*wire type* 0) es, como deben
362 imaginarse, un *varint*. En esta implementación limitada de Protocol
363 Buffers solamente el tipo básico *int* es representado con un *varint*.
365 Delimitado por longitud
366 ~~~~~~~~~~~~~~~~~~~~~~~
368 La representación de valor delimitado por tamaño (*wire type* 2) se
369 compone del largo del valor codificado como *varint* seguido por la
370 cantidad especificada de bytes de datos.
372 Por ejemplo, un *string* con valor "testing", se representaría de la
373 siguiente forma (sólo se muestra el valor, se omite la clave)::
375 0x07 0x74 0x65 0x73 0x74 0x69 0x6e 0x67
376 ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
377 longitud "t" "e" "s" "t" "i" "n" "g"
379 Tanto los atributos de tipo *string* como lo mensajes embebidos se
380 representan con valores delimitados por tamaño.
382 La única forma de distinguir si un valor es un *string* o un mensaje
383 embebido, es utilizando el esquema del mensaje.
386 Ejemplo de mensaje sencillo
387 --------------------------------------------------------------------------
389 Ahora que sabemos como se codifican los *varints*, las claves y los
390 valores, podemos ver un ejemplo de un mensaje simple basado en el
396 La representación de un mensaje de tipo *Test1*, cuyo valor de *a* sea
397 **150**, es la siguiente::
399 0x08 0x96 0x01 = 0000 1000 1001 0110 0000 0001
401 Como vimos, un mensaje son pares (clave, valor), por lo tanto sabemos que
402 lo primero que hay que interpretar es una clave, que a su vez, sabemos
403 que se representa con un *varint*. Vemos que el primer byte tiene el
404 msb desactivado, así que el *varint* ocupa un solo byte: 0x08 = 0000 1000
406 Sabemos que los último 3 bits de una clave, son el *wire type*, por lo
407 tanto en este caso es 000: 0, lo que nos indica que el valor que seguirá
408 a la clave es otro *varint*. Los restantes 4 bits (desplazados hacia la
409 derecha 3 lugares, los lugares que ocupaban los bits del *wire type*)
410 sabemos que almacenan el *tag* del atributo, en este caso 0001 = 1::
420 Ahora decodificamos el valor, que ya sabemos que es un *varint*::
422 0x96 0x01 = 1001 0110 0000 0001
426 Eliminamos msb, invertimos grupos de 7 bits y concatenamos::
432 El resultado es 150. Ahora recordando el esquema del mensaje, observamos
433 que ``int a`` es el primer atributo del mensaje ``Test1``, por lo tanto le
434 corresponde el *tag* **1**. Luego, hemos decodificado un mensaje ``Test1``
435 cuyo atributo ``a`` tiene el valor **150**.
437 Si interpretáramos *tag* para los cuales el mensaje no tuviera un atributo
438 asociado, simplemente ignoramos ese par (clave, valor) y continuamos
439 leyendo los siguientes.
442 Ejemplo de mensaje con *arrays*
443 --------------------------------------------------------------------------
445 Supongamos que agregamos a nuestro esquema anterior el siguiente mensaje::
450 Si un mensaje de tipo ``Test2`` tuviera como atributo ``b`` dos enteros:
451 **150** y **1**, su representación sería::
453 0x08 0x96 0x01 0x08 0x01
454 ---- ^^^^^^^^^ ---- ^^^^
457 | | `-- clave, nuevamente tag = 1, wire type = 0
459 | un varint = 150, igual que en el ejemplo anterior
461 0000 1010 = 0000 1000 <---- clave
469 Como puede observarse, simplemente se repite el *tag* varias veces. Y los
470 valores se agregan al *array* del atributo que corresponde a ese *tag*.
473 Ejemplo de mensaje compuesto
474 --------------------------------------------------------------------------
476 Supongamos que agregamos a nuestro esquema anterior el siguiente mensaje::
481 Si un mensaje de tipo ``Test3`` tuviera como atributo ``c`` un mensaje
482 de tipo ``Test1`` cuyo atributo ``a`` fuera **150**, su representación
485 0x0a 0x03 0x08 0x96 0x01
486 ---- ~~~~ ^^^^^^^^^^^^^^
487 | | igual que el ejemplo del mensaje sencillo
489 | longitud del mensaje embebido como varint = 3
491 0000 1010 = 0000 1010 <---- clave
499 El *wire type* es de tipo *delimitado por longitud*, el *tag* es **1**
500 (``c`` es el primer atributo del mensaje ``Test3``) y el valor de ``c`` es
501 0x08 0x96 0x01, que coincide con la representación de un mensaje de tipo
502 ``Test1`` cuyo valor para el atributo ``a`` sea **150** sin estar embebido
507 ==========================================================================
509 La salida del programa debe tener la siguiente estructura.
511 Cada mensaje debe ser impreso por pantalla con el siguiente formato::
518 Los atributos deben tener una sangría de 2 espacios y deben estar
519 ordenados por número de *tag* de forma incremental.
521 Valores de tipo *int* se imprimen en formato decimal, por ejemplo::
526 Valores de tipo *string* se imprimen entre comillas dobles ``"``,
527 reemplazando las comillas literales ``"`` por ``\"`` y las ``\``
528 literales por ``\\``. Por ejemplo::
532 descripcion: "Cantante
537 Siendo la *descripción* original::
543 Los valores de tipo *array* deben representarse como si fueran mensajes,
544 simulando que su nombre fuera "array" y cada ítem fuera un atributo
545 con clave numérica, igual al índice del elemento en el *array*. Por
546 ejemplo, un *array* de 3 *strings* se representaría así::
554 Los valores de tipo *mensaje* serán impresos de la misma manera que un mensaje
555 regular. Con la sola excepción de que se deberá incrementar el nivel de
556 sangría por cada nivel de anidamiento. Por ejemplo::
569 calle: "Inglaterra 3452"
575 ==========================================================================
577 El programa debe ser invocado de la siguiente forma::
579 ./ej1 esquema mensaje [pb]
584 Archivo con el esquema de los datos (obligatorio)
586 Mensaje (que debe estar definido dentro del esquema) que va a
589 Archivo con **1** mensaje de tipo ``mensaje`` codificado en formato PB
590 (opcional, si no se especifica, se toma la base de datos de la entrada
593 El resultado debe ser impreso por la salida estándar (*stdout*).
596 Ejemplos de invocación
597 --------------------------------------------------------------------------
601 ./ej1 un_esquema UnMensaje una_db > salida
603 Lee los datos del archivo ``una_db``, el esquema del archivo
604 ``un_esquema``, interpreta un mensaje de tipo ``UnMensaje`` e imprime
605 la salida en el archivo ``salida``.
609 ./ej1 un_esquema OtroMensaje < otra_db
611 Lee los datos del archivo ``otra_db``, el esquema del archivo
612 ``un_esquema``, interpreta un mensaje de tipo ``OtroMensaje`` e imprime
613 la salida por *stdout*.
617 ==========================================================================
619 A continuación se presenta una tabla con los códigos de retorno que el
620 programa debe devolver ante las distintas situaciones:
622 ====== ===================================================================
624 ====== ===================================================================
626 1 Parámetros de invocación incorrectos
627 2 Error al abrir archivo de esquema
628 3 Error al abrir archivo de PB
629 4 Error léxico en el archivo de de esquema
630 5 Error sintáctico en el archivo de de esquema
631 6 Error semántico en el archivo de de esquema
632 7 No se encontró el mensaje a interpretar en el archivo de esquema
633 8 Error al decodificar archivo de PB
635 ====== ===================================================================
638 Restricciones de diseño
639 ==========================================================================
641 * El ejercicio debe realizarse utilizando `ISO C99`__.
642 * El programa debe estar correctamente modularizado, separando claramente
643 la interpretación de archivos de esquema de la interpretación de
644 archivos de PB y la salida.
645 * La interpretación de archivos de esquema debe estar bien dividida en
646 `análisis léxico`__, `análisis sintáctico`__ y `análisis
648 * La decodificación de PB debe estar también bien validada, saliendo con
649 el código de error correspondiente ante cualquier situación inesperada
650 (*tag* inválido, fin de archivo prematuro, *wire type* desconocido,
652 * No puede asumirse un tamaño máximo de línea ni de identificadores a
653 leer, ni de cantidad de atributos en un mensaje ni de cantidad de
654 mensajes en un esquema.
655 * Puede asumirse que los *tags* vienen ordenados en un archivo de PB. Es
656 decir, que primero viene el atributo con *tag* **1**, luego el de
657 *tag* **2**, etc. Si el *tag* **3** fuera de tipo *array*, todos los
658 *tags* **3** vendrán juntos, y después que el **2** y antes que el
660 * Puede asumirse que van a estar presentes todos los atributos, excepto
661 los de tipo *array* que podrían no tener elementos (por lo tanto no
662 existir el *tag* correspondiente en el archivo de PB).
663 * Se permite (y recomienda) ir imprimiendo la salida a medida que se
664 interpreta el archivo de PB.
666 __ http://es.wikipedia.org/wiki/Lenguaje_de_programaci%C3%B3n_C#C99
667 __ http://es.wikipedia.org/wiki/Analizador_l%C3%A9xico
668 __ http://es.wikipedia.org/wiki/Analizador_sint%C3%A1ctico
669 __ http://arantxa.ii.uam.es/~alfonsec/docs/compila5.htm