浏览代码

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_ */