Переглянути джерело

ERLANG module

This module provides interact with Erlang node. The module allows sending,
receiving Erlang messages and RPC calls between each other.
Seudin Kasumovic 10 роки тому
батько
коміт
b3e7a54e22

+ 18 - 0
modules/erlang/Makefile

@@ -0,0 +1,18 @@
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=erlang.so
+
+LIBS=-L$(LOCALBASE)/lib/erlang/usr/lib -lei -lpthread
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+
+DEFS+=-I/usr/local/lib/erlang/usr/include
+DEFS+=-DKAMAILIO_MOD_INTERFACE -D_REENTRANT
+
+
+include ../../Makefile.modules
+

+ 762 - 0
modules/erlang/README

@@ -0,0 +1,762 @@
+ERLANG Module
+
+Seudin Kasumovic
+
+   <[email protected]>
+
+Edited by
+
+Seudin Kasumovic
+
+   <[email protected]>
+
+   Copyright © 2015 Bicom Systems Ltd.
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. no_cnodes (int)
+              3.2. cnode_alivename (str)
+              3.3. cnode_host (str)
+              3.4. erlang_nodename (str)
+              3.5. cookie (str)
+              3.6. trace_level (int)
+              3.7. rpc_reply_with_struct (int)
+
+        4. Exported pseudo variables
+
+              4.1. Overview
+              4.2. Attributes
+              4.3. $atom(name)
+              4.4. $list(name)
+              4.5. $tuple(name)
+              4.6. $xbuff(name)
+
+        5. Functions
+
+              5.1. erl_rpc(mod,fun,args,reply)
+              5.2. erl_reg_send(server,msg)
+              5.3. erl_reply(msg)
+
+        6. Event routes
+
+              6.1. Registered pseudo process
+              6.2. event_route[erlang:self]
+
+   2. Using Kamailio from Erlang
+
+        1. RPC calls from Erlang
+
+   3. Developer Guide
+
+        1. Available Functions
+
+              1.1. erl_load_api(erl_api)
+              1.2. rpc(reply,module,function,args)
+              1.3. reg_send(server,msg)
+              1.4. reply(msg)
+              1.5. xavp2xbuff(xbuff,xavp)
+              1.6. xbuff2xavp(xavp,xbuff)
+
+   List of Examples
+
+   1.1. Set no_cnodes parameter
+   1.2. Set cnode_alivename parameter
+   1.3. Set cnode_host parameter
+   1.4. Set erlang_nodename parameter
+   1.5. Set cookie parameter
+   1.6. Set trace_level parameter
+   1.7. Example of using attribute length
+   1.8. Example of using attributes type and format
+   1.9. Example set and print to log atom
+   1.10. Example of using lists
+   1.11. Example of using tuple
+   1.12. Example of using xbuff
+   1.13. Example of using erl_rpc
+   1.14. Example of using erl_reg_send
+   1.15. Example of registered process
+   1.16. Example of registered process
+   1.17. Example of using default event route
+   2.1. Example of RPC call from erlang shell with no response
+   2.2. Example, check is line registered
+   2.3. Example get config variable
+   2.4. Example get dialog statistics
+   3.1. Example of using API
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. no_cnodes (int)
+        3.2. cnode_alivename (str)
+        3.3. cnode_host (str)
+        3.4. erlang_nodename (str)
+        3.5. cookie (str)
+        3.6. trace_level (int)
+        3.7. rpc_reply_with_struct (int)
+
+   4. Exported pseudo variables
+
+        4.1. Overview
+        4.2. Attributes
+        4.3. $atom(name)
+        4.4. $list(name)
+        4.5. $tuple(name)
+        4.6. $xbuff(name)
+
+   5. Functions
+
+        5.1. erl_rpc(mod,fun,args,reply)
+        5.2. erl_reg_send(server,msg)
+        5.3. erl_reply(msg)
+
+   6. Event routes
+
+        6.1. Registered pseudo process
+        6.2. event_route[erlang:self]
+
+1. Overview
+
+   Erlang is a general-purpose programming language and runtime
+   environment. Erlang has built-in support for concurrency, distribution
+   and fault tolerance. This module provides interact with erlang node.
+   The module allows sending, receiving Erlang messages and RPC calls
+   between each other.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * None
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * ei - Erlang interface C library.
+       The routines for handling the erlang binary term format and
+       communicate with with distributed erlang. For more details see
+       Erlang Interface Reference Manual
+     * epmd - Erlang Port Mapper Daemon.
+       This daemon acts as a name server on all hosts involved in
+       distributed Erlang computations. For more details see Erlang Port
+       Mapper Daemon Manual.
+
+   epmd must running on the same host where Kamailio is running. Erlang
+   does not need to be installed on the same host where Kamailio is
+   running.
+
+3. Parameters
+
+   3.1. no_cnodes (int)
+   3.2. cnode_alivename (str)
+   3.3. cnode_host (str)
+   3.4. erlang_nodename (str)
+   3.5. cookie (str)
+   3.6. trace_level (int)
+   3.7. rpc_reply_with_struct (int)
+
+3.1. no_cnodes (int)
+
+   Number of erlang C node processes to be started to handle the
+   communication tasks. A C node is a C program written to act as a hidden
+   node in a distributed Erlang system.
+
+   Default value is 1.
+
+   Example 1.1. Set no_cnodes parameter
+...
+modparam("erlang", "no_cnodes", 2)
+...
+
+3.2. cnode_alivename (str)
+
+   alivename is the registered name of the Kamailio process.
+
+   Note, if the no_cnodes greater then 1, then alivename of Kamailio
+   process will be suffixed with number.
+
+   Example 1.2. Set cnode_alivename parameter
+...
+modparam("erlang", "cnode_alivename", "proxy")
+...
+
+3.3. cnode_host (str)
+
+   C node host is the name of the machine we're running on. If long names
+   are to be used, it should be fully qualified.
+
+   Example 1.3. Set cnode_host parameter
+...
+modparam("erlang", "cnode_host", "kamailio.lan")
+...
+
+3.4. erlang_nodename (str)
+
+   The format of the node name is an name@host where name is the name
+   given by the user and host is the full host name if long names are
+   used, or the first part of the host name if short names are used.
+
+   Example 1.4. Set erlang_nodename parameter
+...
+modparam("erlang", "erlang_nodename", "[email protected]")
+...
+
+3.5. cookie (str)
+
+   Each node has its own magic cookie. When a nodes tries to connect to
+   another node, the magic cookies are compared If they do not match, the
+   connected node rejects the connection.
+
+   Example 1.5. Set cookie parameter
+...
+modparam("erlang", "cookie", "secretcookie")
+...
+
+3.6. trace_level (int)
+
+   Used to set tracing on the distribution. The parameter is different
+   verbosity level. A higher level means more information. Useful in
+   development, but in production should be disabled.
+
+   The different tracelevels has the following messages:
+    1. Verbose error messages
+    2. Above messages and verbose warning messages
+    3. Above messages and progress reports for connection handling
+    4. Above messages and progress reports for communication
+    5. Above messages and progress reports for data conversion
+
+   Default value is 0, no verbose is set. To see trace log on stdout,
+   Kamailio must be started with -E option.
+
+   Example 1.6. Set trace_level parameter
+...
+modparam("erlang", "trace_level", 5)
+...
+
+3.7. rpc_reply_with_struct (int)
+
+   Prepend Erlang atom struct in RPC Kamailio reply where RPC struct is
+   generated. Some libraries for converting erlang term in JSON require
+   atom struct as first element in tuple to convert list of properties
+   into javscript object.
+   Default value is 0 (disabled).
+
+4. Exported pseudo variables
+
+   4.1. Overview
+   4.2. Attributes
+   4.3. $atom(name)
+   4.4. $list(name)
+   4.5. $tuple(name)
+   4.6. $xbuff(name)
+
+4.1. Overview
+
+   Erlang provides a number of data types but not all of them are
+   supported by Kamailio. This module provides several of them as pseudo
+   variables. These pseudo variables allow user to create Erlang message
+   or function arguments and receive result from Erlang node. Some Erlang
+   variables can contain other data types, so we introduced several
+   attributes to determine type, length, or get formatted output useful
+   for debug, quick compare or put into the log.
+
+4.2. Attributes
+
+   Exported pseudo variable has several attributes to get type, length and
+   formatted output readable by transformations or user. Pseudo variable
+   by self determine variable type, but we need to know what type on some
+   position is.
+     * type
+       get variable type. Possible types are: atom, integer, list, string
+       and tuple.
+     * length
+       get length of list or tuple.
+     * format
+       Prints a term, in clear text. It tries to resemble the term
+       printing in the erlang shell.
+
+   Example 1.7. Example of using attribute length
+...
+xlogl("L_DEBUG","The number of elements in list L: $list(L=>length)\n");
+xlogl("L_DEBUG","The number of elements in tuple T: $tuple(T=>length)\n");
+...
+
+> example of log output:
+
+DEBUG: <script>: 120:The number of elements in list L: 4
+DEBUG: <script>: 121:The number of elements in tuple T: 2
+...
+
+   Example 1.8. Example of using attributes type and format
+...
+xlogl("L_DEBUG","list L in clear text: $list(L=>format), the type at index 2: $l
+ist(L[2]=>type)\n");
+xlogl("L_DEBUG","tuple T in clear text: $tuple(T=>format), the type at index 1:
+$tuple[T[1]=>type)\n");
+...
+
+> example of log output:
+
+DEBUG: <script>: 130:list L in clear text: [example, list, 4, "items"], the type
+ at index 2: integer
+DEBUG: <script>: 131:tuple T in clear text: {example, tuple}, the type at index
+1: atom
+...
+
+4.3. $atom(name)
+
+   Atom pseudo variable allows create analog to erlang atom data type.
+   Erlang atom is a literal, a constant with name. Formatted output pseudo
+   variable atom could be enclosed in single quotes (') if it does not
+   begin with a lower-case letter or if it contains other characters than
+   alphanumeric characters, underscore (_), or @.
+
+   Example 1.9. Example set and print to log atom
+...
+$atom(A) = "badrpc";
+xlogl("L_DEBUG","$$atom(A): $atom(A=>format)\n");
+...
+
+> log output is:
+
+DEBUG: <script>: 123:$atom(A): badrpc
+...
+
+4.4. $list(name)
+
+   Compound data type with a variable number of terms. Formally, a list is
+   either the empty list [] or consists of one or more elements.
+
+   Behavior of the list is the same as AVP variable. Set value without
+   index prepends element on list. You are able to replace element at
+   given index. List supports [*] index to get whole list or reset all
+   elements in the list. To create empty list set whole list to $null.
+
+   Example 1.10. Example of using lists
+...
+$atom(E) = "example";
+$list(L) = "list";
+$list(L) = "of";
+$list(L) = $atom(E);
+
+xlogl("L_DEBUG","length(L): $list(L=>length), format(L): $list(L=>format)\n");
+
+# empty list
+$tuple(E[*]) = $null;
+
+xlogl("L_DEBUG","length(E): $list(E=>length), format(E): $list(L=>format)\n");
+...
+
+> log output is:
+
+DEBUG: <script>: 143:length(L): 3, format(L): [example, "of", "list"]
+DEBUG: <script>: 148:length(E): 0, format(E): []
+...
+
+4.5. $tuple(name)
+
+   From the erlang point of view the tuple compound data type with a fixed
+   number of terms. The module implementation of tuple has the same
+   behavior as the list.
+
+   Example 1.11. Example of using tuple
+...
+$atom(line) = "line";
+$atom(id)   = "id";
+$atom(owner)= "owner";
+
+$var(user)  = "Bob";
+
+$tuple(owner) = $var(user);
+$tuple(owner) = $atom(owner);
+
+$tuple(id) = 23;
+$tuple(id) = $atom(id);
+
+$list(L) = $tuple(owner);
+$list(L) = $tuple(id);
+
+$tuple(T) = $list(L);
+$tuple(T) = $atom(line);
+
+xlogl("L_DEBUG","length(T): $tuple(T=>length), format(T): $tuple(T=>format)\n");
+...
+
+> log output is:
+...
+DEBUG: <script>: 153:length(T): 2, format(T): {line, [{id, 23}, {owner, "Bob"}]}
+...
+
+4.6. $xbuff(name)
+
+   xbuff is created as generic pseudo variable to acts as other pseudo
+   variables exported from module. It's useful when in advance we don't
+   know what variable type to expect. The behavior of variable depends of
+   initialization. Module functions expect this PV as return value, and PV
+   for incoming erlang message.
+
+   Example 1.12. Example of using xbuff
+...
+# tuple T from previous example
+
+$xbuff(X) = $tuple(T);
+
+xlogl("L_DEBUG","typeof(X): $xbuff(X=>type), length(X): $xbuff(X=>length), forma
+t(X): $xbuff(X=>format)\n");
+...
+
+> log output is:
+...
+DEBUG: <script>: 410:typeof(X): tuple, length(X): 2, format(X): {line, [{id, 23}
+, {owner, "Bob"}]}
+...
+
+5. Functions
+
+   5.1. erl_rpc(mod,fun,args,reply)
+   5.2. erl_reg_send(server,msg)
+   5.3. erl_reply(msg)
+
+5.1.  erl_rpc(mod,fun,args,reply)
+
+   This function supports calling Erlang functions on remote nodes.
+
+   The parameter mod and fun are module and function name respectively. It
+   can be a static string or a dynamic string value with config variables.
+
+   The parameter args is list of arguments passed to function, so it must
+   be list, or xbuff that contains list.
+
+   The parameter reply is result from RPC call. It must be xbuff to accept
+   any result.
+
+   Function returns false on error to send or wrong arguments passed to
+   function. If executing remote function caused error function still
+   returns true but error is encoded into repl parameter.
+
+   Example 1.13. Example of using erl_rpc
+...
+# example of call erlang:list_to_tuple(["one","two"])
+# on remote node
+
+$list(L) = "two";
+$list(L) = "one";
+
+# put list into list
+$list(args) = $list(L);
+
+erl_rpc("erlang", "list_to_tuple", "$list(args)", "$xbuff(repl)");
+
+xlogl("L_DEBUG","type(repl): $xbuff(repl=>type), format(repl): $xbuff(repl=>form
+at)\n");
+
+> log output:
+...
+DEBUG: <script>: 386:type(repl): tuple, format(repl): {"one", "two"}
+...
+
+5.2. erl_reg_send(server,msg)
+
+   This function sends an Erlang term to a registered process.
+
+   The argument server is the registered name of the intended recipient
+   process on remote node.
+
+   The argument msg is containing the message to be sent.
+
+   Example 1.14. Example of using erl_reg_send
+...
+# example of send message to registered process
+# {notifier,'[email protected]'} ! {example,message}
+
+$atom(example) = "example";
+$atom(message) = "message";
+
+$tuple(M) = $atom(message);
+$tuple(M) = $atom(example);
+
+erl_reg_send("notifier","$tuple(M)");
+...
+
+5.3. erl_reply(msg)
+
+   Function to send message from event route (pseudo process). Function
+   sends reply message msg to the sender process.
+
+   Example 1.15. Example of registered process
+...
+# event route acts as registered process
+event_route[erlang:greetings] {
+
+        xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+        $atom(hello) = "hello";
+        $tuple(reply) = "Erlang";
+        $tuple(reply) = $atom(hello);
+
+        # reply greeting
+        erl_reply("$tuple(reply)");
+}
+...
+
+%% in erlang shell
+
+([email protected])24> {greetings,'[email protected]'} ! {hello,"Kamailio"}.
+{hello,"Kamailio"}
+([email protected])25> flush().
+Shell got {hello,"Erlang"}
+ok
+
+> logged info message:
+INFO: <script>: 951:Received message: {"hello", "Kamailio"}
+>
+
+6. Event routes
+
+   6.1. Registered pseudo process
+   6.2. event_route[erlang:self]
+
+   To allow Kamailio C node to have similar to erlang registered processes
+   we can use event routes. Event routes executed when asynchronous
+   message received from erlang node.
+
+   Event route receive message in $xbuff(msg). Reply variable is optional
+   and can be sent with erl_reply.
+
+6.1. Registered pseudo process
+
+   To create pseudo erlang registered process in Kamailio scrip create
+   event route in form of event_route[erlang:<my_process_name>]. Where
+   <my_process_name> is the name of pseudo process.
+
+   Example 1.16. Example of registered process
+...
+# event route acts as registered process
+event_route[erlang:handler] {
+
+        xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+}
+...
+
+%% in erlang shell send message to registered process
+
+([email protected])12> {handler,'[email protected]'} ! {"hello","Kamailio"}.
+{"hello","Kamailio"}
+
+> logged info message:
+INFO: <script>: 951:Received message: {"hello", "Kamailio"}
+>
+
+6.2. event_route[erlang:self]
+
+   The reserved pseudo process name to receive messages sent to Kamailio C
+   node. The message are sent to non registered process.
+
+   Example 1.17. Example of using default event route
+...
+# default event route from erlang
+event_route[erlang:self] {
+
+        xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+}
+...
+
+%% in erlang shell send message to registered process
+
+([email protected])12> Pid=rpc:call('[email protected]',erlang,whereis,[self]).
+<14808.9.0>
+([email protected])14> Pid ! ["hello from",self()].
+["hello from",<0.247.0>]
+
+> logged info message:
+INFO: <script>: 957:Received message: ["hello from", "<[email protected]>"]
+>
+
+Chapter 2. Using Kamailio from Erlang
+
+   Table of Contents
+
+   1. RPC calls from Erlang
+
+1. RPC calls from Erlang
+
+   This module implements the erlang transport and encoding interface for
+   Kamailio RPCs. It's allow to call any exported RPC in Kamailio from
+   erlang node.
+
+   The string in RPC response is binary encoded to allow from erlang side
+   conversion into appropriate encoding and further transformations.
+
+   Here are some examples:
+
+   Example 2.1. Example of RPC call from erlang shell with no response
+([email protected])26> rpc:call('[email protected]',domain,reload,[]).
+ok
+
+   Example 2.2. Example, check is line registered
+([email protected])29> rpc:call('[email protected]',ul,lookup,["location","1001@
+orange.voip"]).
+[{[{<<"Contact">>,
+    {[{<<"Address">>,
+       <<"sip:[email protected]:5161;transport=udp">>},
+      {<<"Expires">>,96},
+      {<<"Q">>,-1.0},
+      {<<"Call-ID">>,<<"[email protected]">>},
+      {<<"CSeq">>,10335},
+      {<<"User-Agent">>,<<"Grandstream GXP2000 1.2.5.3">>},
+      {<<"Received">>,<<"sip:198.51.100.137:62770">>},
+      {<<"Path">>,
+       <<"&lt;sip:172.16.23.130;lr;received=sip:198.51.100.137:62"...>>},
+      {<<"State">>,<<"CS_SYNC">>},
+      {<<"Flags">>,0},
+      {<<"CFlags">>,64},
+      {<<"Socket">>,<<"udp:172.16.23.130:5070">>},
+      {<<"Methods">>,8159},
+      {<<"Ruid">>,<<"uloc-5534a79b-37ea-8">>},
+      {<<"Instance">>,<<"[not set]">>},
+      {<<"Reg-Id">>,0},
+      {<<"Last-Keepalive">>,1429543986},
+      {<<"Last-Modified">>,1429543906}]}}]}]
+
+   Example 2.3. Example get config variable
+([email protected])26> rpc:call('[email protected]',"cfg","get",["registrar","bi
+ndip"]).
+[<<"172.16.23.130">>]
+
+   Example 2.4. Example get dialog statistics
+([email protected])26>rpc:call('[email protected]',"stats","get_statistics",["di
+alog:"]).
+[<<"dialog:active_dialogs = 3">>,
+ <<"dialog:early_dialogs = 0">>,
+ <<"dialog:expired_dialogs = 0">>,
+ <<"dialog:failed_dialogs = 2">>,
+ <<"dialog:processed_dialogs = 15">>]
+
+Chapter 3. Developer Guide
+
+   Table of Contents
+
+   1. Available Functions
+
+        1.1. erl_load_api(erl_api)
+        1.2. rpc(reply,module,function,args)
+        1.3. reg_send(server,msg)
+        1.4. reply(msg)
+        1.5. xavp2xbuff(xbuff,xavp)
+        1.6. xbuff2xavp(xavp,xbuff)
+
+1. Available Functions
+
+   1.1. erl_load_api(erl_api)
+   1.2. rpc(reply,module,function,args)
+   1.3. reg_send(server,msg)
+   1.4. reply(msg)
+   1.5. xavp2xbuff(xbuff,xavp)
+   1.6. xbuff2xavp(xavp,xbuff)
+
+1.1.  erl_load_api(erl_api)
+
+   Function to be called directly from other modules to load the API. On
+   success return 0, on error 1.
+
+   Meaning of the parameters is as follows:
+     * erl_api_t* erl_api - API bindings structure.
+
+1.2.  rpc(reply,module,function,args)
+
+   This function supports calling Erlang functions on remote nodes. On
+   success function returns 0.
+
+   Meaning of parameters is as follows:
+     * ei_x_buff* reply - dynamic ei buffer where RPC reply returns.
+     * str *module - string containing module name.
+     * str *function - string containing function name.
+     * ei_x_buff *args - dynamic ei buffer with encoded arguments.
+
+1.3.  reg_send(server,msg)
+
+   This function sends an Erlang term to a registered process. On success
+   return 0.
+
+   Meaning of parameters is as follows:
+     * str *server - string containing registered name of the intended
+       recipient process on remote node.
+     * ei_x_buff *msg - dynamic ei buffer with encoded erlang term. The
+       msg must be encoded with version byte.
+
+1.4.  reply(msg)
+
+   Function to send reply on processed message.
+
+   Meaning of parameters is as follows:
+     * ei_x_buff *msg - dynamic ei buffer with encoded erlang term.
+
+1.5.  xavp2xbuff(xbuff,xavp)
+
+   Function encodes XAVP variable into ei dynamic buffer. How to create
+   XAVP variable see source code.
+
+   Meaning of parameters is as follows:
+     * ei_x_buff *xbuff - dynamic ei buffer where XAVP variable will be
+       encoded.
+     * sr_xavp_t *xavp - XAVP variable to be encoded.
+
+1.6.  xbuff2xavp(xavp,xbuff)
+
+   Function decodes ei dynamic buffer into XAVP variable.
+
+   Meaning of parameters is as follows:
+     * sr_xavp_t **xavp - XAVP variable where ei buffer will be decoded.
+     * ei_x_buff *xbuff - dynamic ei buffer.
+
+   Example 3.1. Example of using API
+/* note: new without version byte */
+ei_x_new(&ei_req);
+
+/* ei_x_buff <- XAVP */
+if (erl_api.xavp2xbuff(&ei_req,xreq)) {
+        LM_ERR("failed to encode\n");
+        ei_x_free(&ei_req);
+        return -1;
+}
+
+memset((void*)&ei_rep,0,sizeof(ei_x_buff));
+
+erl_api.rpc(&ei_rep,&module,&function,&ei_req);
+...
+
+/* must be XAVP */
+xrepl->val.type = SR_XTYPE_XAVP;
+
+/* XAVP <- ei_x_buff */
+if (erl_api.xbuff2xavp(&xrepl->val.v.xavp,&ei_rep)){
+        LM_ERR("failed to decode\n");
+        ei_x_free(&ei_req);
+        ei_x_free(&ei_rep);
+        return -1;
+}

+ 514 - 0
modules/erlang/cnode.c

@@ -0,0 +1,514 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../mem/mem.h"
+#define HANDLE_IO_INLINE
+#include "../../io_wait.h"
+#include "../../pt.h"  /* process_count */
+
+#ifndef USE_TCP
+#error	"USE_TCP must be enabled for this module"
+#endif
+
+#include "../../pass_fd.h"
+
+#include "mod_erlang.h"
+#include "erl_helpers.h"
+#include "cnode.h"
+#include "epmd.h"
+#include "worker.h"
+#include "handle_emsg.h"
+
+#define IO_LISTEN_TIMEOUT	10
+#define CONNECT_TIMEOUT	500 /* ms */
+
+static io_wait_h io_h;
+
+cnode_handler_t *enode = NULL;
+
+/**
+ * @brief Initialize Kamailo as C node by active connect as client.
+ */
+int cnode_connect_to(cnode_handler_t *phandler, ei_cnode *ec, const str *nodename )
+{
+	struct sockaddr addr = { 0 };
+	socklen_t addrlen = sizeof(struct sockaddr);
+	int port;
+
+	int rfd;
+	int ai_error;
+	ip_addr_t ip;
+
+	phandler->request.buff = NULL;
+	phandler->response.buff = NULL;
+	phandler->sockfd = -1;
+	phandler->destroy_f = NULL;
+
+	LM_DBG("connecting to Erlang node %.*s ...\n", STR_FMT(nodename));
+
+	if ((rfd = ei_connect_tmo(ec, nodename->s, CONNECT_TIMEOUT)) < 0)
+	{
+		switch (erl_errno)
+		{
+		case EHOSTUNREACH:
+			LM_ERR("remote node %.*s is unreachable.\n", STR_FMT(nodename));
+			break;
+		case ENOMEM:
+			LM_ERR("%s.\n", strerror(erl_errno));
+			break;
+		case EIO:
+			LM_ERR("%s. %s.\n", strerror(erl_errno), strerror(errno));
+			break;
+		default:
+			LM_ERR("%s.\n",strerror(erl_errno));
+			break;
+		}
+		LM_ERR("failed erl_errno=%d %s.\n",erl_errno,strerror(erl_errno));
+		return -1;
+	}
+
+	phandler->ec = *ec;
+	phandler->handle_f = handle_cnode;
+	phandler->wait_tmo_f = wait_cnode_tmo;
+	phandler->destroy_f = destroy_cnode;
+
+	erl_set_nonblock(rfd);
+
+	phandler->sockfd = rfd;
+
+	/* get addr on socket */
+	if ((ai_error = getpeername(rfd, &addr, &addrlen)))
+	{
+		LM_ERR("%s\n",strerror(errno));
+	}
+	else
+	{
+		sockaddr2ip_addr(&ip,&addr);
+		port = sockaddr_port(&addr);
+
+		LM_DBG("... connected to %.*s %s:%u\n", STR_FMT(nodename), ip_addr2strz(&ip), port);
+	}
+
+	strcpy(phandler->conn.nodename, nodename->s);
+
+	/* for #Pid */
+	phandler->ec.self.num = phandler->sockfd;
+	phandler->new = NULL;
+
+	if (ei_x_new(&phandler->request))
+	{
+		LOG(L_ERR,"failed to allocate ei_x_buff: %s.\n", strerror(erl_errno));
+		return -1;
+	}
+
+	if (ei_x_new_with_version(&phandler->response))
+	{
+		LOG(L_ERR,"failed to allocate ei_x_buff: %s.\n", strerror(erl_errno));
+		return -1;
+	}
+
+	enode = phandler;
+	return 0;
+}
+
+int destroy_cnode(handler_common_t *phandler_t)
+{
+	cnode_handler_t *phandler = (cnode_handler_t*)phandler_t;
+
+	if(phandler->request.buff) ei_x_free(&phandler->request);
+	if(phandler->response.buff) ei_x_free(&phandler->response);
+
+	erl_close_socket(phandler->sockfd);
+
+	return 0;
+}
+
+int init_cnode_sockets(int cnode_id)
+{
+	char buff[EI_MAXALIVELEN+1];
+	str alivename;
+	ei_cnode ec;
+	int poll_method;
+	handler_common_t *phandler;
+
+	/* generate cnode name */
+	alivename.s = buff;
+	if (no_cnodes > 1) {
+		alivename.len = snprintf(buff,EI_MAXALIVELEN,"%.*s%d",STR_FMT(&cnode_alivename),cnode_id);
+	} else {
+		alivename = cnode_alivename;
+	}
+
+	if (alivename.len > EI_MAXALIVELEN) {
+		LM_ERR("alive name %.*s too long max allowed size is %d\n",STR_FMT(&cnode_alivename), EI_MAXALIVELEN);
+		return -1;
+	}
+
+	/* init Erlang connection */
+	if (erl_init_ec(&ec,&alivename,&cnode_host,&cookie)) {
+		LM_CRIT("failed to initialize ei_cnode\n");
+		return -1;
+	}
+
+	poll_method=choose_poll_method();
+
+	LM_DBG("using %s as the I/O watch method (auto detected)\n", poll_method_name(poll_method));
+
+	if (init_io_wait(&io_h,get_max_open_fds(),poll_method))
+	{
+		LM_CRIT("init_io_wait failed\n");
+		return -1;
+	}
+
+	phandler = (handler_common_t*)pkg_malloc(sizeof(csockfd_handler_t));
+	if (!phandler) {
+		LM_ERR("not enough memory\n");
+		return -1;
+	}
+
+	io_handler_ins(phandler);
+
+	if (csockfd_init((csockfd_handler_t*)phandler, &ec)) {
+		return -1;
+	}
+
+	if (io_watch_add(&io_h,phandler->sockfd,POLLIN,ERL_CSOCKFD_H,phandler)) {
+		LM_CRIT("io_watch_add failed\n");
+		return -1;
+	}
+
+	phandler = (handler_common_t*)pkg_malloc(sizeof(cnode_handler_t));
+	if (!phandler) {
+		LM_CRIT("not enough memory\n");
+		return -1;
+	}
+
+	io_handler_ins(phandler);
+
+	/* connect to remote Erlang node */
+	if (cnode_connect_to((cnode_handler_t*)phandler,&ec, erlang_nodename.s?&erlang_nodename:&erlang_node_sname)) {
+		/* continue even failed to connect, connection can be established
+		 * from Erlang side too */
+		io_handler_del(phandler);
+	} else if (io_watch_add(&io_h,phandler->sockfd,POLLIN,ERL_CNODE_H,phandler)){
+		LM_CRIT("io_watch_add failed\n");
+		return -1;
+	}
+
+	phandler = (handler_common_t*)pkg_malloc(sizeof(epmd_handler_t));
+	if (!phandler) {
+		LM_CRIT("not enough memory\n");
+		return -1;
+	}
+
+	io_handler_ins(phandler);
+
+	/* start epmd handler - publish Kamailo C node */
+	if (epmd_init((epmd_handler_t*)phandler) < 0 ) {
+		return -1;
+	}
+
+	if (io_watch_add(&io_h,phandler->sockfd,POLLIN,ERL_EPMD_H,phandler)){
+		LM_CRIT("io_watch_add failed\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+void cnode_main_loop(int cnode_id)
+{
+	char _cnode_name[MAXNODELEN];
+
+	if (snprintf(_cnode_name, MAXNODELEN, "%.*s%d@%.*s", STR_FMT(&cnode_alivename), cnode_id+1,STR_FMT(&cnode_host)) >= MAXNODELEN)
+	{
+		LM_CRIT("the node name <%.*s%d@%.*s> is too large max length allowed is %d\n",
+				STR_FMT(&cnode_alivename), cnode_id+1, STR_FMT(&cnode_host), MAXNODELEN-1);
+		return;
+	}
+
+	erl_init_common();
+
+	if (init_cnode_sockets(cnode_id)) {
+		io_handlers_delete();
+		erl_free_common();
+		return;
+	}
+
+	/* main loop */
+	switch(io_h.poll_method){
+		case POLL_POLL:
+			while(1){
+				io_wait_loop_poll(&io_h, IO_LISTEN_TIMEOUT, 0);
+			}
+			break;
+#ifdef HAVE_SELECT
+		case POLL_SELECT:
+			while(1){
+				io_wait_loop_select(&io_h, IO_LISTEN_TIMEOUT, 0);
+			}
+			break;
+#endif
+#ifdef HAVE_SIGIO_RT
+		case POLL_SIGIO_RT:
+			while(1){
+				io_wait_loop_sigio_rt(&io_h, IO_LISTEN_TIMEOUT);
+			}
+			break;
+#endif
+#ifdef HAVE_EPOLL
+			case POLL_EPOLL_LT:
+				while(1){
+					io_wait_loop_epoll(&io_h, IO_LISTEN_TIMEOUT, 0);
+				}
+				break;
+			case POLL_EPOLL_ET:
+				while(1){
+					io_wait_loop_epoll(&io_h, IO_LISTEN_TIMEOUT, 1);
+				}
+				break;
+#endif
+#ifdef HAVE_KQUEUE
+			case POLL_KQUEUE:
+				while(1){
+					io_wait_loop_kqueue(&io_h, IO_LISTEN_TIMEOUT, 0);
+				}
+				break;
+#endif
+#ifdef HAVE_DEVPOLL
+                case POLL_DEVPOLL:
+                	while(1){
+                		io_wait_loop_devpoll(&io_h, IO_LISTEN_TIMEOUT, 0);
+                	}
+                	break;
+#endif
+                default:
+                	LM_CRIT("BUG: io_listen_loop: no support for poll method "
+                			" %s (%d)\n", poll_method_name(io_h.poll_method),
+							io_h.poll_method);
+                	goto error;
+        }
+
+error:
+	LOG(L_CRIT, "ERROR: cnode_main_loop exiting ...\n");
+}
+
+/* generic handle io routine, it will call the appropriate
+ *  handle_xxx() based on the fd_map type
+ *
+ * params:  fm  - pointer to a fd hash entry
+ *          idx - index in the fd_array (or -1 if not known)
+ * return: -1 on error
+ *          0 on EAGAIN or when by some other way it is known that no more
+ *            io events are queued on the fd (the receive buffer is empty).
+ *            Usefull to detect when there are no more io events queued for
+ *            sigio_rt, epoll_et, kqueue.
+ *         >0 on successfully read from the fd (when there might be more io
+ *            queued -- the receive buffer might still be non-empty)
+ */
+int handle_io(struct fd_map* fm, short events, int idx)
+{
+	int type;
+	handler_common_t *phandler;
+
+	phandler = (handler_common_t*)fm->data;
+
+	if (phandler->handle_f(phandler)) {
+		io_watch_del(&io_h,fm->fd,idx,IO_FD_CLOSING);
+		io_handler_del(phandler);
+		if (fm->type == ERL_WORKER_H) {
+			LM_CRIT("error on unix socket, not recoverable error -- aborting\n");
+			abort();
+		}
+		return -1;
+	}
+
+	if (phandler->new) {
+		switch (fm->type) {
+		case ERL_CSOCKFD_H:
+			type = ERL_WORKER_H;
+			break;
+		case ERL_EPMD_H:
+			type = ERL_CNODE_H;
+			break;
+		default:
+			LM_ERR("should not be here!\n");
+			return -1;
+		}
+		LM_DBG("add new handler type=%d\n",type);
+		if (io_watch_add(&io_h,phandler->new->sockfd,POLLIN,type,(void*)phandler->new)) {
+			LM_ERR("failed to add new handler\n");
+			return -1;
+		}
+		io_handler_ins(phandler->new);
+		phandler->new = NULL;
+	}
+
+	return 1;
+}
+
+/**
+ * @brief Handle requests from remote Erlang node.
+ */
+int handle_cnode(handler_common_t *phandler)
+{
+	int got;
+	cnode_handler_t *listener;
+	erlang_msg emsg; /* Incoming message */
+	ei_x_buff *request;
+	ei_x_buff *response;
+	int response_start_index;
+
+	listener = (cnode_handler_t*) phandler;
+	request = &listener->request;
+	response = &listener->response;
+
+	response_start_index = response->index; /* save */
+	request->index = 0;
+
+	listener->tick_tmo = 0; /* reset ERL_TICK timer for all events */
+
+	memset((void*)&emsg,0,sizeof(erlang_msg));
+
+	if ((got = ei_xreceive_msg_tmo(listener->sockfd, &emsg, request,
+			CONNECT_TIMEOUT)) == ERL_TICK)
+	{ /* ignore */
+		LM_DBG("%s received ERL_TICK from <%s>.\n", listener->ec.thisnodename, listener->conn.nodename);
+		return 0;
+	}
+	else if (got == ERL_ERROR)
+	{
+		switch (erl_errno)
+		{
+		case EAGAIN:
+			/* or */
+		case ETIMEDOUT:
+			response->index = response_start_index; /* restore version point */
+			return 0;
+			break;
+		case EIO:
+			LM_ERR("I/O error while receiving message from %s or connection closed.\n", listener->conn.nodename);
+			enode = NULL;
+			return -1;
+			break;
+		case ENOMEM:
+			LM_ERR("Failed to receive message from %s, not enough memory. Closing connection.\n", listener->conn.nodename);
+			enode = NULL;
+			return -1;
+			break;
+		default:
+			LM_ERR("Close connection to %s. %s.\n", listener->conn.nodename, strerror(erl_errno));
+			enode = NULL;
+			return -1;
+		}
+	}
+	else if (got == ERL_MSG)
+	{
+		response->index = response_start_index; /* restore version point */
+		switch (emsg.msgtype)
+		{
+		case ERL_LINK:
+			LM_WARN("ERL_LINK from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_REG_SEND:
+			PRINT_DBG_REG_SEND(listener->conn.nodename,emsg.from, listener->ec.thisnodename, emsg.toname,request);
+			handle_erlang_msg(listener, &emsg);
+			break;
+		case ERL_SEND:
+			PRINT_DBG_SEND(listener->conn.nodename,emsg.to,request);
+			handle_erlang_msg(listener, &emsg);
+			break;
+		case ERL_EXIT:
+			LM_WARN("received ERL_EXIT from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_UNLINK:
+			LM_WARN("ERL_UNLINK from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_NODE_LINK:
+			LM_WARN("ERL_NODE_LINK from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_GROUP_LEADER:
+			LM_WARN("ERL_GROUP_LEADER from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_EXIT2:
+			LM_WARN("ERL_EXIT2 from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		case ERL_PASS_THROUGH:
+			LM_WARN("ERL_PASS_THROUGH from <%s> -- discarding.\n", listener->conn.nodename);
+			break;
+		default:
+			LM_WARN("Received unknown msgtype <%ld> from <%s> -- discarding.\n", emsg.msgtype, listener->conn.nodename);
+			break;
+		}
+	}
+	else
+	{
+		LM_ERR("unknown return value from ei_xreceive_msg_tmo.\n");
+	}
+
+	response->index = response_start_index; /* restore to version point */
+
+	return  0;
+}
+
+int wait_cnode_tmo(handler_common_t *phandler_t)
+{
+	return 0;
+}
+
+int csockfd_init(csockfd_handler_t *phandler, const ei_cnode *ec)
+{
+	phandler->handle_f = handle_csockfd;
+	phandler->wait_tmo_f = NULL;
+	phandler->sockfd = csockfd;
+	phandler->ec = *ec;
+	phandler->new = NULL;
+
+	erl_set_nonblock(csockfd);
+
+	return 0;
+}
+
+int handle_csockfd(handler_common_t *phandler_t)
+{
+	csockfd_handler_t *phandler;
+	int data[2];
+	int fd = -1;
+
+	phandler = (csockfd_handler_t*)phandler_t;
+
+	if (receive_fd(phandler->sockfd,(void*)data,sizeof(data),&fd,0) == -1) {
+		LM_ERR("failed to receive socket: %s\n",strerror(errno));
+		return -1;
+	}
+
+	phandler->new = (handler_common_t*)pkg_malloc(sizeof(worker_handler_t));
+	if(!phandler->new) {
+		LM_ERR("not enough memory\n");
+		return -1;
+	}
+
+	io_handler_ins(phandler->new);
+
+	return worker_init((worker_handler_t*)phandler->new,fd,&phandler_t->ec);
+}

+ 106 - 0
modules/erlang/cnode.h

@@ -0,0 +1,106 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef CNODE_H_
+#define CNODE_H_
+
+#include <ei.h>
+
+/**
+ * Listener for handling requests from connected nodes.
+ */
+typedef struct cnode_handler_s
+{
+	/* d-linked list  */
+	struct handler_common_s *prev;
+	struct handler_common_s *next;
+
+	/* if need to add new in i/o handler */
+	struct handler_common_s *new;
+
+	/*
+	 * this handler is erlang C node server
+	 * handles requests from remote erlang node
+	 */
+	int (*handle_f)(handler_common_t *phandler_t);
+	int (*wait_tmo_f)(handler_common_t *phandler_t);
+	int (*destroy_f)(handler_common_t *phandler_t);
+	int sockfd; /* connection socket to remote erlang node */
+	ei_cnode ec; /* erlang C node (actually it's kamailio node) */
+
+	/*
+	 * connection descriptor
+	 * remote node info
+	 */
+	ErlConnect conn;
+
+	/* request/response buffers */
+	ei_x_buff request;
+	ei_x_buff response;
+
+	/*
+	 * ERL_TICK time out
+	 */
+	int tick_tmo;
+} cnode_handler_t;
+
+/*
+ * active connection to Erlang node
+ */
+extern cnode_handler_t *enode;
+
+typedef struct csockfd_handler_s
+{
+	/* d-linked list  */
+	struct handler_common_s *prev;
+	struct handler_common_s *next;
+	struct handler_common_s *new;
+
+	/* handle receive fd over csockfd */
+	int (*handle_f)(handler_common_t *phandler_t);
+	int (*wait_tmo_f)(handler_common_t *phandler_t);
+	int (*destroy_f)(handler_common_t *phandler_t);
+	int sockfd; /* its csockfd */
+	ei_cnode ec; /* erlang C node (actually it's kamailio node) */
+} csockfd_handler_t;
+
+int init_cnode_sockets(int idx);
+void cnode_main_loop(int idx);
+
+int handle_csockfd(handler_common_t *phandler_t);
+int csockfd_init(csockfd_handler_t *phandler_t, const ei_cnode *ei);
+
+int handle_cnode(handler_common_t *phandler_t);
+int wait_cnode_tmo(handler_common_t *phandler_t);
+int destroy_cnode(handler_common_t *phandler_t);
+
+int cnode_connect_to(cnode_handler_t *phandler, ei_cnode *ec, const str *nodename );
+
+enum erl_handle_type {
+	ERL_EPMD_H = 1,
+	ERL_CNODE_H,
+	ERL_WORKER_H,
+	ERL_CSOCKFD_H
+};
+
+#endif /* CNODE_H_ */

+ 4 - 0
modules/erlang/doc/Makefile

@@ -0,0 +1,4 @@
+docs = erlang.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 38 - 0
modules/erlang/doc/erlang.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+	<bookinfo>
+		<title>ERLANG Module</title>
+		<productname class="trade">sip-router.org</productname>
+		<authorgroup>
+			<author>
+				<firstname>Seudin</firstname>
+				<surname>Kasumovic</surname>
+				<email>[email protected]</email>
+			</author>
+			<editor>
+				<firstname>Seudin</firstname>
+				<surname>Kasumovic</surname>
+				<email>[email protected]</email>
+	    	</editor>
+		</authorgroup>
+		<copyright>
+			<year>2015</year>
+			<holder><ulink url="http://www.bicomsystems.com">Bicom Systems Ltd.</ulink></holder>
+		</copyright>
+	</bookinfo>
+	<toc></toc>
+    
+    <xi:include href="erlang_admin.xml"/>
+    <xi:include href="erlang_use.xml"/>
+    <xi:include href="erlang_devel.xml"/>
+    
+</book>

+ 622 - 0
modules/erlang/doc/erlang_admin.xml

@@ -0,0 +1,622 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+		<title>Overview</title>
+		<para>
+			Erlang is a general-purpose programming language and runtime environment.
+			Erlang has built-in support for concurrency, distribution and fault tolerance.
+			This module provides interact with erlang node. The module allows sending
+			and receiving Erlang messages and RPC calls between each other.
+		</para>
+	</section>
+
+	<section>
+		<title>Dependencies</title>
+		<section>
+			<title>&kamailio; Modules</title>
+			<para>
+				The following modules must be loaded before this module:
+				<itemizedlist mark="none">
+					<listitem>
+						<para>
+							<emphasis>None</emphasis>
+						</para>
+					</listitem>
+				</itemizedlist>
+			</para>
+		</section>
+		<section>
+			<title>External Libraries or Applications</title>
+			<para>
+			The following libraries or applications must be installed before running
+			&kamailio; with this module loaded:
+			<itemizedlist mark="none">
+			<listitem>
+				<para>
+					<emphasis>ei</emphasis> - Erlang interface C library.
+					<para>
+						The routines for handling the erlang binary term format 
+						and communicate with with distributed erlang.
+						For more details see <ulink url="http://www.erlang.org/doc/apps/erl_interface/index.html">Erlang Interface Reference Manual</ulink>
+					</para>
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+					<emphasis>epmd</emphasis> - Erlang Port Mapper Daemon.
+					<para>
+						This daemon acts as a name server on all hosts involved in distributed
+						Erlang computations. For more details see <ulink url="http://www.erlang.org/doc/man/epmd.html">
+						Erlang Port Mapper Daemon Manual</ulink>.
+					</para>
+				</para>
+			</listitem>
+			</itemizedlist>
+			</para>
+			<para>
+				<emphasis>epmd</emphasis> must running on the same host where &kamailio; is running.
+				Erlang does not need to be installed on the same host where &kamailio; is running.
+			</para>
+		</section>
+	</section>
+	<section>
+		<title>Parameters</title>
+		<section id="erlang.p.no_cnodes">
+			<title><varname>no_cnodes</varname> (int)</title>
+			<para>
+				Number of erlang C node processes to be started to handle the communication
+				tasks. A C node is a C program written to act as a hidden node in a distributed Erlang system.
+			</para>
+			<para>
+			<emphasis>
+				Default value is 1.
+			</emphasis>
+			</para>
+			<example>
+				<title>Set <varname>no_cnodes</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "no_cnodes", 2)	
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.p.cnode_alivename">
+			<title><varname>cnode_alivename</varname> (str)</title>
+			<para>
+				<emphasis>alivename</emphasis> is the registered name of the &kamailio; process.
+			</para>
+			<para>
+				<emphasis>Note,</emphasis> if the <varname>no_cnodes</varname> greater then 1,
+				then <emphasis>alivename</emphasis> of &kamailio; process will be suffixed with number.
+			</para>
+			<example>
+				<title>Set <varname>cnode_alivename</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "cnode_alivename", "proxy")
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.p.cnode_host">
+			<title><varname>cnode_host</varname> (str)</title>
+			<para>
+				C node <emphasis>host</emphasis> is the name of the machine we're running on.
+				If long names are to be used, it should be fully qualified.
+			</para>
+			<example>
+				<title>Set <varname>cnode_host</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "cnode_host", "kamailio.lan")
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.p.erlang_nodename">
+			<title><varname>erlang_nodename</varname> (str)</title>
+			<para>
+				The format of the node name is an <emphasis>name@host</emphasis>
+				where name is the name given by the user and host is the full
+				host name if long names are used, or the first part of the host
+				name if short names are used.
+			</para>
+			<example>
+				<title>Set <varname>erlang_nodename</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "erlang_nodename", "[email protected]")
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.p.cookie">
+			<title><varname>cookie</varname> (str)</title>
+			<para>
+				Each node has its own magic cookie. When a nodes tries to connect
+				to another node, the magic cookies are compared If they do not match,
+				the connected node rejects the connection.
+			</para>
+			<example>
+				<title>Set <varname>cookie</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "cookie", "secretcookie")
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.p.trace_level">
+			<title><varname>trace_level</varname> (int)</title>
+			<para>
+				Used to set tracing on the distribution. The parameter is different
+				verbosity level. A higher level means more information. Useful in
+				development, but in production should be disabled.
+			</para>
+			<para>
+				The different tracelevels has the following messages:
+				<itemizedlist>
+					<orderedlist>
+						<listitem>
+							Verbose error messages
+						</listitem>
+						<listitem>
+							Above messages and verbose warning messages
+						</listitem>
+						<listitem>
+							Above messages and progress reports for connection handling
+						</listitem>
+						<listitem>
+							Above messages and progress reports for communication
+						</listitem>
+						<listitem>
+							Above messages and progress reports for data conversion
+						</listitem>
+					</orderedlist>
+				</itemizedlist>
+			</para>
+			<emphasis>
+				Default value is 0, no verbose is set.
+			</emphasis>
+			<emphasis>
+				To see trace log on stdout, &kamailio; must be started with <emphasis>-E</emphasis> option.
+			</emphasis>
+			<example>
+				<title>Set <varname>trace_level</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("erlang", "trace_level", 5)
+...
+				</programlisting>
+			</example>
+		</section>
+		<section>
+			<title><varname>rpc_reply_with_struct</varname> (int)</title>
+			<para>
+				Prepend Erlang atom struct in RPC &kamailio; reply where RPC struct is generated.
+				Some libraries for converting erlang term in JSON require atom struct
+				as first element in tuple to convert list of properties into javscript object.
+			</para>
+			<emphasis>
+				Default value is 0 (disabled).
+			</emphasis>
+		</section>
+	</section>
+	<section>
+		<title>Exported pseudo variables</title>
+		<section>
+			<title>Overview</title>
+			<para>
+				Erlang provides a number of data types but not all of them are supported by &kamailio;.
+				This module provides several of them as pseudo variables. These pseudo variables
+				allow user to create Erlang message or function arguments and receive result from
+				Erlang node. Some Erlang variables can contain other data types, so we introduced several
+				attributes to determine type, length, or get formatted output useful for debug,
+				quick compare or put into the log.
+			</para>
+		</section>
+		<section>
+			<title>Attributes</title>
+			<para>
+				Exported pseudo variable has several attributes to get type, length and formatted output
+				readable by transformations or user. Pseudo variable by self determine variable type, but
+				we need to know what type on some position is. 
+			</para>
+			<itemizedlist>
+				<listitem>
+					<varname>type</varname>
+					<para>
+						get variable type. Possible types are: <emphasis>atom, integer,
+						list, string</emphasis> and <emphasis>tuple</emphasis>.
+					</para>
+				</listitem>
+				<listitem>
+					<varname>length</varname>
+					<para>
+						get length of list or tuple.
+					</para>
+				</listitem>
+				<listitem>
+					<varname>format</varname>
+					<para>
+						Prints a term, in clear text. It tries to resemble the
+						term printing in the erlang shell.
+					</para>
+				</listitem>
+			</itemizedlist>
+			<example>
+				<title>Example of using attribute <emphasis>length</emphasis></title>
+				<programlisting format="linespecific">
+...
+xlogl("L_DEBUG","The number of elements in list L: $list(L=>length)\n");
+xlogl("L_DEBUG","The number of elements in tuple T: $tuple(T=>length)\n");
+...
+
+&gt; example of log output:
+
+DEBUG: &lt;script&gt;: 120:The number of elements in list L: 4
+DEBUG: &lt;script&gt;: 121:The number of elements in tuple T: 2
+...
+				</programlisting>
+			</example>
+			<example>
+				<title>Example of using attributes <emphasis>type</emphasis> and <emphasis>format</emphasis></title>
+				<programlisting format="linespecific">
+...
+xlogl("L_DEBUG","list L in clear text: $list(L=>format), the type at index 2: $list(L[2]=>type)\n");
+xlogl("L_DEBUG","tuple T in clear text: $tuple(T=>format), the type at index 1: $tuple[T[1]=>type)\n");
+...
+
+&gt; example of log output:
+
+DEBUG: &lt;script&gt;: 130:list L in clear text: [example, list, 4, "items"], the type at index 2: integer
+DEBUG: &lt;script&gt;: 131:tuple T in clear text: {example, tuple}, the type at index 1: atom
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.v.atom">
+			<title><varname>$atom(name)</varname></title>
+			<para>
+				Atom pseudo variable allows create analog to erlang atom data type.
+				Erlang atom is a literal, a constant with name. Formatted output pseudo
+				variable atom could be enclosed in single quotes (') if it does 
+				not begin with a lower-case letter or if it contains other characters
+				than alphanumeric characters, underscore (_), or @.
+			</para>
+			<para>
+
+			</para>
+			<example>
+				<title>Example set and print to log atom</title>
+				<programlisting format="linespecific">
+...
+$atom(A) = "badrpc";
+xlogl("L_DEBUG","$$atom(A): $atom(A=>format)\n");
+...
+
+&gt; log output is:
+
+DEBUG: &lt;script&gt;: 123:$atom(A): badrpc
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.v.list">
+			<title><varname>$list(name)</varname></title>
+			<para>
+				Compound data type with a variable number of terms. Formally,
+				a list is either the empty list [] or consists of one or more 
+				elements.
+			</para>
+			<para>
+				Behavior of the list is the same as AVP variable. Set value without
+				index prepends element on list. You are able to replace element at
+				given index. List supports [*] index to get whole list or reset all
+				elements in the list. To create empty list set whole list to
+				<emphasis>$null</emphasis>.
+			</para>
+			<example>
+				<title>Example of using lists</title>
+				<programlisting format="linespecific">
+...
+$atom(E) = "example";
+$list(L) = "list";
+$list(L) = "of";
+$list(L) = $atom(E);
+
+xlogl("L_DEBUG","length(L): $list(L=>length), format(L): $list(L=>format)\n");
+
+# empty list
+$tuple(E[*]) = $null;
+
+xlogl("L_DEBUG","length(E): $list(E=>length), format(E): $list(L=>format)\n");
+...
+
+&gt; log output is:
+
+DEBUG: &lt;script&gt;: 143:length(L): 3, format(L): [example, "of", "list"]
+DEBUG: &lt;script&gt;: 148:length(E): 0, format(E): []
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.v.tuple">
+			<title><varname>$tuple(name)</varname></title>
+			<para>
+				From the erlang point of view the tuple compound data type with
+				a fixed number of terms. The module implementation of tuple has
+				the same behavior as the list.
+			</para>
+			<example>
+				<title>Example of using tuple</title>
+				<programlisting format="linespecific">
+...
+$atom(line) = "line";
+$atom(id)   = "id";
+$atom(owner)= "owner";
+
+$var(user)  = "Bob";
+
+$tuple(owner) = $var(user);
+$tuple(owner) = $atom(owner);
+
+$tuple(id) = 23;
+$tuple(id) = $atom(id);
+
+$list(L) = $tuple(owner);
+$list(L) = $tuple(id);
+
+$tuple(T) = $list(L);
+$tuple(T) = $atom(line);
+
+xlogl("L_DEBUG","length(T): $tuple(T=>length), format(T): $tuple(T=>format)\n");
+...
+
+&gt; log output is:
+...
+DEBUG: &lt;script&gt;: 153:length(T): 2, format(T): {line, [{id, 23}, {owner, "Bob"}]}
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.v.xbuff">
+			<title><varname>$xbuff(name)</varname></title>
+			<para>
+				<emphasis>xbuff</emphasis> is created as generic pseudo variable
+				to acts as other pseudo variables exported from module. It's useful
+				when in advance we don't know what variable type to expect.
+				The behavior of variable depends of initialization. Module
+				functions expect this PV as return value, and PV for incoming
+				erlang message.
+			</para>
+			<example>
+				<title>Example of using xbuff</title>
+				<programlisting format="linespecific">
+...
+# tuple T from previous example
+
+$xbuff(X) = $tuple(T);
+
+xlogl("L_DEBUG","typeof(X): $xbuff(X=>type), length(X): $xbuff(X=>length), format(X): $xbuff(X=>format)\n");
+...
+
+&gt; log output is:
+...
+DEBUG: &lt;script&gt;: 410:typeof(X): tuple, length(X): 2, format(X): {line, [{id, 23}, {owner, "Bob"}]}
+...
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+	<section>
+		<title>Functions</title>
+		<section id="erlang.f.erl_rpc">
+			<title>
+				<function moreinfo="none">erl_rpc(mod,fun,args,reply)</function>
+			</title>
+			<para>
+				This function supports calling Erlang functions on remote nodes.
+			</para>
+			<para>
+				The parameter <emphasis>mod</emphasis> and <emphasis>fun</emphasis>
+				are module and function name respectively. It can be a static 
+				string or a dynamic string value with config variables.
+			</para>
+			<para>
+				The parameter <emphasis>args</emphasis> is list of arguments passed to
+				function, so it must be <emphasis>list</emphasis>, or <emphasis>xbuff</emphasis> that contains list.
+			</para>
+			<para>
+				The parameter <emphasis>reply</emphasis> is result from RPC call. It must
+				be <emphasis>xbuff</emphasis> to accept any result.
+			</para>
+			<para>
+				Function returns false on error to send or wrong arguments passed to function.
+				If executing remote function caused error function still returns
+				true but error is encoded into <emphasis>repl</emphasis> parameter.
+			</para>
+			<example>
+				<title>Example of using erl_rpc</title>
+				<programlisting format="linespecific">
+...
+# example of call erlang:list_to_tuple(["one","two"])
+# on remote node
+
+$list(L) = "two";
+$list(L) = "one";
+
+# put list into list
+$list(args) = $list(L);
+
+erl_rpc("erlang", "list_to_tuple", "$list(args)", "$xbuff(repl)");
+
+xlogl("L_DEBUG","type(repl): $xbuff(repl=>type), format(repl): $xbuff(repl=>format)\n");
+
+&gt; log output:
+...
+DEBUG: &lt;script&gt;: 386:type(repl): tuple, format(repl): {"one", "two"}
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.f.erl_reg_send">
+			<title><function moreinfo="none">erl_reg_send(server,msg)</function></title>
+			<para>
+				This function sends an Erlang term to a registered process.
+			</para>
+			<para>
+				The argument <emphasis>server</emphasis> is the registered name
+				of the intended recipient process on remote node.
+			</para>
+			<para>
+				The argument <emphasis>msg</emphasis> is containing the message to be sent.
+			</para>
+			<example>
+				<title>Example of using erl_reg_send</title>
+				<programlisting format="linespecific">
+...
+# example of send message to registered process
+# {notifier,'[email protected]'} ! {example,message}
+
+$atom(example) = "example";
+$atom(message) = "message";
+
+$tuple(M) = $atom(message);
+$tuple(M) = $atom(example);
+
+erl_reg_send("notifier","$tuple(M)");
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.f.erl_reply">
+			<title><function>erl_reply(msg)</function></title>
+			<para>
+				Function to send message from event route (pseudo process).
+				Function sends reply message <emphasis>msg</emphasis> to the sender process.
+			</para>
+			<example>
+				<title>Example of registered process</title>
+				<programlisting format="linespecific">
+...
+# event route acts as registered process
+event_route[erlang:greetings] {
+
+	xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+	$atom(hello) = "hello";
+	$tuple(reply) = "Erlang";
+	$tuple(reply) = $atom(hello);
+
+	# reply greeting
+	erl_reply("$tuple(reply)");
+}
+...
+
+%% in erlang shell
+
+([email protected])24&gt; {greetings,'[email protected]'} ! {hello,"Kamailio"}.
+{hello,"Kamailio"}
+([email protected])25&gt; flush().
+Shell got {hello,"Erlang"}
+ok
+
+&gt; logged info message:
+INFO: &lt;script&gt;: 951:Received message: {"hello", "Kamailio"}
+&gt;
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+	<section>
+		<title>Event routes</title>
+		<para>
+			To allow &kamailio; C node to have similar to erlang registered processes
+			we can use event routes. Event routes executed when asynchronous message received
+			from erlang node.
+		</para>
+		<para>
+			Event route receive message in <varname>$xbuff(msg)</varname>. Reply
+			variable is optional and can be sent with <emphasis>erl_reply</emphasis>.
+		</para>
+		<section>
+			<title>Registered pseudo process</title>
+			<para>
+				To create pseudo erlang registered process in &kamailio; scrip create event route in form of
+				<varname>event_route[erlang:<emphasis>&lt;my_process_name&gt;</emphasis>]</varname>. Where
+				<emphasis>&lt;my_process_name&gt;</emphasis> is the name of pseudo process.
+			</para>
+			<example>
+				<title>Example of registered process</title>
+				<programlisting format="linespecific">
+...
+# event route acts as registered process
+event_route[erlang:handler] {
+
+	xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+}
+...
+
+%% in erlang shell send message to registered process
+
+([email protected])12> {handler,'[email protected]'} ! {"hello","Kamailio"}.
+{"hello","Kamailio"}
+
+&gt; logged info message:
+INFO: &lt;script&gt;: 951:Received message: {"hello", "Kamailio"}
+&gt;
+				</programlisting>
+			</example>
+		</section>
+		<section id="erlang.e.self">
+			<title><varname>event_route[erlang:self]</varname></title>
+			<para>
+				The reserved pseudo process name to receive messages sent to &kamailio; C node.
+				The message are sent to non registered process.
+			</para>
+			<example>
+				<title>Example of using default event route</title>
+				<programlisting format="linespecific">
+...
+# default event route from erlang
+event_route[erlang:self] {
+
+	xlogl("L_INFO","Received message: $xbuff(msg=>format)\n");
+
+}
+...
+
+%% in erlang shell send message to registered process
+
+([email protected])12> Pid=rpc:call('[email protected]',erlang,whereis,[self]).
+&lt;14808.9.0&gt;
+([email protected])14> Pid ! ["hello from",self()].
+["hello from",&lt;0.247.0&gt;]
+
+&gt; logged info message:
+INFO: &lt;script&gt;: 957:Received message: ["hello from", "&lt;[email protected]>"]
+&gt;
+				</programlisting>
+			</example>
+		</section>
+	</section>
+</chapter>
+

+ 159 - 0
modules/erlang/doc/erlang_devel.xml

@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module Developer's Guide -->
+
+<chapter>
+	<title>&develguide;</title>
+	<section>
+		<title>Available Functions</title>
+		<section>
+			<title>
+				<function moreinfo="none">erl_load_api(erl_api)</function>
+			</title>
+			<para>
+				Function to be called directly from other modules to load the API.
+				On success return 0, on error 1.
+			</para>
+			<para>Meaning of the parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>erl_api_t* erl_api</emphasis> - API bindings structure.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<section>
+			<title>
+				<function moreinfo="none">rpc(reply,module,function,args)</function>
+			</title>
+			<para>
+				This function supports calling Erlang functions on remote nodes. On success function returns 0.
+			</para>
+			<para>Meaning of parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>ei_x_buff* reply</emphasis> - dynamic ei buffer where RPC reply returns.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>str *module</emphasis> - string containing module name.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>str *function</emphasis> - string containing function name.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>ei_x_buff *args</emphasis> - dynamic ei buffer with encoded arguments.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<section>
+			<title>
+				<function moreinfo="none">reg_send(server,msg)</function>
+			</title>
+			<para>
+				This function sends an Erlang term to a registered process. On success return 0.
+			</para>
+			<para>Meaning of parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>str *server</emphasis> - string containing
+					registered name of the intended recipient process on remote node.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>ei_x_buff *msg</emphasis> - dynamic ei buffer
+					with encoded erlang term. The msg must be encoded with version byte.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<section>
+			<title>
+				<function moreinfo="none">reply(msg)</function>
+			</title>
+			<para>
+				Function to send reply on processed message.
+			</para>
+			<para>Meaning of parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>ei_x_buff *msg</emphasis> - dynamic ei buffer
+					with encoded erlang term.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<section>
+			<title>
+				<function moreinfo="none">xavp2xbuff(xbuff,xavp)</function>
+			</title>
+			<para>
+				Function encodes XAVP variable into ei dynamic buffer. How to create XAVP variable
+				see source code.
+			</para>
+			<para>Meaning of parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>ei_x_buff *xbuff</emphasis> - dynamic ei buffer
+					where XAVP variable will be encoded.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>sr_xavp_t *xavp</emphasis> - XAVP variable to be encoded.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<section>
+			<title>
+				<function moreinfo="none">xbuff2xavp(xavp,xbuff)</function>
+			</title>
+			<para>
+				Function decodes ei dynamic buffer into XAVP variable.
+			</para>
+			<para>Meaning of parameters is as follows:</para>
+			<itemizedlist mark="none">
+				<listitem>
+					<para><emphasis>sr_xavp_t **xavp</emphasis> - XAVP variable where ei buffer will be decoded.</para>
+				</listitem>
+				<listitem>
+					<para><emphasis>ei_x_buff *xbuff</emphasis> - dynamic ei buffer.</para>
+				</listitem>
+			</itemizedlist>
+		</section>
+		<example>
+				<title>Example of using API</title>
+				<programlisting format="linespecific" language="C">
+<![CDATA[
+
+/* note: new without version byte */
+ei_x_new(&ei_req);
+
+/* ei_x_buff <- XAVP */
+if (erl_api.xavp2xbuff(&ei_req,xreq)) {
+	LM_ERR("failed to encode\n");
+	ei_x_free(&ei_req);
+	return -1;
+}
+
+memset((void*)&ei_rep,0,sizeof(ei_x_buff));
+
+erl_api.rpc(&ei_rep,&module,&function,&ei_req);
+...
+
+/* must be XAVP */
+xrepl->val.type = SR_XTYPE_XAVP;
+
+/* XAVP <- ei_x_buff */
+if (erl_api.xbuff2xavp(&xrepl->val.v.xavp,&ei_rep)){
+	LM_ERR("failed to decode\n");
+	ei_x_free(&ei_req);
+	ei_x_free(&ei_rep);
+	return -1;
+}
+
+]]>
+				</programlisting>
+			</example>
+	</section>
+</chapter>

+ 95 - 0
modules/erlang/doc/erlang_use.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!--  -->
+
+<chapter>
+	<title>Using &kamailio; from Erlang</title>
+	<section>
+		<title>RPC calls from Erlang</title>
+		<para>
+			This module implements the erlang transport and encoding interface for &kamailio; RPCs. It's allow to call any exported RPC in &kamailio;
+			from erlang node.
+		</para>
+		<para>
+			The string in RPC response is binary encoded to allow from erlang side conversion into appropriate encoding and further transformations.
+		</para>
+		<para>
+			Here are some examples:
+		</para>
+		<example>
+			<title>Example of RPC call from erlang shell with no response</title>
+			<programlisting format="linespecific">
+<![CDATA[
+
+([email protected])26> rpc:call('[email protected]',domain,reload,[]).
+ok
+
+]]>
+			</programlisting>
+		</example>
+		<example>
+			<title>Example, check is line registered</title>
+			<programlisting format="linespecific">
+<![CDATA[
+
+([email protected])29> rpc:call('[email protected]',ul,lookup,["location","[email protected]"]).
+[{[{<<"Contact">>,
+    {[{<<"Address">>,
+       <<"sip:[email protected]:5161;transport=udp">>},
+      {<<"Expires">>,96},
+      {<<"Q">>,-1.0},
+      {<<"Call-ID">>,<<"[email protected]">>},
+      {<<"CSeq">>,10335},
+      {<<"User-Agent">>,<<"Grandstream GXP2000 1.2.5.3">>},
+      {<<"Received">>,<<"sip:198.51.100.137:62770">>},
+      {<<"Path">>,
+       <<"&lt;sip:172.16.23.130;lr;received=sip:198.51.100.137:62"...>>},
+      {<<"State">>,<<"CS_SYNC">>},
+      {<<"Flags">>,0},
+      {<<"CFlags">>,64},
+      {<<"Socket">>,<<"udp:172.16.23.130:5070">>},
+      {<<"Methods">>,8159},
+      {<<"Ruid">>,<<"uloc-5534a79b-37ea-8">>},
+      {<<"Instance">>,<<"[not set]">>},
+      {<<"Reg-Id">>,0},
+      {<<"Last-Keepalive">>,1429543986},
+      {<<"Last-Modified">>,1429543906}]}}]}]
+
+]]>
+			</programlisting>
+		</example>
+		<example>
+			<title>Example get config variable</title>
+			<programlisting format="linespecific">
+<![CDATA[
+
+([email protected])26> rpc:call('[email protected]',"cfg","get",["registrar","bindip"]).
+[<<"172.16.23.130">>]
+
+]]>
+			</programlisting>
+		</example>
+		<example>
+			<title>Example get dialog statistics</title>
+			<programlisting format="linespecific">
+<![CDATA[
+
+([email protected])26>rpc:call('[email protected]',"stats","get_statistics",["dialog:"]).
+[<<"dialog:active_dialogs = 3">>,
+ <<"dialog:early_dialogs = 0">>,
+ <<"dialog:expired_dialogs = 0">>,
+ <<"dialog:failed_dialogs = 2">>,
+ <<"dialog:processed_dialogs = 15">>]
+
+]]>
+			</programlisting>
+		</example>
+	</section>
+</chapter>

+ 150 - 0
modules/erlang/epmd.c

@@ -0,0 +1,150 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <ei.h>
+#include <netinet/ip.h> /*IPTOS_LOWDELAY*/
+
+#include "../../dprint.h"
+#include "../../ip_addr.h"
+
+#include "mod_erlang.h"
+#include "erl_helpers.h"
+#include "epmd.h"
+#include "cnode.h"
+
+#ifndef CONNECT_TIMEOUT
+#define CONNECT_TIMEOUT 500 /* ms */
+#endif
+
+/**
+ * \brief Initialize EPMD handler
+ */
+int epmd_init(epmd_handler_t *epmd)
+{
+	epmd->sockfd = erl_init_node(&epmd->ec, &cnode_alivename, &cnode_host, &cookie);
+	epmd->handle_f = handle_epmd;
+	epmd->wait_tmo_f = NULL;
+	epmd->destroy_f  = NULL;
+	epmd->new        = NULL;
+
+	return epmd->sockfd;
+}
+
+/**
+ * \brief Handle connections from epmd.
+ */
+int handle_epmd(handler_common_t *phandler)
+{
+	struct ip_addr ip;
+	struct sockaddr addr = { 0 };
+	struct sockaddr *paddr;
+	socklen_t addrlen = sizeof(struct sockaddr);
+	int port;
+	int ai_error;
+	int sockfd;
+	int on;
+
+	epmd_handler_t *me;
+	cnode_handler_t *remotenode = NULL;
+	ErlConnect conn;
+
+	paddr = &addr;
+	me = (epmd_handler_t*) phandler;
+
+	if ((sockfd = ei_accept_tmo(&me->ec, me->sockfd, &conn, CONNECT_TIMEOUT))
+			== ERL_ERROR) {
+		LM_ERR("error on accept connection: %s.\n", strerror(erl_errno));
+
+		if (erl_errno == ETIMEDOUT) {
+			return 0;
+		} else if (errno) {
+			LM_ERR("socket error: %s\n", strerror(errno));
+			return -1;
+		} else {
+			/* if errno didn't get set, assume nothing *too much* horrible occurred */
+			LM_NOTICE("ignored error in ei_accept, probably: bad client version, "
+					"bad cookie or bad node name\n");
+		}
+		return 0;
+	}
+
+	/* get remote address info of connected socket */
+	if ((ai_error = getpeername(sockfd, paddr, &addrlen)))
+	{
+		LM_ERR("%s\n",strerror(errno));
+	}
+	else
+	{
+		sockaddr2ip_addr(&ip,paddr);
+		port = sockaddr_port(paddr);
+
+		LM_DBG("connected from %s port %u\n", ip_addr2strz(&ip), port);
+	}
+
+	remotenode = (cnode_handler_t*)pkg_malloc(sizeof(cnode_handler_t));
+	if (!remotenode) {
+		erl_close_socket(sockfd);
+		return -1;
+	}
+
+	/* tos */
+	on=IPTOS_LOWDELAY;
+	if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, (void*)&on,sizeof(on)) ==-1) {
+			LM_WARN("setsockopt set tos failed: %s\n", strerror(errno));
+			/* continue since this is not critical */
+	}
+
+	if (erl_set_nonblock(sockfd)){
+		LM_ERR("set non blocking socket failed\n");
+	}
+
+	memset((void*)remotenode,0,sizeof(cnode_handler_t));
+
+	remotenode->handle_f = handle_cnode;
+	remotenode->wait_tmo_f = wait_cnode_tmo;
+	remotenode->destroy_f = destroy_cnode;
+	remotenode->sockfd = sockfd;
+	remotenode->ec = me->ec;
+	remotenode->conn = conn;
+	/* for #Pid */
+	remotenode->ec.self.num = sockfd;
+
+	if (ei_x_new(&remotenode->request))
+	{
+		LOG(L_ERR,"failed to allocate ei_x_buff: %s.\n", strerror(erl_errno));
+		return -1;
+	}
+
+	if (ei_x_new_with_version(&remotenode->response))
+	{
+		LOG(L_ERR,"failed to allocate ei_x_buff: %s.\n", strerror(erl_errno));
+		return -1;
+	}
+
+	phandler->new = (handler_common_t*)remotenode;
+
+	/* activate node */
+	enode = remotenode;
+
+	return 0;
+}

+ 61 - 0
modules/erlang/epmd.h

@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef EPMD_H_
+#define EPMD_H_
+
+#include <ei.h>
+
+/*
+ * @brief Handler for epmd connections.
+ */
+typedef struct epmd_handler_s
+{
+	/* d-linked list  */
+	struct handler_common_s *prev;
+	struct handler_common_s *next;
+
+	/* if need to add new in i/o handler */
+	struct handler_common_s *new;
+
+	/*
+	 * Handle connections from local epmd daemon.
+	 *
+	 * returns:
+	 *  0 - ok to continue
+	 * -1 - epmd lost (we die regards we can't recovery)
+	 * sockfd - socket of new accepted connection to be added into
+	 * poll
+	 */
+	int (*handle_f)(handler_common_t *handler);
+	int (*wait_tmo_f)(handler_common_t *handler);
+	int (*destroy_f)(handler_common_t *handler);
+	int sockfd; /* socket to epmd */
+	ei_cnode ec; /* erlang C node (actually it's me) */
+
+} epmd_handler_t;
+
+int epmd_init(epmd_handler_t *epmd);
+int handle_epmd(handler_common_t *phandler);
+
+#endif /* EPMD_H_ */

+ 258 - 0
modules/erlang/erl_api.c

@@ -0,0 +1,258 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "mod_erlang.h"
+#include "erl_helpers.h"
+#include "erl_api.h"
+#include "pv_xbuff.h"
+#include "cnode.h"
+
+#include <sys/socket.h>
+#include <ei.h>
+
+int _impl_api_rpc_call(ei_x_buff* reply, const str *module,const str *function, const ei_x_buff *args);
+int _impl_reg_send(const str *server, const ei_x_buff *msg);
+int _impl_reply(const ei_x_buff *msg);
+int xavp2xbuff(ei_x_buff *xbuff, sr_xavp_t *xavp);
+int xbuff2xavp(sr_xavp_t **xavp, ei_x_buff *xbuff);
+
+/*!
+* \brief Function exported by module - it will load the other functions
+ * \param erl_api Erlang API export binding
+ * \return 1
+ */
+int load_erl( erl_api_t *erl_api )
+{
+	erl_api->rpc = _impl_api_rpc_call;
+	erl_api->reg_send = _impl_reg_send;
+	erl_api->reply = _impl_reply;
+	erl_api->xavp2xbuff = xavp2xbuff;
+	erl_api->xbuff2xavp = xbuff2xavp;
+
+	return 1;
+}
+
+/**
+ * API implementation
+ */
+int xavp2xbuff(ei_x_buff *xbuff, sr_xavp_t *xavp)
+{
+	return xavp_encode(xbuff,xavp,0);
+}
+
+
+int xbuff2xavp(sr_xavp_t **xavp, ei_x_buff *xbuff)
+{
+	int i=0, version=0;
+	if (ei_decode_version(xbuff->buff,&i,&version))
+	{
+		LM_DBG("no version byte encoded in reply\n");
+	}
+
+	return xavp_decode(xbuff,&i,xavp,0);
+}
+
+int _impl_api_rpc_call(ei_x_buff *reply, const str *module,const str *function, const ei_x_buff *args)
+{
+	struct msghdr msgh;
+	struct iovec cnt[8];
+	int pid_no = my_pid();
+	eapi_t api = API_RPC_CALL;
+	int buffsz=0;
+	int rc;
+
+	memset(&msgh, 0, sizeof(msgh));
+	memset(&cnt, 0, sizeof(cnt));
+
+	/* Kamailio PID */
+	cnt[0].iov_base = (void*)&pid_no;
+	cnt[0].iov_len  = sizeof(pid_no);
+
+	/* method */
+	cnt[1].iov_base = (void*)&api;
+	cnt[1].iov_len = sizeof(api);
+
+	/* put size of following data */
+	cnt[2].iov_base = (void*)&module->len;
+	cnt[2].iov_len  = sizeof(int);
+
+	cnt[3].iov_base = (void*)&function->len;
+	cnt[3].iov_len  = sizeof(int);
+
+	buffsz = args->index; /* occupied size */
+	cnt[4].iov_base = (void*) &buffsz;
+	cnt[4].iov_len = sizeof(buffsz);
+
+	/* module name */
+	cnt[5].iov_base = (void*)module->s;
+	cnt[5].iov_len  = module->len;
+
+	/* function name */
+	cnt[6].iov_base = (void*)function->s;
+	cnt[6].iov_len  = function->len;
+
+	/* Erlang arguments content */
+	cnt[7].iov_base = (void*)args->buff;
+	cnt[7].iov_len = buffsz; /* occupied size */
+
+	msgh.msg_iov = cnt;
+	msgh.msg_iovlen = 8;
+
+	while ((rc = sendmsg(csockfd, &msgh, 0)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1) {
+		LM_ERR("sendmsg failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	/*receive into reply buffer */
+	cnt[1].iov_base = &buffsz;
+	cnt[1].iov_len  = sizeof(buffsz);
+
+	/* peek reply size safe */
+	msgh.msg_iovlen = 2;
+	while ((rc = recvmsg(csockfd, &msgh, MSG_PEEK)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1) {
+		LM_ERR("recvmsg failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	if (reply->buffsz < buffsz) {
+		ei_x_free(reply);
+		reply->buffsz = buffsz + 1;
+		reply->buff = (char*)malloc(reply->buffsz);
+	}
+
+	cnt[2].iov_base = (void*)reply->buff;
+	cnt[2].iov_len  = buffsz;
+
+	msgh.msg_iovlen = 3;
+	while ((rc = recvmsg(csockfd, &msgh, MSG_WAITALL)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1) {
+		LM_ERR("recvmsg failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	if(pid_no != my_pid()) {
+		/* should never happened */
+		LM_CRIT("BUG: got other process reply (pid_no=%d)\n",pid_no);
+		return -1;
+	}
+
+	return 0;
+}
+
+int _impl_reg_send(const str *server, const ei_x_buff *msg)
+{
+	struct msghdr msgh;
+	struct iovec cnt[6];
+	int pid_no = my_pid();
+	eapi_t api = API_REG_SEND;
+	int buffsz;
+	int rc;
+	int i=0,version;
+
+	memset(&msgh, 0, sizeof(msgh));
+	memset(&cnt, 0, sizeof(cnt));
+
+	if (ei_decode_version(msg->buff,&i,&version)) {
+		LM_ERR("msg must be encoded with version\n");
+		return -1;
+	}
+
+	/* Kamailio PID */
+	cnt[0].iov_base = (void*)&pid_no;
+	cnt[0].iov_len  = sizeof(pid_no);
+
+	/* method */
+	cnt[1].iov_base = (void*)&api;
+	cnt[1].iov_len = sizeof(api);
+
+	/* put size of following data */
+	cnt[2].iov_base = (void*)&server->len;
+	cnt[2].iov_len  = sizeof(int);
+
+	buffsz = msg->index; /* occupied size */
+	cnt[3].iov_base = (void*)&buffsz;
+	cnt[3].iov_len = sizeof(buffsz);
+
+	/* module name */
+	cnt[4].iov_base = (void*)server->s;
+	cnt[4].iov_len  = server->len;
+
+	/* Erlang arguments content */
+	cnt[5].iov_base = (void*)msg->buff;
+	cnt[5].iov_len = buffsz; /* occupied size */
+
+	msgh.msg_iov = cnt;
+	msgh.msg_iovlen = 6;
+
+	while ((rc = sendmsg(csockfd, &msgh, 0)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1) {
+		LM_ERR("sendmsg failed: %s\n",strerror(errno));
+		return -1;
+	}
+
+	/* no reply */
+
+	return 0;
+}
+
+int _impl_reply(const ei_x_buff *msg)
+{
+	int i=0,version;
+
+	if (ei_decode_version(msg->buff,&i,&version)) {
+		LM_ERR("msg must be encoded with version\n");
+		return -1;
+	}
+
+	/* must be in call back / event route */
+
+	if (!enode) {
+		LM_ERR("not in callback\n");
+		return -1;
+	}
+	/* copy into reply */
+	if (enode->response.buffsz < msg->buffsz) {
+		/* realocate */
+		enode->response.buff=realloc(enode->response.buff,msg->buffsz);
+		if (!enode->response.buff) {
+			LM_ERR("realloc failed: not enough memory\n");
+			return -1;
+		}
+		enode->response.buffsz = msg->buffsz;
+	}
+
+	memcpy((void*)enode->response.buff,(void*)msg->buff,msg->buffsz);
+	enode->response.index = msg->index;
+
+	return 0;
+}

+ 92 - 0
modules/erlang/erl_api.h

@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef ERL_API_H_
+#define ERL_API_H_
+
+#include "../../sr_module.h"
+#include "../../str.h"
+#include "../../xavp.h"
+#include <ei.h>
+
+typedef int (*erl_rpc_f)(ei_x_buff* reply, const str *module,const str *function, const ei_x_buff *args);
+typedef int (*erl_reg_send_f)(const str *server,const ei_x_buff *msg);
+typedef int (*erl_reply_f)(const ei_x_buff *msg);
+
+/* data serialization */
+typedef int (*xavp2xbuff_f)(ei_x_buff *xbuff, sr_xavp_t *xavp);
+typedef int (*xbuff2xavp_f)(sr_xavp_t **xavp, ei_x_buff *xbuff);
+
+typedef struct erl_api_s {
+	erl_rpc_f rpc;
+	erl_reg_send_f reg_send;
+	erl_reply_f reply;
+	xavp2xbuff_f xavp2xbuff;
+	xbuff2xavp_f xbuff2xavp;
+} erl_api_t;
+
+typedef  int (*load_erl_f)( erl_api_t* );
+
+/*!
+* \brief API bind function exported by the module - it will load the other functions
+ * \param erl_api Erlang API export binding
+ * \return 1
+ */
+int load_erl( erl_api_t *erl_api );
+
+/*!
+ * \brief Function to be called directly from other modules to load the Erlang API
+ * \param erl_api Erlang API export binding
+ * \return 0 on success, -1 if the API loader could not imported
+ */
+inline static int erl_load_api( erl_api_t *erl_api )
+{
+        load_erl_f load_erl_v;
+
+        /* import the Erlang API auto-loading function */
+        if ( !(load_erl_v=(load_erl_f)find_export("load_erl", 0, 0))) {
+                LM_ERR("failed to import load_erl\n");
+                return -1;
+        }
+        /* let the auto-loading function load all Erlang stuff */
+        load_erl_v( erl_api );
+
+        return 0;
+}
+
+/**
+ * debugging macro
+ * uses LM_DBG to print ei_x_buff
+ */
+#define EI_X_BUFF_PRINT(buf) \
+do{ \
+	char *mbuf = NULL; \
+	int i = 0, v=0; \
+	ei_decode_version((buf)->buff,&i,&v);\
+	i=v?i:0; \
+	ei_s_print_term(&mbuf, (buf)->buff, &i); \
+	LM_DBG(#buf": %s\n", mbuf); \
+	free(mbuf); \
+} while(0)
+
+#endif /* ERL_API_H_ */

+ 401 - 0
modules/erlang/erl_helpers.c

@@ -0,0 +1,401 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "erl_helpers.h"
+#include "mod_erlang.h"
+
+#include "../../resolve.h"
+#include "../../ip_addr.h"
+#include "../../str.h"
+
+#include <netinet/ip.h> /*IPTOS_LOWDELAY*/
+
+/* fall back if Kamailio host name is not given */
+char thishostname[512] = {0};
+struct sockaddr_in *thisaddr = NULL;
+
+int erl_set_nonblock(int sockfd)
+{
+	int flags;
+
+	flags = fcntl(sockfd, F_GETFD);
+
+	if (flags == -1) {
+		LM_ERR("socket %d read settings error: %s\n",sockfd,strerror(errno));
+	} else if (fcntl(sockfd, F_SETFD, flags|O_NONBLOCK) == -1) {
+		LM_ERR("socket %d set O_NONBLOCK failed: %s\n",sockfd,strerror(errno));
+	} else {
+		return 0;
+	}
+
+	return -1;
+}
+
+void erl_close_socket(int sockfd)
+{
+	if (sockfd > 0) {
+		shutdown(sockfd, SHUT_RDWR);
+		close(sockfd);
+	}
+}
+
+/** \brief allocate & bind a server socket using TCP.
+ *
+ * 	Allocate & bind a server socket using TCP.
+ *
+ */
+int erl_passive_socket(const char *hostname, int qlen,
+		struct addrinfo **ai_ret)
+{
+	int sockfd; /* socket descriptor and socket type	*/
+	int on = 1;
+	int error_num = 0;
+	int port;
+	struct addrinfo *ai;
+	struct addrinfo hints;
+	struct ip_addr ip;
+	socklen_t addrlen = sizeof(struct sockaddr);
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+
+	hints.ai_family = AF_UNSPEC;
+	/* = AF_INET;  IPv4 address family */
+	/* = AF_UNSPEC; Allow IPv4 or IPv6 */
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_V4MAPPED;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	if ((error_num = getaddrinfo(hostname, 0 /* unused */, &hints, &ai)))
+	{
+		LM_CRIT("failed to resolve %s: %s\n", hostname, gai_strerror(error_num));
+		return -1;
+	}
+
+	/* Allocate a socket */
+	sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+	if (sockfd < 0)
+	{
+		LM_CRIT("failed to create socket. %s.\n", strerror(errno));
+		freeaddrinfo(ai);
+		return -1;
+	}
+
+	/* initialize TCP */
+#if  !defined(TCP_DONT_REUSEADDR)
+	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int))) {
+		LM_ERR("failed to enable SO_REUSEADDR for socket on %s %s, %s\n",
+				hostname, ip_addr2strz(&ip), strerror(errno));
+	}
+#endif
+	/* tos */
+	on=IPTOS_LOWDELAY;
+	if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, (void*)&on,sizeof(on)) ==-1) {
+			LM_WARN("setsockopt tos: %s\n", strerror(errno));
+			/* continue since this is not critical */
+	}
+
+	/* Bind the socket */
+	if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)
+	{
+		port=sockaddr_port(ai->ai_addr);
+		LM_CRIT("failed to bind socket on %s %s:%u. %s.\n", hostname,
+				ip_addr2strz(&ip), port, strerror(errno));
+
+		erl_close_socket(sockfd);
+		freeaddrinfo(ai);
+		return -1;
+	}
+
+	if (ai->ai_socktype == SOCK_STREAM && listen(sockfd, qlen) < 0)
+	{
+		LM_CRIT("failed to listen socket on %s, %s. %s.\n", hostname,
+				ip_addr2strz(&ip), strerror(errno));
+
+		erl_close_socket(sockfd);
+		freeaddrinfo(ai);
+
+		return -1;
+	}
+
+	/* get addr on socket */
+	if (getsockname(sockfd, ai->ai_addr, &addrlen)){
+		LM_ERR("getsockname failed: %s\n",strerror(errno));
+	}
+
+	if (ai_ret && *ai_ret == NULL)
+	{
+		*ai_ret = ai;
+	}
+	else if (ai_ret)
+	{
+		freeaddrinfo(*ai_ret);
+		*ai_ret = ai;
+	}
+	else
+	{
+		freeaddrinfo(ai);
+	}
+
+	return sockfd;
+}
+
+/** @brief allocate active socket using TCP.
+ *
+ * 	Allocate active client socket using TCP.
+ *
+ */
+int erl_active_socket(const char *hostname, int qlen, struct addrinfo **ai_ret)
+{
+	int error_num = 0;
+	struct addrinfo *ai;
+	struct addrinfo hints;
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+
+	hints.ai_family = AF_UNSPEC;
+	/* = AF_INET;  IPv4 address family */
+	/* = AF_UNSPEC; Allow IPv4 or IPv6 */
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_V4MAPPED;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	if ((error_num = getaddrinfo(hostname, 0 /* unused */, &hints, &ai)))
+	{
+		LM_CRIT("failed to resolve %s: %s\n", hostname, gai_strerror(error_num));
+		return -1;
+	}
+
+	if (ai_ret && *ai_ret == NULL)
+	{
+		*ai_ret = ai;
+	}
+	else if (ai_ret)
+	{
+		freeaddrinfo(*ai_ret);
+		*ai_ret = ai;
+	}
+	else
+	{
+		freeaddrinfo(ai);
+	}
+
+	return 0;
+}
+
+/*
+ * Call before any erl_interface library function.
+ */
+void erl_init_common()
+{
+	/* distribution trace level */
+	if (trace_level)
+	{
+		ei_set_tracelevel(trace_level);
+	}
+}
+
+/*
+ * Free allocated memory for common patterns.
+ */
+void erl_free_common()
+{
+}
+
+/**
+ * @brief Initialize Erlang connection
+ */
+int erl_init_ec(ei_cnode *ec, const str *alivename, const str *hostname, const str *cookie)
+{
+	/* identifies a specific instance of a C node. */
+	short creation = getpid();
+	struct addrinfo *ai = NULL;
+	struct sockaddr *addr = NULL;
+	ip_addr_t ip;
+
+	char nodename[MAXNODELEN];
+
+	int result;
+	int port;
+
+	/* copy the nodename into something we can modify */
+	if (snprintf(nodename, MAXNODELEN, "%.*s@%.*s", STR_FMT(alivename), STR_FMT(hostname)) >= MAXNODELEN) {
+		LM_CRIT("the node name %.*s@%.*s is too large max length allowed is %d\n", STR_FMT(alivename), STR_FMT(hostname), MAXNODELEN-1);
+		return -1;
+	}
+
+	if (erl_active_socket(hostname->s, 128, &ai)) {
+		return -1;
+	}
+
+	addr = (struct sockaddr*) ai->ai_addr;
+	sockaddr2ip_addr(&ip,addr);
+
+	if ((result = ei_connect_xinit(ec, hostname->s, alivename->s, nodename, (Erl_IpAddr) &(((struct sockaddr_in*)ai->ai_addr)->sin_addr), cookie->s, creation)) < 0) {
+
+		LM_CRIT("failed to initialize self as cnode name %s\n", nodename);
+		return -1;
+	}
+
+	port = sockaddr_port(addr);
+
+	LM_DBG("initialized ec for cnode '%s' on %.*s[%s] creation %d.\n", nodename, STR_FMT(hostname), ip_addr2strz(&ip), creation);
+
+	freeaddrinfo(ai);
+	return 0;
+}
+/**
+ * \brief Initialize C node server and returns listen socket.
+ */
+int erl_init_node(ei_cnode *ec, const str *alivename, const str *hostname, const str *cookie)
+{
+	/* identifies a specific instance of a C node. */
+	short creation = getpid();
+	struct addrinfo *ai = NULL;
+	struct sockaddr *addr = NULL;
+	ip_addr_t ip;
+
+	char nodename[MAXNODELEN];
+
+	int result;
+	int listen_fd;
+	int port;
+
+	unsigned timeout_ms = CONNECT_TIMEOUT;
+	int epmdfd;
+
+	/* copy the nodename into something we can modify */
+	if (snprintf(nodename, MAXNODELEN, "%.*s@%.*s", STR_FMT(alivename), STR_FMT(hostname)) >= MAXNODELEN) {
+		LM_CRIT("the node name %.*s@%.*s is too large max length allowed is %d\n", STR_FMT(alivename), STR_FMT(hostname), MAXNODELEN-1);
+		return -1;
+	}
+
+	if ((listen_fd = erl_passive_socket(hostname->s, 128, &ai)) == -1) {
+		return -1;
+	}
+
+	/* use first ip address only, it's internal Erlang connection to empd */
+	addr = (struct sockaddr*) ai->ai_addr;
+
+	if ((result = ei_connect_xinit(ec, hostname->s, alivename->s, nodename, (Erl_IpAddr) &(((struct sockaddr_in*)ai->ai_addr)->sin_addr), cookie->s, creation)) < 0) {
+
+		LM_CRIT("failed to initialize self as node %s\n", nodename);
+		erl_close_socket(listen_fd);
+		return -1;
+	}
+
+	port = sockaddr_port(addr);
+	sockaddr2ip_addr(&ip, addr);
+
+	/* publish */
+	if ((epmdfd = ei_publish_tmo(ec, port, timeout_ms)) == -1) {
+
+		LM_ERR("Failed to publish port %u to epmd, check is epmd started\n", port);
+
+		erl_close_socket(listen_fd);
+		return -1;
+	} else {
+		LM_DBG("listen on %s:%u[%u]/[%d] as %s\n",ip_addr2strz(&ip),port,listen_fd,epmdfd,nodename);
+	}
+
+	freeaddrinfo(ai);
+	return listen_fd;
+}
+
+void io_handler_ins(handler_common_t* phandler)
+{
+	if (io_handlers) {
+		io_handlers->prev = phandler;
+		phandler->next = io_handlers;
+	} else {
+		phandler->next = NULL;
+	}
+
+	phandler->prev = NULL;
+	io_handlers = phandler;
+}
+
+void io_handler_del(handler_common_t* phandler)
+{
+	handler_common_t* p = phandler;
+
+	if (p == io_handlers) {
+		io_handlers = phandler->next;
+	} else {
+		phandler->prev->next = phandler->next;
+	}
+
+	if(phandler->destroy_f) phandler->destroy_f(phandler);
+
+	pkg_free((void*)phandler);
+}
+
+void io_handlers_delete()
+{
+	handler_common_t* p;
+
+	while(io_handlers){
+		p = io_handlers;
+		io_handlers = io_handlers->next;
+		pkg_free((void*)p);
+	}
+}
+
+/**
+ * Decode string/binary into destination buffer.
+ *
+ */
+int ei_decode_strorbin(char *buf, int *index, int maxlen, char *dst)
+{
+	int type, size, res;
+	long len;
+
+	ei_get_type(buf, index, &type, &size);
+
+	if (type == ERL_NIL_EXT || size == 0)
+	{
+		dst[0] = '\0';
+		return 0;
+	}
+
+	if (type != ERL_STRING_EXT && type != ERL_BINARY_EXT)
+	{
+		return -1;
+	}
+	else if (size > maxlen)
+	{
+		LM_ERR("buffer size %d too small for %s with size %d\n",
+				maxlen, type == ERL_BINARY_EXT ? "binary" : "string", size);
+		return -1;
+	}
+	else if (type == ERL_BINARY_EXT)
+	{
+		res = ei_decode_binary(buf, index, dst, &len);
+		dst[len] = '\0';
+	}
+	else
+	{
+		res = ei_decode_string(buf, index, dst);
+	}
+
+	return res;
+}

+ 153 - 0
modules/erlang/erl_helpers.h

@@ -0,0 +1,153 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef ERL_HELPERS_H_
+#define ERL_HELPERS_H_
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <ei.h>
+
+#include "../../str.h"
+#include "../../dprint.h"
+
+#define CONNECT_TIMEOUT	500 /* ms */
+#define sockaddr_port(addr)	((addr)->sa_family == AF_INET ? ntohs(((struct sockaddr_in *)(addr))->sin_port) : ntohs(((struct sockaddr_in6 *)(addr))->sin6_port))
+
+#define EI_X_BUFF_NULL	{0, 0, 0}
+
+extern char thishostname[512];
+
+/* common members of listener */
+typedef struct handler_common_s
+{
+	/* d-linked list  */
+	struct handler_common_s *prev;
+	struct handler_common_s *next;
+
+	/* if need to add new in i/o handler */
+	struct handler_common_s *new;
+
+	/* listener function */
+	int (*handle_f)(struct handler_common_s *phandler_t);
+	int (*wait_tmo_f)(struct handler_common_s *phandler_t);
+	int (*destroy_f)(struct handler_common_s *phandler_t);
+	int sockfd;
+	ei_cnode ec; /* erlang C node descriptor */
+
+} handler_common_t;
+
+extern handler_common_t* io_handlers;
+
+typedef struct erlang_ref_ex_s {
+	erlang_ref ref;
+	char nodename[MAXATOMLEN];
+	int with_node;
+} erlang_ref_ex_t;
+
+void erl_init_common();
+void erl_free_common();
+
+int erl_set_nonblock(int sockfd);
+void erl_close_socket(int sockfd);
+
+int erl_passive_socket(const char *hostname, int qlen, struct addrinfo **ai_ret);
+int erl_active_socket(const char *hostname, int qlen, struct addrinfo **ai_ret);
+
+int erl_init_ec(ei_cnode *ec, const str *alivename, const str *hostname, const str *cookie);
+int erl_init_node(ei_cnode *ec, const str *alivename, const str *hostname, const str *cookie);
+
+void io_handler_ins(handler_common_t* phandler);
+void io_handler_del(handler_common_t* phandler);
+void io_handlers_delete();
+
+#define PRINT_DBG_REG_SEND(node,from,cnode,to,msg) \
+		do { \
+			char *mbuf = NULL; \
+			char *pbuf = NULL; \
+			ei_x_buff xpid = {0,0,0}; \
+			int i=0,v=0; \
+			ei_decode_version((msg)->buff,&i,&v);\
+			i=v?i:0; \
+			ei_s_print_term(&mbuf, (msg)->buff, &i); \
+			ei_x_encode_pid(&xpid,&from); \
+			i = 0; \
+			ei_s_print_term(&pbuf,xpid.buff,&i); \
+			LM_DBG("ERL_REG_SEND: {%s,'%s'} <- %s from {%s,'%s'}\n", to, cnode, mbuf, pbuf, node); \
+			free(mbuf), free(pbuf), ei_x_free(&xpid); \
+		} while(0)
+
+#define PRINT_DBG_SEND(node,to,msg) \
+		do { \
+			char *mbuf = NULL; \
+			char *pbuf = NULL; \
+			ei_x_buff xpid = {0,0,0}; \
+			int i=0,v=0; \
+			ei_decode_version((msg)->buff,&i,&v);\
+			i=v?i:0; \
+			ei_s_print_term(&mbuf, (msg)->buff, &i); \
+			ei_x_encode_pid(&xpid,&to); \
+			i = 0; \
+			ei_s_print_term(&pbuf,xpid.buff,&i); \
+			LM_DBG("ERL_SEND: %s <- %s from '%s'\n", pbuf, mbuf, node); \
+			free(mbuf), free(pbuf), ei_x_free(&xpid); \
+		} while(0)
+
+#define ei_x_print_reg_msg(buf, dest, send) \
+do{ \
+	char *mbuf = NULL; \
+	int i = 1; \
+	ei_s_print_term(&mbuf, (buf)->buff, &i); \
+	if (send) LM_DBG("sending %s to %s\n", mbuf, dest); \
+	else LM_DBG("received %s from/for %s\n", mbuf, dest); \
+	free(mbuf); \
+} while(0)
+
+#define ei_x_print_msg(buf, pid, send) \
+do { \
+	char *pbuf = NULL; \
+	int i = 0; \
+	ei_x_buff pidbuf; \
+	ei_x_new(&pidbuf); \
+	ei_x_encode_pid(&pidbuf, pid); \
+	ei_s_print_term(&pbuf, pidbuf.buff, &i); \
+	ei_x_print_reg_msg(buf, pbuf, send); \
+	free(pbuf); \
+} while(0)
+
+int ei_decode_strorbin(char *buf, int *index, int maxlen, char *dst);
+
+typedef enum {
+	API_RPC_CALL,
+	API_REG_SEND
+} eapi_t;
+
+#endif /* ERL_HELPERS_H_ */

+ 783 - 0
modules/erlang/handle_emsg.c

@@ -0,0 +1,783 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <ei.h>
+
+#include "handle_emsg.h"
+#include "handle_rpc.h"
+#include "erl_helpers.h"
+#include "cnode.h"
+#include "pv_xbuff.h"
+#include "mod_erlang.h"
+
+#include "../../dprint.h"
+#include "../../sr_module.h"
+#include "../../cfg/cfg_struct.h"
+#include "../../lib/kcore/faked_msg.h"
+
+int handle_msg_req_tuple(cnode_handler_t *phandler, erlang_msg * msg);
+int handle_req_ref_tuple(cnode_handler_t *phandler, erlang_msg * msg);
+int handle_rpc_response(cnode_handler_t *phandler, erlang_msg * msg, int arity);
+int handle_rex_call(cnode_handler_t *phandler,erlang_ref_ex_t *ref, erlang_pid *pid);
+int handle_net_kernel(cnode_handler_t *phandler, erlang_msg * msg);
+void encode_error_msg(ei_x_buff *response, erlang_ref_ex_t *ref, const char *type, const char *msg );
+
+int handle_reg_send(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	int rt, backup_rt;
+	struct run_act_ctx ctx;
+	sip_msg_t *fmsg;
+	sip_msg_t tmsg;
+	char *route;
+	size_t sz;
+	ei_x_buff *request = &phandler->request;
+	sr_xavp_t *xreq = NULL;
+	str msg_name = str_init("msg");
+
+	sz = sizeof("erlang")+strlen(msg->toname)+2;
+	route = (char*)pkg_malloc(sz);
+	if (!route) {
+		LM_ERR("not enough memory");
+		return -1;
+	}
+
+	snprintf(route,sz,"erlang:%s",msg->toname);
+
+	rt = route_get(&event_rt, route);
+	if (rt < 0 || event_rt.rlist[rt] == NULL) {
+		LM_WARN("ERL_REG_SEND message to unknown process %s\n", route);
+		pkg_free(route);
+		PRINT_DBG_REG_SEND(phandler->conn.nodename, msg->from, phandler->ec.thisnodename, msg->toname,request);
+		return 0;
+	}
+
+	LM_DBG("executing registered process %s\n", route);
+
+	fmsg = faked_msg_next();
+	memcpy(&tmsg, fmsg, sizeof(sip_msg_t));
+	fmsg = &tmsg;
+
+	if ((xreq = pv_xbuff_get_xbuff(&msg_name))) {
+		LM_DBG("free previous value\n");
+		xavp_destroy_list(&xreq->val.v.xavp);
+	} else {
+		xreq = xbuff_new(&msg_name);
+	}
+
+	if (!xreq) {
+		LM_ERR("failed to create $xbuff(msg) variable\n");
+		goto err;
+	}
+
+	/* decode request into $xbuff(msg) */
+	xreq->val.type = SR_XTYPE_XAVP;
+
+	/* XAVP <- ei_x_buff */
+	if (erl_api.xbuff2xavp(&xreq->val.v.xavp,request)){
+		LM_ERR("failed to decode message\n");
+		goto err;
+	}
+
+	backup_rt = get_route_type();
+	set_route_type(EVENT_ROUTE);
+	init_run_actions_ctx(&ctx);
+	run_top_route(event_rt.rlist[rt], fmsg, &ctx);
+	set_route_type(backup_rt);
+
+	pkg_free(route);
+	free_xbuff_fmt_buff();
+	xavp_destroy_list(xavp_get_crt_list());
+	return 0;
+
+err:
+	pkg_free(route);
+	free_xbuff_fmt_buff();
+	xavp_destroy_list(xavp_get_crt_list());
+	return -1;
+}
+
+int handle_send(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	int rt, backup_rt;
+	struct run_act_ctx ctx;
+	sip_msg_t *fmsg;
+	sip_msg_t tmsg;
+	char route[]="erlang:self";
+	ei_x_buff *request = &phandler->request;
+	sr_xavp_t *xreq = NULL;
+	str msg_name = str_init("msg");
+
+	rt = route_get(&event_rt, route);
+	if (rt < 0 || event_rt.rlist[rt] == NULL) {
+		LM_WARN("ERL_SEND message not handled, missing event route %s\n", route);
+		PRINT_DBG_REG_SEND(phandler->conn.nodename, msg->from, phandler->ec.thisnodename, msg->toname,request);
+		return 0;
+	}
+
+	LM_DBG("executing self process %s\n", route);
+
+	fmsg = faked_msg_next();
+	memcpy(&tmsg, fmsg, sizeof(sip_msg_t));
+	fmsg = &tmsg;
+
+	if ((xreq = pv_xbuff_get_xbuff(&msg_name))) {
+		LM_DBG("free previous value\n");
+		xavp_destroy_list(&xreq->val.v.xavp);
+	} else {
+		xreq = xbuff_new(&msg_name);
+	}
+
+	if (!xreq) {
+		LM_ERR("failed to create $xbuff(msg) variable\n");
+		goto err;
+	}
+
+	/* decode request into $xbuff(msg) */
+	xreq->val.type = SR_XTYPE_XAVP;
+
+	/* XAVP <- ei_x_buff */
+	if (erl_api.xbuff2xavp(&xreq->val.v.xavp,request)){
+		LM_ERR("failed to decode message\n");
+		goto err;
+	}
+
+	backup_rt = get_route_type();
+	set_route_type(EVENT_ROUTE);
+	init_run_actions_ctx(&ctx);
+	run_top_route(event_rt.rlist[rt], fmsg, &ctx);
+	set_route_type(backup_rt);
+
+	free_xbuff_fmt_buff();
+	xavp_destroy_list(xavp_get_crt_list());
+	return 0;
+
+err:
+	free_xbuff_fmt_buff();
+	xavp_destroy_list(xavp_get_crt_list());
+	return -1;
+}
+
+int handle_req_ref_tuple(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	erlang_ref ref;
+	erlang_pid pid;
+	int arity;
+	ei_x_buff *request = &phandler->request;
+	ei_x_buff *response = &phandler->response;
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (ei_decode_ref(request->buff, &request->index, &ref))
+	{
+		LM_WARN("Invalid reference.\n");
+		return -1;
+	}
+
+	if (ei_decode_pid(request->buff, &request->index, &pid))
+	{
+		LM_ERR("Invalid pid in a reference/pid tuple\n");
+		return -1;
+	}
+
+	if (0)
+	{
+		ei_x_encode_atom(response, "ok");
+	}
+	else
+	{
+		ei_x_encode_tuple_header(response, 2);
+		ei_x_encode_atom(response, "error");
+		ei_x_encode_atom(response, "not_found");
+	}
+
+	return -1;
+}
+
+/* catch the response to ei_rpc_to (which comes back as {rex, {Ref, Pid}}
+ The {Ref,Pid} bit can be handled by handle_ref_tuple
+ */
+int handle_rpc_response(cnode_handler_t *phandler, erlang_msg * msg, int arity)
+{
+	int type, size, arity2, tmpindex;
+	ei_x_buff *request = &phandler->request;
+
+	ei_get_type(request->buff, &request->index, &type, &size);
+	switch (type)
+	{
+	case ERL_SMALL_TUPLE_EXT:
+	case ERL_LARGE_TUPLE_EXT:
+		tmpindex = request->index;
+		ei_decode_tuple_header(request->buff, &tmpindex, &arity2);
+		return handle_req_ref_tuple(phandler, msg);
+	default:
+		LM_ERR("Unknown RPC response.\n");
+		break;
+	}
+	/* no reply */
+	return -1;
+}
+
+int handle_msg_req_tuple(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	char tupletag[MAXATOMLEN];
+	int arity;
+	int ret = 0;
+	ei_x_buff *request = &phandler->request;
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+	if (ei_decode_atom(request->buff, &request->index, tupletag))
+	{
+		LM_ERR("error: badarg\n");
+	}
+	else
+	{
+		if (!strncmp(tupletag, "rex", MAXATOMLEN))
+		{
+			ret = handle_rpc_response(phandler, msg, arity);
+		}
+		else
+		{
+			LM_ERR("error: undef\n");
+		}
+	}
+	return ret;
+}
+
+/* respond on net_adm:ping
+ * e.g. message:
+ *
+ * {'$gen_call', {<[email protected]>, #Ref<194674.122.0>}, {is_auth, '[email protected]'}} for net_kernel
+ */
+int handle_net_kernel(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	int version, size, type, arity;
+	char atom[MAXATOMLEN];
+	erlang_ref ref;
+	erlang_pid pid;
+	ei_x_buff *request = &phandler->request;
+	ei_x_buff *response = &phandler->response;
+
+	/* start from first arg */
+	request->index = 0;
+	ei_decode_version(request->buff, &request->index, &version);
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT && type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		return -1;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 3)
+	{
+		LM_ERR("wrong arity\n");
+		return -1;
+	}
+
+	if (ei_decode_atom(request->buff, &request->index, atom) || strncmp(atom,
+			"$gen_call", MAXATOMLEN))
+	{
+		LM_ERR("not atom '$gen_call'\n");
+		return -1;
+	}
+
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT && type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		return -1;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 2)
+	{
+		LM_ERR("wrong arity\n");
+		return -1;
+	}
+
+	if (ei_decode_pid(request->buff, &request->index, &pid)
+			|| ei_decode_ref(request->buff, &request->index, &ref))
+	{
+		LM_ERR("decoding pid and ref error\n");
+		return -1;
+	}
+
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		return -1;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 2)
+	{
+		LM_ERR("bad arity\n");
+		return -1;
+	}
+
+	if (ei_decode_atom(request->buff, &request->index, atom) || strncmp(atom,
+			"is_auth", MAXATOMLEN))
+	{
+		LM_ERR("not is_auth\n");
+		return -1;
+	}
+
+	/* To ! {Tag, Reply} */
+	ei_x_encode_tuple_header(response, 2);
+	ei_x_encode_ref(response, &ref);
+	ei_x_encode_atom(response, "yes");
+
+	ei_x_print_msg(response, &pid, 1);
+
+	ei_send_tmo(phandler->sockfd, &pid, response->buff, response->index, CONNECT_TIMEOUT);
+
+	return -1;
+}
+
+int erlang_whereis(cnode_handler_t *phandler,erlang_ref_ex_t *ref, erlang_pid *pid)
+{
+	ei_x_buff *response = &phandler->response;
+
+	ei_x_encode_pid(response,&phandler->ec.self);
+
+	return 0;
+}
+
+static int handle_erlang_calls(cnode_handler_t *phandler,erlang_ref_ex_t *ref, erlang_pid *pid, const char *method)
+{
+	ei_x_buff *response = &phandler->response;
+
+	if (strcmp(method,"whereis")==0)
+	{
+		return erlang_whereis(phandler,ref,pid);
+	}
+	else {
+		encode_error_msg(response, ref, "badrpc", "Method Not Found");
+	}
+
+	return 0;
+}
+
+/* handle rex calls
+ *
+ * example:
+ *
+ * {call, tbe, dlg_bye, [123, 456], <[email protected]>}
+ *
+ */
+int handle_rex_call(cnode_handler_t *phandler,erlang_ref_ex_t *ref, erlang_pid *pid)
+{
+	char module[MAXATOMLEN];
+	char method[MAXATOMLEN];
+	char proc[2*MAXATOMLEN];
+	erl_rpc_ctx_t ctx;
+	rpc_export_t* exp;
+	int arity;
+	ei_x_buff *request = &phandler->request;
+	ei_x_buff *response = &phandler->response;
+	int size, type;
+
+	/* already decoded {call,
+	 * continue with
+	 * module,method...}
+	 */
+
+	ei_get_type(request->buff,&request->index,&type,&size);
+
+	if (type == ERL_ATOM_EXT || type == ERL_SMALL_ATOM_EXT)
+	{
+		if (ei_decode_atom(request->buff,&request->index,module))
+		{
+			encode_error_msg(response, ref, "error", "Failed to decode module name");
+			return 0;
+		}
+	}
+	else if (ei_decode_strorbin(request->buff, &request->index, MAXATOMLEN, module))
+	{
+		encode_error_msg(response, ref, "error", "Failed to decode module name");
+		return 0;
+	}
+
+	ei_get_type(request->buff,&request->index,&type,&size);
+
+	if (type == ERL_ATOM_EXT || type == ERL_SMALL_ATOM_EXT)
+	{
+		if (ei_decode_atom(request->buff,&request->index,method))
+		{
+			encode_error_msg(response, ref, "error", "Failed to decode method name");
+			return 0;
+		}
+	}
+	else if (ei_decode_strorbin(request->buff, &request->index, MAXATOMLEN, method))
+	{
+		encode_error_msg(response, ref, "error", "Failed to decode method name");
+		return 0;
+	}
+
+	if (strcmp(module,"erlang") == 0)
+	{
+		/* start encoding */
+		ei_x_encode_tuple_header(response, 2);
+		if (ref->with_node)
+		{
+			ei_x_encode_tuple_header(response,2);
+			ei_x_encode_ref(response, &ref->ref);
+			ei_x_encode_atom(response,ref->nodename);
+		}
+		else {
+			ei_x_encode_ref(response, &ref->ref);
+		}
+
+		return handle_erlang_calls(phandler,ref,pid,method);
+	}
+
+	/* be up to date with cfg */
+	cfg_update();
+
+	sprintf(proc,"%s.%s",module,method);
+
+	exp=find_rpc_export(proc,0);
+
+	if (!exp || !exp->function)
+	{
+		encode_error_msg(response, ref, "badrpc", "Method Not Found");
+
+		return 0;
+	}
+
+	ei_get_type(request->buff,&request->index,&type,&size);
+
+	/* open list for decoding */
+	if (ei_decode_list_header(request->buff,&request->index,&arity))
+	{
+		LOG(L_ERR, "Expected list of parameters type=<%c> arity=<%d>\n", type, size);
+		encode_error_msg(response, ref, "badarith", "Expected list of parameters.");
+		return 0;
+	}
+
+	/* start encoding */
+	ei_x_encode_tuple_header(response, 2);
+	if (ref->with_node)
+	{
+		ei_x_encode_tuple_header(response,2);
+		ei_x_encode_ref(response, &ref->ref);
+		ei_x_encode_atom(response,ref->nodename);
+	}
+	else {
+		ei_x_encode_ref(response, &ref->ref);
+	}
+
+	/* init context */
+	ctx.phandler = phandler;
+	ctx.pid = pid;
+	ctx.ref = ref;
+	ctx.response_sent = 0;
+	ctx.request = request;
+	ctx.request_index = request->index;
+	ctx.response = response;
+	ctx.reply_params = 0;
+	ctx.tail = 0;
+	ctx.fault = 0;
+	ctx.fault_p = &ctx.fault;
+	ctx.optional = 0;
+	ctx.no_params = 0;
+	ctx.response_index = response->index;
+	ctx.size = arity;
+
+	/* call rpc */
+	exp->function(&erl_rpc_func_param,(void*)&ctx);
+
+	if (ctx.no_params)
+	{
+		ei_x_encode_list_header(response,ctx.no_params);
+	}
+
+	if (erl_rpc_send(&ctx, 0))
+	{
+		response->index = ctx.response_index;
+		ei_x_encode_atom(response, "error");
+		ei_x_encode_tuple_header(response,2);
+		ei_x_encode_string(response, "Inernal Error: Failed to encode reply");
+	}
+	else
+	{
+		ei_x_encode_empty_list(response);
+	}
+
+	empty_recycle_bin();
+
+	/* we sent response so it's false for up calls */
+	return 0;
+}
+
+/* {'$gen_call', {<[email protected]>, #Ref<62147.65.0>}, {call, tbe, dlg_bye, [123, 456], <[email protected]>}} for rex */
+int handle_rex_msg(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	int version, size, type, arity;
+	char atom[MAXATOMLEN];
+	erlang_ref_ex_t ref;
+	erlang_pid pid;
+	ei_x_buff *request = &phandler->request;
+	ei_x_buff *response = &phandler->response;
+
+	/* start from first arg */
+	request->index = 0;
+	ei_decode_version(request->buff, &request->index, &version);
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT && type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		return -1;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 3)
+	{
+		LM_ERR("wrong arity %d\n", arity);
+		return -1;
+	}
+
+	if (ei_decode_atom(request->buff, &request->index, atom) || strncmp(atom,
+			"$gen_call", MAXATOMLEN))
+	{
+		LM_ERR("not $gen_call\n");
+		return -1;
+	}
+
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT && type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		goto err;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 2)
+	{
+		LM_ERR("wrong arity\n");
+		goto err;
+	}
+
+	if (ei_decode_pid(request->buff, &request->index, &pid) )
+	{
+		LM_ERR("decoding pid error\n");
+		goto err2;
+	}
+
+	ref.with_node = 0;
+	/* we can got with host accompanied {#Ref<18224.110.0>, '[email protected]'} */
+	ei_get_type(request->buff, &request->index, &type, &size);
+	if (type == ERL_REFERENCE_EXT || type == ERL_NEW_REFERENCE_EXT)
+	{
+		if (ei_decode_ref(request->buff, &request->index, &ref.ref))
+		{
+			LM_ERR("decoding ref error\n");
+			goto err2;
+		}
+	}
+	else if (type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple type {#Ref<x.y.z>, '[email protected]'} %c\n", type);
+		goto err;
+	}
+	else {
+		if (ei_decode_tuple_header(request->buff, &request->index, &arity) || arity !=2)
+			goto err2;
+
+		if (ei_decode_ref(request->buff, &request->index, &ref.ref))
+		{
+			LM_ERR("decoding ref error\n");
+			goto err2;
+		}
+
+		if (ei_decode_atom(request->buff, &request->index, ref.nodename))
+		{
+			LM_ERR("decoding node in ref error\n");
+			goto err2;
+		}
+		ref.with_node = 1;
+	}
+
+	ei_get_type(request->buff, &request->index, &type, &size);
+
+	if (type != ERL_SMALL_TUPLE_EXT)
+	{
+		LM_ERR("not a tuple\n");
+		goto err;
+	}
+
+	ei_decode_tuple_header(request->buff, &request->index, &arity);
+
+	if (arity != 5)
+	{
+		LM_ERR("bad arity %d\n", arity);
+		goto err;
+	}
+
+	if (ei_decode_atom(request->buff, &request->index, atom) == 0 && strncmp(atom,
+			"call", MAXATOMLEN) == 0)
+	{
+		return handle_rex_call(phandler, &ref, &pid);
+	}
+
+err:
+	/* To ! {Tag, Reply} */
+	ei_x_encode_tuple_header(response, 2);
+
+	if (ref.with_node)
+	{
+		ei_x_encode_tuple_header(response,2);
+		ei_x_encode_ref(response, &ref.ref);
+		ei_x_encode_atom(response,ref.nodename);
+	}
+	else {
+		ei_x_encode_ref(response, &ref.ref);
+	}
+
+	ei_x_encode_tuple_header(response,2);
+	ei_x_encode_atom(response, "badrpc");
+	ei_x_encode_string(response, "Unsupported rex request.");
+
+	ei_x_print_msg(response, &pid, 1);
+
+	ei_send_tmo(phandler->sockfd, &pid, response->buff, response->index, CONNECT_TIMEOUT);
+err2:
+	return -1;
+}
+
+
+int handle_erlang_msg(cnode_handler_t *phandler, erlang_msg * msg)
+{
+	int type, type2, size, version, arity, tmpindex;
+	int ret = 0;
+	ei_x_buff * request = &phandler->request;
+	ei_x_buff * response = &phandler->response;
+	erlang_pid from;
+
+	if (msg->msgtype == ERL_REG_SEND )
+	{
+		if (!strncmp(msg->toname, "net_kernel",MAXATOMLEN)) {
+			/* respond to ping stuff */
+			ret = handle_net_kernel(phandler, msg);
+		} else if (!strncmp(msg->toname, "rex",MAXATOMLEN)) {
+			/* respond to rex stuff */
+			ret = handle_rex_msg(phandler, msg);
+		} else {
+			/* try registered process */
+			handle_reg_send(phandler,msg);
+		}
+		from = msg->from;
+	} else {
+		/* TODO: fix below after adding #Pid and #Ref in PVs */
+		request->index = 0;
+		ei_decode_version(request->buff, &request->index, &version);
+		ei_get_type(request->buff, &request->index, &type, &size);
+
+		switch (type)
+		{
+		case ERL_SMALL_TUPLE_EXT:
+		case ERL_LARGE_TUPLE_EXT:
+			tmpindex = request->index;
+			ei_decode_tuple_header(request->buff, &tmpindex, &arity);
+			ei_get_type(request->buff, &tmpindex, &type2, &size);
+
+			switch (type2)
+			{
+			case ERL_ATOM_EXT:
+				ret = handle_msg_req_tuple(phandler, msg);
+				break;
+			case ERL_REFERENCE_EXT:
+			case ERL_NEW_REFERENCE_EXT:
+				ret = handle_req_ref_tuple(phandler, msg);
+				break;
+			case ERL_PID_EXT:
+				ei_decode_pid(request->buff,&tmpindex,&from);
+				ret = handle_send(phandler, msg);
+				break;
+			default:
+				LM_ERR("nothing to do with term type=<%d> type2=<%d> -- discarding\n", type, type2);
+				break;
+			}
+			break;
+		default:
+			LM_ERR("not handled term type=<%d> size=<%d> -- discarding\n", type, size);
+			break;
+		}
+	}
+
+	if (ret)
+	{
+		return ret;
+	}
+	else if (response->index > 1)
+	{
+		ei_x_print_msg(response, &from, 1);
+
+		if (ei_send(phandler->sockfd, &from, response->buff, response->index))
+		{
+			LM_ERR("ei_send failed on node=<%s> socket=<%d>, %s\n",
+					phandler->ec.thisnodename,phandler->sockfd, strerror(erl_errno));
+		}
+
+		return ret;
+
+	}
+	else
+	{
+		LM_DBG("** no reply **\n");
+		return 0;
+	}
+}
+
+void encode_error_msg(ei_x_buff *response, erlang_ref_ex_t *ref, const char *type, const char *msg )
+{
+	ei_x_encode_tuple_header(response, 2);
+
+	if (ref->with_node)
+	{
+		ei_x_encode_tuple_header(response,2);
+		ei_x_encode_ref(response, &ref->ref);
+		ei_x_encode_atom(response,ref->nodename);
+	}
+	else {
+		ei_x_encode_ref(response, &ref->ref);
+	}
+
+	ei_x_encode_tuple_header(response,2);
+	ei_x_encode_atom(response, type);
+	ei_x_encode_string(response, msg);
+}

+ 32 - 0
modules/erlang/handle_emsg.h

@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef HANDLE_EMSG_H_
+#define HANDLE_EMSG_H_
+
+#include "erl_helpers.h"
+#include "cnode.h"
+
+int handle_erlang_msg(cnode_handler_t *handler, erlang_msg * msg);
+
+#endif /* HANDLE_EMSG_H_ */

+ 1484 - 0
modules/erlang/handle_rpc.c

@@ -0,0 +1,1484 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+
+#include "handle_rpc.h"
+#include "mod_erlang.h"
+
+/* create empty recycle bin */
+static struct erl_rpc_garbage *recycle_bin = 0;
+
+static int get_int(int *int_ptr,erl_rpc_ctx_t *ctx, int reads, int autoconvert);
+static int get_double(double *double_prt,erl_rpc_ctx_t *ctx, int reads, int autoconvert);
+static int get_str(str *str_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert);
+static int find_member(erl_rpc_ctx_t *ctx, int arity, const char* member_name);
+static int add_to_recycle_bin(int type, void* ptr, erl_rpc_ctx_t *ctx);
+
+erl_rpc_param_t *erl_new_param(erl_rpc_ctx_t *ctx);
+void erl_rpc_append_param(erl_rpc_ctx_t *ctx, erl_rpc_param_t *param);
+
+/*
+ * RPC holder
+ */
+rpc_t erl_rpc_func_param;
+
+/*
+ * Function returns always success - we uses EPMD for transport
+ */
+int erl_rpc_send(erl_rpc_ctx_t *ctx, int depth)
+{
+	if (ctx->response_sent) return 0;
+	ctx->response_sent = 1;
+	erl_rpc_ctx_t *handler;
+	erl_rpc_param_t *fault = *(ctx->fault_p);
+
+	if (fault)
+	{
+		LM_ERR("fault: %d %.*s\n",fault->type, STR_FMT(&fault->value.S));
+		/* restore clear point */
+		ctx->response->index = ctx->response_index;
+
+		/* {error,{struct,[ {"code", 400}, {"error","Error message"}]}}*/
+		if (ei_x_encode_tuple_header(ctx->response,1)) goto error;	/* {error,{_,_}} */
+		if (rpc_reply_with_struct && ei_x_encode_atom(ctx->response,"struct")) goto error;	/* {error,{struct,_}} */
+		if (ei_x_encode_list_header(ctx->response,2)) goto error;	/* {error,{struct,[_,_]}} */
+		if (ei_x_encode_tuple_header(ctx->response,2)) goto error;	/* {error,{struct,[{_,_},_]}} */
+		if (ei_x_encode_atom(ctx->response,"code")) goto error;		/* {error,{struct,[{code,_},_]}} */
+		if (ei_x_encode_long(ctx->response,fault->type)) goto error;/* {error,{struct,[{code,400},_]}} */
+		if (ei_x_encode_tuple_header(ctx->response,2)) goto error;	/* {error,{struct,[{code,400},{_,_}]}} */
+		if (ei_x_encode_binary(ctx->response,"error",sizeof("error")-1)) goto error;	/* {error,{struct,[{code,400},{<<"error">>,_}]}} */
+		if (ei_x_encode_binary(ctx->response,(void*)fault->value.S.s,fault->value.S.len)) /* {error,{struct,[{code,400},{<<"error">>,<<Msg>>}]}} */
+			goto error;
+		if (ei_x_encode_empty_list(ctx->response)) goto error;
+	}
+	else if (ctx->reply_params)
+	{
+		while(ctx->reply_params)
+		{
+			if (ctx->reply_params->member_name)
+			{
+				/* {"member_name", _} */
+				if (ei_x_encode_tuple_header(ctx->response,2)) goto error;
+				if (ei_x_encode_binary(ctx->response,ctx->reply_params->member_name, strlen(ctx->reply_params->member_name)))
+					goto error;
+			}
+			/* {"member_name", MemberValue} */
+			switch (ctx->reply_params->type) {
+				case ERL_INTEGER_EXT:
+					if(ei_x_encode_long(ctx->response,ctx->reply_params->value.n)) goto error;
+					break;
+				case ERL_FLOAT_EXT:
+					if(ei_x_encode_double(ctx->response,ctx->reply_params->value.d)) goto error;
+					break;
+				case ERL_STRING_EXT:
+					if(ei_x_encode_binary(ctx->response,ctx->reply_params->value.S.s,ctx->reply_params->value.S.len)) goto error;
+					break;
+				case ERL_SMALL_TUPLE_EXT: /* add as {struct,list(no_params)} */
+					handler = (erl_rpc_ctx_t*)ctx->reply_params->value.handler;
+					if (ei_x_encode_tuple_header(ctx->response,1)) goto error;
+					if (rpc_reply_with_struct && ei_x_encode_atom(ctx->response,"struct")) goto error;
+					if (ei_x_encode_list_header(ctx->response,handler->no_params)) goto error;
+					if (erl_rpc_send(handler, depth++)) goto error;
+					if (ei_x_encode_empty_list(ctx->response)) goto error;
+					break;
+				case ERL_LIST_EXT: /* add as [list(no_params)] */
+					handler = (erl_rpc_ctx_t*)ctx->reply_params->value.handler;
+					if (ei_x_encode_list_header(ctx->response,handler->no_params)) goto error;
+					if (erl_rpc_send(handler, depth++)) goto error;
+					if (handler->no_params)
+						if (ei_x_encode_empty_list(ctx->response)) goto error;
+					break;
+				default:
+					LM_ERR("Unknown type '%c' for encoding RPC reply\n",ctx->reply_params->type);
+					break;
+			}
+			ctx->reply_params=ctx->reply_params->next;
+		}
+	}
+	else if (!depth)
+	{
+		/* restore start point */
+		LM_WARN("encode empty response -> ok");
+		ctx->response->index = ctx->response_index;
+		if (ei_x_encode_atom(ctx->response,"ok")) goto error;
+	}
+
+	return 0;
+
+error:
+	LM_ERR("error while encoding response\n");
+	return -1;
+}
+
+void erl_rpc_fault(erl_rpc_ctx_t* ctx, int code, char* fmt, ...)
+{
+	static char buf[FAULT_BUF_LEN];
+	erl_rpc_param_t *fault = *(ctx->fault_p);
+	int len;
+
+	va_list ap;
+
+	if (fault) return;
+
+	va_start(ap, fmt);
+	len = vsnprintf(buf, FAULT_BUF_LEN, fmt, ap);
+	va_end(ap);
+
+	fault=(erl_rpc_param_t*)pkg_malloc(sizeof(erl_rpc_param_t));
+
+	if (fault == 0)
+	{
+		LM_ERR("Not enough memory\n");
+		return;
+	}
+
+	if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)fault,ctx))
+	{
+		pkg_free(fault);
+		return;
+	}
+
+	fault->type = code;
+	fault->value.S.s = buf;
+	fault->value.S.len = len;
+	ctx->fault = fault;
+}
+
+int erl_rpc_add(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	void **void_ptr;
+	int int_ptr;
+	double double_ptr;
+	char *char_ptr;
+	str *str_ptr;
+	erl_rpc_ctx_t *handler;
+	erl_rpc_param_t *param;
+
+	int reads=0;
+
+	va_list ap;
+
+	va_start(ap,fmt);
+
+	while(*fmt)
+	{
+		if ((param = erl_new_param(ctx))==0)
+		{
+			goto error;
+		}
+
+		switch(*fmt)
+		{
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			int_ptr = va_arg(ap, int);
+			param->type = ERL_INTEGER_EXT;
+			param->value.n = int_ptr;
+			break;
+
+		case 'f': /* double */
+			double_ptr = va_arg(ap, double);
+			param->type = ERL_FLOAT_EXT;
+			param->value.d = double_ptr;
+			break;
+
+		case 'S': /* str structure */
+			str_ptr = va_arg(ap, str*);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S = *str_ptr;
+
+			break;
+
+		case 's':/* zero terminated string */
+
+			char_ptr = va_arg(ap, char *);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S.len = strlen(char_ptr);
+
+			param->value.S.s = (char*)pkg_malloc(param->value.S.len);
+
+			if (!param->value.S.s)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx))
+			{
+				pkg_free(param->value.S.s);
+				goto error;
+			}
+
+			memcpy(param->value.S.s,char_ptr,param->value.S.len);
+
+			break;
+		case '{':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_SMALL_TUPLE_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+
+		case '[':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_LIST_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+		 default:
+			 LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
+			 goto error;
+		}
+
+		erl_rpc_append_param(ctx,param);
+
+		reads++;
+		fmt++;
+	}
+	va_end(ap);
+	return reads;
+
+error:
+	LM_ERR("Failed to encode parameter #%d into response.\n",reads);
+	va_end(ap);
+	return -reads;
+}
+
+int erl_rpc_scan(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	int* int_ptr;
+	char** char_ptr;
+	str* str_ptr;
+	double* double_ptr;
+	void** void_ptr;
+	str s; /* helper str */
+
+	int reads = 0;
+	int modifiers = 0;
+	int autoconv = 0;
+
+	int type,size;
+	erl_rpc_ctx_t *handler;
+
+	va_list ap;
+
+	va_start(ap,fmt);
+
+	while(*fmt && ctx->size)
+	{
+		/* optional and we at the end of decoding params */
+		if (ctx->optional && !ctx->size)
+		{
+			break;
+		}
+
+		if (ei_get_type(ctx->request->buff,&ctx->request_index,&type,&size))
+		{
+			erl_rpc_fault(ctx,400,"Can't determine data type, for parameter #%d",reads);
+			LM_ERR("Can't determine data type, for parameter #%d",reads);
+
+			goto error;
+		}
+
+		switch(*fmt)
+		{
+		case '*': /* start of optional parameters */
+			modifiers++;
+			ctx->optional = 1;
+			reads++;
+			fmt++;
+			continue;
+		case '.':  /* autoconvert */
+			modifiers++;
+			autoconv = 1;
+			reads++;
+			fmt++;
+			continue;
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			int_ptr = va_arg(ap, int*);
+
+			if (get_int(int_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 'f': /* double */
+			double_ptr = va_arg(ap, double*);
+
+			if (get_double(double_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 'S': /* str structure */
+
+			str_ptr = va_arg(ap, str*);
+
+			if (get_str(str_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 's':/* zero terminated string */
+
+			char_ptr = va_arg(ap, char **);
+			if (get_str(&s,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			*char_ptr = s.s;
+
+			break;
+		case '{':
+			void_ptr = va_arg(ap,void**);
+
+			if (type!=ERL_SMALL_TUPLE_EXT && type!=ERL_LARGE_TUPLE_EXT)
+			{
+				erl_rpc_fault(ctx,400,"Bad type of parameter #%d (t=%c).",reads,type);
+				goto error;
+			}
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			*handler = *ctx; /* copy state */
+			handler->optional = 0;
+			handler->no_params = 0;
+			handler->size = size; /* size of tuple */
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			/* skip element */
+			if (ei_skip_term(ctx->request->buff,&ctx->request_index))
+			{
+				goto error;
+			}
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			break;
+		 default:
+			 LM_ERR("Invalid parameter type in formatting string: %c\n", *fmt);
+			 erl_rpc_fault(ctx, 500, "Server Internal Error (Invalid Formatting String)");
+			 goto error;
+		}
+
+		autoconv = 0; /* reset autovoncersion for next parameter */
+		reads++;
+		fmt++;
+		ctx->size--;
+	}
+    va_end(ap);
+    return reads-modifiers;
+
+error:
+    va_end(ap);
+    return -(reads-modifiers);
+
+}
+
+int erl_rpc_struct_scan(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	int* int_ptr;
+	char** char_ptr;
+	str* str_ptr;
+	double* double_ptr;
+	char* member_name;
+	str s; /* helper str */
+
+	int reads = 0;
+	int modifiers = 0;
+	int index;
+	int autoconv = 0;
+
+	int arity;
+
+	va_list ap;
+
+	/* save index */
+	index = ctx->request_index;
+
+	if(ei_decode_tuple_header(ctx->request->buff,&ctx->request_index, &arity))
+	{
+		erl_rpc_fault(ctx,400,"Bad tuple");
+		return -1;
+	}
+
+	va_start(ap,fmt);
+
+	while(*fmt)
+	{
+		member_name = va_arg(ap, char*);
+
+		if (find_member(ctx,arity,member_name))
+		{
+			goto error;
+		}
+
+		switch(*fmt)
+		{
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			int_ptr = va_arg(ap, int*);
+
+			if (get_int(int_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 'f': /* double */
+			double_ptr = va_arg(ap, double*);
+
+			if (get_double(double_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 'S': /* str structure */
+
+			str_ptr = va_arg(ap, str*);
+
+			if (get_str(str_ptr,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			break;
+		case 's':/* zero terminated string */
+
+			char_ptr = va_arg(ap,char**);
+
+			if (get_str(&s,ctx,reads,autoconv))
+			{
+				goto error;
+			}
+
+			*char_ptr = s.s;
+
+			break;
+
+		 default:
+			 LM_ERR("Invalid parameter type in formatting string: %c\n", *fmt);
+			 erl_rpc_fault(ctx, 500, "Server Internal Error (Invalid Formatting String)");
+			 goto error;
+		}
+
+		reads++;
+		fmt++;
+	}
+
+	/* restore index */
+	ctx->request_index = index;
+
+    va_end(ap);
+    return reads-modifiers;
+
+error:
+    va_end(ap);
+    return -(reads-modifiers);
+}
+
+#define RPC_BUF_SIZE 1024
+
+/*
+ * adds formated string into RPC response buffer as Erlang string/list
+ */
+int erl_rpc_printf(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	int n, buff_size;
+	char *buff = 0;
+	va_list ap;
+	erl_rpc_param_t *param;
+
+	buff = (char*)pkg_malloc(RPC_BUF_SIZE);
+	if (!buff) {
+			erl_rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
+			ERR("No memory left\n");
+			return -1;
+	}
+
+	buff_size = RPC_BUF_SIZE;
+
+	while(1)
+	{
+		/* Try to print in the allocated space. */
+		va_start(ap, fmt);
+		n = vsnprintf(buff, buff_size, fmt, ap);
+		va_end(ap);
+			 /* If that worked, return the string. */
+		if (n > -1 && n < buff_size)
+		{
+			if(add_to_recycle_bin(JUNK_PKGCHAR,(void*)buff,ctx))
+			{
+				goto error;
+			}
+			else if ((param = erl_new_param(ctx)))
+			{
+				param->type = ERL_STRING_EXT;
+				param->value.S.s = buff;
+				param->value.S.len = n;
+				erl_rpc_append_param(ctx,param);
+			}
+			else
+			{
+				goto error;
+			}
+
+			return 0;
+		}
+
+		/* Else try again with more space. */
+		if (n > -1)
+		{	/* glibc 2.1 */
+			buff_size = n + 1; /* precisely what is needed */
+		}
+		else
+		{	/* glibc 2.0 */
+			buff_size *= 2;  /* twice the old size */
+		}
+		if ((buff = pkg_realloc(buff, buff_size)) == 0)
+		{
+			erl_rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
+			ERR("No memory left\n");
+			goto error;
+		}
+	}
+
+	return 0;
+
+error:
+	if(buff) pkg_free(buff);
+	return -1;
+}
+
+int erl_rpc_struct_add(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	void **void_ptr;
+	char *char_ptr;
+	str *str_ptr;
+	erl_rpc_ctx_t *handler;
+	erl_rpc_param_t *param;
+
+	int reads=0;
+
+	va_list ap;
+
+	va_start(ap,fmt);
+
+	while(*fmt)
+	{
+		if ((param = erl_new_param(ctx))==0)
+		{
+			goto error;
+		}
+
+		param->member_name = va_arg(ap, char*);
+
+		switch(*fmt)
+		{
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			param->type = ERL_INTEGER_EXT;
+			param->value.n = va_arg(ap, int);;
+			break;
+
+		case 'f': /* double */
+			param->type = ERL_FLOAT_EXT;
+			param->value.d = va_arg(ap, double);
+			break;
+
+		case 'S': /* str structure */
+			str_ptr = va_arg(ap, str*);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S = *str_ptr;
+			break;
+
+		case 's':/* zero terminated string */
+
+			char_ptr = va_arg(ap, char *);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S.len = strlen(char_ptr);
+
+			param->value.S.s = (char*)pkg_malloc(param->value.S.len);
+
+			if (!param->value.S.s)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx))
+			{
+				pkg_free(param->value.S.s);
+				goto error;
+			}
+
+			memcpy(param->value.S.s,char_ptr,param->value.S.len);
+
+			break;
+
+		case '{':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_SMALL_TUPLE_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+
+		case '[':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_LIST_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+
+		 default:
+			 LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
+			 goto error;
+		}
+
+		erl_rpc_append_param(ctx,param);
+
+		reads++;
+		fmt++;
+	}
+	va_end(ap);
+	return reads;
+
+error:
+
+	LM_ERR("Failed to encode parameter #%d into response.\n",reads);
+	va_end(ap);
+	return -reads;
+}
+
+int erl_rpc_array_add(erl_rpc_ctx_t* ctx, char* fmt, ...)
+{
+	void **void_ptr;
+	char *char_ptr;
+	str *str_ptr;
+	erl_rpc_ctx_t *handler;
+	erl_rpc_param_t *param;
+
+	int reads=0;
+
+	va_list ap;
+
+	va_start(ap,fmt);
+
+	LM_DBG("ctx=%p add fmt=<%s>\n",(void*)ctx,fmt);
+
+	while(*fmt)
+	{
+		if ((param = erl_new_param(ctx))==0)
+		{
+			goto error;
+		}
+
+		param->member_name = NULL;
+
+		switch(*fmt)
+		{
+		case 'b': /* Bool */
+		case 't': /* Date and time */
+		case 'd': /* Integer */
+			param->type = ERL_INTEGER_EXT;
+			param->value.n = va_arg(ap, int);;
+			break;
+
+		case 'f': /* double */
+			param->type = ERL_FLOAT_EXT;
+			param->value.d = va_arg(ap, double);
+			break;
+
+		case 'S': /* str structure */
+			str_ptr = va_arg(ap, str*);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S = *str_ptr;
+			break;
+
+		case 's':/* zero terminated string */
+
+			char_ptr = va_arg(ap, char *);
+
+			param->type = ERL_STRING_EXT;
+			param->value.S.len = strlen(char_ptr);
+
+			param->value.S.s = (char*)pkg_malloc(param->value.S.len);
+
+			if (!param->value.S.s)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR, param->value.S.s, ctx))
+			{
+				pkg_free(param->value.S.s);
+				goto error;
+			}
+
+			memcpy(param->value.S.s,char_ptr,param->value.S.len);
+
+			break;
+
+		case '{':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_SMALL_TUPLE_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+
+		case '[':
+			void_ptr = va_arg(ap,void**);
+
+			handler = (erl_rpc_ctx_t*)pkg_malloc(sizeof(erl_rpc_ctx_t));
+
+			if (!handler)
+			{
+				LM_ERR("Not enough memory\n");
+				goto error;
+			}
+
+			if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)handler,ctx))
+			{
+				pkg_free(handler);
+				goto error;
+			}
+
+			*handler = *ctx;
+			handler->no_params = 0;
+			handler->reply_params=0;
+			handler->tail = 0;
+
+			/* go where we stopped */
+			*(erl_rpc_ctx_t**)void_ptr = handler;
+
+			param->type = ERL_LIST_EXT;
+			param->value.handler = (void*)handler;
+
+			break;
+
+		 default:
+			 LM_ERR("Invalid type '%c' in formatting string\n", *fmt);
+			 goto error;
+		}
+
+		erl_rpc_append_param(ctx,param);
+
+		reads++;
+		fmt++;
+	}
+	va_end(ap);
+	return reads;
+
+error:
+
+	LM_ERR("Failed to encode parameter #%d into response.\n",reads);
+	va_end(ap);
+	return -reads;
+}
+
+int erl_rpc_struct_printf(erl_rpc_ctx_t* ctx, char* name, char* fmt, ...)
+{
+	int n, buff_size;
+	char *buff;
+	va_list ap;
+	erl_rpc_param_t *param;
+
+	LM_ERR("parsing name:%s fmt: %s\n",name, fmt);
+
+	buff = (char*)pkg_malloc(RPC_BUF_SIZE);
+	if (!buff) {
+			ERR("No memory left\n");
+			return -1;
+	}
+
+	buff_size = RPC_BUF_SIZE;
+
+	while(1)
+	{
+		/* Try to print in the allocated space. */
+		va_start(ap, fmt);
+		n = vsnprintf(buff, buff_size, fmt, ap);
+		va_end(ap);
+			 /* If that worked, return the string. */
+		if (n > -1 && n < buff_size)
+		{
+
+			if(add_to_recycle_bin(JUNK_PKGCHAR,(void*)buff,ctx))
+			{
+				goto error;
+			}
+			else if ((param = erl_new_param(ctx)))
+			{
+				param->type = ERL_STRING_EXT;
+				param->value.S.s = buff;
+				param->value.S.len = n;
+				param->member_name = name;
+				erl_rpc_append_param(ctx,param);
+			}
+			else
+			{
+				goto error;
+			}
+
+			return 0;
+		}
+
+		/* Else try again with more space. */
+		if (n > -1)
+		{	/* glibc 2.1 */
+			buff_size = n + 1; /* precisely what is needed */
+		}
+		else
+		{	/* glibc 2.0 */
+			buff_size *= 2;  /* twice the old size */
+		}
+		if ((buff = pkg_realloc(buff, buff_size)) == 0)
+		{
+			ERR("No memory left\n");
+			goto error;
+		}
+	}
+
+	return 0;
+
+error:
+	if(buff) pkg_free(buff);
+	return -1;
+}
+
+int erl_rpc_capabilities(erl_rpc_ctx_t* ctx)
+{
+	return 0; /* no RPC_DELAYED_REPLY */
+}
+
+/** Add a memory to the list of memory blocks that
+ * need to be re-claimed later.
+ *
+ * @param type The type of the memory block.
+ * @param ptr A pointer to the memory block.
+ * @param ctx The context.
+ * @return 0 on success, a negative number on error.
+ * @sa empty_recycle_bin()
+ */
+static int add_to_recycle_bin(int type, void *ptr, erl_rpc_ctx_t *ctx)
+{
+	struct erl_rpc_garbage *p;
+
+	p = (struct erl_rpc_garbage*)pkg_malloc(sizeof(struct erl_rpc_garbage));
+
+	if (!p)
+	{
+			LM_ERR("Not enough memory\n");
+			return -1;
+	}
+
+	p->type = type;
+	p->ptr = ptr;
+	p->next = recycle_bin;
+	recycle_bin = p;
+	return 0;
+}
+
+/** Re-claims all memory allocated in the process of building XML-RPC
+ * reply.
+ */
+void empty_recycle_bin(void)
+{
+        struct erl_rpc_garbage* p;
+             /* Collect garbage */
+        while(recycle_bin)
+        {
+                p = recycle_bin;
+                recycle_bin = recycle_bin->next;
+                switch(p->type)
+                {
+                case JUNK_EI_X_BUFF:
+
+                	if (p->ptr)
+                	{
+                		ei_x_free((ei_x_buff*)p->ptr);
+						pkg_free(p->ptr);
+					}
+
+					break;
+
+                case JUNK_PKGCHAR:
+
+                	if (p->ptr)
+					{
+						pkg_free(p->ptr);
+					}
+
+					break;
+
+                default:
+                	ERR("BUG: Unsupported junk type\n");
+                }
+
+                pkg_free(p);
+        }
+}
+
+/*
+ * Get int parameter
+ */
+static int get_int(int *int_ptr,erl_rpc_ctx_t *ctx, int reads, int autoconvert)
+{
+	int type, size;
+	char *p;
+	char *endptr;
+	double d;
+	long l;
+
+	if (ei_get_type(ctx->request->buff,&ctx->request_index,&type,&size))
+	{
+		if(ctx->optional) return 0;
+
+		erl_rpc_fault(ctx,400,"Can't determine data type of parameter #%d",reads);
+		return -1;
+	}
+
+	switch(type)
+	{
+	case ERL_SMALL_INTEGER_EXT:
+	case ERL_INTEGER_EXT:
+		if(ei_decode_long(ctx->request->buff, &ctx->request_index, &l))
+		{
+			erl_rpc_fault(ctx,400,"Bad value of parameter #%d.",reads);
+			return -1;
+		}
+		*int_ptr = (int)l;
+
+		break;
+	case ERL_STRING_EXT:
+	case ERL_LIST_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		/* allocate buffer */
+		p = (char*)pkg_malloc(size+1);
+
+		if (!p)
+		{
+			erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		*int_ptr = strtol(p,&endptr,10);
+		if (p == endptr)
+		{
+			erl_rpc_fault(ctx,400,"Unable to convert %s into integer for parameter at position %d",p,reads);
+			pkg_free(p);
+			return -1;
+		}
+
+		pkg_free(p);
+		break;
+
+	case ERL_FLOAT_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		if (ei_decode_double(ctx->request->buff,&ctx->request_index,&d))
+		{
+			erl_rpc_fault(ctx,400, "Can't read parameter #%d",reads);
+			return -1;
+		}
+
+		*int_ptr=(int)d;
+		break;
+
+	default:
+		LM_ERR("Unsupported type ('%c') for conversion into integer parameter #%d.\n",type,reads);
+		erl_rpc_fault(ctx,400,"Unsupported type ('%c') for conversion into integer parameter #%d.",type,reads);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int get_double(double *double_prt,erl_rpc_ctx_t *ctx, int reads, int autoconvert)
+{
+	int type, size;
+	char *p;
+	char *endptr;
+	long n;
+
+	if (ei_get_type(ctx->request->buff,&ctx->request_index,&type,&size)){
+		erl_rpc_fault(ctx,400,"Can't determine data type of parameter #%d",reads);
+		return -1;
+	}
+
+	switch(type)
+	{
+	case ERL_FLOAT_EXT:
+
+		if (ei_decode_double(ctx->request->buff,&ctx->request_index,double_prt))
+		{
+			erl_rpc_fault(ctx,400, "Bad value of parameter #%d.",reads);
+			return -1;
+		}
+
+		break;
+	case ERL_STRING_EXT:
+	case ERL_LIST_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		/* allocate buffer */
+		p = (char*)pkg_malloc(size+1);
+
+		if (!p)
+		{
+			erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		*double_prt = strtod(p,&endptr);
+		if (p == endptr)
+		{
+			erl_rpc_fault(ctx,400,"Unable to convert %s into double, parameter at position #%d",p,reads);
+			pkg_free(p);
+			return -1;
+		}
+
+		pkg_free(p);
+		break;
+
+	case ERL_SMALL_INTEGER_EXT:
+	case ERL_INTEGER_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		if(ei_decode_long(ctx->request->buff, &ctx->request_index, &n))
+		{
+			erl_rpc_fault(ctx,400,"Can't read parameter #%d",reads);
+			return -1;
+		}
+
+		*double_prt=n;
+
+		break;
+	default:
+		erl_rpc_fault(ctx,400,"Can't convert to double parameter #%d.",reads);
+		return -1;
+	}
+
+	return 0;
+}
+
+#define MAX_DIGITS	20
+
+static int get_str(str *str_ptr, erl_rpc_ctx_t *ctx, int reads, int autoconvert)
+{
+	int type, size;
+	char *p;
+	double d;
+	long n;
+
+	if (ei_get_type(ctx->request->buff,&ctx->request_index,&type,&size))
+	{
+		erl_rpc_fault(ctx,400,"Can't determine data type of parameter #%d",reads);
+		return -1;
+	}
+
+	switch(type)
+	{
+	case ERL_FLOAT_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		if (ei_decode_double(ctx->request->buff,&ctx->request_index,&d))
+		{
+			erl_rpc_fault(ctx,400, "Bad value of parameter #%d.",reads);
+			return -1;
+		}
+
+		p=(char*)pkg_malloc(MAX_DIGITS);
+
+		if (!p)
+		{
+			erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		if (add_to_recycle_bin(JUNK_PKGCHAR, p, ctx))
+		{
+			pkg_free(p);
+			return -1;
+		}
+
+		str_ptr->len=snprintf(p, MAX_DIGITS, "%f", d);
+		str_ptr->s = p;
+
+		break;
+
+	case ERL_STRING_EXT:
+	case ERL_LIST_EXT:
+	case ERL_BINARY_EXT:
+
+		/* allocate buffer */
+		p = (char*)pkg_malloc(size+1);
+
+		if (!p)
+		{
+			erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		if (add_to_recycle_bin(JUNK_PKGCHAR, p, ctx))
+		{
+			pkg_free(p);
+			return -1;
+		}
+
+		if(ei_decode_strorbin(ctx->request->buff,&ctx->request_index,size+1,p))
+		{
+			erl_rpc_fault(ctx,400, "Can't read parameter #%d",reads);
+			return -1;
+		}
+
+		str_ptr->s=p;
+		str_ptr->len=size;
+
+		break;
+
+	case ERL_SMALL_INTEGER_EXT:
+	case ERL_INTEGER_EXT:
+
+		if (autoconvert == 0)
+		{
+			erl_rpc_fault(ctx,400,"Bad type of parameter #%d",reads);
+			return -1;
+		}
+
+		if (ei_decode_long(ctx->request->buff,&ctx->request_index,&n))
+		{
+			erl_rpc_fault(ctx,400, "Bad value of parameter #%d.",reads);
+			return -1;
+		}
+
+		p=(char*)pkg_malloc(MAX_DIGITS);
+
+		if (!p)
+		{
+			erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		if (add_to_recycle_bin(JUNK_PKGCHAR, p, ctx))
+		{
+			pkg_free(p);
+			return -1;
+		}
+
+		str_ptr->len=snprintf(p, MAX_DIGITS, "%ld", n);
+		str_ptr->s = p;
+
+		break;
+	default:
+		erl_rpc_fault(ctx,400,"Can't convert to string parameter #%d.",reads);
+		return -1;
+	}
+
+	LM_ERR("parameter #%d:<%.*s>\n",reads,STR_FMT(str_ptr));
+
+	return 0;
+}
+
+/*
+ * Find member in tuple (aka RPC struct)
+ */
+static int find_member(erl_rpc_ctx_t *ctx, int arity, const char* member_name)
+{
+	int index,i=0;
+	int type,size;
+	char key_name[MAXATOMLEN];
+
+	/* save position */
+	index = ctx->request_index;
+
+	/* { name, Value, name, Value...} */
+	while (i < arity)
+	{
+		if (ei_get_type(ctx->request->buff,&ctx->request_index,&type,&size))
+		{
+			erl_rpc_fault(ctx,400,"Bad struct member type");
+			goto error;
+		}
+
+		if(ei_decode_atom(ctx->request->buff,&ctx->request_index, key_name))
+		{
+			erl_rpc_fault(ctx,400,"Bad member name");
+			goto error;
+		}
+
+		if (strcasecmp(member_name,key_name))
+		{
+			if(ei_skip_term(ctx->request->buff,&ctx->request_index))
+			{
+				erl_rpc_fault(ctx,400,"Unexpected end of struct tuple");
+				goto error;
+			}
+			continue;
+		}
+		else
+		{
+			/* return at current position */
+			return 0;
+		}
+
+		i++;
+	}
+
+	erl_rpc_fault(ctx,400, "Member %s not found",member_name);
+
+error:
+	ctx->request_index = index;
+	return -1;
+}
+
+void init_rpc_handlers()
+{
+	erl_rpc_func_param.send = (rpc_send_f)erl_rpc_send;
+	erl_rpc_func_param.fault = (rpc_fault_f)erl_rpc_fault;
+	erl_rpc_func_param.add = (rpc_add_f)erl_rpc_add;
+	erl_rpc_func_param.scan = (rpc_scan_f)erl_rpc_scan;
+	erl_rpc_func_param.rpl_printf = (rpc_rpl_printf_f)erl_rpc_printf;
+	erl_rpc_func_param.struct_add = (rpc_struct_add_f)erl_rpc_struct_add;
+	erl_rpc_func_param.array_add = (rpc_array_add_f)erl_rpc_array_add;
+	erl_rpc_func_param.struct_scan = (rpc_struct_scan_f)erl_rpc_struct_scan;
+	erl_rpc_func_param.struct_printf = (rpc_struct_printf_f)erl_rpc_struct_printf;
+	erl_rpc_func_param.capabilities = (rpc_capabilities_f)erl_rpc_capabilities;
+	erl_rpc_func_param.delayed_ctx_new = 0;
+	erl_rpc_func_param.delayed_ctx_close = 0;
+}
+
+erl_rpc_param_t *erl_new_param(erl_rpc_ctx_t *ctx)
+{
+	erl_rpc_param_t *p = (erl_rpc_param_t *)pkg_malloc(sizeof(erl_rpc_param_t));
+
+	if (add_to_recycle_bin(JUNK_PKGCHAR,(void*)p,ctx))
+	{
+		erl_rpc_fault(ctx,500, "Internal Server Error (No memory left)");
+		LM_ERR("Not enough memory\n");
+
+		pkg_free(p);
+		return 0;
+	}
+
+	p->next = 0;
+	p->member_name = 0;
+	return p;
+}
+
+void erl_rpc_append_param(erl_rpc_ctx_t *ctx, erl_rpc_param_t *param)
+{
+
+	if (ctx->tail)
+	{
+		ctx->tail->next = param;
+		ctx->tail = param;
+	}
+	else
+	{
+		ctx->reply_params = ctx->tail = param;
+	}
+
+	param->next = 0;
+	ctx->no_params++;
+}

+ 102 - 0
modules/erlang/handle_rpc.h

@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../rpc.h"
+#include "erl_helpers.h"
+#include "cnode.h"
+
+#ifndef HANDLE_RPC_H_
+#define HANDLE_RPC_H_
+
+#define FAULT_BUF_LEN 1024
+
+/*
+ * RPC structure to store reply before marshaling.
+ */
+typedef struct erl_rpc_param_s {
+	int type;
+	union {
+		int n;
+		double d;
+		str S;
+		void* handler;
+	} value;
+	char *member_name;
+	struct erl_rpc_param_s *next;
+} erl_rpc_param_t;
+
+typedef struct erl_rpc_ctx {
+	cnode_handler_t *phandler;
+	erlang_ref_ex_t *ref;
+	erlang_pid *pid;
+	ei_x_buff *request;
+	int request_index;
+	ei_x_buff *response;
+	int response_sent;
+	int response_index;
+	erl_rpc_param_t *reply_params; /* encoded into reply as {ok,[<reply_params>]} */
+	erl_rpc_param_t *tail;
+	erl_rpc_param_t *fault; /* is set has precedence on reply_params
+	 	 	 	 	 	 	   encoded as {error, {code, <fault message>}} */
+	erl_rpc_param_t **fault_p;
+	int no_params; /* number of encoding params */
+	int optional; /* are params optional */
+	int size; /* size of decoding structure or main list */
+} erl_rpc_ctx_t;
+
+int erl_rpc_send(erl_rpc_ctx_t* ctx, int depth);					/* Send the reply to the Erlang Node */
+void erl_rpc_fault(erl_rpc_ctx_t* ctx, int code, char* fmt, ...);	/* Signal a failure to the client */
+int erl_rpc_add(erl_rpc_ctx_t* ctx, char* fmt, ...);				/* Add a new piece of data to the result */
+int erl_rpc_scan(erl_rpc_ctx_t* ctx, char* fmt, ...);				/* Retrieve request parameters */
+int erl_rpc_printf(erl_rpc_ctx_t* ctx, char* fmt, ...);				/* Add printf-like formated data to the result set */
+int erl_rpc_struct_add(erl_rpc_ctx_t* ctx, char* fmt, ...);			/* Add a new structure into structure */
+int erl_rpc_array_add(erl_rpc_ctx_t* ctx, char* fmt, ...);			/* Add a new structure into array */
+int erl_rpc_struct_scan(erl_rpc_ctx_t* ctx, char* fmt, ...);		/* Scan attributes of a structure */
+int erl_rpc_struct_printf(erl_rpc_ctx_t* ctx, char* name, char* fmt, ...); /* Struct version of rpc_printf */
+int erl_rpc_capabilities(erl_rpc_ctx_t* ctx);	/* capabilities */
+
+void init_rpc_handlers();
+void empty_recycle_bin(void);
+
+/**
+ * Garbage collection data structure.
+ */
+struct erl_rpc_garbage {
+        enum {
+                JUNK_EI_X_BUFF,   /**< This type indicates that the memory block was
+								   * allocated for the erlang EI interface, this
+								   * type needs to be freed differently
+								   */
+                JUNK_PKGCHAR      /** This type indicates a pkg_malloc'ed string */
+        } type;               /**< Type of the memory block */
+        void* ptr;            /**< Pointer to the memory block obtained from pkg_malloc */
+        struct erl_rpc_garbage* next; /**< The linked list of all allocated memory blocks */
+};
+
+
+/** Pointers to the functions that implement the RPC interface
+ * of Erlang module
+ */
+extern rpc_t erl_rpc_func_param;
+
+#endif /* HANDLE_RPC_H_ */

+ 1067 - 0
modules/erlang/mod_erlang.c

@@ -0,0 +1,1067 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../../ver.h"
+#include "../../sr_module.h"
+#include "../../pt.h"
+#include "../../cfg/cfg_struct.h"
+#include "../../mod_fix.h"
+#include "../../script_cb.h"
+
+#ifndef USE_TCP
+#error	"USE_TCP must be enabled for this module"
+#endif
+
+#include "../../pass_fd.h"
+
+#include "mod_erlang.h"
+#include "erl_helpers.h"
+#include "cnode.h"
+#include "handle_rpc.h"
+
+#include "pv_xbuff.h"
+#include "pv_tuple.h"
+#include "pv_atom.h"
+#include "pv_list.h"
+
+MODULE_VERSION
+
+/* module exports */
+static int child_init(int rank);
+static int mod_init(void);
+static void mod_destroy(void);
+static int postprocess_request(struct sip_msg *msg, unsigned int flags, void *_param);
+
+/*  exported functions */
+static int erl_rpc(struct sip_msg *msg, char *module, char *function, char *args, char *reply);
+static int erl_reg_send_k(struct sip_msg *msg, char *_server, char *_emsg);
+static int erl_reply_k(struct sip_msg *msg, char *_emsg);
+
+/* fix-ups */
+static int fixup_rpc(void** param, int param_no);
+static int fixup_reg(void** param, int param_no);
+static int fixup_reply(void** param, int param_no);
+
+/* initialize common vars */
+str cookie = STR_NULL;
+int trace_level = 0;
+str cnode_alivename = STR_NULL;
+str cnode_host = STR_NULL;
+int no_cnodes=1;
+int rpc_reply_with_struct = 0;
+
+str erlang_nodename  = STR_NULL;
+str erlang_node_sname = STR_NULL;
+
+int *usocks[2];
+int csockfd;
+
+handler_common_t* io_handlers = NULL;
+
+erl_api_t erl_api;
+
+static pv_export_t pvs[] = {
+		{
+				{ "tuple", (sizeof("tuple")-1) },
+				PVT_OTHER,
+				pv_tuple_get,
+				pv_tuple_set,
+				pv_xbuff_parse_name,
+				0,
+				0,
+				0
+		},
+		{
+				{ "atom", (sizeof("atom")-1) },
+				PVT_OTHER,
+				pv_atom_get,
+				pv_atom_set,
+				pv_atom_parse_name,
+				0,
+				0,
+				0
+		},
+		{
+				{ "list", (sizeof("list")-1) },
+				PVT_OTHER,
+				pv_list_get,
+				pv_list_set,
+				pv_xbuff_parse_name,
+				0,
+				0,
+				0
+		},
+		{
+				{ "xbuff", (sizeof("xbuff")-1) },
+				PVT_OTHER,
+				pv_xbuff_get,
+				pv_xbuff_set,
+				pv_xbuff_parse_name,
+				0,
+				0,
+				0
+		},
+		{{0,0}, 0, 0, 0, 0, 0, 0, 0}
+};
+
+/* exported parameters */
+static param_export_t parameters[] =
+{
+		/* Kamailo C node parameters */
+		{ "no_cnodes", PARAM_INT, &no_cnodes },
+		{ "cnode_alivename", PARAM_STR, &cnode_alivename },
+		{ "cnode_host", PARAM_STR, &cnode_host },
+
+		/* Erlang node parameters */
+		{ "erlang_nodename", PARAM_STR, &erlang_nodename },
+		{ "erlang_node_sname", PARAM_STR, &erlang_node_sname },
+
+		{ "cookie", PARAM_STR, &cookie },
+		{ "trace_level", PARAM_INT, &trace_level }, /* tracing level on the distribution */
+		{ "rpc_reply_with_struct", PARAM_INT, &rpc_reply_with_struct},
+		{ 0, 0, 0 }
+};
+
+/* exported commands */
+
+static cmd_export_t commands[] =
+{
+		{"erl_rpc", (cmd_function)erl_rpc, 4, fixup_rpc, 0, ANY_ROUTE},
+		{"erl_reg_send", (cmd_function)erl_reg_send_k, 2, fixup_reg, 0, ANY_ROUTE},
+		{"erl_reply", (cmd_function)erl_reply_k, 1, fixup_reply, 0, EVENT_ROUTE},
+		{"load_erl",(cmd_function)load_erl,0, 0,         0,         0}, /* API loader */
+		{ 0, 0, 0, 0, 0, 0 }
+};
+
+
+struct module_exports exports = {
+		"erlang",
+		DEFAULT_DLFLAGS,
+		commands,
+		parameters,
+		NULL,
+		NULL,
+		pvs,
+		NULL,
+		mod_init,
+		NULL,
+		mod_destroy,
+		child_init
+};
+
+/**
+ * \brief Initialize Erlang module
+ */
+static int mod_init(void)
+{
+	/* check required parameters */
+
+	if (!cookie.s || cookie.len == 0)
+	{
+		LM_CRIT("Erlang cookie parameter is required\n");
+		return -1;
+	}
+	cookie.s[cookie.len]=0;
+
+	if ((!erlang_nodename.s || erlang_nodename.len == 0)
+			&& (!erlang_node_sname.s || erlang_node_sname.len == 0)) {
+		LM_CRIT("Erlang node name is required\n");
+		return -1;
+	}
+	if (erlang_nodename.s) {
+		erlang_nodename.s[erlang_nodename.len]=0;
+	}
+	if (erlang_node_sname.s) {
+		erlang_node_sname.s[erlang_node_sname.len]=0;
+	}
+
+	if (!cnode_alivename.s || !cnode_alivename.len) {
+		LM_CRIT("Kamailio C node alive name is required\n");
+		return -1;
+	}
+	cnode_alivename.s[cnode_alivename.len]=0;
+
+	if (!cnode_host.s || !cnode_host.len) {
+		LM_WARN("Kamailio host name is not set, trying with gethostname...\n");
+		return -1;
+	}
+
+	if (erl_load_api(&erl_api)) {
+		LM_CRIT("failed to load erl API\n");
+		return -1;
+	}
+
+	if (compile_xbuff_re()) {
+		return -1;
+	}
+
+	if (register_script_cb(postprocess_request, POST_SCRIPT_CB | REQUEST_CB, 0)
+			!= 0)
+	{
+		LOG(L_CRIT, "could not register request post processing call back.\n");
+		return -1;
+	}
+
+	/* init RPC handler for Erlang calls */
+	init_rpc_handlers();
+
+	/* add space for extra processes */
+	register_procs(no_cnodes);
+
+	/* add child to update local config framework structures */
+	cfg_register_child(no_cnodes);
+
+	return 0;
+}
+
+#define MAX_CNODE_DESC_LEN (MAXNODELEN + sizeof("Erlang C node "))
+
+/**
+ * \brief Initialize Erlang module children
+ */
+static int child_init(int rank)
+{
+	char _cnode_desc[MAX_CNODE_DESC_LEN];
+	int pair[2], data[2];
+	int i,j,pid;
+
+	if (rank == PROC_INIT) {
+
+#ifdef SHM_MEM
+		usocks[KSOCKET]=(int*)shm_malloc(sizeof(int)*no_cnodes);
+		if (!usocks[KSOCKET]) {
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		usocks[CSOCKET]=(int*)shm_malloc(sizeof(int)*no_cnodes);
+		if (!usocks[CSOCKET]) {
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+#else
+		usocks[KSOCKET]=(int*)pkg_malloc(sizeof(int)*no_cnodes);
+		if (!usocks[KSOCKET]) {
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+
+		usocks[CSOCKET]=(int*)pkg_malloc(sizeof(int)*no_cnodes);
+		if (!usocks[CSOCKET]) {
+			LM_ERR("Not enough memory\n");
+			return -1;
+		}
+#endif
+
+		for(i=0;i<no_cnodes;i++) {
+			if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair)) {
+				LOG(L_CRIT,"failed create socket pair: %s. -- don't start -- \n", strerror(errno));
+				return -1;
+			}
+			usocks[KSOCKET][i]=pair[KSOCKET];
+			usocks[CSOCKET][i]=pair[CSOCKET];
+		}
+
+		return 0;
+	}
+
+	if (rank == PROC_MAIN) {
+		for (j=0; j<no_cnodes; j++) {
+
+			snprintf(_cnode_desc, MAX_CNODE_DESC_LEN, "%s%.*s%d@%.*s", "Erlang C Node ", STR_FMT(&cnode_alivename), j+1, STR_FMT(&cnode_host));
+
+			if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair)) {
+				LOG(L_CRIT,"failed create socket pair: %s. -- don't start -- \n", strerror(errno));
+				return -1;
+			}
+
+			if ((pid = fork_process(PROC_NOCHLDINIT, _cnode_desc, 0)) == -1) {
+				return -1; /* error -- don't start -- */
+			} else if (pid == 0) {
+				/* child */
+				if(cfg_child_init()) {
+					LM_CRIT("failed cfg_child_init\n");
+					return -1;
+				}
+
+				for (i=0;i<no_cnodes;i++)
+				{
+					close(usocks[KSOCKET][i]);
+					if (i!=j) close(usocks[CSOCKET][i]);
+				}
+
+				csockfd = usocks[CSOCKET][j];
+
+				/* enter Erlang C Node main loop (cnode process) */
+				cnode_main_loop(j);
+
+				LM_CRIT("failed to start Erlang C node main loop!\n");
+
+				return -1;
+			}
+
+			/* parent */
+		}
+
+		return 0;
+	}
+
+	for (i=0;i<no_cnodes;i++) {
+		close(usocks[CSOCKET][i]);
+	}
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair)) {
+		LOG(L_CRIT,"failed create socket pair: %s. -- don't start -- \n", strerror(errno));
+		return -1;
+	}
+
+	data[0] = pair[CSOCKET];
+	data[1] = my_pid();
+
+	if (send_fd(usocks[KSOCKET][process_no % no_cnodes],(void*)&data,sizeof(data),pair[CSOCKET])==-1) {
+		LM_CRIT("failed to send socket %d over socket %d to cnode\n",pair[CSOCKET],usocks[KSOCKET][process_no % no_cnodes]);
+		return -1;
+	}
+
+	csockfd = pair[KSOCKET];
+
+	erl_set_nonblock(csockfd);
+
+	erl_init_common();
+
+	for (i=0;i<no_cnodes;i++) {
+		close(usocks[KSOCKET][i]);
+	}
+
+	return 0;
+}
+
+/**
+ * @brief Destroy module allocated resources
+ */
+static void mod_destroy(void)
+{
+#ifdef SHM_MEM
+		shm_free(usocks[0]);
+		shm_free(usocks[1]);
+#else
+		pkg_free(usocks[0]);
+		pkg_free(usocks[1]);
+#endif
+		free_tuple_fmt_buff();
+		free_atom_fmt_buff();
+		free_list_fmt_buff();
+		free_xbuff_fmt_buff();
+}
+
+static int postprocess_request(struct sip_msg *msg, unsigned int flags, void *_param)
+{
+	free_tuple_fmt_buff();
+	free_atom_fmt_buff();
+	free_list_fmt_buff();
+	free_xbuff_fmt_buff();
+	return 0;
+}
+
+/**
+ * Erlang RPC.
+ */
+static int erl_rpc(struct sip_msg *msg, char *_m, char *_f, char *_a, char *_r)
+{
+	erl_param_t *m=(erl_param_t*)_m;
+	erl_param_t *f=(erl_param_t*)_f;
+	erl_param_t *a=(erl_param_t*)_a;
+	erl_param_t *r=(erl_param_t*)_r;
+
+	str module;
+	str function;
+	str vname;
+	sr_xavp_t *xreq=NULL;
+	sr_xavp_t *xrepl=NULL;
+	pv_spec_t sp;
+	pv_spec_t *nsp = NULL;
+	pv_param_t  pvp;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	int idx;
+	int idxf;
+	int attr;
+	ei_x_buff ei_req;
+	ei_x_buff ei_rep;
+
+	switch (m->type) {
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&module,msg,&m->value.fp)) {
+			LM_ERR("can't get module name\n");
+		}
+		break;
+	default:
+		LM_ERR("unexpected type for module name parameter\n");
+		return -1;
+	}
+
+	switch (f->type) {
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&function,msg,&f->value.fp)) {
+			LM_ERR("can't get function name\n");
+		}
+		break;
+	default:
+		LM_ERR("unexpected type for function name parameter\n");
+		return -1;
+	}
+
+	switch(a->type){
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&vname,msg,&a->value.fp)){
+			LM_ERR("can't get name of arguments parameter\n");
+			return -1;
+		}
+		xreq = pv_list_get_list(&vname);
+		if (!xreq){
+			xreq = pv_xbuff_get_xbuff(&vname);
+		}
+		if (!xreq) {
+			LM_ERR("can't find variable $list(%.*s) nor $xbuff(%.*s)",STR_FMT(&vname),STR_FMT(&vname));
+			return -1;
+		}
+		break;
+	case ERL_PARAM_XBUFF_SPEC:
+		sp = a->value.sp;
+		pvp = sp.pvp; /* work on copy */
+
+		if (pvp.pvn.type != PV_NAME_INTSTR || !(pvp.pvn.u.isname.type & AVP_NAME_STR)) {
+			LM_ERR("unsupported name of list\n");
+			return -1;
+		}
+
+		if( pvp.pvn.type == PV_NAME_PVAR) {
+			nsp = pvp.pvn.u.dname;
+		}
+
+		if (nsp) {
+			pvi = &nsp->pvp.pvi;
+			pvn = &nsp->pvp.pvn;
+		} else {
+			pvi = &pvp.pvi;
+			pvn = &pvp.pvn;
+		}
+
+		if (sp.setf == pv_list_set ) {
+			xreq = pv_list_get_list(&pvn->u.isname.name.s);
+		} else if (sp.setf == pv_xbuff_set) {
+			xreq = pv_xbuff_get_xbuff(&pvn->u.isname.name.s);
+		}  else if (sp.setf == pv_tuple_set) {
+			xreq = pv_tuple_get_tuple(&pvn->u.isname.name.s);
+		}
+
+		/* fix index */
+		attr = xbuff_get_attr_flags(pvi->type);
+		pvi->type = xbuff_fix_index(pvi->type);
+
+		/* get the index */
+		if(pv_get_spec_index(msg, &pvp, &idx, &idxf))
+		{
+			LM_ERR("invalid index\n");
+			return -1;
+		}
+
+		if (xbuff_is_attr_set(attr)) {
+			LM_WARN("attribute is not expected here!\n");
+		}
+
+		if (!xreq) {
+			LM_ERR("undefined variable '%.*s'\n",STR_FMT(&pvn->u.isname.name.s));
+			return -1;
+		}
+
+		xreq = xreq->val.v.xavp;
+
+		if ((idxf != PV_IDX_ALL) && !xbuff_is_no_index(attr) ) {
+			xreq = xavp_get_nth(&xreq->val.v.xavp,idx,NULL);
+		}
+
+		if (!xreq) {
+			LM_ERR("undefined value in '%.*s' at index %d\n",STR_FMT(&pvn->u.isname.name.s),idx);
+			return -1;
+		}
+
+		if (xreq->val.type != SR_XTYPE_XAVP || xreq->name.s[0] != 'l') {
+			LM_ERR("given value in parameter args is not list\n");
+			return -1;
+		}
+		break;
+	default:
+		LM_ERR("unexpected type for arguments parameter\n");
+		return -1;
+	}
+
+	switch(r->type){
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&vname,msg,&r->value.fp)){
+			LM_ERR("can't get name of arguments parameter\n");
+			return -1;
+		}
+		xrepl = pv_xbuff_get_xbuff(&vname);
+		break;
+	case ERL_PARAM_XBUFF_SPEC:
+		sp = r->value.sp;
+		pvp = sp.pvp; /* work on copy */
+
+		if (pvp.pvn.type != PV_NAME_INTSTR || !(pvp.pvn.u.isname.type & AVP_NAME_STR)) {
+			LM_ERR("unsupported name of xbuff\n");
+			return -1;
+		}
+
+		if( pvp.pvn.type == PV_NAME_PVAR) {
+			nsp = pvp.pvn.u.dname;
+		}
+
+		if (nsp) {
+			pvi = &nsp->pvp.pvi;
+			pvn = &nsp->pvp.pvn;
+		} else {
+			pvi = &pvp.pvi;
+			pvn = &pvp.pvn;
+		}
+
+		if (sp.setf == pv_xbuff_set ) {
+			xrepl = pv_xbuff_get_xbuff(&pvn->u.isname.name.s);
+		} else {
+			LM_ERR("unsupported variable type, xbuff only\n");
+			return -1;
+		}
+
+		/* fix index */
+		attr = xbuff_get_attr_flags(pvi->type);
+		pvi->type = xbuff_fix_index(pvi->type);
+
+		/* get the index */
+		if(pv_get_spec_index(msg, &pvp, &idx, &idxf))
+		{
+			LM_ERR("invalid index\n");
+			return -1;
+		}
+
+		if (xbuff_is_attr_set(attr)) {
+			LM_WARN("attribute is not expected here!\n");
+		}
+
+		if ((idxf != PV_IDX_ALL) && !xbuff_is_no_index(attr) ) {
+			LM_ERR("index is not expected here!\n");
+			return -1;
+		}
+
+		break;
+	default:
+		LM_ERR("unexpected type for arguments parameter\n");
+		return -1;
+	}
+
+	/* note: new without version byte */
+	ei_x_new(&ei_req);
+
+	/* ei_x_buff <- XAVP */
+	if (erl_api.xavp2xbuff(&ei_req,xreq)) {
+		LM_ERR("failed to encode\n");
+		ei_x_free(&ei_req);
+		return -1;
+	}
+
+	memset((void*)&ei_rep,0,sizeof(ei_x_buff));
+
+	erl_api.rpc(&ei_rep,&module,&function,&ei_req);
+
+	if (xrepl) {
+		xavp_destroy_list(&xrepl->val.v.xavp);
+	} else {
+		xrepl = xbuff_new(&pvn->u.isname.name.s);
+	}
+
+	/* must be XAVP */
+	xrepl->val.type = SR_XTYPE_XAVP;
+
+	/* XAVP <- ei_x_buff */
+	if (erl_api.xbuff2xavp(&xrepl->val.v.xavp,&ei_rep)){
+		LM_ERR("failed to decode\n");
+		ei_x_free(&ei_req);
+		ei_x_free(&ei_rep);
+		return -1;
+	}
+
+	ei_x_free(&ei_req);
+	ei_x_free(&ei_rep);
+
+	return 1;
+}
+
+static int fixup_rpc(void** param, int param_no)
+{
+	erl_param_t *erl_param;
+
+	str s;
+
+	erl_param=(erl_param_t*)pkg_malloc(sizeof(erl_param_t));
+	if(!erl_param) {
+		LM_ERR("no more memory\n");
+		return -1;
+	}
+	memset(erl_param,0,sizeof(erl_param_t));
+
+	if(param_no==1 || param_no==2) {
+		if (fix_param_types(FPARAM_STR|FPARAM_STRING|FPARAM_AVP|FPARAM_PVS|FPARAM_PVE,param)){
+			LM_ERR("wrong parameter #%d\n",param_no);
+			return -1;
+		}
+		erl_param->type = ERL_PARAM_FPARAM;
+		erl_param->value.fp = *(fparam_t*)*param;
+	}
+
+	if (param_no==3 || param_no==4) {
+
+		s.s = (char*)*param; s.len = strlen(s.s);
+
+		if (pv_parse_avp_name(&erl_param->value.sp,&s)) {
+			LM_ERR("failed to parse parameter #%d\n",param_no);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		}
+
+		if (erl_param->value.sp.pvp.pvn.type == PV_NAME_INTSTR) {
+			if (fix_param_types(FPARAM_STR|FPARAM_STRING,param)) {
+				LM_ERR("wrong parameter #%d\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+			erl_param->type = ERL_PARAM_FPARAM;
+			erl_param->value.fp = *(fparam_t*)*param;
+		} else if(pv_parse_spec( &s, &erl_param->value.sp)==NULL || erl_param->value.sp.type!=PVT_OTHER) {
+
+			/* only XBUFF is accepted for args and reply */
+			LM_ERR("wrong parameter #%d: accepted types are list of xbuff\n",param_no);
+			pv_spec_free(&erl_param->value.sp);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		} else {
+			/* lets check what is acceptable */
+			if (erl_param->value.sp.setf != pv_list_set && erl_param->value.sp.setf != pv_xbuff_set) {
+				LM_ERR("wrong parameter #%d: accepted types are list of xbuff\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+			erl_param->type = ERL_PARAM_XBUFF_SPEC;
+		}
+	}
+
+	*param = (void*)erl_param;
+
+	return 0;
+}
+
+static int erl_reg_send_k(struct sip_msg *msg, char *_server, char *_emsg)
+{
+	erl_param_t *param_server=(erl_param_t*)_server;
+	erl_param_t *param_emsg=(erl_param_t*)_emsg;
+
+	str server;
+	str str_msg;
+	sr_xavp_t *xmsg=NULL;
+	pv_spec_t sp;
+	pv_spec_t *nsp = NULL;
+	pv_param_t  pvp;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	int idx;
+	int idxf;
+	int attr;
+	ei_x_buff ei_msg;
+
+	switch (param_server->type) {
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&server,msg,&param_server->value.fp)) {
+			LM_ERR("can't get server process name\n");
+		}
+		break;
+	default:
+		LM_ERR("unexpected type for server name parameter\n");
+		return -1;
+	}
+
+	ei_x_new_with_version(&ei_msg);
+
+	switch(param_emsg->type){
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&str_msg,msg,&param_emsg->value.fp)){
+			LM_ERR("can't get emsg parameter\n");
+			goto err;
+		}
+
+		ei_x_encode_string_len(&ei_msg,str_msg.s,str_msg.len);
+
+		break;
+	case ERL_PARAM_XBUFF_SPEC:
+		sp = param_emsg->value.sp;
+		pvp = sp.pvp; /* work on copy */
+
+		if (pvp.pvn.type != PV_NAME_INTSTR || !(pvp.pvn.u.isname.type & AVP_NAME_STR)) {
+			LM_ERR("unsupported name of list\n");
+			return -1;
+		}
+
+		if( pvp.pvn.type == PV_NAME_PVAR) {
+			nsp = pvp.pvn.u.dname;
+		}
+
+		if (nsp) {
+			pvi = &nsp->pvp.pvi;
+			pvn = &nsp->pvp.pvn;
+		} else {
+			pvi = &pvp.pvi;
+			pvn = &pvp.pvn;
+		}
+
+		if (sp.setf == pv_list_set ) {
+			xmsg = pv_list_get_list(&pvn->u.isname.name.s);
+		} else if (sp.setf == pv_xbuff_set) {
+			xmsg = pv_xbuff_get_xbuff(&pvn->u.isname.name.s);
+		}  else if (sp.setf == pv_tuple_set) {
+			xmsg = pv_tuple_get_tuple(&pvn->u.isname.name.s);
+		}
+
+		/* fix index */
+		attr = xbuff_get_attr_flags(pvi->type);
+		pvi->type = xbuff_fix_index(pvi->type);
+
+		/* get the index */
+		if(pv_get_spec_index(msg, &pvp, &idx, &idxf))
+		{
+			LM_ERR("invalid index\n");
+			return -1;
+		}
+
+		if (xbuff_is_attr_set(attr)) {
+			LM_WARN("attribute is not expected here!\n");
+		}
+
+		if (!xmsg) {
+			LM_ERR("undefined variable '%.*s'\n",STR_FMT(&pvn->u.isname.name.s));
+			return -1;
+		}
+
+		xmsg = xmsg->val.v.xavp;
+
+		if ((idxf != PV_IDX_ALL) && !xbuff_is_no_index(attr) ) {
+			xmsg = xavp_get_nth(&xmsg->val.v.xavp,idx,NULL);
+		}
+
+		if (!xmsg) {
+			LM_ERR("undefined value in '%.*s' at index %d\n",STR_FMT(&pvn->u.isname.name.s),idx);
+			goto err;
+		}
+
+		/* ei_x_buff <- XAVP */
+		if (erl_api.xavp2xbuff(&ei_msg,xmsg)) {
+			LM_ERR("failed to encode %.*s\n",STR_FMT(&pvn->u.isname.name.s));
+			goto err;
+		}
+
+		break;
+	default:
+		LM_ERR("unexpected type for emsg parameter\n");
+		return -1;
+	}
+
+	if (erl_api.reg_send(&server,&ei_msg)) {
+		goto err;
+	}
+
+	ei_x_free(&ei_msg);
+
+	return 1;
+
+err:
+	ei_x_free(&ei_msg);
+
+	return -1;
+}
+
+static int fixup_reg(void** param, int param_no)
+{
+	erl_param_t *erl_param;
+
+	str s;
+
+	erl_param=(erl_param_t*)pkg_malloc(sizeof(erl_param_t));
+
+	if(!erl_param) {
+		LM_ERR("no more memory\n");
+		return -1;
+	}
+
+	memset(erl_param,0,sizeof(erl_param_t));
+
+	if(param_no==1) {
+		if (fix_param_types(FPARAM_STR|FPARAM_STRING|FPARAM_AVP|FPARAM_PVS|FPARAM_PVE|FPARAM_INT,param)){
+			LM_ERR("wrong parameter #%d\n",param_no);
+			return -1;
+		}
+		erl_param->type = ERL_PARAM_FPARAM;
+		erl_param->value.fp = *(fparam_t*)*param;
+	}
+
+	if (param_no==2) {
+
+		s.s = (char*)*param; s.len = strlen(s.s);
+
+		if (pv_parse_avp_name(&erl_param->value.sp,&s)) {
+			LM_ERR("failed to parse parameter #%d\n",param_no);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		}
+
+		if (erl_param->value.sp.pvp.pvn.type == PV_NAME_INTSTR) {
+			if (fix_param_types(FPARAM_STR|FPARAM_STRING,param)) {
+				LM_ERR("wrong parameter #%d\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+			LM_INFO("param emsg is PV_NAME_INTSTR\n");
+			erl_param->type = ERL_PARAM_FPARAM;
+			erl_param->value.fp = *(fparam_t*)*param;
+		} else if(pv_parse_spec( &s, &erl_param->value.sp)==NULL) {
+
+			/* only XBUFF is accepted for emsg and reply */
+			LM_ERR("wrong parameter #%d\n",param_no);
+			pv_spec_free(&erl_param->value.sp);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		} else {
+			if (erl_param->value.sp.type ==PVT_XAVP) {
+				LM_ERR("XAVP not acceptable for parameter #%d\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+
+			if (erl_param->value.sp.setf == pv_list_set
+					|| erl_param->value.sp.setf == pv_xbuff_set
+					|| erl_param->value.sp.setf == pv_tuple_set
+					|| erl_param->value.sp.setf == pv_atom_set) {
+
+				erl_param->type = ERL_PARAM_XBUFF_SPEC;
+			} else {
+				erl_param->type = ERL_PARAM_FPARAM;
+				erl_param->value.fp = *(fparam_t*)*param;
+			}
+		}
+	}
+
+	*param = (void*)erl_param;
+
+	return 0;
+}
+
+static int erl_reply_k(struct sip_msg *msg, char *_emsg)
+{
+	erl_param_t *param_emsg=(erl_param_t*)_emsg;
+
+	str str_msg;
+	sr_xavp_t *xmsg=NULL;
+	pv_spec_t sp;
+	pv_spec_t *nsp = NULL;
+	pv_param_t  pvp;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	int idx;
+	int idxf;
+	int attr;
+	ei_x_buff ei_msg;
+
+	ei_x_new_with_version(&ei_msg);
+
+	switch(param_emsg->type){
+	case ERL_PARAM_FPARAM:
+		if(get_str_fparam(&str_msg,msg,&param_emsg->value.fp)){
+			LM_ERR("can't get emsg parameter\n");
+			goto err;
+		}
+
+		ei_x_encode_string_len(&ei_msg,str_msg.s,str_msg.len);
+
+		break;
+	case ERL_PARAM_XBUFF_SPEC:
+		sp = param_emsg->value.sp;
+		pvp = sp.pvp; /* work on copy */
+
+		if (pvp.pvn.type != PV_NAME_INTSTR || !(pvp.pvn.u.isname.type & AVP_NAME_STR)) {
+			LM_ERR("unsupported name of list\n");
+			return -1;
+		}
+
+		if( pvp.pvn.type == PV_NAME_PVAR) {
+			nsp = pvp.pvn.u.dname;
+		}
+
+		if (nsp) {
+			pvi = &nsp->pvp.pvi;
+			pvn = &nsp->pvp.pvn;
+		} else {
+			pvi = &pvp.pvi;
+			pvn = &pvp.pvn;
+		}
+
+		if (sp.setf == pv_list_set ) {
+			xmsg = pv_list_get_list(&pvn->u.isname.name.s);
+		} else if (sp.setf == pv_xbuff_set) {
+			xmsg = pv_xbuff_get_xbuff(&pvn->u.isname.name.s);
+		}  else if (sp.setf == pv_tuple_set) {
+			xmsg = pv_tuple_get_tuple(&pvn->u.isname.name.s);
+		}
+
+		/* fix index */
+		attr = xbuff_get_attr_flags(pvi->type);
+		pvi->type = xbuff_fix_index(pvi->type);
+
+		/* get the index */
+		if(pv_get_spec_index(msg, &pvp, &idx, &idxf))
+		{
+			LM_ERR("invalid index\n");
+			return -1;
+		}
+
+		if (xbuff_is_attr_set(attr)) {
+			LM_WARN("attribute is not expected here!\n");
+		}
+
+		if (!xmsg) {
+			LM_ERR("undefined variable '%.*s'\n",STR_FMT(&pvn->u.isname.name.s));
+			return -1;
+		}
+
+		xmsg = xmsg->val.v.xavp;
+
+		if ((idxf != PV_IDX_ALL) && !xbuff_is_no_index(attr) ) {
+			xmsg = xavp_get_nth(&xmsg->val.v.xavp,idx,NULL);
+		}
+
+		if (!xmsg) {
+			LM_ERR("undefined value in '%.*s' at index %d\n",STR_FMT(&pvn->u.isname.name.s),idx);
+			goto err;
+		}
+
+		/* ei_x_buff <- XAVP */
+		if (erl_api.xavp2xbuff(&ei_msg,xmsg)) {
+			LM_ERR("failed to encode %.*s\n",STR_FMT(&pvn->u.isname.name.s));
+			goto err;
+		}
+
+		break;
+	default:
+		LM_ERR("unexpected type for emsg parameter\n");
+		return -1;
+	}
+
+	if (erl_api.reply(&ei_msg)) {
+		goto err;
+	}
+
+	ei_x_free(&ei_msg);
+
+	return 1;
+
+err:
+	ei_x_free(&ei_msg);
+
+	return -1;
+}
+
+static int fixup_reply(void** param, int param_no)
+{
+	erl_param_t *erl_param;
+
+	str s;
+
+	erl_param=(erl_param_t*)pkg_malloc(sizeof(erl_param_t));
+
+	if(!erl_param) {
+		LM_ERR("no more memory\n");
+		return -1;
+	}
+
+	memset(erl_param,0,sizeof(erl_param_t));
+
+	if (param_no==1) {
+
+		s.s = (char*)*param; s.len = strlen(s.s);
+
+		if (pv_parse_avp_name(&erl_param->value.sp,&s)) {
+			LM_ERR("failed to parse parameter #%d\n",param_no);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		}
+
+		if (erl_param->value.sp.pvp.pvn.type == PV_NAME_INTSTR) {
+			if (fix_param_types(FPARAM_STR|FPARAM_STRING,param)) {
+				LM_ERR("wrong parameter #%d\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+			LM_INFO("param emsg is PV_NAME_INTSTR\n");
+			erl_param->type = ERL_PARAM_FPARAM;
+			erl_param->value.fp = *(fparam_t*)*param;
+		} else if(pv_parse_spec( &s, &erl_param->value.sp)==NULL) {
+
+			/* only XBUFF is accepted for emsg and reply */
+			LM_ERR("wrong parameter #%d\n",param_no);
+			pv_spec_free(&erl_param->value.sp);
+			pkg_free((void*)erl_param);
+			return E_UNSPEC;
+		} else {
+			if (erl_param->value.sp.type ==PVT_XAVP) {
+				LM_ERR("XAVP not acceptable for parameter #%d\n",param_no);
+				pkg_free((void*)erl_param);
+				return E_UNSPEC;
+			}
+
+			if (erl_param->value.sp.setf == pv_list_set
+					|| erl_param->value.sp.setf == pv_xbuff_set
+					|| erl_param->value.sp.setf == pv_tuple_set
+					|| erl_param->value.sp.setf == pv_atom_set) {
+
+				erl_param->type = ERL_PARAM_XBUFF_SPEC;
+			} else {
+				erl_param->type = ERL_PARAM_FPARAM;
+				erl_param->value.fp = *(fparam_t*)*param;
+			}
+		}
+	}
+
+	*param = (void*)erl_param;
+
+	return 0;
+}

+ 77 - 0
modules/erlang/mod_erlang.h

@@ -0,0 +1,77 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef MOD_ERLANG_H_
+#define MOD_ERLANG_H_
+
+#include "../../str.h"
+#include "../../pvar.h"
+#include "../../xavp.h"
+#include "erl_api.h"
+
+extern str cookie; /* Erlang cookie */
+extern int trace_level; /* Tracing level on the distribution */
+extern str cnode_alivename;
+extern str cnode_host;
+extern int no_cnodes;
+extern int rpc_reply_with_struct;
+
+extern str erlang_nodename;
+extern str erlang_node_sname;
+
+/* sockets kamailio <-> cnode */
+extern int *usocks[2];
+
+extern int csockfd; /* socket to cnode process */
+
+#define KSOCKET	0
+#define CSOCKET	1
+
+/**
+ * types of command parameter
+ */
+typedef enum {
+	ERL_PARAM_FPARAM,
+	ERL_PARAM_XBUFF_SPEC
+} erl_param_type;
+
+/**
+ * command parameter
+ *
+ * combine str and PV
+ */
+typedef struct erl_param_s {
+	erl_param_type type;
+	union {
+		fparam_t fp;
+		pv_spec_t sp;
+	} value;
+} erl_param_t;
+
+/**
+ * Erlang ei bind
+ */
+
+extern erl_api_t erl_api;
+
+#endif /* MOD_ERLANG_H_ */

+ 396 - 0
modules/erlang/pv_atom.c

@@ -0,0 +1,396 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+
+#include "../../str.h"
+#include "../../xavp.h"
+#include "../../pvapi.h"
+
+#include "pv_atom.h"
+#include "pv_xbuff.h"
+
+#include <ei.h>
+
+static str atom_list=str_init("[atoms]");
+static char *atom_fmt_buff = NULL;
+static int counter;
+
+int pv_atom_parse_name(pv_spec_t *sp, str *in)
+{
+	char *p;
+	str idx;
+	str name;
+	str attr;
+
+	if (in->s == NULL || in->len <= 0)
+		return -1;
+
+	p = in->s;
+
+	name.s = p;
+
+	while (is_in_str(p, in)) {
+		if (*p == '[' || *p== '=')
+			break;
+		if (!is_pv_xbuff_valid_char(*p)) {
+			LM_ERR("invalid character in var name %.*s at %d\n",STR_FMT(in),p-in->s);
+			goto error;
+		}
+		p++;
+	}
+
+	/* from in->s to p */
+	name.len = p - in->s;
+
+	if (pv_parse_avp_name(sp,&name))
+		goto error;
+
+	if (is_in_str(p,in) && *p =='[')
+	{
+		idx.s=++p;
+
+		while (is_in_str(p,in)) {
+			if (*p == ']' || *p == '=')
+				break;
+			p++;
+		}
+
+		if (is_in_str(p,in) && *p==']') {
+			idx.len = p - idx.s;
+
+			LM_ERR("index isn't allowed for this variable\n");
+			goto error;
+		}
+		p++;
+	} else {
+		xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_NO_IDX);
+	}
+
+	if (is_in_str(p,in) && *p =='=')
+	{
+		p++;
+
+		if (!is_in_str(p,in) || *p!='>') {
+			LM_ERR("invalid operator (expected =>) for accessing attribute in token %.*s at position %d\n",STR_FMT(in),p-in->s);
+			goto error;
+		}
+
+		attr.s = ++p;
+
+		while (is_in_str(p,in)) {
+			if (!is_pv_xbuff_valid_char(*p)) {
+				LM_ERR("invalid character in attribute name in token %.*s at %d\n",STR_FMT(in),p-in->s);
+				goto error;
+			}
+			p++;
+		}
+
+		attr.len = p - attr.s;
+
+		if (attr.len > 0 ) {
+
+			if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_TYPE))) {
+				xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_ATTR_TYPE);
+			} else if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_FORMAT))) {
+				xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_ATTR_FORMAT);
+			} else if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_LENGTH))) {
+				LM_ERR("attribute isn't supported for this variable\n");
+				goto error;
+			} else {
+				LM_ERR("unknown attribute %.*s\n",STR_FMT(&attr));
+				goto error;
+			}
+		}
+	}
+
+	if (p < in->s + in->len) {
+		LM_ERR("unexpected token in %.*s at %d\n",STR_FMT(in),p-in->s);
+		goto error;
+	}
+
+	return 0;
+
+error:
+
+	return -1;
+}
+
+sr_xavp_t *xavp_get_atoms()
+{
+	sr_xavp_t *list;
+	list = xavp_get(&atom_list,NULL);
+
+	if(!list) counter = 0;
+
+	return list;
+}
+
+int pv_atom_new_xavp(sr_xval_t *xval, pv_value_t *pval, int *counter)
+{
+	char s[32];
+	str name;
+	sr_xavp_t *xavp = NULL;
+	sr_xval_t nval;
+	xbuff_type_t type;
+
+	if (!xval) return -1;
+
+	memset((void*)xval,0,sizeof(sr_xval_t));
+
+	if (pval->flags&PV_VAL_NULL) {
+		nval.type = SR_XTYPE_NULL;
+	} else if (pval->flags&PV_VAL_INT) {
+		LM_ERR("can't convert integer to atom\n");
+		return -1;
+	} else if (pval->flags&PV_VAL_STR) {
+		/* check what it is */
+		if (xbuff_match_type_re(&pval->rs,&type,&xavp)) {
+			nval.type = SR_XTYPE_STR;
+			nval.v.s = pval->rs;
+		} else {
+			switch (type) {
+			case XBUFF_TYPE_ATOM:
+			case XBUFF_TYPE_STR:
+				break;
+			case XBUFF_TYPE_TUPLE:
+			case XBUFF_TYPE_LIST:
+			case XBUFF_TYPE_INT:
+				LM_ERR("can't convert integer, tuple or list into atom\n");
+				return -1;
+				break;
+			default:
+				LM_ERR("BUG: unexpected XBUFF type!\n");
+				return -1;
+			}
+
+			/* copy tree */
+			nval.type = SR_XTYPE_STR;
+			nval.v.s = xavp->val.v.s;
+		}
+	}
+
+	name.s = s;
+	name.len = snprintf(s,31,"a%d",(*counter)++) + 1;
+
+	xavp = xavp_new_value(&name,&nval);
+
+	if (!xavp) {
+		return -1;
+	}
+
+	xval->type = SR_XTYPE_XAVP;
+	xval->v.xavp = xavp;
+
+	return 0;
+}
+
+int pv_atom_set(struct sip_msg* msg,  pv_param_t* param, int op, pv_value_t* val)
+{
+	str name;
+	sr_xavp_t *atoms_root;
+	sr_xavp_t *atom;
+	sr_xavp_t *new,*old=NULL;
+	sr_xavp_t *atom_xavp;
+	sr_xval_t atom_val;
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR)) {
+		LM_ERR("invalid variable name type\n");
+		return -1;
+	}
+
+	if(pv_xbuff_new_xavp(&atom_xavp,val,&counter,'a')) {
+		LM_ERR("failed to create new value\n");
+		return -1;
+	}
+
+	/* atom name */
+	name = param->pvn.u.isname.name.s;
+
+	memset((void*)&atom_val,0,sizeof(sr_xval_t));
+
+	atoms_root = xavp_get_atoms();
+
+	if(!atoms_root) {
+
+		atom_val.type = SR_XTYPE_XAVP;
+		atom_val.v.xavp = atom_xavp;
+		atom = xavp_add_xavp_value(&atom_list,&name,&atom_val,xavp_get_crt_list());
+
+		if (!atom)
+			goto err;
+
+		return 0;
+	}
+
+	atom = xavp_get_child(&atom_list, &name);
+
+	if (!atom) {
+
+		atom_val.type = SR_XTYPE_XAVP;
+		atom_val.v.xavp = atom_xavp;
+
+		new = xavp_add_value(&name,&atom_val,&atoms_root->val.v.xavp);
+
+		if (!new)
+			goto err;
+
+		return 0;
+	}
+
+	old = atom->val.v.xavp;
+	new = atom_xavp;
+
+	if (old) {
+		xavp_destroy_list(&old);
+	}
+
+	atom->val.v.xavp = new;
+
+	return 0;
+
+err:
+	LM_ERR("failed to set atom value\n");
+	xavp_destroy_list(&atom_xavp);
+
+	return -1;
+}
+int pv_atom_get_value(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp)
+{
+	static char _pv_xavp_buf[128];
+	str s;
+
+	if (!avp) return pv_get_null(msg,param,res);
+
+	switch(avp->val.type) {
+	case SR_XTYPE_NULL:
+		return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_STR:
+		if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>", avp)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_XAVP:
+		if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>", avp->val.v.xavp)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_DATA:
+	case SR_XTYPE_INT:
+	case SR_XTYPE_TIME:
+	case SR_XTYPE_LONG:
+	case SR_XTYPE_LLONG:
+		LM_ERR("BUG: unexpected atom value\n");
+		return pv_get_null(msg, param, res);
+		break;
+	default:
+		return pv_get_null(msg, param, res);
+	}
+	s.s = _pv_xavp_buf;
+	s.len = strlen(_pv_xavp_buf);
+	return pv_get_strval(msg, param, res, &s);
+}
+
+int pv_atom_get(struct sip_msg* msg,  pv_param_t* param, pv_value_t* res)
+{
+	str name;
+	sr_xavp_t *atoms_root;
+	sr_xavp_t *atom;
+	sr_xavp_t *xavp;
+	int attr;
+	int i;
+
+	ei_x_buff xbuff;
+
+	if(param==NULL)
+	{
+		LM_ERR("bad parameters\n");
+		return -1;
+	}
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR))
+				return -1;
+
+	/* tuple name */
+	name = param->pvn.u.isname.name.s;
+	/* attributes */
+	attr = xbuff_get_attr_flags(param->pvi.type);
+
+	atoms_root = xavp_get_atoms();
+	if(!atoms_root) {
+		return pv_get_null(msg,param,res);
+	}
+
+	atom = xavp_get(&name,atoms_root->val.v.xavp);
+	if (!atom) {
+		return pv_get_null(msg,param,res);
+	}
+
+	xavp = atom->val.v.xavp;
+
+	switch (xbuff_is_attr_set(attr)) {
+	case XBUFF_ATTR_TYPE:
+		return pv_get_strval(msg,param,res,&xbuff_types[XBUFF_TYPE_ATOM]);
+		break;
+	case XBUFF_ATTR_LENGTH: /* always 1 */
+		return pv_get_uintval(msg,param,res,1);
+		break;
+	case XBUFF_ATTR_FORMAT:
+		/*
+		 * Prints a term, in clear text, to the PV value pointed to by res.
+		 * It tries to resemble the term printing in the erlang shell.
+		 */
+		ei_x_new_with_version(&xbuff);
+		if (xavp && xavp_encode(&xbuff,xavp,1)) {
+			ei_x_free(&xbuff);
+			return -1;
+		} else {
+			ei_x_encode_atom(&xbuff,"undefined");
+		}
+		i = 1;
+		if (ei_s_print_term(&atom_fmt_buff,xbuff.buff,&i)<0) {
+			LM_ERR("BUG: xbuff[index] doesn't contain a valid term!\n");
+			ei_x_free(&xbuff);
+			return -1;
+		}
+		i = pv_get_strzval(msg,param,res,atom_fmt_buff);
+		ei_x_free(&xbuff);
+		return i;
+	}
+
+	if (!xavp) {
+		return pv_get_null(msg,param,res);
+	}
+
+	return pv_atom_get_value(msg,param,res,xavp);
+}
+
+/*
+ * free format buffer for tuple
+ */
+void free_atom_fmt_buff() {
+	if (atom_fmt_buff) {
+		free(atom_fmt_buff);
+	}
+	atom_fmt_buff = 0;
+}

+ 35 - 0
modules/erlang/pv_atom.h

@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_ATOM_H_
+#define PV_ATOM_H_
+
+#include "../../pvar.h"
+int pv_atom_parse_name(pv_spec_t *sp, str *in);
+
+int pv_atom_set(struct sip_msg*, pv_param_t*, int, pv_value_t*);
+int pv_atom_get(struct sip_msg*, pv_param_t*, pv_value_t*);
+
+void free_atom_fmt_buff();
+
+#endif /* PV_ATOM_H_ */

+ 392 - 0
modules/erlang/pv_list.c

@@ -0,0 +1,392 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+
+#include "../../pvar.h"
+
+#include "pv_list.h"
+#include "pv_xbuff.h"
+
+static str list_list=str_init("[lists]");
+
+static int counter;
+static char *list_fmt_buff = NULL;
+
+sr_xavp_t *xavp_get_lists()
+{
+	sr_xavp_t *list;
+	list = xavp_get(&list_list,NULL);
+
+	if(!list) counter = 0;
+
+	return list;
+}
+
+sr_xavp_t *pv_list_get_list(str *name)
+{
+	return xavp_get_child(&list_list, name);
+}
+
+int pv_list_set(struct sip_msg* msg,  pv_param_t* param, int op, pv_value_t* val)
+{
+	str name;
+	sr_xavp_t *lists_root;
+	sr_xavp_t *list;
+	sr_xavp_t *lh,*new,*old,*prv=NULL;
+	sr_xavp_t *list_xavp;
+	sr_xavp_t *item_xavp;
+	sr_xval_t list_val;
+	pv_param_t p;
+	pv_value_t empty;
+	int idx = 0;
+	int idxf = 0;
+	int attr = 0;
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR)) {
+		LM_ERR("invalid variable name type\n");
+		return -1;
+	}
+
+	memset((void*)&empty,0,sizeof(pv_value_t));
+	empty.flags = PV_VAL_NULL;
+
+	/* list name */
+	name = param->pvn.u.isname.name.s;
+
+	lists_root = xavp_get_lists();
+
+	if(!lists_root) {
+
+		if(pv_xbuff_new_xavp(&list_xavp,&empty,&counter,'l')) {
+			LM_ERR("failed to create new value\n");
+			return -1;
+		}
+
+		list_val.type = SR_XTYPE_XAVP;
+		list_val.v.xavp = list_xavp;
+		list = xavp_add_xavp_value(&list_list,&name,&list_val,xavp_get_crt_list());
+
+		if (!list)
+			goto err;
+	}
+
+	list=xavp_get_child(&list_list, &name);
+
+	if (!list) {
+
+		if(pv_xbuff_new_xavp(&list_xavp,&empty,&counter,'l')) {
+			LM_ERR("failed to create new value\n");
+			return -1;
+		}
+
+		list_val.type = SR_XTYPE_XAVP;
+		list_val.v.xavp = list_xavp;
+
+		list=xavp_add_value(&name,&list_val,&lists_root->val.v.xavp);
+
+		if (!list)
+			goto err;
+	}
+
+	lh = list->val.v.xavp;
+
+	if(pv_xbuff_new_xavp(&item_xavp,val,&counter,0)) {
+		LM_ERR("failed to create new value\n");
+		return -1;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(p.pvi.type);
+	p.pvi.type = xbuff_fix_index(p.pvi.type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	if (xbuff_is_attr_set(attr)) {
+		LM_ERR("read only attribute %.*s\n",STR_FMT(&xbuff_attr_name(attr)));
+		return -1;
+	}
+
+	/* prepend on list when no index */
+	if (xbuff_is_no_index(attr)) {
+		if (lh->val.type == SR_XTYPE_NULL) {
+			lh->val.type = SR_XTYPE_XAVP;
+			lh->val.v.xavp = item_xavp;
+			return 0;
+		} else {
+			return xavp_add(item_xavp,&lh->val.v.xavp);
+		}
+	}
+
+	if (idxf == PV_IDX_ALL) {
+		xavp_destroy_list(&lh->val.v.xavp);
+		if (item_xavp->val.type == SR_XTYPE_NULL) {
+			/* create empty list */
+			xavp_destroy_list(&item_xavp);
+
+			memset((void*)&lh->val,0,sizeof(sr_xval_t));
+			lh->val.type = SR_XTYPE_NULL;
+			return 0;
+		} else {
+			return xavp_add(item_xavp,&lh->val.v.xavp);
+		}
+	}
+
+	/* set by index */
+	old = xavp_get_nth(&lh->val.v.xavp,idx,&prv);
+	new = item_xavp;
+
+	if (old) {
+		new->next = old->next;
+		if (prv) {
+			prv->next = new;
+		} else {
+			new->next = old->next;
+			lh->val.v.xavp = new;
+		}
+		old->next = NULL;
+		xavp_destroy_list(&old);
+	} else {
+		if (prv) {
+			prv->next = new;
+		} else {
+			lh->val.v.xavp = new;
+		}
+	}
+
+	return 0;
+
+err:
+	LM_ERR("failed to add value into list\n");
+	xavp_destroy_list(&list_xavp);
+	xavp_destroy_list(&item_xavp);
+
+	return -1;
+}
+
+int pv_list_get_value(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp)
+{
+	static char _pv_xavp_buf[128];
+	str s;
+
+	if (!avp) return pv_get_null(msg,param,res);
+
+	switch(avp->val.type) {
+	case SR_XTYPE_NULL:
+		return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_INT:
+		return pv_get_sintval(msg, param, res, avp->val.v.i);
+		break;
+	case SR_XTYPE_STR:
+		switch (avp->name.s[0]) {
+		case 'a':
+			if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		default:
+			return pv_get_strval(msg, param, res, &avp->val.v.s);
+		}
+		break;
+	case SR_XTYPE_TIME:
+		if(snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LONG:
+		if(snprintf(_pv_xavp_buf, 128, "%ld", (long unsigned)avp->val.v.l)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LLONG:
+		if(snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_XAVP:
+		switch(avp->name.s[0]) {
+		case 't':
+			if(snprintf(_pv_xavp_buf, 128, "<<tuple:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		case 'l':
+		default:
+			if(snprintf(_pv_xavp_buf, 128, "<<list:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+		}
+		break;
+	case SR_XTYPE_DATA:
+		if(snprintf(_pv_xavp_buf, 128, "<<binary:%p>>", avp->val.v.data)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	default:
+		return pv_get_null(msg, param, res);
+	}
+	s.s = _pv_xavp_buf;
+	s.len = strlen(_pv_xavp_buf);
+	return pv_get_strval(msg, param, res, &s);
+}
+
+int pv_list_get(struct sip_msg* msg,  pv_param_t* param, pv_value_t* res)
+{
+	str name;
+	pv_spec_t *nsp = NULL;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	sr_xavp_t *lists_root;
+	sr_xavp_t *list;
+	sr_xavp_t *lh;
+	sr_xavp_t *xavp;
+	pv_param_t p;
+	int idx=0;
+	int idxf=0;
+	int attr;
+	int i,count;
+
+	ei_x_buff xbuff;
+
+	if(param==NULL)
+	{
+		LM_ERR("bad parameters\n");
+		return -1;
+	}
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR))
+				return -1;
+
+	if( param->pvn.type == PV_NAME_PVAR) {
+			nsp = param->pvn.u.dname;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	if (nsp) {
+		pvi = &nsp->pvp.pvi;
+		pvn = &nsp->pvp.pvn;
+	} else {
+		pvi = &p.pvi;
+		pvn = &p.pvn;
+	}
+
+	/* list name */
+	name = pvn->u.isname.name.s;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(pvi->type);
+	pvi->type = xbuff_fix_index(pvi->type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	lists_root = xavp_get_lists();
+	if(!lists_root) {
+		return pv_get_null(msg,param,res);
+	}
+
+	list = xavp_get(&name,lists_root->val.v.xavp);
+	if (!list) {
+		return pv_get_null(msg,param,res);
+	}
+
+	lh = list->val.v.xavp;
+
+	switch (xbuff_is_attr_set(attr)) {
+	case XBUFF_ATTR_TYPE:
+		if (xbuff_is_no_index(attr)) {
+			return pv_get_strval(msg,param,res,&xbuff_types[XBUFF_TYPE_LIST]);
+		} else {
+			xavp = xavp_get_nth(&lh->val.v.xavp,idx,NULL);
+			return pv_xbuff_get_type(msg,param,res,xavp);
+		}
+		break;
+	case XBUFF_ATTR_LENGTH:
+		xavp = xbuff_is_no_index(attr) ? lh : xavp_get_nth(&lh->val.v.xavp,idx,NULL);
+
+		if (xavp) {
+			count = xavp_get_count(xavp->val.v.xavp);
+			return pv_get_uintval(msg,param,res,(unsigned int)count);
+		} else {
+			return pv_get_null(msg,param,res);
+		}
+		break;
+	case XBUFF_ATTR_FORMAT:
+		/*
+		 * Prints a term, in clear text, to the PV value pointed to by res.
+		 * It tries to resemble the term printing in the erlang shell.
+		 */
+		ei_x_new_with_version(&xbuff);
+
+		xavp = xbuff_is_no_index(attr) ? lh : xavp_get_nth(&lh->val.v.xavp,idx,NULL);
+		if (!xavp || xavp_encode(&xbuff,xavp,1)) {
+			ei_x_free(&xbuff);
+			return -1;
+		}
+		i = 1;
+		if (ei_s_print_term(&list_fmt_buff,xbuff.buff,&i)<0) {
+			LM_ERR("BUG: xbuff[index] doesn't contain a valid term!\n");
+			ei_x_free(&xbuff);
+			return -1;
+		}
+		i = pv_get_strzval(msg,param,res,list_fmt_buff);
+		ei_x_free(&xbuff);
+		return i;
+	}
+
+	/* get whole list */
+	if ((idxf == PV_IDX_ALL) || xbuff_is_no_index(attr)) {
+		return pv_list_get_value(msg,param,res,list);
+	}
+
+	if (lh->val.type == SR_XTYPE_NULL) {
+		return pv_get_null(msg,param,res);
+	}
+
+	/* get by idx */
+	xavp = xavp_get_nth(&lh->val.v.xavp,idx,NULL);
+	if (!xavp) {
+		return pv_get_null(msg,param,res);
+	}
+
+	return pv_list_get_value(msg,param,res,xavp);
+}
+
+/*
+ * free format buffer for list
+ */
+void free_list_fmt_buff() {
+	if (list_fmt_buff) {
+		free(list_fmt_buff);
+	}
+	list_fmt_buff = 0;
+}

+ 36 - 0
modules/erlang/pv_list.h

@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+#ifndef PV_LIST_H_
+#define PV_LIST_H_
+
+#include "../../pvar.h"
+#include "../../xavp.h"
+
+int pv_list_set(struct sip_msg*, pv_param_t*, int, pv_value_t*);
+int pv_list_get(struct sip_msg*, pv_param_t*, pv_value_t*);
+
+sr_xavp_t *pv_list_get_list(str *name);
+
+void free_list_fmt_buff();
+
+#endif /* PV_LIST_H_ */

+ 393 - 0
modules/erlang/pv_tuple.c

@@ -0,0 +1,393 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+
+#include "../../pvar.h"
+
+#include "pv_tuple.h"
+#include "pv_xbuff.h"
+
+static str tuple_list=str_init("[tuples]");
+
+static int counter;
+static char *tuple_fmt_buff = NULL;
+
+sr_xavp_t *xavp_get_tuples()
+{
+	sr_xavp_t *list;
+	list = xavp_get(&tuple_list,NULL);
+
+	if(!list) counter = 0;
+
+	return list;
+}
+
+sr_xavp_t *pv_tuple_get_tuple(str *name)
+{
+	return xavp_get_child(&tuple_list,name);
+}
+
+int pv_tuple_set(struct sip_msg* msg,  pv_param_t* param, int op, pv_value_t* val)
+{
+	str name;
+	sr_xavp_t *tuples_root;
+	sr_xavp_t *tuple;
+	sr_xavp_t *th,*new,*old,*prv=NULL;
+	sr_xavp_t *tuple_xavp;
+	sr_xavp_t *elem_xavp;
+	sr_xval_t tuple_val;
+	pv_param_t p;
+	pv_value_t empty;
+	int idx = 0;
+	int idxf = 0;
+	int attr = 0;
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR)) {
+		LM_ERR("invalid variable name type\n");
+		return -1;
+	}
+
+	memset((void*)&empty,0,sizeof(pv_value_t));
+	empty.flags = PV_VAL_NULL;
+
+	memset((void*)&tuple_val,0,sizeof(sr_xval_t));
+
+	/* list name */
+	name = param->pvn.u.isname.name.s;
+
+	tuples_root = xavp_get_tuples();
+
+	if(!tuples_root) {
+
+		if(pv_xbuff_new_xavp(&tuple_xavp,&empty,&counter,'t')) {
+			LM_ERR("failed to create new value\n");
+			return -1;
+		}
+		tuple_val.type = SR_XTYPE_XAVP;
+		tuple_val.v.xavp = tuple_xavp;
+
+		tuple = xavp_add_xavp_value(&tuple_list,&name,&tuple_val,xavp_get_crt_list());
+
+		if (!tuple)
+			goto err;
+	}
+
+	tuple=xavp_get_child(&tuple_list, &name);
+
+	if (!tuple) {
+
+
+		if(pv_xbuff_new_xavp(&tuple_xavp,&empty,&counter,'t')) {
+			LM_ERR("failed to create new value\n");
+			return -1;
+		}
+
+		tuple_val.type = SR_XTYPE_XAVP;
+		tuple_val.v.xavp = tuple_xavp;
+
+		tuple=xavp_add_value(&name,&tuple_val,&tuples_root->val.v.xavp);
+
+		if (!tuple)
+			goto err;
+	}
+
+	th = tuple->val.v.xavp;
+
+	if(pv_xbuff_new_xavp(&elem_xavp,val,&counter,0)) {
+		LM_ERR("failed to create new value\n");
+		return -1;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(p.pvi.type);
+	p.pvi.type = xbuff_fix_index(p.pvi.type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	if (xbuff_is_attr_set(attr)) {
+		LM_ERR("read only attribute %.*s\n",STR_FMT(&xbuff_attr_name(attr)));
+		return -1;
+	}
+
+	/* prepend on list when no index */
+	if (xbuff_is_no_index(attr)) {
+		if (th->val.type == SR_XTYPE_NULL) {
+			th->val.type = SR_XTYPE_XAVP;
+			th->val.v.xavp = elem_xavp;
+			return 0;
+		} else {
+			return xavp_add(elem_xavp,&th->val.v.xavp);
+		}
+	}
+
+	if (idxf == PV_IDX_ALL) {
+		xavp_destroy_list(&th->val.v.xavp);
+		if (elem_xavp->val.type == SR_XTYPE_NULL) {
+			/* create empty list */
+			xavp_destroy_list(&elem_xavp);
+
+			memset((void*)&th->val,0,sizeof(sr_xval_t));
+			th->val.type = SR_XTYPE_NULL;
+			return 0;
+		} else {
+			return xavp_add(elem_xavp,&th->val.v.xavp);
+		}
+	}
+
+	/* set by index */
+	old = xavp_get_nth(&th->val.v.xavp,idx,&prv);
+	new = elem_xavp;
+
+	if (old) {
+		new->next = old->next;
+		if (prv) {
+			prv->next = new;
+		} else {
+			new->next = old->next;
+			th->val.v.xavp = new;
+		}
+		old->next = NULL;
+		xavp_destroy_list(&old);
+	} else {
+		if (prv) {
+			prv->next = new;
+		} else {
+			th->val.v.xavp = new;
+		}
+	}
+
+	return 0;
+
+err:
+	LM_ERR("failed to add value into tuple\n");
+	xavp_destroy_list(&tuple_xavp);
+	xavp_destroy_list(&elem_xavp);
+
+	return -1;
+}
+
+int pv_tuple_get_value(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp)
+{
+	static char _pv_xavp_buf[128];
+	str s;
+
+	if (!avp) return pv_get_null(msg,param,res);
+
+	switch(avp->val.type) {
+	case SR_XTYPE_NULL:
+		return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_INT:
+		return pv_get_sintval(msg, param, res, avp->val.v.i);
+		break;
+	case SR_XTYPE_STR:
+		switch (avp->name.s[0]) {
+		case 'a':
+			if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		default:
+			return pv_get_strval(msg, param, res, &avp->val.v.s);
+		}
+		break;
+	case SR_XTYPE_TIME:
+		if(snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LONG:
+		if(snprintf(_pv_xavp_buf, 128, "%ld", (long unsigned)avp->val.v.l)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LLONG:
+		if(snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_XAVP:
+		switch(avp->name.s[0]) {
+		case 'l':
+			if(snprintf(_pv_xavp_buf, 128, "<<list:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		case 't':
+		default:
+				if(snprintf(_pv_xavp_buf, 128, "<<tuple:%p>>", avp->val.v.xavp)<0)
+					return pv_get_null(msg, param, res);
+		}
+		break;
+	case SR_XTYPE_DATA:
+		if(snprintf(_pv_xavp_buf, 128, "<<binary:%p>>", avp->val.v.data)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	default:
+		return pv_get_null(msg, param, res);
+	}
+	s.s = _pv_xavp_buf;
+	s.len = strlen(_pv_xavp_buf);
+	return pv_get_strval(msg, param, res, &s);
+}
+
+int pv_tuple_get(struct sip_msg* msg,  pv_param_t* param, pv_value_t* res)
+{
+	str name;
+	pv_spec_t *nsp = NULL;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	sr_xavp_t *tuples_root;
+	sr_xavp_t *tuple;
+	sr_xavp_t *th;
+	sr_xavp_t *xavp;
+	pv_param_t p;
+	int idx=0;
+	int idxf=0;
+	int attr;
+	int i,count;
+
+	ei_x_buff xbuff;
+
+	if(param==NULL)
+	{
+		LM_ERR("bad parameters\n");
+		return -1;
+	}
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR))
+				return -1;
+
+	if( param->pvn.type == PV_NAME_PVAR) {
+			nsp = param->pvn.u.dname;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	if (nsp) {
+		pvi = &nsp->pvp.pvi;
+		pvn = &nsp->pvp.pvn;
+	} else {
+		pvi = &p.pvi;
+		pvn = &p.pvn;
+	}
+
+	/* list name */
+	name = pvn->u.isname.name.s;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(pvi->type);
+	pvi->type = xbuff_fix_index(pvi->type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	tuples_root = xavp_get_tuples();
+	if(!tuples_root) {
+		return pv_get_null(msg,param,res);
+	}
+
+	tuple = xavp_get(&name,tuples_root->val.v.xavp);
+	if (!tuple) {
+		return pv_get_null(msg,param,res);
+	}
+
+	th = tuple->val.v.xavp;
+
+	switch (xbuff_is_attr_set(attr)) {
+	case XBUFF_ATTR_TYPE:
+		if (xbuff_is_no_index(attr)) {
+			return pv_get_strval(msg,param,res,&xbuff_types[XBUFF_TYPE_TUPLE]);
+		} else {
+			xavp = xavp_get_nth(&th->val.v.xavp,idx,NULL);
+			return pv_xbuff_get_type(msg,param,res,xavp);
+		}
+		break;
+	case XBUFF_ATTR_LENGTH:
+		xavp = xbuff_is_no_index(attr) ? th : xavp_get_nth(&th->val.v.xavp,idx,NULL);
+		if (xavp) {
+			count = xavp_get_count(xavp->val.v.xavp);
+			return pv_get_uintval(msg,param,res,(unsigned int)count);
+		} else {
+			return pv_get_null(msg,param,res);
+		}
+		break;
+	case XBUFF_ATTR_FORMAT:
+		/*
+		 * Prints a term, in clear text, to the PV value pointed to by res.
+		 * It tries to resemble the term printing in the erlang shell.
+		 */
+		ei_x_new_with_version(&xbuff);
+		xavp = xbuff_is_no_index(attr) ? th : xavp_get_nth(&th->val.v.xavp,idx,NULL);
+		if (!xavp || xavp_encode(&xbuff,xavp,1)) {
+			ei_x_free(&xbuff);
+			return -1;
+		}
+		i = 1;
+		if (ei_s_print_term(&tuple_fmt_buff,xbuff.buff,&i)<0) {
+			LM_ERR("BUG: xbuff[index] doesn't contain a valid term!\n");
+			ei_x_free(&xbuff);
+			return -1;
+		}
+		i = pv_get_strzval(msg,param,res,tuple_fmt_buff);
+		ei_x_free(&xbuff);
+		return i;
+	}
+
+	/* get whole list */
+	if ((idxf == PV_IDX_ALL) || xbuff_is_no_index(attr)) {
+		return pv_tuple_get_value(msg,param,res,tuple);
+	}
+
+	if (th->val.type == SR_XTYPE_NULL) {
+		return pv_get_null(msg,param,res);
+	}
+
+	/* get by idx */
+	xavp = xavp_get_nth(&th->val.v.xavp,idx,NULL);
+	if (!xavp) {
+		return pv_get_null(msg,param,res);
+	}
+
+	return pv_tuple_get_value(msg,param,res,xavp);
+}
+
+/*
+ * free format buffer for tuple
+ */
+void free_tuple_fmt_buff() {
+	if (tuple_fmt_buff) {
+		free(tuple_fmt_buff);
+	}
+	tuple_fmt_buff = 0;
+}

+ 37 - 0
modules/erlang/pv_tuple.h

@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_TUPLE_H_
+#define PV_TUPLE_H_
+
+#include "../../pvar.h"
+#include "../../xavp.h"
+
+int pv_tuple_set(struct sip_msg*, pv_param_t*, int, pv_value_t*);
+int pv_tuple_get(struct sip_msg*, pv_param_t*, pv_value_t*);
+
+sr_xavp_t *pv_tuple_get_tuple(str *name);
+
+void free_tuple_fmt_buff();
+
+#endif /* PV_TUPLE_H_ */

+ 1153 - 0
modules/erlang/pv_xbuff.c

@@ -0,0 +1,1153 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+
+#include "../../str.h"
+#include "../../hashes.h"
+
+#include "pv_xbuff.h"
+
+str xbuff_attributes[] = {
+		STR_STATIC_INIT("type"),
+		STR_STATIC_INIT("format"),
+		STR_STATIC_INIT("length"),
+		STR_NULL
+};
+
+str xbuff_types[] = {
+		STR_STATIC_INIT("atom"),
+		STR_STATIC_INIT("integer"),
+		STR_STATIC_INIT("string"),
+		STR_STATIC_INIT("tuple"),
+		STR_STATIC_INIT("list"),
+		STR_NULL
+};
+
+/**
+ * atom,tuple,xbuff & list regex
+ */
+regex_t xbuff_type_re = {0};
+
+static str xbuff_list=str_init("[xbuffs]");
+
+static int counter;
+static char *xbuff_fmt_buff = NULL;
+
+sr_xavp_t *xavp_get_xbuffs()
+{
+	sr_xavp_t *list;
+	list = xavp_get(&xbuff_list,NULL);
+
+	if(!list) counter = 0;
+
+	return list;
+}
+
+sr_xavp_t *pv_xbuff_get_xbuff(str *name)
+{
+	return xavp_get_child(&xbuff_list,name);
+}
+
+sr_xavp_t *xbuff_new(str *name)
+{
+	sr_xavp_t *xbuffs_root;
+	sr_xavp_t *xbuff;
+	sr_xval_t xbuff_val;
+
+	memset((void*)&xbuff_val,0,sizeof(sr_xval_t));
+	xbuff_val.type = SR_XTYPE_NULL;
+
+	xbuffs_root = xavp_get_xbuffs();
+
+	if(!xbuffs_root)
+	{
+		xbuff = xavp_add_xavp_value(&xbuff_list,name,&xbuff_val,xavp_get_crt_list());
+	}
+
+	xbuff=xavp_get_child(&xbuff_list, name);
+
+	if (!xbuff) {
+
+		xbuff_val.type = SR_XTYPE_NULL;
+		xbuff_val.v.xavp = NULL;
+
+		xbuff=xavp_add_value(name,&xbuff_val,&xbuffs_root->val.v.xavp);
+	}
+
+	return xbuff;
+}
+
+/***
+ * if compile success returns 0
+ */
+int compile_xbuff_re()
+{
+	char *pattern = "^<<\\(tuple\\|list\\|atom\\):\\(0x[[:xdigit:]]\\+\\)>>$";
+	size_t bfsz = 128;
+	char errbuff[128];
+	int e;
+
+	if((e=regcomp(&xbuff_type_re,pattern,0))) {
+		regerror(e,&xbuff_type_re,errbuff,bfsz);
+		LM_ERR("failed to compile pattern '%s' error: %s\n",pattern,errbuff);
+		return -1;
+	}
+
+	return 0;
+}
+
+int xbuff_match_type_re(str *s, xbuff_type_t *type, sr_xavp_t **addr)
+{
+	size_t nmatch = 3;
+	regmatch_t matches[3];
+	int e;
+	size_t bfsz = 128;
+	char errbuff[128];
+	str tname;
+	str a;
+	xbuff_type_t t;
+
+	matches[0].rm_so = 0;
+	matches[0].rm_eo = s->len;
+
+	e = regexec(&xbuff_type_re,s->s,nmatch,matches,REG_STARTEND);
+
+	if (e == 0) {
+
+		tname.s   = s->s + matches[1].rm_so;
+		tname.len = matches[1].rm_eo - matches[1].rm_so;
+
+		a.s   = s->s + matches[2].rm_so;
+		a.len = matches[2].rm_eo - matches[1].rm_so;
+
+		if (STR_EQ(tname,xbuff_types[XBUFF_TYPE_ATOM])) {
+			t = XBUFF_TYPE_ATOM;
+		} else if (STR_EQ(tname,xbuff_types[XBUFF_TYPE_LIST])) {
+			t = XBUFF_TYPE_LIST;
+		} else if (STR_EQ(tname,xbuff_types[XBUFF_TYPE_TUPLE])) {
+			t = XBUFF_TYPE_TUPLE;
+		} else {
+			LM_ERR("BUG: unknown xbuff type");
+			return -1;
+		}
+
+		if(type) *type = t;
+
+		if (addr)
+			sscanf(a.s,"%lx>>",(long unsigned int *)addr);
+
+		return 0;
+
+	} else if (e != REG_NOMATCH) {
+		regerror(e,&xbuff_type_re,errbuff,bfsz);
+		LM_ERR("regexec error: %s\n",errbuff);
+	}
+
+	return -1;
+}
+
+int is_pv_xbuff_valid_char(char c)
+{
+	if((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z')
+			|| (c=='_'))
+		return 1;
+	return 0;
+}
+
+int pv_xbuff_parse_name(pv_spec_t *sp, str *in)
+{
+	char *p;
+	str idx;
+	str name;
+	str attr;
+
+	if (in->s == NULL || in->len <= 0)
+		return -1;
+
+	p = in->s;
+
+	name.s = p;
+
+	while (is_in_str(p, in)) {
+		if (*p == '[' || *p== '=')
+			break;
+		if (!is_pv_xbuff_valid_char(*p)) {
+			LM_ERR("invalid character in var name %.*s at %d\n",STR_FMT(in),p-in->s);
+			goto error;
+		}
+		p++;
+	}
+
+	/* from in->s to p */
+	name.len = p - in->s;
+
+	if (pv_parse_avp_name(sp,&name))
+		goto error;
+
+	if (is_in_str(p,in) && *p =='[')
+	{
+		idx.s=++p;
+
+		while (is_in_str(p,in)) {
+			if (*p == ']' || *p == '=')
+				break;
+			p++;
+		}
+
+		if (is_in_str(p,in) && *p==']') {
+			idx.len = p - idx.s;
+
+			if (pv_parse_index(sp,&idx))
+				goto error;
+		}
+		p++;
+	} else {
+		xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_NO_IDX);
+	}
+
+	if (is_in_str(p,in) && *p =='=')
+	{
+		p++;
+
+		if (!is_in_str(p,in) || *p!='>') {
+			LM_ERR("invalid operator (expected =>) for accessing attribute in token %.*s at position %d\n",STR_FMT(in),p-in->s);
+			goto error;
+		}
+
+		attr.s = ++p;
+
+		while (is_in_str(p,in)) {
+			if (!is_pv_xbuff_valid_char(*p)) {
+				LM_ERR("invalid character in attribute name in token %.*s at %d\n",STR_FMT(in),p-in->s);
+				goto error;
+			}
+			p++;
+		}
+
+		attr.len = p - attr.s;
+
+		if (attr.len > 0 ) {
+
+			if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_TYPE))) {
+				xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_ATTR_TYPE);
+			} else if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_FORMAT))) {
+				xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_ATTR_FORMAT);
+			} else if (STR_EQ(attr,xbuff_attr_name(XBUFF_ATTR_LENGTH))) {
+				xbuff_set_attr_flag(sp->pvp.pvi.type,XBUFF_ATTR_LENGTH);
+			} else {
+				LM_ERR("unknown attribute %.*s\n",STR_FMT(&attr));
+				goto error;
+			}
+
+			if (sp->pvp.pvi.type & PV_IDX_ALL) {
+				LM_ERR("index [*] (all) isn't compatible with attribute %.*s\n",STR_FMT(&attr));
+				goto error;
+			}
+		}
+	}
+
+	if (p < in->s + in->len) {
+		LM_ERR("unexpected token in %.*s at %d\n",STR_FMT(in),p-in->s);
+		goto error;
+	}
+
+	return 0;
+
+error:
+
+	return -1;
+}
+
+
+int pv_xbuff_new_xavp(sr_xavp_t **new, pv_value_t *pval, int *counter, char prefix)
+{
+	char s[101];
+	str name;
+	sr_xavp_t *xavp = NULL;
+	sr_xavp_t *cxavp = NULL;
+	sr_xval_t nval;
+	xbuff_type_t type;
+
+	if (!new) return -1;
+
+	memset((void*)&nval,0,sizeof(sr_xval_t));
+
+	if (pval->flags&PV_VAL_NULL) {
+		nval.type = SR_XTYPE_NULL;
+		s[0] = prefix ? prefix : 'n';
+	} else if (pval->flags&PV_VAL_INT) {
+		nval.type = SR_XTYPE_INT;
+		nval.v.i = pval->ri;
+		s[0] = prefix ? prefix : 'i';
+	} else if (pval->flags&PV_VAL_STR) {
+		/* check what it is */
+		if (xbuff_match_type_re(&pval->rs,&type,&xavp)) {
+			nval.type = SR_XTYPE_STR;
+			nval.v.s = pval->rs;
+			s[0] = prefix ? prefix : 's';
+		} else {
+			switch (type) {
+			case XBUFF_TYPE_ATOM:
+				s[0] = 'a';
+				nval = xavp->val;
+				break;
+			case XBUFF_TYPE_LIST:
+				s[0] = 'l';
+				/* copy tree */
+				cxavp = xbuff_copy_xavp(xavp);
+
+				if (!cxavp)
+					return -1;
+
+				if (type == XBUFF_TYPE_ATOM) {
+					nval.type = SR_XTYPE_XAVP;
+					nval.v.xavp = cxavp;
+				} else {
+					nval = cxavp->val;
+
+					/* free overhead */
+					cxavp->next = NULL;
+					cxavp->val.v.xavp = NULL;
+					xavp_destroy_list(&cxavp);
+				}
+				break;
+			case XBUFF_TYPE_TUPLE:
+				s[0] = 't';
+
+				/* copy tree */
+				cxavp = xbuff_copy_xavp(xavp);
+
+				if (!cxavp)
+					return -1;
+
+				if (type == XBUFF_TYPE_ATOM) {
+					nval.type = SR_XTYPE_XAVP;
+					nval.v.xavp = cxavp;
+				} else {
+					nval = cxavp->val;
+
+					/* free overhead */
+					cxavp->next = NULL;
+					cxavp->val.v.xavp = NULL;
+					xavp_destroy_list(&cxavp);
+				}
+				break;
+			case XBUFF_TYPE_INT:
+			case XBUFF_TYPE_STR:
+			default:
+				LM_ERR("BUG: unexpected XBUFF type!\n");
+				return -1;
+			}
+		}
+	}
+
+	name.s = s;
+	name.len = snprintf(s+1,99,"%d",(*counter)++) + 1;
+
+	cxavp = xavp_new_value(&name,&nval);
+
+	if (!cxavp) {
+		return -1;
+	}
+
+	*new = cxavp;
+
+	return 0;
+}
+
+int pv_xbuff_get_type(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp)
+{
+	if (!avp) return pv_get_null(msg,param,res);
+
+	switch(avp->name.s[0]){
+	case 'n':
+		return pv_get_null(msg,param,res);
+		break;
+	case 'i':
+		return pv_get_strval(msg,param,res,&xbuff_types[XBUFF_TYPE_INT]);
+		break;
+	case 'l':
+		return pv_get_strval(msg,param,res,&xbuff_types[XBUFF_TYPE_LIST]);
+		break;
+	case 'a':
+		return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_ATOM]);
+		break;
+	case 's':
+		return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_STR]);
+		break;
+	case 't':
+		return pv_get_strval(msg, param, res, &xbuff_types[XBUFF_TYPE_TUPLE]);
+		break;
+	}
+
+	return pv_get_null(msg, param, res);
+}
+
+int pv_xbuff_set(struct sip_msg* msg,  pv_param_t* param, int op, pv_value_t* val)
+{
+	str name;
+	sr_xavp_t *xbuffs_root;
+	sr_xavp_t *xbuff;
+	sr_xavp_t *new,*old,*prv=NULL;
+	sr_xavp_t *xbuff_xavp;
+	sr_xval_t xbuff_val;
+	pv_param_t p;
+	int idx = 0;
+	int idxf = 0;
+	int attr = 0;
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR)) {
+		LM_ERR("invalid variable name type\n");
+		return -1;
+	}
+
+	/* xbuff name */
+	name = param->pvn.u.isname.name.s;
+
+	xbuffs_root = xavp_get_xbuffs();
+
+	if(!xbuffs_root) {
+
+		memset((void*)&xbuff_val,0,sizeof(sr_xval_t));
+
+		xbuff_val.type = SR_XTYPE_NULL;
+
+		xbuff = xavp_add_xavp_value(&xbuff_list,&name,&xbuff_val,xavp_get_crt_list());
+
+		if (!xbuff)
+			goto err;
+	}
+
+	if(pv_xbuff_new_xavp(&xbuff_xavp,val,&counter,0)) {
+		LM_ERR("failed to create new value\n");
+		return -1;
+	}
+
+	xbuff=xavp_get_child(&xbuff_list, &name);
+
+	if (!xbuff) {
+
+		xbuff_val.type = SR_XTYPE_NULL;
+		xbuff_val.v.xavp = NULL;
+
+		xbuff=xavp_add_value(&name,&xbuff_val,&xbuffs_root->val.v.xavp);
+
+		if (!xbuff)
+			goto err;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(p.pvi.type);
+	p.pvi.type = xbuff_fix_index(p.pvi.type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	if (xbuff_is_attr_set(attr)) {
+		LM_ERR("read only attribute %.*s\n",STR_FMT(&xbuff_attr_name(attr)));
+		return -1;
+	}
+
+	/* check container type */
+
+	if (xbuff->val.v.xavp == NULL) {
+		xbuff->val.type = SR_XTYPE_XAVP;
+		xbuff->val.v.xavp = xbuff_xavp;
+		return 0;
+	}
+
+	switch (xbuff->val.v.xavp->name.s[0]) {
+	case 'l': /* list  */
+	case 't': /* tuple */
+
+		/* prepend on list when no index */
+		if (xbuff_is_no_index(attr)) {
+			return xavp_add(xbuff_xavp,&xbuff->val.v.xavp);
+		}
+
+		/* reset list given value */
+		if (idxf == PV_IDX_ALL) {
+			xavp_destroy_list(&xbuff->val.v.xavp);
+			if (xbuff_xavp->val.type == SR_XTYPE_NULL) {
+				/* create empty list/tuple */
+				xavp_destroy_list(&xbuff_xavp);
+				return 0;
+			} else {
+				return xavp_add(xbuff_xavp,&xbuff->val.v.xavp);
+			}
+		}
+
+		/* set by index */
+		old = xavp_get_nth(&xbuff->val.v.xavp,idx,&prv);
+		new = xbuff_xavp;
+
+		if (old) {
+			new->next = old->next;
+			if (prv) {
+				prv->next = new;
+			} else {
+				new->next = old->next;
+				xbuff->val.v.xavp = new;
+			}
+			old->next = NULL;
+			xavp_destroy_list(&old);
+		} else {
+			if (prv) {
+				prv->next = new;
+			} else {
+				xbuff->val.v.xavp = new;
+			}
+		}
+		break;
+	case 'n': /* null (empty) */
+	case 's': /* string  */
+	case 'a': /* atom    */
+	case 'i': /* integer */
+	default:  /* default */
+		xavp_destroy_list(&xbuff->val.v.xavp);
+		xbuff->val.v.xavp = xbuff_xavp;
+		break;
+	}
+
+	return 0;
+
+err:
+	LM_ERR("failed to add value into xbuff\n");
+	xavp_destroy_list(&xbuff_xavp);
+
+	return -1;
+}
+
+int pv_xbuff_get_value(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp)
+{
+	static char _pv_xavp_buf[128];
+	str s;
+
+	if (!avp) return pv_get_null(msg,param,res);
+
+	switch(avp->val.type) {
+	case SR_XTYPE_NULL:
+		return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_INT:
+		return pv_get_sintval(msg, param, res, avp->val.v.i);
+		break;
+	case SR_XTYPE_STR:
+		switch (avp->name.s[0]) {
+		case 'a':
+			if(snprintf(_pv_xavp_buf, 128, "<<atom:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		default:
+			return pv_get_strval(msg, param, res, &avp->val.v.s);
+		}
+		break;
+	case SR_XTYPE_TIME:
+		if(snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LONG:
+		if(snprintf(_pv_xavp_buf, 128, "%ld", (long unsigned)avp->val.v.l)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_LLONG:
+		if(snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	case SR_XTYPE_XAVP:
+		switch(avp->name.s[0]) {
+		case 't':
+			if(snprintf(_pv_xavp_buf, 128, "<<tuple:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+			break;
+		case 'l':
+		default:
+			if(snprintf(_pv_xavp_buf, 128, "<<list:%p>>", avp->val.v.xavp)<0)
+				return pv_get_null(msg, param, res);
+//			break;
+//		default:
+//			LM_ERR("unexpected type!\n");
+//			return pv_get_null(msg, param, res);
+		}
+		break;
+	case SR_XTYPE_DATA:
+		if(snprintf(_pv_xavp_buf, 128, "<<binary:%p>>", avp->val.v.data)<0)
+			return pv_get_null(msg, param, res);
+		break;
+	default:
+		return pv_get_null(msg, param, res);
+	}
+	s.s = _pv_xavp_buf;
+	s.len = strlen(_pv_xavp_buf);
+	return pv_get_strval(msg, param, res, &s);
+}
+
+int pv_xbuff_get(struct sip_msg* msg,  pv_param_t* param, pv_value_t* res)
+{
+	str name;
+	pv_spec_t *nsp = NULL;
+	pv_name_t *pvn;
+	pv_index_t *pvi;
+	sr_xavp_t *xbuffs_root;
+	sr_xavp_t *xbuff;
+	sr_xavp_t *xavp;
+	pv_param_t p;
+	int idx=0;
+	int idxf=0;
+	int attr;
+	int i,count;
+
+	ei_x_buff x_buff;
+
+	if(param==NULL)
+	{
+		LM_ERR("bad parameters\n");
+		return -1;
+	}
+
+	if (param->pvn.type != PV_NAME_INTSTR || !(param->pvn.u.isname.type & AVP_NAME_STR))
+				return -1;
+
+	if( param->pvn.type == PV_NAME_PVAR) {
+			nsp = param->pvn.u.dname;
+	}
+
+	/* work on copy of index! */
+	p = *param;
+
+	if (nsp) {
+		pvi = &nsp->pvp.pvi;
+		pvn = &nsp->pvp.pvn;
+	} else {
+		pvi = &p.pvi;
+		pvn = &p.pvn;
+	}
+
+	/* list name */
+	name = pvn->u.isname.name.s;
+
+	/* fix index */
+	attr = xbuff_get_attr_flags(pvi->type);
+	pvi->type = xbuff_fix_index(pvi->type);
+
+	/* get the index */
+	if(pv_get_spec_index(msg, &p, &idx, &idxf))
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	xbuffs_root = xavp_get_xbuffs();
+	if(!xbuffs_root) {
+		return pv_get_null(msg,param,res);
+	}
+
+	xbuff = xavp_get(&name,xbuffs_root->val.v.xavp);
+	if (!xbuff) {
+		return pv_get_null(msg,param,res);
+	}
+
+	xavp = xbuff->val.v.xavp;
+
+	switch (xbuff_is_attr_set(attr)) {
+	case XBUFF_ATTR_TYPE:
+		if (xbuff_is_no_index(attr)) {
+			return pv_xbuff_get_type(msg,param,res,xavp);
+		} else {
+			if(xavp && (xavp->name.s[0]=='l'||xavp->name.s[0]=='t')) {
+				xavp=xavp->val.v.xavp;
+			}
+			xavp = xavp_get_nth(&xavp,idx,NULL);
+			return pv_xbuff_get_type(msg,param,res,xavp);
+		}
+		break;
+	case XBUFF_ATTR_LENGTH:
+		if (xbuff_is_no_index(attr)) {
+			xavp = xbuff->val.v.xavp;
+		} else {
+			if(xavp && (xavp->name.s[0]=='l'||xavp->name.s[0]=='t')) {
+				xavp=xavp->val.v.xavp;
+			}
+			xavp = xavp_get_nth(&xavp,idx,NULL);
+		}
+		count = xavp ? xavp_get_count(xavp->val.v.xavp) : 0;
+		return pv_get_uintval(msg,param,res,(unsigned int)count);
+		break;
+	case XBUFF_ATTR_FORMAT:
+		if (xbuff_is_no_index(attr)) {
+			xavp = xbuff->val.v.xavp;
+		} else {
+			if(xavp && (xavp->name.s[0]=='l'||xavp->name.s[0]=='t')) {
+				xavp=xavp->val.v.xavp;
+			}
+			xavp = xavp_get_nth(&xavp,idx,NULL);
+		}
+
+		/*
+		 * Prints a term, in clear text, to the PV value pointed to by res.
+		 * It tries to resemble the term printing in the erlang shell.
+		 */
+		ei_x_new_with_version(&x_buff);
+		if (!xavp || xavp_encode(&x_buff,xavp,1)) {
+			ei_x_free(&x_buff);
+			return pv_get_null(msg,param,res);
+		}
+
+		i = 1;
+		if (ei_s_print_term(&xbuff_fmt_buff,x_buff.buff,&i)<0) {
+			LM_ERR("BUG: xbuff doesn't contain a valid term!\n");
+			ei_x_free(&x_buff);
+			return -1;
+		}
+		i = pv_get_strzval(msg,param,res,xbuff_fmt_buff);
+		ei_x_free(&x_buff);
+		return i;
+	}
+
+	if (!xavp) {
+		return pv_get_null(msg,param,res);
+	}
+
+	/* get whole xbuff */
+	if (idxf == PV_IDX_ALL) {
+		return pv_xbuff_get_value(msg,param,res,xavp);
+	}
+
+	/* get by idx */
+	if (xavp->name.s[0]=='l'||xavp->name.s[0]=='t') {
+		xavp = xavp->val.v.xavp;
+	}
+
+	xavp = xavp_get_nth(&xavp,idx,NULL);
+	if (!xavp) {
+		return pv_get_null(msg,param,res);
+	}
+
+	return pv_xbuff_get_value(msg,param,res,xavp);
+}
+
+/**
+ * Recursive copy XAVPs, preserve order
+ */
+sr_xavp_t *xbuff_copy_xavp(sr_xavp_t *xavp)
+{
+	sr_xavp_t *new = NULL;
+	sr_xavp_t *cp  = NULL;
+
+	if (!xavp) return NULL;
+
+	while (xavp) {
+		if (new) {
+			new->next = xavp_new_value(&xavp->name,&xavp->val);
+			new = new->next;
+		} else {
+			new = xavp_new_value(&xavp->name,&xavp->val);
+		}
+
+		if (!new) {
+			LM_ERR("not enough memory\n");
+			return cp;
+		}
+
+		if (!cp) cp = new;
+
+		if (xavp->val.type == SR_XTYPE_XAVP)
+			new->val.v.xavp = xbuff_copy_xavp(xavp->val.v.xavp);
+
+		xavp = xavp->next;
+	}
+
+	return cp;
+}
+
+/**
+ * XAVP extension
+ */
+
+/* copy from xavp.c (not exported from XAVP) */
+sr_xavp_t *xavp_new_value(str *name, sr_xval_t *val)
+{
+	sr_xavp_t *avp;
+	int size;
+	unsigned int id;
+
+	if(name==NULL || name->s==NULL || val==NULL)
+		return NULL;
+	id = get_hash1_raw(name->s, name->len);
+
+	size = sizeof(sr_xavp_t) + name->len + 1;
+	if(val->type == SR_XTYPE_STR)
+		size += val->v.s.len + 1;
+	avp = (sr_xavp_t*)shm_malloc(size);
+	if(avp==NULL)
+		return NULL;
+	memset(avp, 0, size);
+	avp->id = id;
+	avp->name.s = (char*)avp + sizeof(sr_xavp_t);
+	memcpy(avp->name.s, name->s, name->len);
+	avp->name.s[name->len] = '\0';
+	avp->name.len = name->len;
+	memcpy(&avp->val, val, sizeof(sr_xval_t));
+	if(val->type == SR_XTYPE_STR)
+	{
+		avp->val.v.s.s = avp->name.s + avp->name.len + 1;
+		memcpy(avp->val.v.s.s, val->v.s.s, val->v.s.len);
+		avp->val.v.s.s[val->v.s.len] = '\0';
+		avp->val.v.s.len = val->v.s.len;
+	}
+
+	return avp;
+}
+
+sr_xavp_t *xavp_get_nth(sr_xavp_t **list, int idx, sr_xavp_t **prv)
+{
+	sr_xavp_t *avp;
+	int n = 0;
+
+	if (list && *list)
+		avp = *list;
+	else
+		return NULL;
+
+	while (avp) {
+
+		if (idx == n)
+			return avp;
+		n++;
+
+		if (prv)
+			*prv = avp;
+
+		avp = avp->next;
+	}
+
+	return NULL;
+}
+
+/**
+ * Encode XAVPs into ei_x_buff
+ */
+int xavp_encode(ei_x_buff *xbuff, sr_xavp_t *xavp,int level)
+{
+	int n;
+
+	while(xavp) {
+		switch (xavp->name.s[0]) {
+		case 'a':
+			ei_x_encode_atom_len(xbuff,xavp->val.v.s.s,xavp->val.v.s.len);
+			break;
+		case 's':
+			ei_x_encode_string_len(xbuff,xavp->val.v.s.s,xavp->val.v.s.len);
+			break;
+		case 'i':
+			ei_x_encode_long(xbuff,xavp->val.v.i);
+			break;
+		case 't':
+			n = xavp_get_count(xavp->val.v.xavp);
+			ei_x_encode_tuple_header(xbuff,n);
+			if (xavp_encode(xbuff,xavp->val.v.xavp,level+1)) return -1;
+			break;
+		case 'l':
+			n = xavp_get_count(xavp->val.v.xavp);
+			ei_x_encode_list_header(xbuff, n);
+			if (xavp_encode(xbuff, xavp->val.v.xavp, level + 1)) return -1;
+			ei_x_encode_empty_list(xbuff);
+			break;
+		case 'n':
+			ei_x_encode_atom(xbuff,"undefined");
+			break;
+		default:
+			LM_ERR("BUG: unknown type for %.*s\n",STR_FMT(&xavp->name));
+			return -1;
+		}
+		xavp = xavp->next;
+	}
+
+	return 0;
+}
+
+/**
+ * Decode XAVP from ei_x_buff
+ */
+int xavp_decode(ei_x_buff *xbuff, int *index, sr_xavp_t **xavp,int level)
+{
+	int i=0;
+	int type, size, arity;
+	int l;
+	char _s[128];
+	char _fmt[128];
+	str name;
+	sr_xval_t val;
+	sr_xavp_t **tail;
+	sr_xavp_t *new;
+	char *pbuf=0;
+	erlang_pid pid;
+	erlang_ref ref;
+	erlang_fun fun;
+	double d;
+	char *p = NULL;
+
+	name.s = _s;
+
+	if (!xavp || !xbuff) return -1;
+
+	if (ei_get_type(xbuff->buff,index,&type,&size)) {
+		LM_ERR("failed to get type\n");
+		return -1;
+	}
+
+
+	switch (type) {
+	case ERL_ATOM_EXT:
+
+		name.len = snprintf(_s,sizeof(_s),"a%d",counter++);
+		pbuf = (char*)pkg_realloc(pbuf,size+1);
+
+		if (!pbuf) {
+			LM_ERR("not enough memory!\n");
+			return -1;
+		}
+
+		ei_decode_atom(xbuff->buff,index,pbuf);
+
+		val.type = SR_XTYPE_STR;
+		val.v.s.s = pbuf;
+		val.v.s.len = size;
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+
+		break;
+	case ERL_LIST_EXT:
+	case ERL_SMALL_TUPLE_EXT:
+	case ERL_LARGE_TUPLE_EXT:
+
+		name.len = snprintf(_s,sizeof(_s),"%c%d", type == ERL_LIST_EXT ? 'l' : 't', counter++);
+
+		val.v.xavp = NULL;
+
+		if (type == ERL_LIST_EXT) {
+			ei_decode_list_header(xbuff->buff,index,&arity);
+		} else {
+			ei_decode_tuple_header(xbuff->buff,index,&arity);
+		}
+
+		if (arity == 0) {
+			val.type = SR_XTYPE_NULL;
+		} else {
+			val.type = SR_XTYPE_XAVP;
+		}
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+
+		tail = &(*xavp)->val.v.xavp;
+
+		for(l=0;l<arity;l++) {
+
+			new = NULL;
+
+			if (xavp_decode(xbuff,index,&new,level+1)) {
+				LM_ERR("failed to decode %.*s\n",STR_FMT(&name));
+				return -1;
+			}
+
+			if (!new) {
+				LM_ERR("failed to create new xavp!\n");
+				goto err;
+			}
+
+			*tail = new;
+			tail = &new->next;
+		}
+
+		break;
+	case ERL_STRING_EXT:
+		name.len = snprintf(_s,sizeof(_s),"s%d",counter++);
+
+		pbuf = (char*)pkg_realloc(pbuf,size+1);
+
+		if (!pbuf) {
+			LM_ERR("not enough memory!\n");
+			return -1;
+		}
+
+		ei_decode_string(xbuff->buff,index,pbuf);
+
+		val.type = SR_XTYPE_STR;
+		val.v.s.s = pbuf;
+		val.v.s.len = size;
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+	case ERL_SMALL_INTEGER_EXT:
+		name.len = snprintf(_s,sizeof(_s),"i%d",counter++);
+
+		ei_decode_long(xbuff->buff,index,&val.v.l);
+		val.type = SR_XTYPE_INT;
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+	case ERL_SMALL_BIG_EXT:
+	case ERL_INTEGER_EXT:
+		name.len = snprintf(_s,sizeof(_s),"i%d",counter++);
+
+		if (size > sizeof(long)) {
+			ei_decode_longlong(xbuff->buff,index,&val.v.ll);
+			val.type = SR_XTYPE_LLONG;
+		} else {
+			ei_decode_long(xbuff->buff,index,&val.v.l);
+			val.type = SR_XTYPE_LONG;
+		}
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+	case ERL_FLOAT_EXT:
+	case NEW_FLOAT_EXT:
+		name.len = snprintf(_s,sizeof(_s),"s%d",counter++);
+
+		ei_decode_double(xbuff->buff,index,&d);
+		val.v.s.s = _fmt;
+		val.v.s.len = snprintf(_fmt,sizeof(_fmt),"%g",d);
+
+		val.type = SR_XTYPE_STR;
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+
+	case ERL_PID_EXT:
+	case ERL_REFERENCE_EXT:
+	case ERL_NEW_REFERENCE_EXT:
+		name.len = snprintf(_s,sizeof(_s),"s%d",counter++);
+		i = *index;
+
+		if (type==ERL_PID_EXT) ei_decode_pid(xbuff->buff,index,&pid);
+		else ei_decode_ref(xbuff->buff,index,&ref);
+
+		if (ei_s_print_term(&p,xbuff->buff,&i)<0) {
+			LM_ERR("failed to decode %s\n",type==ERL_PID_EXT?"pid":"reference");
+			goto err;
+		}
+		val.type = SR_XTYPE_STR;
+		val.v.s.s = p;
+		val.v.s.len = strlen(p);
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+	case ERL_FUN_EXT:
+		name.len = snprintf(_s,sizeof(_s),"s%d",counter++);
+		i = *index;
+
+		ei_decode_fun(xbuff->buff,index,&fun);
+
+		if (ei_s_print_term(&p,xbuff->buff,&i)<0) {
+			LM_ERR("failed to decode fun\n");
+			goto err;
+		}
+		val.type = SR_XTYPE_STR;
+		val.v.s.s = p;
+		val.v.s.len = strlen(p);
+
+		*xavp = xavp_new_value(&name,&val);
+		if (!*xavp) {
+			LM_ERR("failed to create new xavp!\n");
+			goto err;
+		}
+		break;
+	default:
+		LM_ERR("unknown type %c(%d)\n",(char)type,type);
+	}
+
+	pkg_free(pbuf);
+	free(p);
+	return 0;
+
+err:
+	pkg_free(pbuf);
+	free(p);
+	return -1;
+}
+
+int xavp_get_count(sr_xavp_t *list)
+{
+	int count = 0;
+
+	while(list) {
+		list = list->next;
+		count++;
+	}
+	return count;
+}
+
+/*
+ * free format buffer for list
+ */
+void free_xbuff_fmt_buff() {
+	if (xbuff_fmt_buff) {
+		free(xbuff_fmt_buff);
+	}
+	xbuff_fmt_buff = 0;
+}
+
+void xbuff_destroy_all()
+{
+	sr_xavp_t *list;
+	list = xavp_get_xbuffs();
+	if (list) xavp_destroy_list(&list);
+}

+ 100 - 0
modules/erlang/pv_xbuff.h

@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_XBUFF_H_
+#define PV_XBUFF_H_
+
+#include "../../pvar.h"
+#include "../../xavp.h"
+
+#include <regex.h>
+#include <ei.h>
+
+typedef enum {
+	XBUFF_ATTR_TYPE   = (1<<2),
+	XBUFF_ATTR_FORMAT = (1<<3),
+	XBUFF_ATTR_LENGTH = (1<<4),
+	XBUFF_NO_IDX      = (1<<5)
+} xbuff_attr_t;
+
+typedef enum {
+	XBUFF_TYPE_ATOM,
+	XBUFF_TYPE_INT,
+	XBUFF_TYPE_STR,
+	XBUFF_TYPE_TUPLE,
+	XBUFF_TYPE_LIST,
+	XBUFF_TYPE_COUNT
+} xbuff_type_t;
+
+#define XBUFF_IDX_MASK      3
+
+int pv_xbuff_parse_name(pv_spec_t *sp, str *in);
+int pv_xbuff_new_xavp(sr_xavp_t **new, pv_value_t *pval, int *counter, char prefix);
+int pv_xbuff_get_type(struct sip_msg *msg, pv_param_t *param,
+		pv_value_t *res, sr_xavp_t *avp);
+
+sr_xavp_t *pv_xbuff_get_xbuff(str *name);
+int xavp_decode(ei_x_buff *xbuff, int *index, sr_xavp_t **xavp,int level);
+sr_xavp_t *xbuff_new(str *name);
+
+extern str xbuff_attributes[];
+
+extern str xbuff_types[];
+
+#define xbuff_attr_name(flag) (xbuff_attributes[(flag)>>XBUFF_IDX_MASK])
+#define xbuff_set_attr_flag(type,flag) type |= flag
+#define xbuff_get_attr_flags(type) ((type)&~XBUFF_IDX_MASK)
+#define xbuff_is_attr_set(flags) ((attr)&~XBUFF_NO_IDX)
+#define xbuff_is_no_index(attr) ((attr)&XBUFF_NO_IDX)
+#define xbuff_fix_index(type) ((type)&XBUFF_IDX_MASK)
+
+int compile_xbuff_re();
+int xbuff_match_type_re(str *s, xbuff_type_t *type, sr_xavp_t **addr);
+
+int is_pv_xbuff_valid_char(char c);
+
+sr_xavp_t *xbuff_copy_xavp(sr_xavp_t *xavp);
+
+int pv_xbuff_set(struct sip_msg*, pv_param_t*, int, pv_value_t*);
+int pv_xbuff_get(struct sip_msg*, pv_param_t*, pv_value_t*);
+void free_xbuff_fmt_buff();
+
+/* destroy all xbuffs */
+void xbuff_destroy_all();
+
+/**
+ * atom,tuple,xbuff and list
+ */
+extern regex_t xbuff_type_re;
+
+/**
+ * XAVP extension
+ */
+
+sr_xavp_t *xavp_new_value(str *name, sr_xval_t *val);
+sr_xavp_t *xavp_get_nth(sr_xavp_t **list, int idx, sr_xavp_t **prv);
+int xavp_get_count(sr_xavp_t *list);
+int xavp_encode(ei_x_buff *xbuff, sr_xavp_t *xavp,int level);
+
+
+#endif /* PV_XBUFF_H_ */

+ 356 - 0
modules/erlang/worker.c

@@ -0,0 +1,356 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdlib.h>
+#include "worker.h"
+#include "cnode.h"
+#include "../../mem/mem.h"
+
+#include "erl_api.h"
+
+int worker_rpc_impl(ei_cnode *ec, int s, int wpid);
+int worker_reg_send_impl(ei_cnode *ec, int s, int wpid);
+
+int worker_init(worker_handler_t *phandler, int fd, const ei_cnode *ec)
+{
+	if (erl_set_nonblock(fd)){
+		LM_ERR("set non blocking failed\n");
+	}
+
+	phandler->handle_f = handle_worker;
+	phandler->wait_tmo_f = wait_tmo_worker;
+	phandler->destroy_f = NULL;
+	phandler->sockfd = fd;
+	phandler->ec = *ec;
+	phandler->next = NULL;
+
+	return 0;
+}
+
+int handle_worker(handler_common_t *phandler)
+{
+	worker_handler_t* w = (worker_handler_t*)phandler;
+	struct msghdr msg;
+	struct iovec cnt[2];
+	int wpid = 0;
+	eapi_t api;
+	int rc;
+
+	memset((void*)&msg,0,sizeof(msg));
+
+	/* Kamailio worker PID */
+	cnt[0].iov_base = &wpid;
+	cnt[0].iov_len  = sizeof(wpid);
+
+	/* method */
+	cnt[1].iov_base = &api;
+	cnt[1].iov_len  = sizeof(api);
+
+	msg.msg_iov = cnt;
+	msg.msg_iovlen = 2;
+
+	while ((rc = recvmsg(w->sockfd, &msg, MSG_WAITALL)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc < 0){
+		LM_ERR("recvmsg failed (socket=%d): %s\n",w->sockfd,strerror(errno));
+		return -1;
+	}
+
+	switch(api) {
+	case API_RPC_CALL:
+		if (worker_rpc_impl(&w->ec,w->sockfd,wpid))
+			return -1;
+		break;
+	case API_REG_SEND:
+		if (worker_reg_send_impl(&w->ec,w->sockfd,wpid))
+			return -1;
+		break;
+	default:
+		LM_ERR("BUG: bad method or not implemented!\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int wait_tmo_worker(handler_common_t* phandler){
+
+	return 0;
+}
+
+/**
+ * internal implementation
+ */
+int worker_rpc_impl(ei_cnode *ec, int s,int wpid)
+{
+	str module = STR_NULL;
+	str function = STR_NULL;
+	ei_x_buff args;
+	ei_x_buff reply;
+	struct msghdr msgh;
+	struct iovec cnt[6];
+	int rc;
+
+	memset((void*)&args,0,sizeof(args));
+	memset((void*)&reply,0,sizeof(reply));
+	memset((void*)&msgh,0,sizeof(msgh));
+
+	/* module name length */
+	cnt[0].iov_base = &module.len;
+	cnt[0].iov_len  = sizeof(int);
+
+	/* function name length */
+	cnt[1].iov_base = &function.len;
+	cnt[1].iov_len  = sizeof(int);
+
+	/* Erlang args size */
+	cnt[2].iov_base = &args.buffsz;
+	cnt[2].iov_len  = sizeof(int);
+
+	/* get data size */
+	msgh.msg_iov    = cnt;
+	msgh.msg_iovlen = 3;
+	while ((rc = recvmsg(s, &msgh, MSG_PEEK)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1){
+		LM_ERR("recvmsg failed (socket=%d): %s\n",s,strerror(errno));
+		goto err;
+	}
+
+	/* allocate space */
+	module.s = (char*)pkg_malloc(module.len+1);
+	if (!module.s) {
+		LM_ERR("not enough memory\n");
+		goto err;
+	}
+
+	function.s = (char*)pkg_malloc(function.len+1);
+	if (!function.s) {
+		LM_ERR("not enough memory\n");
+		goto err;
+	}
+
+	args.buff = (char*)malloc(args.buffsz);
+	if (!args.buff) {
+		LM_ERR("malloc: not enough memory\n");
+		goto err;
+	}
+
+	/* buffers */
+	cnt[3].iov_base = module.s;
+	cnt[3].iov_len  = module.len;
+
+	cnt[4].iov_base = function.s;
+	cnt[4].iov_len  = function.len;
+
+	cnt[5].iov_base = args.buff;
+	cnt[5].iov_len  = args.buffsz;
+
+	/* get whole data */
+	msgh.msg_iovlen = 6;
+	while ((rc = recvmsg(s, &msgh, MSG_WAITALL)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1){
+		LM_ERR("recvmsg failed (socket=%d): %s\n",s,strerror(errno));
+		goto err;
+	}
+
+	/* fix str */
+	module.s[module.len] = 0;
+	function.s[function.len] = 0;
+
+	LM_DBG("rpc: %.*s:%.*s(args)\n",STR_FMT(&module),STR_FMT(&function));
+
+	EI_X_BUFF_PRINT(&args);
+
+	if(!enode) {
+		LM_NOTICE("there is no connected Erlang node\n");
+		/* reply up with error */
+		ei_x_format(&reply, "{error,cnode,~a}", "no_erlang_node");
+		goto reply;
+	}
+
+	/* do RPC */
+	if ((rc = ei_rpc(ec,enode->sockfd,module.s,function.s,args.buff,args.buffsz,&reply)) == ERL_ERROR)
+	{
+		reply.index = 0; /* re-use reply buffer */
+
+		if (erl_errno)
+		{
+			ei_x_format(&reply, "{error,cnode,~s}", strerror(erl_errno));
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>: %s\n",enode->conn.nodename,enode->sockfd,strerror(erl_errno));
+		}
+		else if (errno)
+		{
+			ei_x_format(&reply, "{error,cnode,~s}", strerror(errno));
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>: %s\n",enode->conn.nodename,enode->sockfd,strerror(errno));
+		}
+		else
+		{
+			ei_x_format(&reply, "{error,cnode,~s}", "Unknown error.");
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>, Unknown error.\n",ec->thisalivename,enode->sockfd);
+		}
+	}
+
+reply:
+	EI_X_BUFF_PRINT(&reply);
+
+	cnt[0].iov_base = (void*)&wpid;
+	cnt[0].iov_len  = sizeof(int);
+
+	/* send reply to Kamailio worker */
+	cnt[1].iov_base = (void*)&reply.buffsz;
+	cnt[1].iov_len  = sizeof(int);
+
+	cnt[2].iov_base = (void*)reply.buff;
+	cnt[2].iov_len  = reply.buffsz;
+
+	msgh.msg_iovlen = 3;
+	while ((rc = sendmsg(s, &msgh, 0)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1) {
+		LM_ERR("sendmsg failed on socket=%d rpid_no=%d; %s\n",s, wpid, strerror(errno));
+		goto err;
+	};
+
+	pkg_free(module.s);
+	pkg_free(function.s);
+	free(args.buff);
+	ei_x_free(&reply);
+	return 0;
+
+err:
+	pkg_free(module.s);
+	pkg_free(function.s);
+	free(args.buff);
+	ei_x_free(&reply);
+	abort(); /* cant't recover */
+	return -1;
+}
+
+int worker_reg_send_impl(ei_cnode *ec, int s,int wpid)
+{
+	str server = STR_NULL;
+	ei_x_buff emsg;
+	struct msghdr msgh;
+	struct iovec cnt[6];
+	int rc;
+
+	memset((void*)&emsg,0,sizeof(emsg));
+
+	memset((void*)&msgh,0,sizeof(msgh));
+
+	/* server name length */
+	cnt[0].iov_base = &server.len;
+	cnt[0].iov_len  = sizeof(int);
+
+	/* Erlang args size */
+	cnt[1].iov_base = &emsg.buffsz;
+	cnt[1].iov_len  = sizeof(int);
+
+	/* get data size */
+	msgh.msg_iov    = cnt;
+	msgh.msg_iovlen = 2;
+
+	while ((rc = recvmsg(s, &msgh, MSG_PEEK)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1){
+		LM_ERR("recvmsg failed (socket=%d): %s\n",s,strerror(errno));
+		return -1;
+	}
+
+	/* allocate space */
+	server.s = (char*)pkg_malloc(server.len+1);
+	if (!server.s) {
+		LM_ERR("not enough memory\n");
+		goto err;
+	}
+
+	emsg.buff = (char*)malloc(emsg.buffsz);
+	if (!emsg.buff) {
+		LM_ERR("malloc: not enough memory\n");
+		goto err;
+	}
+
+	/* buffers */
+	cnt[2].iov_base = server.s;
+	cnt[2].iov_len  = server.len;
+
+	cnt[3].iov_base = emsg.buff;
+	cnt[3].iov_len  = emsg.buffsz;
+
+	/* get whole data */
+	msgh.msg_iovlen = 4;
+	while ((rc = recvmsg(s, &msgh, MSG_WAITALL)) == -1 && errno == EAGAIN)
+		;
+
+	if (rc == -1){
+		LM_ERR("recvmsg failed (socket=%d): %s\n",s,strerror(errno));
+		goto err;
+	}
+
+	/* fix str */
+	server.s[server.len] = 0;
+
+	if(!enode) {
+		LM_NOTICE("there is no connected Erlang node\n");
+		goto err;
+	}
+
+	LM_DBG(">> {%.*s,'%s'} ! emsg\n",STR_FMT(&server),enode->conn.nodename);
+
+	EI_X_BUFF_PRINT(&emsg);
+
+	/* do ERL_REG_SEND */
+	if ((rc = ei_reg_send(ec,enode->sockfd,server.s,emsg.buff,emsg.buffsz)) == ERL_ERROR)
+	{
+		if (erl_errno)
+		{
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>: %s\n",enode->conn.nodename,enode->sockfd,strerror(erl_errno));
+		}
+		else if (errno)
+		{
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>: %s\n",enode->conn.nodename,enode->sockfd,strerror(errno));
+		}
+		else
+		{
+			LM_ERR("ei_rpc failed on node=<%s> socket=<%d>, Unknown error.\n",ec->thisalivename,enode->sockfd);
+		}
+	}
+
+	pkg_free(server.s);
+	free(emsg.buff);
+
+	return 0;
+
+err:
+	pkg_free(server.s);
+	free(emsg.buff);
+
+	return -1;
+}

+ 59 - 0
modules/erlang/worker.h

@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2015 Bicom Systems Ltd, (bicomsystems.com)
+ *
+ * Author: Seudin Kasumovic ([email protected])
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef WORKER_H_
+#define WORKER_H_
+
+#include <ei.h>
+#include "erl_helpers.h"
+
+typedef struct worker_handler_s
+{
+	/* d-linked list  */
+	struct handler_common_s *prev;
+	struct handler_common_s *next;
+
+	/* if need to add new in i/o handler */
+	struct handler_common_s *new;
+
+	/*
+	 * handler for socket pair requests
+	 *
+	 * if function return
+	 * 0  - ok
+	 * -1 - RPC error (we die)
+	 * -2 - remote C Node disconnected (remove node from list)
+	 */
+	int (*handle_f)(handler_common_t *phandler_t);
+	int (*wait_tmo_f)(handler_common_t *phandler_t);
+	int (*destroy_f)(handler_common_t *handler);
+	int sockfd; /* kamailio to cnode socket r/w */
+	ei_cnode ec; /* erlang C node (actually it's me) */
+
+} worker_handler_t;
+
+int worker_init(worker_handler_t *phandler, int fd, const ei_cnode *ec);
+int handle_worker(handler_common_t *phandler_t);
+int wait_tmo_worker(handler_common_t *phandler_t);
+
+#endif /* WORKER_H_ */