B2B_ENTITIES
     __________________________________________________________

   Table of Contents

   1. Admin Guide

        1.1. Overview
        1.2. Dependencies

              1.2.1. OpenSIPS Modules
              1.2.2. External Libraries or Applications

        1.3. Exported Parameters

              1.3.1. server_hsize (int)
              1.3.2. client_hsize (int)
              1.3.3. script_req_route (str)
              1.3.4. script_reply_route (str)
              1.3.5. db_url (str)
              1.3.6. cachedb_url (str)
              1.3.7. cachedb_key_prefix (string)
              1.3.8. update_period (int)
              1.3.9. b2b_key_prefix (string)
              1.3.10. db_mode (int)
              1.3.11. db_table (str)
              1.3.12. cluster_id (int)
              1.3.13. passthru_prack (int)
              1.3.14. advertised_contact (str)
              1.3.15. ua_default_timeout (str)

        1.4. Exported Functions

              1.4.1. ua_session_server_init([key], [flags])
              1.4.2. ua_session_update(key, method, [body],
                      [extra_headers], [content_type])

              1.4.3. ua_session_reply(key, method, code, [reason],
                      [body], [extra_headers], [content_type])

              1.4.4. ua_session_terminate(key, [extra_headers])

        1.5. Exported MI Functions

              1.5.1. b2be_list
              1.5.2. ua_session_client_start
              1.5.3. ua_session_update
              1.5.4. ua_session_reply
              1.5.5. ua_session_terminate
              1.5.6. ua_session_list

        1.6. Exported Events

              1.6.1. E_UA_SESSION

   2. Developer Guide

        2.1. b2b_load_api(b2b_api_t* api)
        2.2. server_new
        2.3. client_new
        2.4. send_request
        2.5. send_reply
        2.6. entity_delete
        2.7. restore_logic_info
        2.8. update_b2bl_param

   3. Contributors

        3.1. By Commit Statistics
        3.2. By Commit Activity

   4. Documentation

        4.1. Contributors

   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 server_hsize parameter
   1.2. Set client_hsize parameter
   1.3. Set script_req_route parameter
   1.4. Set script_repl_route parameter
   1.5. Set db_url parameter
   1.6. Set cachedb_url parameter
   1.7. Set cachedb_key_prefix parameter
   1.8. Set update_period parameter
   1.9. Set b2b_key_prefix parameter
   1.10. Set db_mode parameter
   1.11. Set db_table parameter
   1.12. Set cluster_id parameter
   1.13. Set passthru_prack parameter
   1.14. Set advertised_contact parameter
   1.15. Set ua_default_timeout parameter
   1.16. ua_session_server_init usage
   1.17. ua_session_update usage
   1.18. ua_session_reply usage
   1.19. ua_session_terminate usage
   2.1. b2b_api_t structure

Chapter 1. Admin Guide

1.1. Overview

   The B2BUA implementation in OpenSIPS is separated in two
   layers:
     * a lower one(coded in this module)- which implements the
       basic functions of a UAS and UAC
     * a upper one - which represents the logic engine of B2BUA,
       responsible of actually implementing the B2BUA services
       using the functions offered by the low level.

   This module stores records corresponding to the dialogs in
   which the B2BUA is involved. It exports an API to be called
   from other modules which offers functions for creating a new
   dialog record, for sending requests or replies in one dialog
   and will also notify the upper level module when a request or
   reply is received inside one stored dialog. The records are
   separated in two types: b2b server entities and b2b client
   entities depending on the mode they are created. An entity
   created for a received initial message will be a server entity,
   while a entity that will send an initial request(create a new
   dialog) will be a b2b client entity. The name corresponds to
   the behavior in the first transaction - if UAS - server entity
   and if UAC - client entity. This module does not implement a
   B2BUA alone, but needs a B2B logic implementing module.

   The module is able to respond to authentication challanges if
   the uac_auth module is loaded first. The list of credentials
   for b2b authentication is also provided by the uac_auth module.

1.2. Dependencies

1.2.1. OpenSIPS Modules

     * tm
     * a db module
     * uac_auth (mandatory if authentication is required)

1.2.2. External Libraries or Applications

   The following libraries or applications must be installed
   before running OpenSIPS with this module loaded:
     * none

1.3. Exported Parameters

1.3.1. server_hsize (int)

   The size of the hash table that stores the b2b server entities.
   It is the 2 logarithmic value of the real size.

   Default value is “9” (512 records).

   Example 1.1. Set server_hsize parameter
...
modparam("b2b_entities", "server_hsize", 10)
...

1.3.2. client_hsize (int)

   The size of the hash table that stores the b2b client entities.
   It is the 2 logarithmic value of the real size.

   Default value is “9” (512 records).

   Example 1.2. Set client_hsize parameter
...
modparam("b2b_entities", "client_hsize", 10)
...

1.3.3. script_req_route (str)

   The name of the b2b script route that will be called when B2B
   requests are received.

   Example 1.3. Set script_req_route parameter
...
modparam("b2b_entities", "script_req_route", "b2b_request")
...

1.3.4. script_reply_route (str)

   The name of the b2b script route that will be called when B2B
   replies are received.

   Example 1.4. Set script_repl_route parameter
...
modparam("b2b_entities", "script_reply_route", "b2b_reply")
...

1.3.5. db_url (str)

   Database URL. It is not compulsory, if not set data is not
   stored in database.

   Example 1.5. Set db_url parameter
...
modparam("b2b_entities", "db_url", "mysql://opensips:opensipsrw@127.0.0.
1/opensips")
...

1.3.6. cachedb_url (str)

   URL of a NoSQL database to be used. Only Redis is supported at
   the moment.

   Example 1.6. Set cachedb_url parameter
...
modparam("b2b_entities", "cachedb_url", "redis://localhost:6379/")
...

1.3.7. cachedb_key_prefix (string)

   Prefix to use for every key set in the NoSQL database.

   Default value is “b2be$”.

   Example 1.7. Set cachedb_key_prefix parameter
...
modparam("b2b_entities", "cachedb_key_prefix", "b2b")
...

1.3.8. update_period (int)

   The time interval at which to update the info in database.

   Default value is “100”.

   Example 1.8. Set update_period parameter
...
modparam("b2b_entities", "update_period", 60)
...

1.3.9. b2b_key_prefix (string)

   The string to use when generating the key ( it is inserted in
   the SIP messages as callid or to tag. It is useful to set this
   prefix if you use more instances of opensips B2BUA cascaded in
   the same architecture. Sometimes opensips B2BUA looks at the
   callid or totag to see if it has the format it uses to
   determine if the request was sent by it.

   Default value is “B2B”.

   Example 1.9. Set b2b_key_prefix parameter
...
modparam("b2b_entities", "b2b_key_prefix", "B2B1")
...

1.3.10. db_mode (int)

   The B2B modules have support for the 3 type of database storage

     * NO DB STORAGE - set this parameter to 0
     * WRITE THROUGH (synchronous write in database) - set this
       parameter to 1
     * WRITE BACK (update in db from time to time) - set this
       parameter to 2

   Default value is “2” (WRITE BACK).

   Example 1.10. Set db_mode parameter
...
modparam("b2b_entities", "db_mode", 1)
...

1.3.11. db_table (str)

   The name of the table that will be used for storing B2B
   entities

   Default value is “b2b_entities”

   Example 1.11. Set db_table parameter
...
modparam("b2b_entities", "db_table", "some table name")
...

1.3.12. cluster_id (int)

   The ID of the cluster this instance belongs to. Setting this
   parameter enables clustering support for the OpenSIPS B2BUA by
   replicating the B2B entities (B2B dialogs) between instances.
   This also ensures restart persistency through the clusterer
   module's data "sync" mechanism.

   This OpenSIPS cluster exposes the "b2be-entities-repl"
   capability in order to mark nodes as eligible for becoming data
   donors during an arbitrary sync request. Consequently, the
   cluster must have at least one node marked with the "seed"
   value as the clusterer.flags column/property in order to be
   fully functional. Consult the clusterer - Capabilities chapter
   for more details.

   Default value is “0” (clustering disabled)

   Example 1.12. Set cluster_id parameter
...
modparam("b2b_entities", "cluster_id", 10)
...

1.3.13. passthru_prack (int)

   This parameter allows to control, whether a PRACK should be
   generated locally (=0) or if we request it to be end-to-end
   (=1).

   Default value is “0” (generate PRACK locally)

   Example 1.13. Set passthru_prack parameter
...
modparam("b2b_entities", "passthru_prack", 1)
...

1.3.14. advertised_contact (str)

   Contact to use in generated messages for UA session started
   with the ua_session_client_start MI function.

   Example 1.14. Set advertised_contact parameter
...
modparam("b2b_entities", "advertised_contact", "opensips@10.10.10.10:506
0")
...

1.3.15. ua_default_timeout (str)

   Default timeout, in seconds, for UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function. After this interval a BYE
   will be sent and the session will be deleted.

   If not set the default is 43200 (12 hours).

   Example 1.15. Set ua_default_timeout parameter
...
modparam("b2b_entities", "ua_default_timeout", 7200)
...

1.4. Exported Functions

1.4.1.  ua_session_server_init([key], [flags])

   This function initializes a new UA session by processing an
   initial INVITE. Further requests/replies received belonging to
   this session will only be handled via the E_UA_SESSION event.

   Parameters:
     * key (var, optional) - Variable to return the b2b entity key
       of the new UA session.
     * flags (string, optional) - configures options for this UA
       session via the following flags:
          + t[nn] - maximum duration of this session in seconds.
            After this timeout a BYE will be sent and the session
            will be deleted. If this is not set, the default
            timeout, configured with ua_default_timeout will be
            used. Example: t3600
          + a - report the receving of ACK requests via the
            E_UA_SESSION event.
          + r - report the receving of replies via the
            E_UA_SESSION event.
          + d - disable the automatic sending of ACK upon receving
            a 200 OK reply for INVITE (in case of UAC session) or
            re-INVITE.
          + h - provide the headers of the SIP request/reply in
            the E_UA_SESSION event.
          + b - provide the body of the SIP request/reply in the
            E_UA_SESSION event.
          + n - do not trigger the E_UA_SESSION event (with
            event_type NEW) for initial INVITES handled with this
            function.

   This function can be used from REQUEST_ROUTE.

   Example 1.16. ua_session_server_init usage
...
if(is_method("INVITE") && !has_totag()) {
   ua_session_server_init($var(b2b_key), "arhb");

   ua_session_reply($var(b2b_key), "INVITE", 200, "OK", $var(my_sdp));

   exit;
}
...

1.4.2.  ua_session_update(key, method, [body], [extra_headers],
[content_type])

   Sends a sequential request for a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Parameters:
     * key (string) - b2b entity key of the UA session.
     * method (string) - name of the SIP method for this request.
     * body (string, optional) - body to include in the SIP
       message.
     * extra_headers (string, optional) - extra headers to include
       in the SIP message.
     * content_type (string, optional) - Content-Type header. If
       the parameter is missing and a body is provided,
       "Content-Type: application/sdp" will be used.

   This function can be used from REQUEST_ROUTE, EVENT_ROUTE.

   Example 1.17. ua_session_update usage
...
ua_session_update($var(b2b_key), "OPTIONS");
...

1.4.3.  ua_session_reply(key, method, code, [reason], [body],
[extra_headers], [content_type])

   Sends a reply for a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Parameters:
     * key (string) - b2b entity key of the UA session.
     * method (string) - name of the SIP method that is replied
       to.
     * code (int) - reply code.
     * reason (string, optional) - reply reason string.
     * body (string, optional) - body to include in the SIP
       message.
     * extra_headers (string, optional) - extra headers to include
       in the SIP message.
     * content_type (string, optional) - Content-Type header. If
       the parameter is missing and a body is provided,
       "Content-Type: application/sdp" will be used.

   This function can be used from REQUEST_ROUTE, EVENT_ROUTE.

   Example 1.18. ua_session_reply usage
...
ua_session_reply($var(b2b_key), "INVITE", 180, "Ringing");
...

1.4.4.  ua_session_terminate(key, [extra_headers])

   Terminate a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Parameters:
     * key (string) - b2b entity key of the UA session.
     * extra_headers (string, optional) - extra headers to include
       in the SIP message

   This function can be used from REQUEST_ROUTE, EVENT_ROUTE.

   Example 1.19. ua_session_terminate usage
...
ua_session_terminate($var(b2b_key));
...

1.5. Exported MI Functions

1.5.1.  b2be_list

   This command can be used to list the internals of the b2b
   entities.

   Name: b2be_list

   Parameters: none

   MI FIFO Command Format:
        opensips-cli -x mi b2be_list

1.5.2.  ua_session_client_start

   This command starts a new UAC session by sending an initial
   INVITE. Further requests/replies received belonging to this
   session will only be handled via the E_UA_SESSION event.

   Name: ua_session_client_start

   Parameters:
     * ruri - Request URI
     * to - To URI; can also be specified as: display_name,uri in
       order to set a Display Name, eg.
       Alice,sip:alice@opensips.org.
     * from - From URI; can also be specified as: display_name,uri
       in order to set a Display Name, eg.
       Alice,sip:alice@opensips.org
     * proxy (optional) - URI of the outbound proxy to send the
       INVITE to
     * body (optional) - message body
     * content_type (optional) - Content Type header to use. If
       missing and a body is provided, "Content-Type:
       application/sdp" will be used.
     * extra_headers (optional) - extra headers
     * flags (optional) - flags with the same meaning as for the
       flags paramater of ua_session_server_init().
     * socket (optional) - OpenSIPS sending socket

   opensips-cli Command Format:
opensips-cli -x mi ua_session_client_start ruri=sip:bob@opensips.org \
to=sip:bob@opensips.org from=sip:alice@opensips.org flags=arhb

1.5.3.  ua_session_update

   Sends a sequential request for a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Name: ua_session_update

   Parameters:
     * key - b2b entity key of the UA session.
     * method - name of the SIP method for this request.
     * body (optional) - body to include in the SIP message.
     * extra_headers (optional) - extra headers to include in the
       SIP message.
     * content_type (string) - Content-Type header. If the
       parameter is missing and a body is provided, "Content-Type:
       application/sdp" will be used.

   opensips-cli Command Format:
opensips-cli -x mi ua_session_update key=B2B.436.1925389.1649338095 meth
od=OPTIONS

1.5.4.  ua_session_reply

   Sends a reply for a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Name: ua_session_reply

   Parameters:
     * key - b2b entity key of the UA session.
     * method - name of the SIP method that is replied to.
     * code - reply code
     * reason - reply reason string
     * body (optional) - body to include in the SIP message
     * extra_headers (optional) - extra headers to include in the
       SIP message
     * content_type (optional) - Content-Type header. If the
       parameter is missing and a body is provided, "Content-Type:
       application/sdp" will be used.

   opensips-cli Command Format:
opensips-cli -x mi ua_session_reply key=B2B.436.1925389.1649338095 metho
d=OPTIONS code=200 reason=OK

1.5.5.  ua_session_terminate

   Terminate a UA session started with the
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Name: ua_session_terminate

   Parameters:
     * key - b2b entity key of the UA session.
     * extra_headers (optional) - extra headers to include in the
       SIP message

   opensips-cli Command Format:
opensips-cli -x mi ua_session_terminate key=B2B.436.1925389.1649338095

1.5.6.  ua_session_list

   List information about UA sessions started with
   ua_session_server_init() function or the
   ua_session_client_start MI function.

   Name: ua_session_list

   Parameters:
     * key (optional) - b2b entity key of the UA session to list.
       If missing, all sessions will be listed.

   MI FIFO Command Format:
        opensips-cli -x mi ua_session_list

1.6. Exported Events

1.6.1.  E_UA_SESSION

   This event is triggered for requests/replies belonging to an
   ongoing UA session started with the ua_session_server_init()
   function or the ua_session_client_start MI function.

   Note that replies will not be reported at all unless the r flag
   was set when initiating the UA session. Also ACK requests are
   only reported if the a flag was set.

   Parameters:
     * key - b2b entity key of the UA session.
     * entity_type - indicates whether this is a UAS or UAc
       entity.
     * event_type - the type of event:
          + NEW - for initial INVITE requests, handled with the
            ua_session_server_init() function.
          + EARLY - for 1xx provisional responses
          + ANSWERED - for 2xx successful responses
          + REJECTED - for 3xx-6xx failure responses
          + UPDATED - for any sequential requests, including ACK
            but excluding BYE/CANCEL
          + TERMINATED - for BYE or CANCEL requests
     * status - the reply status code if the message is a SIP
       reply
     * reason - the reply reason if the message is a SIP reply
     * method - the SIP method name
     * body - SIP message body
     * headers - full list of all SIP headers in the message.

Chapter 2. Developer Guide

   The module provides an API that can be used from other OpenSIPS
   modules. The API offers the functions for creating and handing
   dialogs. A dialog can be created on a receipt initial message,
   and this will correspond to a b2b server entity, or initiated
   by the server and in this case a client entity will be created
   in b2b_entities module.

2.1.  b2b_load_api(b2b_api_t* api)

   This function binds the b2b_entities modules and fills the
   structure the exported functions that will be described in
   detail.

   Example 2.1. b2b_api_t structure
...
typedef struct b2b_api {
        b2b_server_new_t          server_new;
        b2b_client_new_t          client_new;

        b2b_send_request_t        send_request;
        b2b_send_reply_t          send_reply;

        b2b_entity_delete_t       entity_delete;

        b2b_restore_linfo_t       restore_logic_info;
        b2b_update_b2bl_param_t   update_b2bl_param;
}b2b_api_t;
...

2.2.  server_new

   Field type:
...
typedef str* (*b2b_server_new_t) (struct sip_msg* , str* local_contact,
                b2b_notify_t , str *mod_name, str* logic_key, struct b2b
_tracer *tracer,
                void *param, b2b_param_free_cb free_param);
...

   This function asks the b2b_entities modules to create a new
   server entity record. The internal processing actually extracts
   the dialog information from the message and constructs a record
   that will be stored in a hash table. The second parameters is a
   pointer to a function that the b2b_entities module will call
   when a event will come for that dialog (a request or reply).
   The third parameter is a pointer to a value that will be stored
   and given as a parameter when the notify function will be
   called(it has to be allocated in shared memory).

   The return value is an identifier for the record that will be
   mentioned when calling other functions that represent actions
   in the dialog(send request, send reply).

   The notify function has the following prototype:
...
typedef int (*b2b_notify_t)(struct sip_msg* msg, str* id, int type, void
* param);
...

   This function is called when a request or reply is received for
   a dialog handled by b2b_entities. The first parameter is the
   message, the second is the identifier for the dialog, the third
   is a flag that says which is the type of the message(it has two
   possible values - B2B_REQUEST and B2B_REPLY). The last
   parameter is the parameter by the upper module when the entity
   was created.

2.3.  client_new

   Field type:
...
typedef str* (*b2b_client_new_t) (client_info_t* , b2b_notify_t b2b_cbac
k,
                                b2b_add_dlginfo_t add_dlginfo_f, str *mo
d_name, str *logic_key,
                                struct b2b_tracer *tracer, void *param,
b2b_param_free_cb free_param);
...

   This function asks the b2b_entities modules to create a new
   client entity record and also create a new dialog by sending an
   initial message. The parameters are all the values needed for
   the initial request to which the notify function and parameter
   are added. The b2b_cback parameter is a pointer to the callback
   that must be called when an event happens(receiving a reply or
   request) in the dialog created with this function. The
   add_dlginfo_f parameter is also a function pointer to a
   callback that will be called when a final success response will
   be received for the created dialog. The callback will receive
   as parameter the complete dialog information for the record. It
   should be stored and used when calling send_request or
   send_reply functions.

   The return value is an identifier for the record that will be
   mentioned when calling other functions that represent actions
   in the dialog(send request, send reply).

2.4.  send_request

   Field type:
...
typedef int (*b2b_send_request_t)(enum b2b_entity_type ,str* b2b_key, st
r* method,
                str* extra_headers, str* body, b2b_dlginfo_t*);
...

   This function asks the b2b_entities modules to send a request
   inside a b2b dialog identified by b2b_key. The first parameter
   is the entity type and can have two values: B2B_SERVER and
   B2B_CLIENT. The second is the identifier returned by the create
   function(server_new or client_new) and the next are the
   informations needed for the new request: method, extra_headers,
   body. The last parameter contains the dialog information -
   callid, to tag, from tag. These are needed to make a perfect
   match to of b2b_entities record for which a new request must be
   sent.

   The return value is 0 for success and a negative value for
   error.

2.5.  send_reply

   Field type:
...
typedef int (*b2b_send_reply_t)(enum b2b_entity_type et, str* b2b_key, i
nt code, str* text,
                str* body, str* extra_headers, b2b_dlginfo_t* dlginfo);
...

   This function asks the b2b_entities modules to send a reply
   inside a b2b dialog identified by b2b_key. The first parameter
   is the entity type and can have two values: B2B_SERVER and
   B2B_CLIENT. The second is the identifier returned by the create
   function(server_new or client_new) and the next are the
   informations needed for the new reply: code, text, body,
   extra_headers. The last parameter contains the dialog
   information used for matching the right record.

   The return value is 0 for success and a negative value for
   error.

2.6.  entity_delete

   Field type:
...
typedef void (*b2b_entity_delete_t)(enum b2b_entity_type et, str* b2b_ke
y,
         b2b_dlginfo_t* dlginfo);
...

   This function must be called by the upper level function to
   delete the records in b2b_entities. The records are not cleaned
   up by the b2b_entities module and the upper level module must
   take care to delete them.

2.7.  restore_logic_info

   Field type:
...
typedef int (*b2b_restore_linfo_t)(enum b2b_entity_type type, str* key,
                b2b_notify_t cback, void *param, b2b_param_free_cb free_
param);
...

   This function is used at startup when loading the data from the
   database to restore the pointer to the callback function.

2.8.  update_b2bl_param

   Field type:
...
typedef int (*b2b_update_b2bl_param_t)(enum b2b_entity_type type, str* k
ey,
                str* param, int replicate);
...

   This function can be used to change the logic param stored for
   an entity ( useful in case an entity is moved between logic
   records).

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. Anca Vamanu 183 94 6839 1860
   2. Vlad Patrascu (@rvlad-patrascu) 127 62 5443 1147
   3. Razvan Crainea (@razvancrainea) 86 70 794 523
   4. Bogdan-Andrei Iancu (@bogdan-iancu) 60 51 515 202
   5. Ovidiu Sas (@ovidiusas) 57 42 987 348
   6. Liviu Chircu (@liviuchircu) 21 17 97 128
   7. Vlad Paiu (@vladpaiu) 6 4 74 47
   8. Carsten Bock 6 4 66 40
   9. Maksym Sobolyev (@sobomax) 6 4 29 22
   10. Nick Altmann (@nikbyte) 6 3 166 29

   All remaining contributors: Giedrius, Stanislaw Pitucha, Peter
   Lemenkov (@lemenkov), Ionut Ionita (@ionutrazvanionita),
   @DMOsipov, Stéphane Alnet (@shimaore), Henk Hesselink, Ryan
   Bullock (@rrb3942), Ibrahim Shahzad, Walter Doekes (@wdoekes).

   (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.  Bogdan-Andrei Iancu (@bogdan-iancu) Aug 2009 - Dec 2025
   2.  Ovidiu Sas (@ovidiusas)             Nov 2010 - Nov 2025
   3.  Razvan Crainea (@razvancrainea)     Dec 2010 - Oct 2025
   4.  Ibrahim Shahzad                     Jul 2025 - Jul 2025
   5.  Liviu Chircu (@liviuchircu)         Mar 2014 - Sep 2024
   6.  Maksym Sobolyev (@sobomax)          Jan 2021 - Nov 2023
   7.  Vlad Patrascu (@rvlad-patrascu)     May 2017 - Jun 2023
   8.  Giedrius                            Apr 2023 - May 2023
   9.  Carsten Bock                        Mar 2022 - Apr 2022
   10. Nick Altmann (@nikbyte)             Jan 2013 - Feb 2022

   All remaining contributors: Peter Lemenkov (@lemenkov),
   @DMOsipov, Ionut Ionita (@ionutrazvanionita), Walter Doekes
   (@wdoekes), Vlad Paiu (@vladpaiu), Ryan Bullock (@rrb3942),
   Stéphane Alnet (@shimaore), Anca Vamanu, Henk Hesselink,
   Stanislaw Pitucha.

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

Chapter 4. Documentation

4.1. Contributors

   Last edited by: Liviu Chircu (@liviuchircu), Vlad Patrascu
   (@rvlad-patrascu), Carsten Bock, Razvan Crainea
   (@razvancrainea), Peter Lemenkov (@lemenkov), Bogdan-Andrei
   Iancu (@bogdan-iancu), Vlad Paiu (@vladpaiu), Ovidiu Sas
   (@ovidiusas), Anca Vamanu.

   Documentation Copyrights:

   Copyright © 2009 Anca-Maria Vamanu

   Copyright © 2022 ng-voice GmbH
