jueves, 11 de noviembre de 2010

Creando un disector para el Wireshark.

Wireshark es un analizador de paquetes multiplataforma, indispensable para hacer troubleshooting en networking. Cuando surge la necesidad de escribir software que involucre algún tipo de comunicación remota entre 2 o más puntas, muchas veces los problemas de comunicación aparecen. Wireshark es espectacular para depurar este tipo de problemas y hoy por hoy es la herramienta de análisis de tráfico (de red) número uno alrededor del mundo.
Estructuralmente está dividido es una serie de paquetes a los cuales se les llama disectores. Aunque Wireshark es capaz de capturar cualquier tipo de paquetes, el payload o carga útil puede no ser legible si no contamos con un disector especializado que lo corte en porciones entendibles a simple vista.

Disector

Básicamente, es un plugin que el programa principal carga para descomponer convenientemente un paquete que responde a las características que el disector determina. Por ejemplo, si llega un paquete en puerto 6789, utilizando como transporte TCP y existe un plugin cargado con éstas dos condiciones satisfechas, el Wireshark utilizará nuestro plugin para descomponer el paquete y mostrarnos su contenido de acuerdo a lo que nosotros hayamos especificado.

El disector es un programa escrito en C que debe ser compilado como shared object (DLL en Windows) y debe contener una serie de funciones estándares a los que Wireshark llamará en tiempo de ejecución para registrar el plugin. La carga del mismo responde al comportamiento típico de dynamic linking, visible en la grandiosa mayoría del software moderno escrito de manera modular.

El protocolo CRD

Me tocó hacer un disector de un protocolo complejo pero, por fines pedagógicos, decidí crear uno súper sencillo (al que llamaremos CRD) de modo a demostrar que escribir extensiones no es tan complicado como parece y más bien requiere invertir tiempo en leer la documentación y alguno que otro disector de ejemplo disponible en el trunk de su repositorio SVN.

Abajo especifico la estructura del protocolo. Cuenta con tres campos definidos de la siguiente manera:

TipoMensaje:
determina si el mensaje es de petición o de respuesta.



CódigoRespuesta: determina el resultado de la operación a través de un código numérico.


Mensaje: texto de longitud variable enviado como anexo a la respuesta.

Juntando todos los campos quedamos así:


Aplicando la teoría

Ahora que tenemos nuestro protocolo definido podemos empezar la captura pero para esto necesitamos hacer un servidor TCP que nos responda un string válido con las características de nuestro protocolo y con este propósito en mente, escribí un script PHP que responda a eventuales clientes con un texto predeterminado.

$message1 = "01hola mundo a las ";

$socket = stream_socket_server("tcp://localhost:9999", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);

for(;;)
{
$client = stream_socket_accept($socket);

fwrite($client, $message1.date("Y-m-d h:i:s"));

fclose($client);

}

Con este sencillo script al conectarnos a localhost en puerto 9999 utilizando telnet por ejemplo, obtenemos el siguiente resultado:

carlos@carlosrd-laptop:~> telnet localhost 9999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
01hola mundo a las 2010-11-13 05:47:13
Ahora, cómo se ve esa captura desde el Wireshark sin nuestro disector?



La verdad que se ve horrible aunque no sea muy difícil leerlo pero cuando trabajemos con un protocolo real, tratar de hacer una lectura comprensiva exitosa desde la vista de arriba, nos tomaría el doble de tiempo y esfuerzo.

Haciendo el disector

Para escribir un disector se necesita tener un conocimiento medio-avanzado del lenguaje C, mientras más complejo sea nuestro protocolo, más difícil será crearle un disector. Además de C, es necesario leer extensivamente la documentación del developer disponible aquí. No pretendo transcribir lo que ya está escrito porque sería una perdida de tiempo así que es muy importante leer el manual antes de seguir. Una vez terminado eso, se necesita tener el ambiente de desarrollo listo para compilar nuestro plugin:
  • Tener instalado los fuentes del Wireshark o haciendo svn checkout de su repositorio
  • Tener instalado los archivos de cabecera de libpcap
  • Tener instalado automake
La lista de requisitos continúa pero en general no suele ser necesario instalar nada más que lo arriba mencionado.

En mi caso particular de usuario de Linux, tener el ambiente listo me tomó menos de 5 minutos. Si usás Windows esto se puede llegar a complicar (bastante) pero es igualmente posible y documentación sobre cómo hacerlo está disponible en Internet.

El código está comentado en porciones que consideré necesarias. Comenté muy poco lo que ciertas funciones usadas hacen porque eso es más bien ámbito del manual del desarrollador.

Bueno, show me the code:

/**
* CRD Protocol dissector
*
* Carlos Ruiz Díaz
*
* carlos.ruizdiaz@gmail.com
* http://tebicuary.blogspot.com/
* @caruizdiaz
*
* Asunción, Paraguay - November 2010
*
*/

//#define IGNORE

#ifndef IGNORE

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <epan/packet.h>
#include <string.h>

/*
* Protocol Logic Implementation
*/

#define _REQUEST_VALUE 0
#define _RESPONSE_VALUE 1

/*
* Definimos el nombre corto del protocolo que aparecerá en la vista
* de disectores instalados
*
*/
#define PROTO_TAG_CRD "CRD"

/*
* Variable que contiene el ID que será asignado dinámicamente a nuestro disector por el Wireshark
*/
static int proto_crd = -1;

static dissector_handle_t data_handle = NULL;
static dissector_handle_t crd_handle = NULL;

static void dissect_crd(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
static void dissect_content(tvbuff_t *tvb, proto_tree *tree);
static void substr(const char* origin, int start, int length, char* result);

/*
* Puerto por defecto en el que el protocolo opera
*/
static int global_crd_port = 9999;

/*
* value_string es una estructura especializada creada para hacer lookups
* usando la función val_to_str() a través de una clave.
*
* Abajo definimos la lista de posibles valores que puede contener el campo
* nro. 2 de nuestro protocolo. Los nros. corresponden al valor transportado
* y la descripción literal a su significado.
*/
static const value_string transaction_result_names[] =
{
{ 00, "Proceso Exitoso" },
{ 01, "Error de proceso" },
{ 02, "Estado desconocido" }
};

static gint hf_crd_pdu = -1;
static gint hf_crd_body_data = -1;

static gint ett_crd_pdu = -1;
static gint ett_crd_body_data = -1;

void proto_reg_handoff_crd(void)
{
static gboolean initialized = FALSE;

if (!initialized)
{
data_handle = find_dissector("data");
crd_handle = create_dissector_handle(dissect_crd, proto_crd);
dissector_add("tcp.port", global_crd_port, crd_handle);
}
}

void proto_register_crd(void)
{
static hf_register_info hf[] =
{
{ &hf_crd_pdu,
{ "PDU", "crd", FT_STRING, BASE_NONE, NULL, 0x0,
"CRD PDU", HFILL }},

{ &hf_crd_body_data,
{ "Body", "crd.body", FT_NONE, BASE_NONE, NULL, 0x0,
"CRD Body contents", HFILL }}
};

static gint *ett[] =
{
&ett_crd_pdu,
&ett_crd_body_data
};

proto_crd = proto_register_protocol("CRD Protocol - Ejemplo de uso", "CRD", "crd");

proto_register_field_array (proto_crd, hf, array_length (hf));
proto_register_subtree_array (ett, array_length (ett));

register_dissector("crd", dissect_crd, proto_crd);
}

static void dissect_crd(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
/*
* De acuerdo a la definición de protocolo, el primer byte indica el tipo de mensaje que es.
* 0 para request
* 1 para response
*
* Obtenemos el primer byte y lo convertimos a entero
*/
int type = atoi(tvb_get_string(tvb, 0, 1));

/*
* Chequeamos si en la columna puede escribirse, si se puede,
* escribimos en ella el nombre del protocolo
*/
if (check_col(pinfo->cinfo, COL_PROTOCOL))
col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_CRD);

if (check_col(pinfo->cinfo, COL_INFO))
{
col_clear(pinfo->cinfo, COL_INFO);

/*
* En el cuadro de paquetes capturados, cuando se reciba uno que coincida con nuestro protocolo,
* escribimos como etiqueta el puerto de origen, el de destino y el tipo de mensaje.
*/
col_add_fstr(pinfo->cinfo, COL_INFO, "%d > %d Message Type: [%s]",
pinfo->srcport, pinfo->destport,
((type == _REQUEST_VALUE) ? "REQUEST" : "RESPONSE"));
}

/*
* Si la variable tree es NULL, significa que el usuario no expandió el árbol de captura,
* entonces retornamos porque no hay nada que mostrar
*/
if (tree == NULL)
return;

dissect_content(tvb, tree);

}

static void dissect_content(tvbuff_t *tvb, proto_tree *tree)
{
proto_item *ti = NULL;

/* PDU */
proto_tree *pdu_tree = NULL;
proto_item *pdu_item = NULL;

/* BODY */
proto_tree *body_tree = NULL;
proto_item *body_item = NULL;


/*
* Obtenemos la longitud del mensaje
*/
int message_length = tvb_reported_length(tvb);

/*
* Lo guardamos en un buffer temporal de modo a simplificar el código
*/
char message[message_length + 1];

/*
* Creamos la variable que contendrá el estado (numérico) de la transacción
*/
char transaction_result[3];

/*
* Contenido de longitud variable del texto del mensaje.
*/
int message_text_length = message_length - 2;
char message_text[message_text_length];

/*
* Leemos del buffer de entrada desde el offset 0 hasta el final .
*/
strcpy(message, tvb_get_string(tvb, 0, message_length));

/*
* Extraemos 2 bytes del mensaje original obviando el marcador de tipo de mensaje
*/
substr(message, 1, 2, transaction_result);

/*
* Extraemos el texto del mensaje (de longitud variable) empezando desde el byte nro. 4
* y hasta el final.
*/
substr(message, 2, message_text_length, message_text);

ti = proto_tree_add_item(tree, proto_crd, tvb, 0, -1, FALSE);

pdu_item = proto_tree_add_text(tree, tvb, 0, -1, "PDU");
body_item = proto_tree_add_text(tree, tvb, 0, -1, "Cuerpo");

/* PDU subtree */
pdu_tree = proto_item_add_subtree(pdu_item, ett_crd_pdu);
proto_tree_add_text(pdu_tree, tvb, 0, -1, "Longitud: %d", message_length);
proto_tree_add_text(pdu_tree, tvb, 0, -1, "Contenido: %s", message);

/* BODY subtree */
body_tree = proto_item_add_subtree(body_item, ett_crd_body_data);
proto_tree_add_text(body_tree, tvb, 0, -1, "Resultado: %s",
val_to_str((guint32) atoi(transaction_result),
transaction_result_names,
"Unknown (0x%02x)"));
proto_tree_add_text(body_tree, tvb, 0, -1, "Mensaje: %s", message_text);

}

static void substr(const char* origin, int start, int length, char* result)
{
int currentIndex = 0;
int i = 0;
int end = start + length;

for(i = start; i < end; i++)
result[currentIndex++] = origin[i];

}
#endif // IGNORE


Ahora que ya tenemos el programa listo, lo último que debemos hacer es:
  • Llevar el código al directorio plugins de nuestro working copy y crearle un directorio con una estructura copiada de cualquier de otro plugin existente en el mismo directorio
  • Crear entradas que referencien a nuestro disector en el archivo configure.in disponible en la raíz de nuestra copia
  • Correr el script autogen.sh
  • Correr el script configure
  • Ir al directorio de nuestro plugin
  • Compilar el plugin usando make
  • Copiar el resultado de la compilación disponible en .libs/crd.so al directorio de plugins del Wireshark, en mi caso, /usr/lib/wireshark/plugins/1.0.4/
Con estos pasos, la próxima vez que abra el Wireshark, nuestro plugin estará cargado y listo para usarse.

La misma captura que hicimos anteriormente deberá ser visible mucho más elegantemente y quedaría así:



En vez de tener un paquete capturado mostrando un texto plano, tenemos una separación por bloques, con una descripción de los códigos de los primeros dos campos y la separación del mensaje de respuesta en una etiqueta independiente. Este resultado es mucho más representativo y sin duda, mucho más útil que el anterior.

Eso es todo por ahora, gracias por la lectura :) .



martes, 21 de septiembre de 2010

Actualizar anuncios automáticamente en Clasipar.

Todos sabemos que clasipar es el sitio número 1 para la compra/venta de lo que sea en Paraguay.

Cuando uno trata de vender algo le dedica tiempo a buscar compradores o a actualizar sus anuncios de modo a maximizar sus posibilidades y es justamente acá donde entré a improvisar una vez más.

Definición de la problemática.
  • Tengo varios anuncios ofreciendo productos
  • Tengo que entrar a actualizar diariamente mis anuncios pero son varios y me hacen perder mucho tiempo
  • Muchas veces me olvido y pierdo oportunidades
Es un quilombo, pero tengo la suerte de ser informático (he'i) y las computadoras son mi área de expertise por lo puedo abusar sin remordimiento de ellas (de las computadoras).

El programa

Se trata de un Internet bot que recorre clasipar como su fuera un humano. El programa recibe las instrucciones desde un archivo de texto de donde lee qué anuncio(s) actualizar, se conecta a Internet, navega clasipar como si fuera un browser normal (Internet Explorer o Firefox por ejemplo), explora el panel de control del usuario, busca cuál anuncio actualizar y genera un HTTP request con los datos necesarios.

Está hecho en C# (me encanta), corriendo sobre Linux (aunque también funciona en Windows y MacOS) y funcionando sin problemas desde hace más de un mes.

El programa es relativamente corto en líneas de código pero requiere de un background decente sobre el protocolo HTTP. Me tomé mi tiempo snifeando el tráfico de mi browser conectado a clasipar para averiguar qué datos necesitaba y bajo qué formato se hacía la transmisión. Wireshark es un espectáculo para esto y pronto pude hacer reverse engineering de lo que el sitio esperaba. El siguiente paso fue identificar las cookies y las variables POST que cambiaban (y a veces no) en cada petición. Los datos enviados venían de hidden input boxes en algunos casos por lo que no me pude salvar del pattern matching usando expresiones regulares. La basura puesta por google analytics en el request fue especialmente rompebolas y mi falta de experiencia analizando su tráfico me hizo perder tiempo pues no sabía si estos datos eran necesarios y lo peor, de dónde venían. Finalmente, leyendo el fuente HTML de respuesta pude terminar el análisis y reunir lo necesario para construir el programa.

El código

Los espacios de nombre importados hablan de las porciones del framework que usé
using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;

using HtmlAgilityPack;

using ClasiparBot.Utils;

El entry point solo contiene lo siguiente ya que la lógica está contenida dentro de la clase ClasiBot.

public static void Main (string[] args)
{
if (args.Length != 1)
{
Debug.PrintError("MainClass.Main(): Invalid command line parameter(s)");
return;
}

if (!File.Exists(args[0]))
{
Debug.PrintError("MainClass.Main(): Configuration file is missing");
return;
}

List <botparameters > botParams = ParseConfigurationFile(args[0]);

foreach(BotParameters botParameter in botParams)
Debug.Notify("MainClass.Main(): Error free? ", new ClasiBot(botParameter).StartBot().ToString().ToUpper());
}

El método StartBot()
ServicePointManager.Expect100Continue = false;

HttpWebRequest request = (HttpWebRequest) WebRequest.Create(_parameter.ManagementLink);
request.UserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9\r\n";

request.CookieContainer = new CookieContainer();

HttpWebResponse response = (HttpWebResponse) request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());

HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(reader.ReadToEnd());

string hashID = ParseHashID(_parameter.ManagementLink.ToString()).Trim();
string authToken = htmlDoc.DocumentNode.SelectSingleNode("//input[@name='authenticity_token']").Attributes["value"].Value;

Debug.Notify("ClasiBot.StartBot(): HashID is", hashID);
Debug.Notify("ClasiBot.StartBot(): Authenticity token is", authToken);

string output = AccessDashboard(request.CookieContainer, hashID, authToken);
output = PerformAnnouncementOperation(request.CookieContainer);
Debug.Notify("ClasiBot.StartBot(): Operation result is '", ParseResult(output), "'");

Este método genera el URI dependiendo de la decisión de actualizar, concretar o eliminar el anuncio.
string BuildURIByOperation(Uri mainUri, AnnouncementOperation operation, Uri announcementUri)
{
string operationUri = "http://" + mainUri.Host + "/ad/";

switch(operation)
{
case AnnouncementOperation.Refresh:
operationUri += "refresh_ad";
break;
case AnnouncementOperation.Delete:
operation
Uri += "delete_ad";
break;
case AnnouncementOperation.MarkAsSold:
operationUri += "mark_as_sol";
break;
}

operationUri += announcementUri.AbsolutePath.Replace(".html", "");

return operationUri;

}


Este método determina el resultado de la operación
public string ParseResult(string html)
{
Match match = Regex.Match(html, @"
(?.+)
");

if (match.Success)
return match.Groups["message"].Value;

match = Regex.Match(html, @"
(?.+)
");

if (!match.Success)
throw new Exception("Unable to match result in output html");

return match.Groups["message"].Value;
}
La salida

Faltan algunas otras cosas pero esto explica cómo funciona el programa para el que sabe cómo leerlo.

No publico todo el fuente porque aunque lo que hago no es ilegal, puede perjudicar su negocio y lo último que yo quiero es dañar a la industria paraguaya.

Este programa es más bien una h erramienta personal y este post pretende ser un artículo científico para gente que esté interesada en el funcionamiento de los bots.

miércoles, 25 de agosto de 2010

Un traductor confiable en tu celular

Sigo en la búsqueda del servicio perfecto que a todo el mundo le haga falta y que sea fácil de alcanzar. Como todavía no llego a esa meta sigo tratando y hoy les traigo otro de mis intentos.

¿Cuántas veces necesitaste traducir algo y no pudiste?

En mi caso particular esto es imposible de contabilizar, de hecho, cuando no estoy frente a mi PC conectada a Internet con Google Translator abierto, creo que me resulta difícil sino imposible conseguir una traducción.

Bueno, sin más introducción les presento el traductor español/inglés - inglés/español del mismísimo Google en tu celular!

¿Cómo funciona?
  • Envía ain + texto-en-castellano al 0981157325 para traducir un texto (o palabra) de castellano a inglés. Ejemplo: ain hola mundo!
  • Envía aes + texto-en-inglés al mismo número para el proceso inverso, español a inglés. Ejemplo: aes hello world!
Para hacerte más claro el panorama: ain viene de A-Inglés y aes de A-Español.

Lo único que tenés que hacer ahora es probar :)



viernes, 20 de agosto de 2010

La guía telefónica del país en tu celular

No exagero. Podés tener la guía de todos los usuarios de línea fija (COPACO) en tu celular al alcance de un mensaje de texto.

Muchas veces recibí llamadas de línea baja desde números desconocidos. Muchas de esas llamadas resultaron ser importantes por lo que perdí oportunidades que pude haber aprovechado si hubiera sabido quién llamaba o si hubiera podido identificar el número cuando no estaba registrado entre mis contactos.

Con este nuevo proyecto pretendo ayudar a las personas a identificar esas llamadas perdidas que podrían llegar a ser relevantes en cierto aspecto, así, ya no te quedás con la duda de saber quién te llamó.

La guía telefónica

Lo que hago no es ilegal en ningún aspecto hasta donde llega mi conocimiento legislativo. Uso una base de datos pública, no muy user friendly, le creé una interfaz un poco más intuitiva y la expongo al público con ánimos de ayudar. Es un servicio gratuito que seguirá vivo mientras dure mi saldo para mensajes. No veo nada de malo en esto.

Cómo usar

Enviá quien + nro-linea-baja al 0981157325 y recibirás un mensaje con el nombre del titular y la dirección registrada.

Por ejemplo: "quien 021998877". Quince segundos después estarás recibiendo la información que buscás.

A tener en cuenta
  • Si el número de teléfono que buscas no existe o esta mal formado, no se responde el mensaje informando tal cosa con el fin de ahorrar saldo.
  • Si el número que buscas existe, está bien construido y no recibís respuesta, probablemente me quedé sin saldo.
  • Si el número que buscás tiene menos de 6 dígitos de largo o más 6 dígitos de largo (longitud(número) <> 6) , tenés que enviar el mensaje de ésta forma: área.número. Por ejemplo: 0541.66777.
En general, es fácil de usar.

Probá

El sistema está on-line, probá, comprobá que funciona y hacele propaganda para que otros puedan usar :) .

viernes, 13 de agosto de 2010

Ejemplo 4: demo usando Windows Forms

Seguimos con los ejemplos. Lo siguiente es un programa hecho en Visual Studio utilizando Windows Forms.

Les cito lo que puede hacer:
  • Enviar mensajes por número
  • Enviar mensajes grupales por lista de números cargados a mano o desde un archivo
  • Leer los mensajes respondidos por número
  • Leer todos los mensajes respondidos
  • Chequear mensajes respondidos por tiempo (cada 5 segundos por ejemplo)
No hay mucho más que decir aparte de esto. Bajá el código y probá la aplicación :)

Les dejo las capturas.










Como siempre, también funciona desde Linux.




jueves, 12 de agosto de 2010

Ejemplo 3: un proyecto ejecutable

Esta vez les dejo un proyecto de consola utilizando el SDK. Pueden bajarlo y compilarlo utilizando el C# compiler de Visual Studio.

Este ejemplo esta basado en los otros dos anteriores pero incluye un feature todavía no explicado pero entendible al leer el código.

Como les dije, todo esto es multiplataforma:

Desde Windows



Desde GNU/Linux





Ejemplo 2: smsSDK para recibir mensajes.

Ahora les traigo la segunda parte de la serie de tutoriales explicativos del smsSDK.
Esta vez voy a explicar cómo hacemos para leer las respuestas de los mensajes que enviamos.

Voy a dejar que el código hable.

public static void Main (string[] args)
{
Credencial credencial = new Credencial("demo", "d3m0");

Mensajero mensajero = new Mensajero(credencial);

/*
* Leemos la o las respuestas pasando nuestra credencial
* y el numero del cual queremos saber su respuesta
*/
ContenidoRespuesta respuesta = mensajero.LeerRespuesta(credencial, "09811111222");

/*
* Verificamos el estado de la transaccion
*/
switch(respuesta.Codigo)
{
case CodigoRespuesta.Accepted:
case CodigoRespuesta.OK:
Console.WriteLine("Pedido procesado con exito!");
break;
default:
Console.WriteLine("Hubo un error al procesar el pedido. Error: '{0}'", respuesta.Codigo.ToString());
return;
}

/*
* La propiedad "HayDatos" es true cuando hay mensajes
* respondidos para el numero que pasamos.
*/

if (!respuesta.HayDatos)
{
Console.WriteLine("No hay mensajes respondidos");
return;
}

/*
* Las respuestas son alamacenadas en "MensajeRespondido.Datos".
* Esta propiedad es un array de objetos de
* la clase "MensajeRespondido".
*
* A continuacion, iteramos sobre los datos para leer las respuestas
* e imprimirlas en pantalla.
*/
foreach(MensajeRespondido mensajeRespondido in respuesta.Datos)
{
Console.WriteLine("==========================================");
Console.WriteLine("Codigo Mensaje: {0}", mensajeRespondido.ID);
Console.WriteLine("Numero: {0}", mensajeRespondido.Numero);
Console.WriteLine("Texto Enviado: {0}", mensajeRespondido.TextoEnviado);
Console.WriteLine("Texto Recibido: {0}", mensajeRespondido.TextoRespondido);
Console.WriteLine("Fecha: {0}", mensajeRespondido.FechaRespuesta);
}

Console.WriteLine("*** LISTO ***");

}
Qué pasa si queremos recibir las respuestas de todos los números a los cuales enviamos un mensaje?. Fácil cambiamos la línea:
ContenidoRespuesta respuesta = mensajero.LeerRespuesta(credencial, "0981111222");
por lo que viene:

ContenidoRespuesta respuesta = mensajero.LeerRespuesta(credencial, Mensajero.TODOS_LOS_NUMEROS);
En mis siguientes entradas voy a explicar el modelo asíncrono de envío y recepción de mensajes. En estos dos ejemplos se puede ver que las invocaciones a los métodos son bloqueantes, cosa que puede llegar a incomodar cuando trabajamos con interfaces gráficas o cuando nuestra aplicación es multipropósito.

miércoles, 11 de agosto de 2010

Ejemplo 1: smsSDK para enviar mensajes

Es súper fácil. Voy a tratar de ser lo más pedagógico posible, no voy a mezclar los conceptos y voy a escribir un ejemplo para cada funcionalidad, así es más fácil de entender.

Lo siguiente es una aplicación de consola en C# que envía un mensaje de texto a cierto número utilizando el SDK.

El ejemplo es autodescriptivo y está comentado.

class MainClass
{
public static void Main (string[] args)
{
/*
* Creamos una instancia de la clase credencial que
* contenga nuestro usuario y password en el servidor
*/
Credencial credencial = new Credencial("demo", "d3m0");

/*
* Creamos una instancia de la clase Mensajero
* usando la credencial que acabamos de crear
*/
Mensajero mensajero = new Mensajero(credencial);


/*
* Creamos un "MensajeDeTexto" a enviar. Cargamos
* el mensaje y el numero de celular al cual queremos enviar.
*
*/
MensajeDeTexto sms = new MensajeDeTexto();
sms.NumeroCelular = "0981111222";
sms.Texto = "Hola Mundo desde smsSDK 0.1!";


/*
* Usando el mensajero que creamos previamente enviamos
* el mensaje utilizando el "MensajeDeTexto" y la "Credencial"
* que creamos. Ademas, capturamos la respuesta del servidor
* para conocer el estado de la transaccion.
*
*/
ContenidoRespuesta respuesta = mensajero.EnviarMensaje(sms, credencial);

switch(respuesta.Codigo)
{
case CodigoRespuesta.Accepted:
case CodigoRespuesta.OK:
Console.WriteLine("Pedido procesado con exito!");
break;
default:
Console.WriteLine("Hubo un error al procesar el pedido. Error: '{0}'", respuesta.Codigo.ToString());
break;
}

Console.WriteLine("LISTO");

}
}

El SDK

Como había dicho en mi entrada anterior, el proyecto está construido en C# usando el perfil NET_2 para hacerlo compatible con aplicaciones viejas hechas en C#1, C#2 y VisualBasic.NET 7 y 8 pero, lo más importante, para ser usable sobre Linux y MacOS usando el compilador de C# implementado por Mono a partir del ECMA-CLI.

Básicamente, el SDK consiste en una serie de clases especializadas para realizar el envío y recepción de mensajes de texto conectándose a un servidor externo (mi servidor) a través de Internet. La autenticación se realiza por medio de HTTP BASIC AUTHENTICATION por lo que conectarse a mi servidor no debería ser un problema ya que solo se utiliza el protocolo HTTP, sencillo y fácil de entender.

En este caso, tengo las clases escritas en C# pero la lógica puede ser perfectamente reimplementada en PHP o Java (por ejemplo) si es que existe alguna necesidad de usar esto desde otra tecnología de desarrollo.

No voy a profundizar mucho en el funcionamiento interno del SDK ya que voy a publicar el código, asi que si están interesados en cómo funciona, pueden descargar los fuentes y echarle una leída :).

Para que vean que realmente es fácil, pongo el diagrama de las principales clases. El código está en castellano para que sea apto para todo público, es decir, no hace falta que sepas inglés para usarlo.




El código

Originalmente lo escribí completamente en Linux usando Monodevelop, pero para asegurarme de que puede ser abierto por Visual Studio, lo llevé a Windows y confirmo que sí se puede.

En las siguientes entradas se vienen los ejemplos de uso.

Descarga

lunes, 9 de agosto de 2010

Kit de desarrollo para Envío/Recepción de SMS

Muchas veces, durante el transcurso de mi vida como programador, quise agregarle funcionalidades de envío de mensajes de texto a los varios programas que había desarrollado pero esto casi nunca fue posible y cuando lo fue, tenía que enviarlos a través de mails que no siempre llegaban y que cuando llegaban, no podías responderlos con facilidad, eran difíciles de leer o de identificar al remitente pero lo peor de todo esto, era que al poco tiempo, la IP de mi servidor de correo era bloqueada por la operadora haciendo inservible todo el trabajo que había hecho.

El producto: smsSDK versión 0.1

Este SDK permite el envío y recepción de mensajes de texto a través de un número real, no hay emails ni algún otro truco raro. Es un número de teléfono como el que tenés en tu aparato celular y podes usarlo como tal. Los mensajes que salen por este servicio LLEGAN, por el siempre hecho de que son enviados usando el servicio de SMS de la operadora, pagando un saldo en guaraníes y remitidos desde una cuenta (número de teléfono) comprado y pagado mensualmente por mi grupo.

El hecho de que puedas enviar usando un número real, te permite poder recibir una respuesta. Por ejemplo: si envias el texto "hola mundo" desde el 09xx111222 al celular 09xx222333, este último puede perfectamente responder tu mensaje y vos capturar su respuesta utilizando el SDK.

¿A quiénes va dirigido ésto?

Por tratarse de un kit de desarrollo, es de esperarse que vaya dirigido a los programadores. La idea es que cualquiera que necesite agregarle este tipo de funcionalidad a su trabajo, lo haga sin mayores complicaciones y en un tiempo bajísimo ya que la curva de aprendizaje del SDK es casi una recta :)

¿Para qué sirve?

Le podés dar muchísimas utilidades. A continuación te cito algunas ideas:
  • Si tenés una tienda de comercio, podés enviar mensajes de texto a tus clientes informando de nuevos productos, ofertas o promociones.
  • Si tenés o formás parte de un grupo que involucre un alto número de personas, como una cooperativa, club social o fraternidad, podés enviar mensajes masivos avisando de reuniones y como el SDK te permite leer las respuestas de cada uno, sabés quiénes van y quiénes no.
  • Si tenés un consultorio médico podés hacer confirmación automatizada de citas, cosa que las secretarias siempre hacen, pero a mano.
  • Podés hacer un recordatorio que te envíe un mensaje de texto al celular de acuerdo a fechas importantes dentro de tu agenda.
¿Cómo funciona?

Es siempre mejor explicar algo con un ejemplo y así lo voy a hacer, pero en mis siguientes posts. Mientras, lo voy a describir en palabras sencillas.

El SDK esta hecho en C#, lo podés usar desde cualquier proyecto .NET. Necesitas incluir el ensamblado DLL como referencia, importar los espacios de nombre, tener una conexión decente a Internet para conectarte a mis servidores y ya está, solo eso.



¿Cuesta algo?

El SDK no cuesta nada, voy a liberar el código fuente para que cualquiera pueda usarlo, cambiarlo o estudiarlo.

El saldo para enviar mensajes sí cuesta, pero esa es otra historia que la vamos a ir cubriendo más adelante.

Si te interesa, fijate en lo que viene muy próximamente en las siguientes entradas.

lunes, 26 de julio de 2010

Delivery, un enfoque informático

Yo personalmente uso mucho el delivery de comidas de cierto conocido local comercial que no vale la pena mencionar. El problema con esto es que después de convertirte en un cliente frecuente que llama siempre a pedir lo mismo o que si cambia sus gustos sabe bien que pedir, que te atienda la operadora con ese infernal ruido, te pregunte tu número de teléfono (what, again?!) y te haga repetir mil veces lo que pediste se vuelve algo tedioso y con consecuencias nerviosas difíciles de ocultar.

Que tal si te atiende un operador inteligente que te liste los menús mas comunes y te pase con la operadora ruidosa solo cuando lo que escuchaste no esta ahí (poco probable) ?. Bueno, a mi me encanta la idea.

¿Cómo solucionamos esto?

La verdad que es un poco complicado pero básicamente necesitamos lo siguiente:
  1. Una entrada de línea telefónica a través de un media gateway que nos haga de pasarela entre la red celular (GSM) y/o fija (PSTN).
  2. Un PBX, pero no cualquier PBX, tiene que ser un IPPBX porque queremos poder programarlo y no gastar ni un guaraní en adquirirlo e instalarlo. Para esto tenemos varias opciones pero mi favorita es Asterisk .
  3. Las voces del IVR . Son las grabaciones que se reproducirán cuando alguien llame e interactúe con la central.Enlace
  4. Nuestro cerebro e Internet para programar el dialplan que básicamente, es lo más dificil en todo esto.
Necesitamos saber quién es el cliente que llama, para esto tenemos su caller-id. Con ese dato podemos ir a tratar de identificarlo en una base de datos (MySQL en mi caso) buscando su nombre y dirección particular. Si no lo encontramos, lo derivamos directamente con la operadora para que le tome sus datos y lo dé de alta en el sistema, cosa que se hace siempre cuando llamas por primera vez.

Si el cliente ya está registrado, le reproducimos el menú estadísticamente más pedido o todo el menú si quieren (no es buena idea). Por ejemplo:
  • Llamo y me atiende una IVR deciéndome: "Bienvenido al bar X" y me reproduce el menú.
  • "Presione 1 para pizzas, 2 para lomitos, 3 para bebidas, 0 (cero) para hablar con un operador".
  • Me gustaría comer pizza y presiono 1 reproduciéndome...
  • "Presione 1 para napolitana, 2 para cuatro quesos, 3 para muzarela, 4 para peperoni."
  • Me gusta la de 4 quesos y presiono 2 y me reproduce...
  • "Seleccione la cantidad que desea"
  • Bueno, estoy con algunas personas y quiero tres, entonces aprieto 3...
  • "Usted ha pedido 3 pizzas de cuatro quesos, el precio total es, 90 mil guaraníes, presione 1 para confirmar, 2 para cambiar el pedido, 3 para salir"
  • Quiero confirmar, presiono 1...
  • "Su pedido ha sido procesado con éxito, adiós", y termina la transacción. Opcionalmente, recibís un SMS que te confirma que tu pedido fue atendido y otro mensaje cuando se encuentra en tránsito, es decir, cuando el delivery guy se está yendo a tu casa en su moto.
Cuando llega un nuevo pedido se levanta un ticket o alerta que la cajera o algún encargado puede ver en su pantalla y éste se procesa como cualquier otro pedido hecho físicamente en la caja.

¿Cuánto tiempo tardé en pedir?

Escuchar toda la grabación me tomó 30 segundos o menos, tiempo decente pero, cuando me sepa de memoria el menú, al llamar puedo presionar 123 (1 + 2 + 3) de seguido y lo siguiente que escucharé será: "Usted ha pedido 3 pizzas de cuatro quesos, el precio total es, 90 mil guaraníes, presione 1 para confirmar, 2 para cambiar el pedido, 3 para salir", entonces, presiono 1 y listo. Cuánto tardé?, menos de 10 segundos.

Como había dicho, lo más difícil de programar fue el dialplan. Tenía 4 opciones para esto, usar el seudolenguaje de programación de extensiones (no!!!), AEL (tampoco, pero lo usé solo para algunas operaciones de muy bajo nivel), AGI (mucho mejor, pero no) y FastAGI, el ganador.

Por qué fastAGI? porque utiliza sockets sobre IP lo que me permite manejar la carga en una suerte de computación distribuida, mucho mas escalable y encima elegante.

Hay demasiadas cosas por explicar sobre cómo funciona Asterisk y fastAGI. tal vez en otra ocasión me siente a hacer un ensayo, pero por ahora les dejo un snippet esencial para el manejo de pedidos, hecho en C#, para que se den una idea.


public override void Service(AGIRequest param1, AGIChannel param2)
{
try
{
Answer();
StreamFile("pizza/bienvenidos");
if (!IsClientRegistered(param1.CallerId))
{
Console.WriteLine("CallerID {0} is not registered, transferring to operator.", param1.CallerId);
StreamFile("pizza/numero_desconocido");
SetVariable("GO_TO_OPERATOR", "yes");
AddCustomer(param1.CallerId);
return;
}

SetVariable("GO_TO_OPERATOR", "no");

if (CustomerHasPendingOrders(param1.CallerId))
{
StreamOrderStatus();
if (Ask("13", "pizza/1_nuevaorden_3_salir") == ConfirmationRequest.Exit)
throw new Exception("User exited transaction");
}

MenuLevel menu;
PhoneKey pressedKey = '\0';
ConfirmationRequest confimation;
List customerChoice = new List();
bool makeMoreOrders = false;
ulong total = 0;
/*
* Say menu options
*/
while(true)
{
menu = _rootMenu;
makeMoreOrders = false;
while(true)
{
pressedKey = StreamMenu(menu);

if (menu.IsRoot && pressedKey.IsGoToOperator)
{
SetVariable("GO_TO_OPERATOR", "yes");
return;
}

if (!menu.IsRoot && pressedKey.IsGoBack)
{
menu = menu.Parent;
continue;
}

if (!menu.HasSubLevels)
{
MenuOption option = menu[pressedKey];
int quantity = AskForQuantity();

StreamCustomerOrder(option, quantity);

confimation = Ask("123", "pizza/1_confirmar_2_ordenar_3_salir");

if (confimation == ConfirmationRequest.Exit)
throw new Exception("User canceled the transaction");
else if(confimation == ConfirmationRequest.OrderMore)
makeMoreOrders = true;

customerChoice.Add(new SelectedProduct(menu[pressedKey], quantity));
total += option.Price * (ulong) quantity;

break;
}

menu = menu.GetSubMenu(pressedKey);
}

if (makeMoreOrders)
continue;

StreamFile("pizza/costo_total");
SayNumber(total.ToString());
StreamFile("pizza/guaranies");

if (Ask("13", "pizza/1_confirmar_3_salir") == ConfirmationRequest.Exit)
throw new Exception("User canceled transaction in the last minute");

foreach(SelectedProduct selection in customerChoice)
AddOrder(param1.CallerId, selection.SelectedOption.ProductID, selection.Quantity, selection.TotalPrice);

StreamFile("pizza/pedido_procesado_adios");

break;
}
}
catch(Exception ex)
{
Console.WriteLine("PizzaIVR.Service(): {0}", ex.Message);

}

Hangup();
}

Chequeo de pagos, en detalle

En mi post anterior hablé sobre un producto de automatización de chequeo de pagos. Ahora pretendo profundizar un poco más sobre el flujo de datos que se sigue cuando un cobrador llama.

El gráfico da una idea visual de lo que hablo y lo hice tratando de simplificar un diagrama de flujo real que muchas veces resulta inentendible para el que no sabe como leerlo.


  1. Cuando el cobrador llama la central telefónica lo atiende (sin molestar a la secretaria) y dependiendo de si reconoce o no su CID (número desde donde llama) le reproduce un audio que dice: "introduzca su código de usuario" y luego "introduzca su pin". Si el CID es reconocido, entonces directamente le reproduce la segunda grabación.
  2. El cobrador usa el teclado de su teléfono para realizar el proceso de autenticación (digitar usuario y pin) y si el proceso resulta exitoso se salta al siguiente paso, sino, con tres intentos fallidos la central corta la comunicación y registra lo sucedido.
  3. El cobrador autenticado escucha la grabación que le cuenta cuántos pagos tiene disponible, si no tiene ninguno, le reproduce una grabación que le dice esto y la transacción es finalizada por la central.
  4. Si el cobrador cuenta con uno o más pagos disponibles, tiene la opción de escuchar un detalle de los mismos, el monto o cualquier otra información que quiera agregarle.
  5. La central finaliza la transacción y registra la llamada.
Esto es, en lineas generales, lo que sucede internamente cuando el sistema recibe un llamada.

El futuros posts, voy a hablar de la tecnología usada para hacer esto posible.

Tu secretaria está cansada de atender a los cobradores

Las secretarias tienen mucho trabajo que hacer. Además de atender permanentemente el teléfono tienen que estar cumpliendo con otras funciones mas elaboradas como hacer planillas y presentar informes. No sería bueno sacarle un poco de carga y automatizar ciertas funciones que te hagan quedar bien con tus clientes/proveedores y con tu secretaria?, seguramente sí.

Les presento otro de mis proyectos. Esta vez se trata de un sistema de chequeo de pagos disponibles por vía telefónica pero a través de un IVR, totalmente automático. Si tenes varios proveedores que llaman con cierta frecuencia a preguntar sobre cheques o pagos disponibles cada fin de mes o por semana, seguramente tu secretaria esta cansada de responder con "si hay un cheque para usted", "aún no se habilitó el pago" o peor todavía, "espere un momento que me fijo" empezando a buscar entre las decenas de cheques o en una planilla Excel que le toma 30 segundos encontrar y abrir manteniendo al llamador on hold, la linea ocupada y perdiendo clientes.


Cómo funciona



  • En tu empresa, se habilita un acceso telefónico. Puede ser un número de teléfono celular o de línea fija.
  • En una base de datos y a través de una interfaz web o de escritorio, se dan de alta los cobradores, esto es, su nombre o descripción, el número de teléfono desde el cual suelen llamar, su pin (contraseña numérica corta) y cualquier otra información que consideren relevante. Esta carga se hace una sola vez por cada proveedor.
  • Se entrega al cobrador de tu proveedor su código (número de 4 dígitos o menos), su pin y el número de teléfono a donde tienen que llamar para consultar si tiene pagos disponibles y...
  • Cada vez que hay un pago para cierto proveedor, la secretaria actualiza la base de datos con solamente 2 clicks en la interfaz web y listo!.
Con estos pasos tenés un sistema de chequeo de pagos muy elegante y fácil de usar que además facilita la vida de ambas partes, la de la secretaria y la del cobrador.

En mi próxima entrada estaré explicando el flujo que se sigue cuando el cobrador llama.

Elecciones municipales 2010 y una herramienta útil

Es bien sabido por nosotros los paraguayos que nuestro sistema electoral no es el mejor y mucho menos el más moderno.

Muchas veces no tenemos las ganas suficientes para levantarnos un domingo para ir a un lugar desordenado, ruidoso y caótico como son los centros de votación, es perfectamente comprensible eso.

Otro problema muy común en ciudades grandes es saber dónde ir a votar, en qué mesa. Yo estoy fanatizado por saber de antemano qué hacer y evitar la aleatoriedad de la situación que siempre se presta a causar una gran pérdida de tiempo y es por esto que tomé parte de mi tiempo libre en experimentar y desarrollar algo que pretende ser útil, por lo menos mínimamente.

Bueno, como arreglar el tema del desorden en los centros de votación escapa completamente de mis manos, decidí tratar de arreglar el problema de saber en dónde votar y para éste fin desarrollé un sistema basado en SMS (mensajes de texto) de la red GSM que te informa la dirección de tu centro de votación y el número mesa de acuerdo a tu número de Cédula de Identidad enviado por SMS.




El gráfico de arriba muestra que su funcionamiento es bastante sencillo. Por ejemplo, si yo usuario quiero saber dónde ir a votar el día de las elecciones lo único que tengo que hacer es:
  • Envío un mensaje de texto (que cuesta Gs. 65!) con mi número de CI a cierto número, por ejemplo 1234 o 09xx 112233
  • Recibo una respuesta en menos de 30 segundos con mi nombre y apellido, municipio, la dirección donde debo ir a votar y el número de mesa
Entonces, con información en mano, me voy directo donde debería y me ahorro todos los problemas que cité anteriormente y además, contribuyo al orden porque no tengo que andar dando vueltas preguntando y formando filas que no me corresponden.

La tecnología detrás de esta implementación es bastante amplia. A continuación cito las principales involucradas:
  • GSM gateway con un chip de una operadora local
  • SIP Server
  • Linux
  • Aplicación C# corriendo sobre Mono
  • Base de datos MySQL
  • Varias otras herramientas de desarrollo
Si están familiarizados con las herramientas que usé, se darán cuenta que son libres, es decir, no uso Windows, no uso Oracle o SQL Server y uso C# pero no sobre Microsoft CLI (.NET), sino sobre Mono y no usando Visual Studio, sino monodevelop.

Lo que quiero rescatar con esto es que la solución es muy barata y si fuera por mi, publicaría el número para que la gente empiece a utilizarlo pero como no tengo suficientes recursos (saldo) y me autofinancio mis proyectos, necesitaría más bien un esponsor que me ayude a mejorar el mundo :) .

La tecnología está disponible y nos da facilidades. En nuestro país hay muchos talentos, conozco gente brillante que necesita la oportunidad para surgir. Así como podemos sacar buenos jugadores de fútbol, también podemos sacar excelentes ingenieros, pero repito, necesitamos la oportunidad.

En futuras entradas estaré publicando más información sobre el funcionamiento interno, el diseño y el core en general.

Aprovecho también para agradecer a un amigo mío que solucionó una serie de problemas relacionados a la plataforma y por ende, me ayudó a terminar esto.