Seas Module
     __________________________________________________________

   Table of Contents

   1. The Sip Express Application Server User's Guide

        1.1. Application Servers

              1.1.1. Sip Express Application Server module
                      overview

              1.1.2. Application Servers
              1.1.3. Dependencies
              1.1.4. Exported Parameters
              1.1.5. Exported Functions

        1.2. WeSIP Application Server

              1.2.1. The Servlet programming paradigm: Sip/Http
                      Servlets

              1.2.2. Configuring WeSIP to work with SEAS
              1.2.3. Configuration Examples

   2. Developer Guide

        2.1. Internals
        2.2. SEAS Protocol

              2.2.1. The SEAS protocol
              2.2.2. General codification of a header

   3. Contributors

        3.1. By Commit Statistics
        3.2. By Commit Activity

   4. Documentation

        4.1. Contributors

   List of Figures

   1.1. SipServlet UML diagram
   2.1. Overview of Seas Event Dispatcher process operation
   2.2. SIP Messages and control flow within SER
   2.3. General codification of a SIP header in SEAS protocol
   2.4. Example of a from header SEAS-protocol codification
   2.5. SEAS-codification of a SIP URI (byte meanings are shown)
   2.6. Example of a SEAS SIP URI codification
   2.7. SEAS codification of From and To headers
   2.8. SEAS codification of a Contact header
   2.9. SEAS codification of a Route Header
   2.10. SEAS codification of Authentication/Authorization headers
   2.11. SEAS codification of a SIP First Line
   2.12. SEAS Headers Index section overview
   2.13. SEAS SIP-Message codification
   2.14. Different kinds of SEAS codified Events and Actions

   List of Tables

   3.1. Top contributors by DevScore^(1), authored commits^(2) and
          lines added/removed^(3)

   3.2. Most recently active contributors^(1) to this module

   List of Examples

   1.1. Set listen_sockets parameter
   1.2. as_relay_t usage
   1.3. Typical example of an HttpServlet
   1.4. Typical Sip Servlet Example
   1.5. Server
   1.6. Service

Chapter 1. The Sip Express Application Server User's Guide

1.1. Application Servers

1.1.1. Sip Express Application Server module overview

   SEAS module enables OpenSIPS to transfer the execution logic
   control of a sip message to a given external entity, called the
   Application Server. When the OpenSIPS script is being executed
   on an incoming SIP message, invocation of the as_relay_t()
   function makes this module send the message along with some
   transaction information to the specified Application Server.
   The Application Server then executes some call-control logic
   code, and tells OpenSIPS to take some actions, ie. forward the
   message downstream, or respond to the message with a SIP repy,
   etc.

   The module acts implements a network protocol acting as the
   interface between OpenSIPS internal API and the external
   Application Server entity.

   There's only one relevant function, as_relay_t, exported by
   this module. This function receives as a parameter the name of
   the application server to which the message should be relaied.
   Every message relaied to an Application Server is automatically
   associated to a SIP transaction (a transaction is created for
   it). Just after the message is relaied to the Application
   Server, the script stops its execution on the message, because
   the control of message-processing is now in the Application
   Server.

   In the context of SEAS module, relaying a message to an App
   Server, is _not_ done in SIP protocol, but in a special
   protocol by means of which the SEAS module and the Application
   Server comunicate efficiently and seamlessly. This procotol is
   specially designed so that a message doesn't need to be parsed
   again once it arrives at the Application Server. This protocol
   carries information regarding the internal structure of the SIP
   message (to avoid reparsing) and also information about the
   associated transaction (recall that invoking as_relay_t
   indirectly calls t_newtran). This way, all the SIP-Transaction
   machinery, and the SIP-Message parsing, is handled at the
   OpenSIPS core, while the execution of the Application Logic is
   carried in the Application Server.

   The SEAS module and protocol provide a means by which an
   external entity can utilize OpenSIPS as a transaction-stateful
   SIP-stack to act on behalf of it. This means that this external
   entity (which we call the Application Server) is notified
   whenever a SIP-Request enters OpenSIPS, and this external
   entity can then order OpenSIPS to execute some actions, either
   replying the request, or generating new UAC transactions.

   This version of SEAS works with VozTelecom's WeSIP Application
   Server. This Application Server is a SipServlet JAVA Container.

1.1.2. Application Servers

   When OpenSIPS starts and SEAS module is loaded, a new process
   is spawn which listens on a server-socket (IP and port are
   specified as a parameter in the config script). From then on,
   the Application Servers can connect to that socket so that
   OpenSIPS can relay messages to them. When an Application Server
   connects to the socket, it sends its name through the socket,
   so every App Server is identified with a name. Within the
   OpenSIPS script, invoking as_relay_t() receives a string as a
   parameter, which specifies the name of an application server to
   which the message has to be sent. If that concrete application
   server hasn't already connected to the module, the function
   returns a negative value, otherwise (the Application Server is
   connected), the message is relaied to it.

1.1.3. Dependencies

1.1.3.1. OpenSIPS Modules

   SEAS module relies on the Transaction Module (TM module) for
   operation.

1.1.3.2. External Applications

   Using the SEAS module requires to have an Application Server
   running and connected to a particular instance of OpenSIPS.

   At the moment, the only Application Server that works with SEAS
   is WeSIP Application Server, which can be downloaded from
   www.wesip.eu, and used freely for non-comercial purposes.

1.1.4. Exported Parameters

1.1.4.1. listen_sockets (string)

   The listen_sockets string tells SEAS where to listen for
   incoming connections of Application Servers. It has the form:
   "ip:port". SEAS will open two server-sockets on that IP, at the
   specified port, and another at port+1. Application Servers must
   be configured to connect to that port.

   In case this parameter is ommited, SEAS listens on the default
   IP which OpenSIPS is using, and opens the ports 5080 and 5081
   to listen for Application Servers.

   Example 1.1. Set listen_sockets parameter
...
modparam("seas", "listen_sockets","127.0.0.1:5080")
...

1.1.5. Exported Functions

1.1.5.1. as_relay_t(name)

   Creates a new transaction (if it isn't already created) and
   sends the SIP Request and transaction information to the
   Application Server specified in the parameter. Every
   Application Server connected to OpenSIPS through the SEAS
   module, must be identified with a different name.

   Parameters:
     * name (string)

   This function can be used within REQUEST_ROUTE.

   Example 1.2. as_relay_t usage
...
if (!as_relay_t("app_server_1")) {
log("Error sending to app server");
t_reply(500,"App Server not connected");
}
...

1.1.5.1.1. Return value

   In case the Application Server is connected to OpenSIPS, the
   function does _not_ return, the Application Server is now in
   charge of processing the request, and it may then reply to the
   request, initiate new transactions, or whatever the application
   being executed wants.

   In case the Application Server identified by the string
   parameter passed to as_relay_t() is not connected to OpenSIPS,
   the function returns 0, so that the script can continue
   processing the request.

1.2. WeSIP Application Server

   At the moment, the only Application Server known to work with
   SEAS is WeSIP. You can download a copy from www.wesip.eu.

   WeSIP is a converged Sip/Http Servlet Container.

1.2.1. The Servlet programming paradigm: Sip/Http Servlets

   Servlets are pieces of code that encapsulate the logic of an
   application. Servlets are deployed into an Application Server.
   Whenever a user requests service, the Application Server
   processes the request, and passes control to the servlet. The
   servlet then executes some logic, may it be a query to a
   database, the execution of a business process, the creation of
   customized content for the user, or whatever the service
   programmer could imagine. When the servlet finishes the
   execution, it creates a response and gives it back to the
   Application Server, which is in charge of making it reach back
   to the user. The Application Server implements the network
   protocol, it takes care of everything needed for a proper
   communication between user and server, so the servlet doesn’t
   have to care about these things. The servlet uses a set of
   resources from the Application Server, such as Session
   management, service routing or chaining, and request/response
   header composition.

   In HttpServlets, a service programmer has to implement a method
   in a JAVA class, which could be called doGet() or doPost().
   Whenever an HTTP request arrived at the server, one of these
   functions was called with the request as a parameter, so the
   logic of the application was executed over that particular
   request.

   HttpServlet has been extensively used over the past years, in
   all kinds of business and web services.

   This is how a typical HttpServlet looks like:

   Example 1.3. Typical example of an HttpServlet
public final class Hello extends HttpServlet {
protected void doGet(HttpServletRequest request,HttpServletResponse resp
onse)
      throws IOException, ServletException
    {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Sample Application Servlet</title>");
writer.println("</head>");
writer.println("<body bgcolor=white>");
writer.println("<table border=\"0\" width=\"100%\">");
Enumeration names = request.getHeaderNames();
while (names.hasMoreElements()) {
    String name = (String) names.nextElement();
    writer.println("<tr>");
    writer.println("<th align=\"right\">"+name+":</th>");
    writer.println("<td>"+request.getHeader(name)+"</td>");
    writer.println("</tr>");
}
writer.println("</table>");
writer.println("</body>");
writer.println("</html>");
    }
}

   The successor of HttpServlet for SIP networks, is the
   SipServlet API. Making most of the success of HttpServlet, the
   SipServlet API follows the same programming paradigm, so that
   SIP application programmers can reuse their knowledge in the
   field.

   SipServlet API works the same way as HttpServlet: an
   Application Server implements a SIP Stack and executes all the
   complex protocol logic. It receives and pre-processes the
   requests from the network, and at the right moment, passes
   control to the servlet doXxx() method, where the programmer
   implemented the application logic. Depending on what kind of
   SIP Message it was, a method or another will be executed. For
   example, if an INVITE is received, the doInvite() method will
   be invoked in the servlet.

   The application can then access all the parts of the request
   and do its work. When the service has been executed, it passes
   control back to the Application Server with a response, so that
   it can be forwarded to the user, and the service be satisfied.

   Sip Servlets can be used to implement basic SIP network
   functionalities (such as Proxy or Registrar servers), but their
   true power emerges in the implementation of value-added
   services, which greatly surpasses the basic service
   functionality of plain SIP servers.

   Examples of value-added services, are Virtual PBX or IPCentrex,
   Attended call forwarding, Instant Messaging, etc.

   This is the appearance a typical SipServlet:

   Example 1.4. Typical Sip Servlet Example
public class ProxyServlet extends SipServlet {
    protected void doInvite(SipServletRequest req) throws
ServletException, IOException
    {
        if (req.isInitial()) {
            Proxy proxy = req.getProxy();
            proxy.setRecordRoute(false);
            proxy.setParallel(parallel);
            proxy.setSupervised(supervised);
            SipURI rrURI = proxy.getRecordRouteURI();
            rrURI.setParameter("foo", "bar");
            req.setContent("Method is INVITE", "text/plain");
            proxy.proxyTo(uris);
        } else {
            log("re-INVITE");
        }
    }
    protected void doAck(SipServletRequest req) throws
ServletException, IOException
    {
        log("doAck " + req.getRequestURI());
        if (req.isInitial()) {
            throw new ServletException("unexpectedly got initial ACK");

   The servlet programming API is event-ridden: every time a
   request comes into the Application Server (may it be an Http or
   SIP one), the specific servlet is executed and the service
   provided within it.

   It is a very straightforward way of programming services, and
   the Servlet API provides very easy and powerful means to access
   information about the SIP-session or Http-session, about the
   request or response, about the state of the dialog, or whatever
   it is needed.

   The application programmer has a rich framework of resources
   that allow him to focus only on the service logic, without
   having to worry about the underlying protocol specifics (SIP or
   HTTP).

   Figure 1.1. SipServlet UML diagram
   SipServlet UML diagram

   The Servlet programming language is JAVA, which offers a wide
   spectrum of programming API’s dealing with all kinds of
   techniques, tools and resources, which also are available
   seamlessly from the Servlet context.

   This makes the SipServlet API very desirable for all kinds
   application developers.

   SipServlet allows a rapid SIP application development and
   deployment, and also provides a reliable and secure framework
   of service execution (the JAVA sandbox and the Application
   Server execution environment).

1.2.1.1. Converged Http/Sip Servlet Containers

   SipServlets achieve the most of it when they can be deployed
   along with HttpServlets, in the same Application Server (also
   known as Servlet Container). This environment truly realizes
   the power of converged voice/data networks: Http protocol
   represents one of the most powerful data transmission protocols
   used in modern networks (think of the SOAP web-services
   protocol), and SIP is the protocol of choice in most of the
   modern and future voice over IP (VoIP) networks for the
   signaling part. So an Application Server capable of combining
   and leveraging the power of these two APIs will be the most
   successful.

   Convergence of SIP and HTTP protocols into the same Application
   Server offers, amongst others, the following key advantages:

   -It doesn’t require to have 2 different servers (Http and Sip)
   so it relieves from maintenance problems, and eases user and
   configuration provisioning.

   -It offers great convenience to the application programmer to
   have all the classes related to the different protocols handled
   within the same code.

   -As it eases development of interactive and multimedia
   services, realizing the power of well-known web-services and
   intermixing them with new voice services.

   These are some simple, but suggestive examples of services that
   could be developed within a converged Http/Sip servlet:

   -IP Centrex: through the use of the Web-interface, users could
   have a layout of the office in a web page, and see what phones
   were ringing at a given moment, so they could pick-up a call
   ringing in another phone in their own desktop. Or they could
   forward a call to another party by clicking on the web page and
   selecting which of the office phones it had to be transferred
   to.

   -Voicemail: users could upload an audio file to the server
   through a web-page, to be used as the automatic answering
   message, and then also download their voicemail through the
   web-page, or organize the messages and remove the old ones.

   -Instant Messaging: users could continue a voice call by
   starting or joining a new Instant Messaging session carried
   over a web-page.

   -Click-to-dial: users could initiate SIP sessions only by
   clicking a link on a web page, without the need of the
   Web-Browser being SIP-aware nor needing even a SIP phone: the
   server could handle all the logic so the user who clicked could
   receive a call from the server’s SIP network.

1.2.2. Configuring WeSIP to work with SEAS

   The WeSIP Application Server configuration file is based on the
   Apache Tomcat configuration system: It is an XML-formatted
   file, in which the different components of the server are
   specified.

   The default config file that comes with the WeSIP distribution
   package should be suitable for most of the deployment
   configurations.

1.2.2.1. Server

   The topmost element in the XML configuration file is the
   "server" which has 2 xml attributes, called "port" and
   "shutdown". The former specifies a port on which the WeSIP AS
   will listen for the shutdown command, and the latter is the
   magic word that will make the server shutdown.

   Example 1.5. Server
<Server port="8005" shutdown="SHUTDOWN" >

   if you send the magic word "SHUTDOWN" to the port 8005 of the
   localhost, the server will stop cleanly.

1.2.2.2. Service

   Nested within the Server element, must be a "Service" element,
   with an attribute called "name" which specifies the name for
   the service. This attribute is not very relevant, you can call
   it whatever you like.

   Example 1.6. Service

   <Service name="WeSIP-Standalone">

   Within the Service element must be two or more elements: the
   connectors and the engines. A connector is the instance that
   will receive messages from the network. You can specify HTTP
   connectors and/or SIP connectors. Every connector needs an
   attribute called "className" which specifies which class will
   be responsible for receiving the messages from the network. For
   HTTP connectors, the classname must be
   "org.apache.catalina.connector.http.HttpConnector" and for SIP
   connectors "com.voztele.sipservlet.connector.SipConnector".

1.2.2.3. Connector

   The SIP Connector uses 4 attributes:
className="com.voztele.sipservlet.connector.SipConnector"

   specifies the classname of the connector.
minProcessors="5"

   specifies the minimum number of SIPprocessor instances (and
   threads in the pool) to process incoming SIP messages. More
   processors should allow more load to be processed. This is the
   minimum number of instances, even if they are spare and not
   working.
maxProcessors="10"

   specifies the maximum number of SIP processors used (a negative
   value specifies that there is no limit).
addresses="localhost:5060"

   Specifies the SIP address and port in which the Application
   Server from which the Application Server will process the SIP
   messages. This Addres is where OpenSIPS listens for the
   messages, so in fact, OpenSIPS is listening on them, but
   OpenSIPS passes the messages to WeSIP, so WeSIP must be aware
   of this IP/port.

Warning

   this attribute MUST match one of the listening points declared
   within OpenSIPS in the "listen" parameters.

   For example in opensips.cfg:
listen = tcp:localhost:5060
listen = udp:localhost:5060

   Within the SIP Connector element there must be an
   ExtraProperties element, containing nestes Property elements.
   Each property element specifies a parameter for the SIP Stack.
   Each property is specified by a key and a value. The most
   significant keys are:
     * com.voztele.javax.sip.SER_ADDRESS
       This specifies the IP and port in which the OpenSIPS is
       listening for Application Servers to connect and
       register.This specifies the IP and port in which the
       OpenSIPS is listening for Application Servers to connect
       and register.

Warning
       This needs to match the listen_sockets seas module
       parameter within the OpenSIPS configuration file. Ie.:
modparam("seas", "listen_sockets","127.0.0.1:5080")
     * javax.sip.STACK_NAME
       Specifies the name identifying this instance of the
       Application Server.

Warning
       This is the name you will set in the OpenSIPS configuration
       script when you invoke the WeSIP Application Server, by
       calling the as_relay_t function. This is the name you pass
       as the parameter of the function. If you have different
       WeSIP instances all connecting to the same OpenSIPS, they
       must each one have a different STACK_NAME", and within
       OpenSIPS you can call each of them by invoking as_relay_t()
       with a different name. Example:
<Property key="javax.sip.STACK_NAME" value="app_server_one" />
     * com.voztele.javax.sip.THREAD_POOL_SIZE (integer)
       Specifies the number of threads there must be in the pool
       to process incoming SIP messages. If unspecificed, the
       default is "infinity".
     * com.voztele.javax.sip.SPIRAL_HDR
       This property tells WeSIP and SEAS that every SipRequest
       and UAC transaction generated from WeSIP, must spiral
       through SER, and will be added a special Header called
       "X-WeSIP-SPIRAL: true" this will make all the outgoing
       messages pass again through the OpenSIPS script, so that
       they can be accounted or whatever the configurator wants.
       For example, the configuration script could go:
route{
        if(is_present_hf("X-WeSIP-SPIRAL")){
 /* account, log, register, or whatever */
                t_relay();
        }else{
                as_relay_t("app_server_1");
        }
}

1.2.2.4. Engine

   The Engine must also be nested within the Server element, along
   with the Connectors. It must have a "name" attribute with
   whatever name you feel like. It needs to have another attribute
   called "defaultHost" which will be the default host to which to
   pass the incoming request (in HTTP/1.0 the requests dont have a
   Host header, so they will be passed to this default host, in
   SIP, this attribute doesn't have a meaning.). In order to have
   this Engine handling also SIP messages, the "className"
   attribute of the Engine must be
   "com.voztele.sipservlet.core.ConvergedEngine".

   Within the Engine, there can be one or more Hosts, each one
   specified within a "Host" element nested in the engine.

1.2.2.5. Mapper

   A mapper is used to map an incoming request to one or another
   SIP or HTTP host. In case it is a SIP request, the mapping is
   done based on the sip.xml deployment descriptor rules. The
   classname of the SIP mapper MUST BE
   "com.voztele.sipservlet.core.EngineSipMapper". The "mapper"
   element must also have a "protocol" attribute, specifying which
   protocol this mapper handles. In case of the SIP mapper it must
   be "SIP/2.0". The HTTP mapper's classname must be
   "org.apache.catalina.core.StandardEngineMapper" and the
   protocol attribute "HTTP/1.1"

1.2.2.6. Realm

   The authentication in HTTP is performed in Apache-Tomcat
   through Realms. The memory realm is (textual copy from the
   Apache-Tomcat javadoc"): "Simple implementation of Realm that
   reads an XML file to configure the valid users, passwords, and
   roles."

   The classname must be "org.apache.catalina.realm.MemoryRealm"

   A "pathname" attribute can be specified to tell the Realm which
   file contains the usernames, passwords and roles. If not
   specified, it is "conf/wesip-users.xml"

1.2.2.7. Host

   A Host represents a VirtualHost in HTTP/1.1 servers, so the
   requests will be dispatched to one or another virtual host
   depending on the Host: header. In SIP this doesn't make much
   sense, because there's no such Host: header, and virtual
   hosting is not done in this way. Every host must have a "name"
   attribute which specifies the name of the virtual host, it must
   also have a "nameSip" attribute which MUST MATCH the IP or
   hostname _and_ port" specified in OpenSIPS listen parameters
   and in the Sip Connector the hostname and the port must be
   separated with an underscore. for example:
   nameSip="localhost_5060" or nameSip="192.168.1.1_5060" The next
   important attribute that must have the Host element is
   "appBase" which declares the directory where the WEB and SIP
   applications reside. It usually is a directory called apps in
   the directory from which the server runs. The attribute
   "unpackWARs" says the WeSIP Application Server to unpack the
   Web or Sip Application Archives (.war or .sar extensions) found
   inside the appBase directory. It should usually be set to
   "true". The "port" attribute specifies the port where this host
   is going to receive SIP messages . This only has to do with the
   SIP protocol, not with HTTP. It must be the same as the port
   specified in OpenSIPS parameter "listen_sockets" (for the seas
   module). The "autoDeploy" attribute tells the host to monitor
   the "appBase" directory for new application archives (.sar or
   .war) so they can automatically be deployed. This parameter
   should be set to "true". The "className" used for the Host
   _must_be_ "com.voztele.sipservlet.core.ConvergedHost"

1.2.2.8. Mapper

   Hosts must also have a nested Mapper element, but when the
   mapper is inside a Host (and not in an Engine) the classnames
   must be "com.voztele.sipservlet.core.SipHostMapper" for the
   "SIP/2.0" protocol and
   "org.apache.catalina.core.HttpHostMapper" for the "HTTP/1.1"
   protocol. (2 mappers must be nested inside the Host).

1.2.3. Configuration Examples

   In general, you can configure WeSIP to work with your OpenSIPS
   in two ways: have 2 OpenSIPS instances, the first acting as
   Proxy/Registrar/Redirect and the second cooperating with WeSIP
   to act as the Application Server. This is the preferred
   deployment layout, as the first OpenSIPS works as usual, and
   the requests that need special services are relaied to another
   OpenSIPS which acts on behalf of the WeSIP AS. This
   configuration profile distributes load (call-routing logic in
   one instance, and Application Services in the other), and is
   also more fault-tolerant. On the other hand, you can have all
   your call-routing logic and Application Server on the same
   OpenSIPS, having one script handle all the logic, and then
   invoking the App Server at any point.

1.2.3.1. opensips.cfg in standalone

log_level=3        # debug level (cmd line: -dddddddddd)
log_stderror=no    # (cmd line: -E)
check_via=no    # (cmd. line: -v)
dns=no          # (cmd. line: -r)
rev_dns=no      # (cmd. line: -R)
port=5060
children=4
loadmodule "/usr/local/lib/opensips/modules/sl.so"
loadmodule "/usr/local/lib/opensips/modules/tm.so"
loadmodule "/usr/local/lib/opensips/modules/rr.so"
loadmodule "/usr/local/lib/opensips/modules/maxfwd.so"
loadmodule "/usr/local/lib/opensips/modules/usrloc.so"
loadmodule "/usr/local/lib/opensips/modules/registrar.so"
loadmodule "/usr/local/lib/opensips/modules/textops.so"
loadmodule "/usr/local/lib/opensips/modules/seas.so"
loadmodule "/usr/local/lib/opensips/modules/mi_fifo.so"

modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("usrloc", "db_mode",   0)
modparam("seas", "listen_sockets", "127.0.0.1:5080");

route{
       if (!mf_process_maxfwd_header("10")) {
               sl_send_reply("483","Too Many Hops");
               exit;
       };
       if ($ml >=  2048 ) {
               sl_send_reply("513", "Message too big");
               exit;
       };
       if (!$rm=="REGISTER")
               record_route();
       if (loose_route()) {
               append_hf("P-hint: rr-enforced\r\n");
               route(1);
       };
       if (is_myself("$rd")) {
               if ($rm=="REGISTER") {
                       save("location");
                       exit;
               };
               lookup("aliases");
               if (!is_myself("$rd")) {
                       append_hf("P-hint: outbound alias\r\n");
                       route(1);
               };
               if (!lookup("location")) {
                       sl_send_reply("404", "Not Found");
                       exit;
               };
               append_hf("P-hint: usrloc applied\r\n");
       };
       route(1);
}
route[1] {
                if(!as_relay_t("app_server_one")){
                       t_reply("500","Application Server error");
               }
}

1.2.3.2. opensips.cfg working as WeSIP front-end

log_level=9       # debug level (cmd line: -dddddddddd)
log_stderror=yes    # (cmd line: -E)

check_via=no    # (cmd. line: -v)
dns=no          # (cmd. line: -r)
rev_dns=no      # (cmd. line: -R)
port=5060
children=4

listen = tcp:localhost:5060
listen = udp:localhost:5060

mpath="/home/elias/src/sipservlet/seas"

loadmodule "modules/tm/tm.so"
loadmodule "modules/seas/seas.so"
loadmodule "modules/mi_fifo/mi_fifo.so"

modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("seas", "listen_sockets","127.0.0.1:5080")

route{
                if(!as_relay_t("app_server_1")){
                        t_reply("500","Application Server error");
                }

}

1.2.3.3. Server.xml

<Server port="8005" shutdown="SHUTDOWN" debug="0">
  <Service name="WeSIP-Standalone">
    <Connector className="org.apache.catalina.connector.http.HttpConnect
or"
        port="8080" minProcessors="5" maxProcessors="75"
        enableLookups="true" address="localhost" acceptCount="10" debug=
"10" />
        <Connector className="com.voztele.sipservlet.connector.SipConnec
tor"
        minProcessors="5" maxProcessors="75"
        addresses="localhost:5060" >
               <ExtraProperties>
                       <Property key="com.voztele.javax.sip.SER_ADDRESS"
 value="127.0.0.1:5080" />
                       <Property key="javax.sip.STACK_NAME" value="app_s
erver_one" />
                       <Property key="com.voztele.javax.sip.THREAD_POOL_
SIZE" value="10" />
               </ExtraProperties>
       </Connector>
    <Engine name="Standalone" defaultHost="localhost" debug="10"
       className="com.voztele.sipservlet.core.ConvergedEngine">

        <Logger className="org.apache.catalina.logger.SystemOutLogger"
           timestamp="true"/>
        <Mapper className="org.apache.catalina.core.StandardEngineMapper
" protocol="HTTP/1.1"/>
        <Mapper className="com.voztele.sipservlet.core.EngineSipMapper"
protocol="SIP/2.0"/>
        <Realm  className="org.apache.catalina.realm.MemoryRealm" />
        <Host name="localhost" nameSip="localhost_5060" debug="10" appBa
se="webapps" unpackWARs="true"
        port="5060" autoDeploy="true" className="com.voztele.sipservlet.
core.ConvergedHost">
               <Mapper className="com.voztele.sipservlet.core.SipHostMap
per" protocol="SIP/2.0"/>
               <Mapper className="org.apache.catalina.core.HttpHostMappe
r" protocol="HTTP/1.1"/>
     </Host>
  </Engine>
</Service>
</Server>

Chapter 2. Developer Guide

2.1. Internals

   The SEAS module runs within the Open Sip Express Router aka.
   OpenSIPS. OpenSIPS uses a pool of processes to execute the
   script logic on every new message received. These are called
   the worker processes. One of these processes will be selected
   to process the script, and at some point it will find a
   function invoking the relay of the SIP message to one of the
   Application Servers registered. This function has been called
   as_relay_t, which stands for Application Server relay (the _t
   stands for TransactionStatefully), and receives as the only
   parameter the name of the application server to be invoked.

   The process will execute the as_relay_t function, which looks
   up in a table if there is a registered Application Server with
   that name. If there is one, the process will craft the SEAS
   header for the SIP message being handled, put it in a shared
   memory segment, and write the address of that segment to a pipe
   (4 bytes pointer in IA32).

   This way, we will have all the OpenSIPS processes composing the
   SEAS header along with the SIP message, and putting its shared
   memory address into that pipe. This technique of inter-process
   communication avoids race conditions because writing to a pipe
   is granted to be an atomic operation if the data to write is
   less than _POSIX_PIPE_BUF, which usually is 512 bytes.

   At the initialization of OpenSIPS, the SEAS module creates the
   discussed pipe, so that all the OpenSIPS worker processes
   inherit the file descriptor associated to the pipe. Then it
   spawns a new process, which will be the one to open two server
   sockets, and wait for the Application Servers to connect and
   register.

   Each Application Server wishing to receive events from
   OpenSIPS, will have to open a socket to the module (the port
   and IP of the socket are defined at start time in the script).
   After connection, it has to print its identification name. The
   SEAS process (from now on, called event dispatcher) will then
   register it in its internal structures, so that the OpenSIPS
   processes can push events for it. The following picture, shows
   the internals of the SEAS Event dispatcher process:

   Figure 2.1. Overview of Seas Event Dispatcher process operation
   Overview of Seas Event Dispatcher process operation

   Within the SER server, the flowing of SIP Messages and control
   flow, is depicted in the following diagram:

   Figure 2.2. SIP Messages and control flow within SER
   SIP Messages and control flow within SER

2.2. SEAS Protocol

   SIP is a very flexible protocol. It can be very easily extended
   with new features, and SIP entities have a high level of
   freedom in composing the SIP messages, for example setting IPs
   or hostnames in URIs, reordering header fields, folding
   headers, aggregating/scattering headers, etc.

   This flexibility, though, makes it difficult to implement
   efficiently, because parsing of text headers requires a lot of
   state.

   OpenSIPS implements a very efficient parsing mechanism and
   SIP-transaction machinery. The goal of the SEAS protocol is to
   keep all this information that has been already extracted at
   OpenSIPS, so that it can be reused at the Application Server.

2.2.1. The SEAS protocol

   The SEAS protocol is a layer of information regarding the
   internal structure of a SIP message that is added whenever SEAS
   sends a SIP event to the Application Servers. The protocol is
   used for communication between OpenSIPS and the Application
   Servers.

   Once an incoming SIP message has reached the worker process
   within OpenSIPS, it copies its content into a private memory
   area (which is, a memory chunk not shared across processes). In
   this point, the message first line is parsed to know whether it
   is a SIP request or response.

   OpenSIPS uses a technique called lazy-parsing, which consists
   in delaying the parse of headers until some piece of the code
   requires it.

   As the SIP message goes traversing functions and the script
   code, a function called parse_msg() gets called again and
   again, and the SIP message gets parsed further and further.
   Each call to parse_msg passes an integer value argument (32
   bits) in which every bit signals a header to be parsed, if they
   are already parsed (because a previous invocation of
   parse_msg), the function returns immediately, otherwise, the
   SIP message is scanned and parsed until all the headers
   requested get parsed.

   In each call to parse_msg, different parts of the message are
   analyzed, and different SIP header-specific structures get
   filled. Each one of this structures, give quick access to each
   of the parts of a SIP message header.

   For example, a Via header struct is called via_body, and has
   these members: name, version, transport, host, proto, port,
   port_str, params, comment, received, rport, etc. each of these
   members gives quick access to each of the parts of the header.
   For example, a via header like this: “Via: SIP/2.0/UDP
   192.168.1.64:5070;branch=z9hG4bK-c02c60cc” would have the
   member proto pointing to the “U” of “UDP”, and a length of 3,
   the host member would be pointing to “192.168.1.64” and have a
   length of 12, the branch member would be pointing to
   “z9hG4bK-c02c60cc” and a length of 16, and so on.

   This structure is the result of the parsing. All this
   meta-information regarding the SIP message structure, is stored
   in a sip_msg structure, using dynamically-allocated memory
   segments.

   OpenSIPS defines different structure types describing different
   SIP headers, such as via_body, to_body, cseq_body, via_param,
   and so on. These structures are generally composed of another
   kind of structure called str.

   The str structure is a key component of OpenSIPS's high
   performance. In the C programming language, a string's length
   is known because a '0' (null-character) is found at the end of
   it. This forces each of the string manipulation functions to
   keep looking for a '0' in the byte stream, which is quite
   processor consuming. Instead of this, OpenSIPS defines a
   structure composed of a char pointer and an integer. The char
   points to the start of a string, and the integer gives its
   length, thus avoiding the '0' lookup problem, and giving a
   significant performance boost.

   This structure has been quite useful to the design of the SEAS
   protocol, because it enables the description of the SIP message
   anatomy by giving pointers to each of its fields, and integers
   describing each of its lengths.

   Knowing that a SIP header does not usually occupy more than a
   few characters (always less than 256), the pointer in the
   structure has been relativized to the beginning of the SIP
   message or the beginning of the SIP header, and the integer
   giving the length, has been casted to an unsigned byte (256
   values, so 256 characters maximum length).

   When messages get transferred from OpenSIPS to the Application
   Server, it is optimum to keep this worthy meta-information
   regarding the SIP message, so that it can be used at the AS
   part. For this to be possible, it is needed to store the
   pointers to each of the syntactic structures and their length.

   In general, pointers are variables that point to a region in
   the memory of a computer. The region of the memory is counted
   from the 0x00000000 address in IA32 architectures (from the
   beginning).

   C provides functionality to do any kind of arithmetic
   operations over pointers (add, subtract, multiply and divide),
   so that the euclidean distance over the one-dimension address
   space can be calculated just by subtracting a base address from
   another pointer.

   These pointers will have to be transmitted through the network,
   along with the SIP message, so for the pointers to keep their
   meaning, they need to be relativized to a known point, and the
   most meaningful known point in a SIP message is its start.

   So making the pointers relative to the message start, gives two
   important features: first, it makes the pointers still valid
   when they arrive at another computer (because they are relative
   to the beginning of the message), and they occupy far less
   memory, because from a 4-byte pointer (in IA32) it gets
   translated to a 1 or 2 byte index, because an important amount
   of redundant information is elicited (we already know that each
   of the parts of the message belong to the message, so why carry
   the message begin address in each of the pointers ?).

   The SIP messages are composed of protocol headers and a
   payload. The headers section don't usually surpass the 1500
   byte limit, amongst other reasons, because the usual Maximum
   Transmission Unit in Ethernet networks is 1500 bytes and the
   protocol was initially designed to work on UDP. For that
   reason, 11 bits should be enough to address a particular region
   within the SIP message, because it yields 2048 positions. The
   closest greater value to 11 bits multiple of a byte (the basic
   TCP network transport unit) is 16 bits, or 2 bytes, which makes
   it possible to address 65536 positions from the beginning.

   For the SEAS protocol to be extensible and
   platform-independent, all the 2-byte pointers or indexes to
   each of the message regions are sent in network-byte-order, or
   big endian. This is also useful in the JAVA part to retrieve
   the indexes, because the JAVA natively uses a big-endian
   representation of integers, regardless the architecture on
   which it runs.

   For each kind of standard SIP header (this is, the headers
   referred to in the SIP specification) there is a code
   specification, regarding the composition of the header. Each
   one of its parts points to one the several components of the
   header. For example, a From header always has a SipURI and may
   have several parameters, amongst others, a tag. Then, the From
   header code has a field indicating where the URI starts, a
   codification of the URI, and several pointers that point to
   each one of the parameter names and values. This is the
   codification of the From header. All the other headers have a
   similar codification.

2.2.2. General codification of a header

   Every header codification, regardless it is known to the server
   or not, begins with a 2-byte unsigned integer, which points to
   the beginning of that header counted from the SIP message begin
   (a SIP message start based pointer to the header). Following
   these two bytes is another byte giving the length of the name,
   and another byte giving the length of the entire header
   (including name and value).

   Figure 2.3. General codification of a SIP header in SEAS
   protocol
   General codification of a SIP header in SEAS protocol

   For example:

   Figure 2.4. Example of a from header SEAS-protocol codification
   Example of a from header SEAS-protocol codification

2.2.2.1. Codification of a generic URI

   As the SIP URI is one of the most used types in a SIP message,
   a special structure has been defined to describe the contents
   of it. A URI is always included inside a SIP header, or may be
   in the first line of a SIP Request (as the request URI).

   The codification of any URI is as follows:

   Figure 2.5. SEAS-codification of a SIP URI (byte meanings are
   shown)
   SEAS-codification of a SIP URI (byte meanings are shown)

   What follows is an example of a SIP URI codification with the
   SEAS protocol.

   Figure 2.6. Example of a SEAS SIP URI codification
   Example of a SEAS SIP URI codification

   The first byte in the encoded-URI structure, gives the index
   where the URI starts, counting from the beginning of the SIP
   header where it appears. The next two bytes are flags
   indicating known fields present in the URI (such as port, host,
   user, etc.).

   All the following bytes are uri-start based pointers to the
   fields that are present in the URI, as specified by the flags.
   They must appear in the same order shown in the flags, and only
   appear if the flag was set to 1.

   The end of the field, will be the place where the following
   pointer points to, minus one (note that all the fields present
   in a URI are preceded by 1 character, ie
   sip[:user][:passwod][@host][:port][;param1=x][;param2=y][?hdr1=
   a][&hdr2=b]) it will also be necessary to have a pointer at the
   end, pointing two past the end of the URI, so that the length
   of the last header can be computed.

   The reason to have the “other parameters” and headers flags at
   the beginning (just after the strictly URI stuff), is that it
   will be necessary to know the length of the parameters section
   and the headers section. The parameters can appear in an
   arbitrary order, they won't be following the convention of
   transport-ttl-user-method-maddr-lr, so we can't rely on the
   next pointer to compute the length of the previous pointer
   field, as the ttl parameter can appear before the transport
   parameter. So the parameter pointers must have 2 bytes:
   pointer+length.

2.2.2.2. Codification of To and From headers

   To and From headers follow the same structure, so the same
   codification structure has been used to describe both. The
   structure is depicted in the drawing:

   Figure 2.7. SEAS codification of From and To headers
   SEAS codification of From and To headers

2.2.2.3. Codification of Contact

   The contact header is one of those SIP headers that can be
   combined, which means that if several headers of the same type
   are present in the message, they can be aggregated in a single
   header, having the header values separated by a comma. Thus, a
   single Contact header can contain more than one contact-value.
   For this reason, the Contact codification is composed of a
   several Contact codifications concatenated, and a byte at the
   beginning telling how much Contact codifications are present.
   The code is depicted in the following drawing:

   Figure 2.8. SEAS codification of a Contact header
   SEAS codification of a Contact header

2.2.2.4. Codification of Route and Record Route headers

   Both Route and Record-Route headers follow an identical
   structure, and it is also permitted to combine several headers
   into one, with their bodies (or header values) separated by
   commas. In this case, both kinds of headers follow the same
   structure, defined as follows:

   Figure 2.9. SEAS codification of a Route Header
   SEAS codification of a Route Header

2.2.2.5. Codification of Accept and Content-Type headers

   These two kinds of headers carry mime type and subtype
   definitions in the form “type/subtype” (ie. text/xml,
   application/sdp or whatever). For internal handling of this
   headers, SER codifies the known types and subtypes into a
   single 32 bit integer, with the highest two bytes giving the
   mime type, and the lowest two bytes giving the subtype.

   The difference is that Accept header can also be combined,
   carrying more than one header value in a single header row.
   Thus the Accept header has a leading byte giving the number of
   mime type/subtype integers present, while the Content-Type only
   uses 4 bytes (a 32-bit integer) giving the type/subtype.

2.2.2.6. Codification of Authorization headers

   SIP has inherited the authentication scheme from HTTP, which is
   based on a digest scheme. There are several headers regarding
   these authorization scheme, namely Proxy-Authenticate,
   WWW-Authenticate, Authorization and Proxy-Authorization. All of
   them can be codified using the same schema, which is as
   follows:

   Figure 2.10. SEAS codification of Authentication/Authorization
   headers
   SEAS codification of Authentication/Authorization headers

   For each field present, there are 2 bytes, one pointing the
   place where it starts, the next giving how long this field is.
   The URI is a special case, and is composed of 1 byte telling
   how long is the URI structure, and then the encoded URI
   structure.

2.2.2.7. Codification of Allow headers

   Allow headers carry request methods that a user agent or proxy
   understands or is willing to accept. In SER, request methods
   are codified into a 32-bit integer, each of its bits signals a
   different kind of header. The Allow header is codified copying
   that integer into the payload of the header.

2.2.2.8. Codification of Content-Disposition headers

   The content-disposition is encoded within 2 bytes: the first is
   a header-start based pointer to where the content-disposition
   value starts, and the second is its length. If there are
   parameters present, each of them uses 1 byte pointing to where
   the parameter name starts, and 1 byte pointing to where the
   parameter value starts. From these two values, the parameter
   name and value lengths can be inferred.

2.2.2.9. Codification of Content-Length header

   The content length header is codified as a 4-byte unsigned
   integer, in network byte order.

2.2.2.10. Codification of Cseq header

   The Cseq header is codified using 9 bytes. The first one is a
   number corresponding to the internal value that SER assigns to
   that request method (the method ID). The following 4 bytes are
   an unsigned 32-bit integer according to the Cseq number. The
   next two bytes are the header based pointer to the beginning of
   the Cseq number and its length, and two more bytes pointing to
   the beginning of the method name and its length.

2.2.2.11. Codification of Expires header

   The expires header is composed of 6 bytes. The first four bytes
   are an unsigned 32-bit integer with the parsed value of the
   header (which is the number of seconds before a request
   expires). Then follows 1 byte pointing to the beginning of the
   header value (the expires value as a string) and a byte giving
   the length of the value.

2.2.2.12. Codification of a SIP message

2.2.2.12.1. The general message information section

   In SER, not only the headers are parsed with a high degree of
   optimization, but also the first line is. So for the SEAS
   protocol to realize this improvement, a codification for the
   first line of every SIP messages has also been defined.

   The first two bytes of the codification are a 2-byte unsigned
   integer. If its value is equal or greater than 100, then this
   is a response, and the integer represents its status code. If
   its value is smaller than 100, then it is a request, and the
   integer represents the method of the request being transported.

   Figure 2.11. SEAS codification of a SIP First Line
   SEAS codification of a SIP First Line

   The next two bytes are an unsigned integer which is a pointer
   to where the actual SIP message starts, beginning from the
   start of the codified payload.

   The next two bytes are also an unsigned integer giving the SIP
   message length.

   The next bytes differ on the meaning depending on whether the
   message is a SIP Request or Response.

   In case it is a Request:

   The next two bytes, are a SIP-message-start based pointer to
   where the method begins, and the method length.

   The next two bytes, are a SIP-message-start based pointer to
   where the Request URI begins, and the request URI length.

   The next two bytes, are a SIP-message-start based pointer to
   where the version identifier begins, and the version identifier
   length.

   In case it was a Response:

   The next two bytes, are a SIP-message-start based pointer to
   where the response code begins, and the response code length.

   The next two bytes, are a SIP-message-start based pointer to
   where the reason phrase begins, and the reason phrase length.

   The next two bytes, are a SIP-message-start based pointer to
   where the version identifier begins, and the version identifier
   length.

   In case the message is a SIP response, the following bytes
   correspond to the Request URI codification. The first byte is
   the length of the URI codification, followed by the URI code.

   The last byte in this set, is the number of headers present in
   the SIP message. After this byte, goes a section, called the
   Message Headers Index, which gives quick access to each of the
   headers and their codifications present in the message.

2.2.2.12.2. The headers index section

   As it has been already discussed, the aim of SEAS project is to
   achieve as high a performance as possible. One of the
   techniques enabling high performance in text-based servers is
   the so called lazy parsing. To enable the laziest possible
   parsing at the Application Server endpoint, a mechanism has
   been used so that access to a requested SIP header can be
   delayed until the application requests it, and the access can
   be direct to that header, without parsing the former headers
   present in the SIP message. Recall that one of the performance
   drawbacks of the SIP protocol is that headers of any type can
   be spread all along the header section, not having the
   constraint of putting the most critical sip-specific headers at
   the beginning and ordered (which would be, in fact, very
   desirable).

   For this to be possible, there is a section right after the
   beginning of the payload (the general message information
   section) which is a kind of hash table, giving quick access to
   the codes (as explained in the previous sections) of each of
   the headers present in the message.

   This sort of hash table, is composed of triplets of bytes. The
   first byte of each three is a code indicating which kind of
   header it points to (whether it is a From, To, Call-ID, Route
   header, etc). Then follows a 2 byte network-byte-order integer
   that points to a section in the codified-header where the body
   of this header is more specifically described.

   This gives really fast access to any of the headers. For
   example, if all the Route Headers were requested by the
   application, then a lookup in this table would be necessary,
   looking for the value '9' (corresponding to the Route header)
   in each of the positions multiple of 3 (0,3,6,9,12, etc). This
   can be done in a extremely fast and easy way, as this snipped
   of pseudo code explains:

   for(int j=0,int i=0;i<table_length;i+=3){

   if(payload[i]==9)

   results[j++]=i;

   }

   this would let in the “results” array all the indexes in the
   headers table that refer to a Route header. Then, the Route
   codification for each of the headers could be reached thanks to
   the two-byte unsigned integer that follows each of the header
   identifiers.

   Figure 2.12. SEAS Headers Index section overview
   SEAS Headers Index section overview

   So a SIP message codified by the SEAS protocol, has the
   following layout:

   Figure 2.13. SEAS SIP-Message codification
   SEAS SIP-Message codification

   SIP Messages are a fundamental part of the protocol, but they
   are not the only one. Transaction play a very important role in
   the SIP protocol, within SER and in any JAIN-SIP
   implementation. For this reason, the SEAS protocol also needs
   to define and implement some semantics regarding transaction
   handling. The events related to a transaction are: Incoming
   Request, Outgoing Request, Incoming Response, Outgoing
   Response, Timeout and Transport Error.

   So the SEAS protocol defines a specific format for each one of
   these events. Internally, SER stores the transactions in a hash
   table. This hash table generates an integer for each
   transaction applying a hash function to its Via branch
   parameter, this integer is the hash index, and it identifies in
   which slot within the hash table the transaction is stored. The
   transaction table usually uses 65536 entries, so the hash
   collision is pretty unlikely. Anyway, every hash entry is in
   reality a linked list of transactions, so in the case a hash
   collision (two transactions being assigned to the same hash
   slot) the transactions are added to the same slot, each one
   being identified by another integer called the label. The label
   within a hash slot, is initially generated randomly, and then
   increased by one each time a transaction falls in the same
   slot. So every transaction is identified by a hash index and a
   label.

   For incoming SIP requests, a transaction is generated at SER,
   and the SEAS module gets that transaction identifier (hash
   index + label), then grabs the source and destination IP, port
   and transport from every message, and crafts a SEAS RequestIn
   event. This kind of event carries all this information within
   it.

   In order to send Responses out for the Server Transactions,
   JAIN can send a type of Action messages, that order SER to send
   them to the network. These messages follow a structure very
   similar to that of RequestIn events: they start with the Action
   length in bytes, then follows a byte giving the type of action,
   then follows the Hash Index and the Label associated with the
   transaction that is being replied, and finally the SIP Message
   in raw format. It doesn’t use the SEAS codification described
   above, because SER can easily parse the JAIN provided Response
   to process it and send it out, so the pre-parsing is not needed
   in that direction.

   In order to generate Client Transactions, that is, sending SIP
   Requests out, JAIN utilizes another kind of action called Seas
   Request Action. In this case, when JAIN generates the Request
   to be sent out, it doesn’t have any means to know the
   transaction identifier (hash index and label) that will be
   assigned to it by SER, so a new mechanism has bee implemented
   to correlate JAIN requests to SER transactions. Basically,
   JAIN-SIP assigns a unique identifier (an integer) that is
   incremented by one for each new Client Transaction generated.
   This identifier is passed to SER along with the SIP Request, so
   when a SIP Response arrives to SER regarding that transaction,
   SER sends a ResponseIn event to the JAIN stack, containing both
   the initial integer identifying the transaction at JAIN and the
   hash index and label that have been assigned to the
   transaction. This way, JAIN can correlate its own identifiers
   with the identifiers used within SER.

   Figure 2.14. Different kinds of SEAS codified Events and
   Actions
   Different kinds of SEAS codified Events and Actions

   In case there is a Transaction Timeout, it is notified to the
   JAIN SIP Stack by passing it a Seas Incoming Response with a
   flag called Faked Reply, and a Response code number 408
   (Request Timeout).

Chapter 3. Contributors

3.1. By Commit Statistics

   Table 3.1. Top contributors by DevScore^(1), authored
   commits^(2) and lines added/removed^(3)
     Name DevScore Commits Lines ++ Lines --
   1. Elias Baixas 123 12 12255 736
   2. Bogdan-Andrei Iancu (@bogdan-iancu) 54 36 720 663
   3. Daniel-Constantin Mierla (@miconda) 49 8 1085 1773
   4. Razvan Crainea (@razvancrainea) 25 19 219 174
   5. Liviu Chircu (@liviuchircu) 18 14 85 138
   6. Anca Vamanu 11 2 332 302
   7. Henning Westerholt (@henningw) 10 6 25 115
   8. Vlad Patrascu (@rvlad-patrascu) 6 4 42 47
   9. Andrei Pisau 4 2 45 4
   10. Vlad Paiu (@vladpaiu) 3 1 6 3

   All remaining contributors: Konstantin Bokarius, Dan Pascu
   (@danpascu), Ionut Ionita (@ionutrazvanionita), John Riordan,
   Peter Lemenkov (@lemenkov), Sergio Gutierrez, Walter Doekes
   (@wdoekes), Edson Gellert Schubert, Stanislaw Pitucha.

   (1) DevScore = author_commits + author_lines_added /
   (project_lines_added / project_commits) + author_lines_deleted
   / (project_lines_deleted / project_commits)

   (2) including any documentation-related commits, excluding
   merge commits. Regarding imported patches/code, we do our best
   to count the work on behalf of the proper owner, as per the
   "fix_authors" and "mod_renames" arrays in
   opensips/doc/build-contrib.sh. If you identify any
   patches/commits which do not get properly attributed to you,
   please submit a pull request which extends "fix_authors" and/or
   "mod_renames".

   (3) ignoring whitespace edits, renamed files and auto-generated
   files

3.2. By Commit Activity

   Table 3.2. Most recently active contributors^(1) to this module
                      Name                   Commit Activity
   1.  Vlad Patrascu (@rvlad-patrascu)     May 2017 - Apr 2019
   2.  Walter Doekes (@wdoekes)            Apr 2019 - Apr 2019
   3.  Bogdan-Andrei Iancu (@bogdan-iancu) Dec 2006 - Apr 2019
   4.  Liviu Chircu (@liviuchircu)         Mar 2014 - Dec 2018
   5.  Peter Lemenkov (@lemenkov)          Jun 2018 - Jun 2018
   6.  Razvan Crainea (@razvancrainea)     Oct 2011 - Nov 2017
   7.  Ionut Ionita (@ionutrazvanionita)   Nov 2014 - Nov 2014
   8.  Vlad Paiu (@vladpaiu)               Sep 2011 - Sep 2011
   9.  Stanislaw Pitucha                   Jul 2010 - Jul 2010
   10. Anca Vamanu                         Oct 2007 - Sep 2009

   All remaining contributors: John Riordan, Sergio Gutierrez, Dan
   Pascu (@danpascu), Andrei Pisau, Henning Westerholt
   (@henningw), Daniel-Constantin Mierla (@miconda), Konstantin
   Bokarius, Edson Gellert Schubert, Elias Baixas.

   (1) including any documentation-related commits, excluding
   merge commits

Chapter 4. Documentation

4.1. Contributors

   Last edited by: Vlad Patrascu (@rvlad-patrascu), Peter Lemenkov
   (@lemenkov), Liviu Chircu (@liviuchircu), Bogdan-Andrei Iancu
   (@bogdan-iancu), Daniel-Constantin Mierla (@miconda),
   Konstantin Bokarius, Edson Gellert Schubert, Elias Baixas.

   Documentation Copyrights:

   Copyright © 2006 VozTelecom Sistemas
