l'essentiel est invisible pour les yeux

Monday, June 30, 2008

Tutorial for how to create Erlang linked-in driver.

Erlang provides mechanism to communicate between Erlang and other programs written by C/C++ or other. This entry introduce how to create simple linked-in driver. Create shared library and Erlang interface to use Erlang's linked-in driver.

Goal: To create HAVAL bindings for Erlang.
HAVAL is fast cryptographic hash algorithm is invented by Yuliang Zheng, Josef Pieprzyk, and Jennifer Seberry in 1992. This library is pretty simple, so this is good choice for this tutorial.

Step 0: Files


config.h (Generated by configure script in HAVAL package)
haval.h
haval.c
haval.erl
haval_drv.cc
Makefile


haval.c and haval.h are included haval-1.1.tar.gz (C source code).

You can checkout these sample sources with Git. see haval_erlang.

% git clone git://github.com/rakuto/erlang_haval.git erlang_haval


Step 1: Create Erlang's interface
Erlang provides port mechanism in order to communicate between Erlang and other loaded shared object. See also Writing an Erlang Port using OTP Principles


-module(haval).
-author('Rakuto Furutani <xri://=rakuto>').

-define(DRV_INFO, 1).
-define(DRV_HAVAL_STRING, 2).
-define(DRV_HAVAL_FILE, 3).

-export([start/0, stop/0]).
-export([info/0, haval_string/1, haval_file/1]).
-ifdef(debug).
-export([test/0, test/1]).
-endif.

%%
%% Public interface
%%
start() ->
start("haval_drv"),
ok.

start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit({error, could_not_load_driver})
end,
register_lib(SharedLib).

stop() ->
[{port, Port}| _] = ets:lookup(haval_table, port),
Port ! {close, self()},
ok.

%% TODO: Implement this function return the information for module
info() -> ok.

%% Caluculate a value of HAVAL from string
haval_string(Str) ->
binary_to_term(control(?DRV_HAVAL_STRING, Str)).

%% Caluculate a value of HAVAL from file
haval_file(FileName) ->
binary_to_term(control(?DRV_HAVAL_FILE, FileName)).

-ifdef(debug).
test() ->
haval:start(),
Str = "I love Erlang.",
FileName = "haval.erl",
Hash1 = haval:haval_string(Str),
Hash2 = haval:haval_file(FileName),
io:format("HAVAL(~p) = ~p ~n", [Str, Hash1]),
io:format("HAVAL(~p) = ~p ~n", [FileName, Hash2]),
haval:stop(),
halt().

test([Str|_]) ->
haval:start(),
Hash = haval:haval_string(Str),
io:format("HAVAL(~p) = ~p ~n", [Str, Hash]),
haval:stop(),
halt().
-endif.

%%
%% Internal functions
%%
register_lib(SharedLib) ->
Port = open_port({spawn, SharedLib}, []),
Tab = ets:new(haval_table, [set, protected, named_table]),
ets:insert(Tab, {port, Port}).

control(Cmd, Data) ->
[{port, Port}| _] = ets:lookup(haval_table, port),
erlang:port_control(Port, Cmd, Data).



Step 2: Create C/C++ shared library
Shared library writted by C++ is loaded by Erlang interface when call erl_ddll::load_driver/2. This driver uses ei library in order to manipulate Erlang binary term.


/**
* HAVAL (cryptographic hash function) bindings for Erlang
*
* @author Rakuto Furutani <xri://=rakuto>
* @date 2008/06/28
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <new>
#include "ei.h"
#include "erl_driver.h"
#include "erl_interface.h"
extern "C" {
// Define constant for haval.h
#define PASS 3
#define NUMBER_OF_BLOCKS 5000
#define FPTLEN 256
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1
#endif

#include "haval.h"
}

// Functions are provided by this driver
#define DRV_INFO 1
#define DRV_HAVAL_STRING 2
#define DRV_HAVAL_FILE 3

/* Driver Interface Declarations */
static ErlDrvData start_haval_driver(ErlDrvPort port, char *command);
static void stop_haval_driver(ErlDrvData drv_data);
static int control(ErlDrvData drv_data, unsigned int command, char *buf,
int len, char **rbuf, int rlen);
static void haval_to_hex(unsigned char*, char*);
static ErlDrvBinary* ei_x_to_new_binary(const ei_x_buff*);

/* Driver Entry */
static ErlDrvEntry haval_driver_entry = {
NULL,
start_haval_driver,
stop_haval_driver,
NULL,
NULL,
NULL,
"haval_drv",
NULL,
NULL,
control,
NULL,
NULL
};

typedef struct _drv_data {
ErlDrvPort port;
} drv_data_t;

/* DRIVER INTERFACE */

static ErlDrvData start_haval_driver(ErlDrvPort port, char *command)
{
drv_data_t *data;

data = (drv_data_t*) driver_alloc(sizeof(drv_data_t));
data->port = port;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData) data;
}

static void stop_haval_driver(ErlDrvData drv_data)
{
driver_free((char*) drv_data);
}

static int control(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen)
{
int ret = -1;
char hex[(FPTLEN >> 3) << 1];
unsigned char fingerprint[FPTLEN >> 3];
char *arg1;
ei_x_buff x_buff;

try {
// argument
arg1 = new char[len + 1];
strncpy(arg1, buf, len);
strcat(arg1, "�0");

ei_x_new_with_version(&x_buff);
switch(command) {
case DRV_INFO:
ei_x_encode_string(&x_buff, "info");
ret = sizeof("info") * sizeof(char);
break;
case DRV_HAVAL_STRING:
haval_string(arg1, fingerprint);
haval_to_hex(&fingerprint[0], &hex[0]);
ret = sizeof(hex);
ei_x_encode_string(&x_buff, hex);
break;
case DRV_HAVAL_FILE:
if(!haval_file(arg1, fingerprint)) {
haval_to_hex(&fingerprint[0], &hex[0]);
ret = sizeof(hex);
ei_x_encode_string(&x_buff, hex);
} else {
erl_err_sys("haval_file");
}
break;
}
if(ret > 0) *rbuf = reinterpret_cast<char*>(ei_x_to_new_binary(&x_buff));
ei_x_free(&x_buff);
} catch(std::bad_alloc) {
erl_err_sys("can not allocate memory");
}

return ret;
}

static void haval_to_hex(unsigned char *fingerprint, char *hex)
{
for(int i=0; i < FPTLEN >> 3; ++i) {
sprintf(&hex[i << 1], "%02X", fingerprint[i]);
}
}

// Init the driver
extern "C" DRIVER_INIT(haval_drv)
{
return &haval_driver_entry;
}

// Utilities
static ErlDrvBinary* ei_x_to_new_binary(const ei_x_buff *x_buff)
{
ErlDrvBinary *bin = driver_alloc_binary(x_buff->index);
if(bin != NULL) {
memcpy(bin->orig_bytes, x_buff->buff, x_buff->index);
}
return bin;
}


Step 3: Compile all sources
Create Makefile for build.
FYI: If you want to build linked-in driveron Mac OS, see Compile Erlang linked-in driver on Mac OSX (Darwin)


.PHONY: clean
.SUFFIXES: .o .c .cc .erl .beam

OS= ${shell uname}
CC=gcc
CXX=g++
CXXFLAGS=-Wall -g

# Erlang
ERL_INCLUDE = -I/usr/local/lib/erlang/usr/include
ERL_LIBS = -L/usr/local/lib/erlang/usr/lib \
-lerts
EI_INCLUDE = -I/usr/local/lib/erlang/lib/erl_interface-3.5.6/include
EI_LIBS = -L/usr/local/lib/erlang/lib/erl_interface-3.5.6/lib \
-lei \
-lerl_interface

TARGET_LIB = haval_drv.so
ifeq ($(OS), Darwin)
EXTRA_OPTIONS = -fno-common -bundle -undefined suppress -flat_namespace
endif
ALL: $(TARGET_LIB) haval.beam

.erl.beam:
erlc -W -Ddebug $<

.c.o:
$(CC) $(CFLAGS) -c $<

.cc.o:
$(CXX) $(CXXFLAGS) $(ERL_INCLUDE) $(EI_INCLUDE) -c $<

haval_drv.so: haval.o haval_drv.o
$(CXX) -o $@ $^ $(ERL_LIBS) $(EI_LIBS) $(EXTRA_OPTIONS) -fpic -O2

clean:
rm -f *.beam *.o *.so



Step 4: Run and test

% erl -noshell -run haval test
HAVAL("I love Erlang.") = "3AECEDF5133ED147C704A25C2CCA9A994FBB984FBCB22C1A523F31963E418498"
HAVAL("haval.erl") = "E006098555DFF788E73C7A263615DF60CC82396B1BCE7AEB5FB7050C376F2B03"