Most of Sofia-SIP software is written as portable. All core modules are (or at least should be) written in ANSI C 89 with some ANSI C 99 features. If there are platform specific parts, they are collected to separate C files and isolated from the rest of the software with a wrapper interface.
SU module handles abstraction to OS specific functionality such as memory management, sockets, threads and time functions.
The following ANSI C 99 features are to be used in Sofia-SIP software:
The following ANSI C 99 features shall not be used in Sofia-SIP software:
As you should know, the length of native storage size depends on hardware, OS and compiler. This means in practice that the length of int or long is not necessarily 32 bits. As a consequence, you need to make sure that the value you intend to store in the int, can actually fit in int on different platforms. As a rule of thumb, if the integer value can exceed 8 bits, you should use types that have a defined length.
Nevertheless its OK to use native integer types if you bear in mind what was said above. The original reason for having only native data type was performance. The int type is always stored in the fastest (and usually biggest size) possible.
Never assume anything on the length of the type. Alway use sizeof() operator to find out the length.
C 99 standard defines the following fixed length data types:
To use these data types you must include the <sofia-sip/su_types.h> header, which takes care of including correct file. If su includes are not available, you must include the following code segment to each file where you plan to use them:
#if HAVE_STDINT_H #include <stdint.h> #elif HAVE_INTTYPES_H #include <inttypes.h> #else #error Define HAVE_STDINT_H as 1 if you have <stdint.h>, \ or HAVE_INTTYPES_H if you have <inttypes.h> #endif
The host byte order on different platforms vary. When you do only local processing, need not to worry about the byte order. But as soon as you start writing code that send or receives anything to the network, you need to start worrying.
If you wish to convert the byte order, it is simply done by calling one the following functions:
The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
You need to include <netinet/in.h> or <sofia-sip/su.h> to use these functions.
By default, compilers usually arrange structures so that they are quick to access. This means that most fields in the structure start at the 32 bit boundary. If you need to conserve memory, you may use structure packing.
To tell the compiler that you only need certain amount of bits to store a variable, you can use bit-fields. Compiler may or may not pack the bit-fields.
struct foo { unsigned bar:5; unsigned foo:2; unsigned :0; int something; }
If compiler decides to pack this structure, this code generates a structure that has bar and foo in the first seven bits, and then something beginning from the next 32 bit boundary.
One problem arises when using packed bit-fields: on ARM it is generally not possible to access a 32 bit field that does not start from the 32-bit boundary. Hence the example has the :0 padding member in the structure. Seems handy but beware: initialization of this structure fails on some ARM gcc compilers. (Ask Kai Vehmanen for details).
A way to force packing of a structure is to use preprocessor directive #pragma(pack)
. This directive is compiler specific, so if you plan to write truly portable code, you cannot use it. We have used it in some parts of the Sofia-SIP though. Only alternative is to write functions that fetch the desired bits from a 32 bit field with bit operations; not very handy and error prone.
The same aligment problem also arises if you cast for example char buffer to a int32_t. You can only read int32_t from the 32bit boundary on ARM platform. So be careful.
As a conclusion, when using bit-fields and stucture packing, beware of the pitfalls. If you don't really need to use them (as in parsing binary protocols), don't use them.
A Sofia-SIP library module can be defined as a subdirectory under the libsofia-sip-ua directory hierarchy that contains a file <modulename>.docs (where the <modulename> of course referes to the actual name of the module).
In case you like to start developing a new module, please contact Sofia-SIP development team so that they can help you to set up the basic module for you.
An overview of the contents of a module directory:
While C does not provide any special object-oriented features on its own, it is possible to program in object-oriented way using C, too. Sofia code make use of many object-oriented features while being written entirely in C.
Data hiding is a practice that makes separation between two modules very clear. Outside code cannot directly access the data within a module, but it has to use functions provided for that purpose. Data hiding also makes it easier to define a protocol between two objects - all communication happens using function calls.
How to implement data hiding in C? Easiest answer is to only declare data structures in the header files, not to define them. We have a typedef msg_t for struct msg_s in <sofia-sip/msg.h>, but the actual structure is defined in "msg_internal.h". Programs outside msg module does not have access to the struct msg_s , but they have to to access the msg_t object through method functions provided in <sofia-sip/msg.h>. The msg implementation is also free to change the internal layout of the structure, only keeping the function interface unmodified.
Abstract interface is another object-oriented practice used in Sofia. Parser headers, defined in <sofia-sip/msg_types.h>, is a good example of abstract interface. The type for message headers, msg_header_t, is defined using two C structures struct msg_common_s (msg_common_t), and struct msg_hclass_s (msg_hclass_t).
Abstract interface is achieved using virtual function table in msg_hclass_t structure, bit like C++ typically implements abstract classes and virtual functions. For implemenation of each header, the function table is initialized with functions responsible for decoding, encoding and manipulating the header structure. Unlike C++, the class of the object (msg_hclass_t) is represented by a real data structure which also contains header-specific data, like header name.
msg_hclass_t sip_contact_class[] = {{ /* hc_hash: */ sip_contact_hash, /* hc_parse: */ sip_contact_d, /* hc_print: */ sip_contact_e, /* hc_dxtra: */ sip_contact_dup_xtra, /* hc_dup_one: */ sip_contact_dup_one, /* hc_update: */ sip_contact_update, /* hc_name: */ "Contact", /* hc_len: */ sizeof("Contact") - 1, /* hc_short: */ "m", /* hc_size: */ MSG_ALIGN(sizeof(sip_contact_t), sizeof(void*)), /* hc_params: */ offsetof(sip_contact_t, m_params), /* hc_kind: */ msg_kind_append, /* hc_critical: */ 0 }};
Inheritance is a object-oriented practice that has limited use in Sofia. Most common example of inheritance is use of su_home_t. Many objects are derived from su_home_t, which means that they can use the various home-based memory management functions from <su_alloc.h>.
In this sence, inheritance means that a pointer to a derived object can be casted as a pointer to a base object. In other words, the derived object must have the base object at the beginning of its memory area:
struct derived { struct base base[1]; int extra; char *data; };
There are three alternatives to cast a pointer to derived to a pointer to base:
struct base *base1 = (struct base *)derived; struct base *base2 = &derived->base; struct base *base3 = derived->base;
The third alternative works because base was used as a 1-element array.
There are a few template types implemented as macros in Sofia libraries. They include hash table, defined in <sofia-sip/htable.h>, which can be used to define hash tables types and accessor functions for different object, and red-black tree, defined in <sofia-sip/rbtree.h>.
The home-based memory management is useful when a lot of memory blocks are allocated for given task. The allocations are done via the memory home, which keeps a reference to each allocated memory block. When the memory home is then freed, it will free all memory blocks to which it has reference. This simplifies application logic because application code does not need to keep track of the allocated memory and free every allocated block separately.
See documentation of <sofia-sip/su_alloc.h> and memory managment tutorial for more information of memory management services.
A typical example of use of a memory home is to have a memory home structure (su_home_t) as part of operation context information structure.
/* context info structure */ struct context { su_home_t ctx_home[1]; /* memory home */ other_t *ctx_other_stuff; /* example of memory areas */ ... }; /* context pointer */ struct context *ctx; /* Allocate memory for context structure and initialize memory home */ ctx = su_home_clone(NULL, sizeof (struct context)); /* Allocate memory and register it with memory home */ ctx->ctx_other_stuff = su_zalloc(ctx->ctx_home, sizeof(other_t)); ... processing and allocating more memory ... /* Release registered memory areas, home, and context structure */ su_home_zap(ctx->ctx_home);
Another place where home-based memory management makes programmers life easier is case where a sub-procedure makes multiple memory allocations and, in case the sub-procedure fails, all the allocations must be released and, in case the sub-procedure is succesfull, all allocations must be controlled by upper level memory management.
/* example sub-procedure. top_home is upper-level memory home */ int sub_procedure( su_home_t *top_home, ... ) { su_home_t temphome[1] = { SU_HOME_INIT(temphome) }; ... allocations and other processing ... /* was processing successfull ? */ if (success) { /* ok -> move registered allocated memory to upper level memory home */ su_home_move( top_home, temphome ); } /* destroy temporary memory home (and registered allocations) */ /* Note than in case processing was succesfull the memory */ /* registrations were already moved to upper level home. */ su_home_deinit(temphome); /* return ok/not-ok */ return success; }
See <sofia-sip/tstdef.h> for example of how to write module tests with macros provided by Sofia.
Here are some ideas of what you should test:
Automake, which is used to build Sofia SIP, has builtin support for unit tests. To add an automatically run test to your module, you just have to add the following few lines to your module's Makefile.am (of course, you have to write the test programs, too):
TESTS = test_foo test_bar check_PROGRAMS = test_foo test_bar test_foo_SOURCES = foo.c foo.h test_foo_LDADD = -L. -lmy test_bar_SOURCES = bar.c bar.h test_foo_LDADD = -L. -lmy
Each test program should either return zero for success or a non-zero error code in its main function. Now when you run "make check", my_test_foo and my_test_bar will be built and then run. Make will print a summary of how the tests went. As these tests are run from the build system, the tests must be non-interactive (no questions asked) and not rely on any files that are not in version control system.
Sofia SIP's top-level makefile contains a recursive check target, so you can use "cd sofia-sip ; make check" to run all the existing tests with a single command.
Sofia-SIP build system is based on the GNU tools automake, autoconf and libtool. This toolset has become the de-facto way of building Linux software. Because of this there is a lot of publicly available documentation about these tools, and of course, lots and lots of examples.
A good introduction to these tools is available at developer.gnome.org. Autobook provides more detailed documentation for autoconf and automake. The GNU make manual is also a good source of information.
At top-level there is a shell script called autogen.sh. It calls a convenient tool called autoreconf which will generate configure script for you. It also fixes the mode bits: the mode bits are not stored in darcs version control system.
$ sh autogen.sh $ ./configure ... with your configure options ...
The configure.ac file (older autoconf versions used also configure.in) contains the primary configuration for autoconf. configure.ac contains checks for all external libraries, non-standard language and compiler features that are needed to build the target module.
This file is created by the developer of the module.
Sofia-SIP's own autoconf macros are stored in the top-level direcry called m4. These macros, along with all other macros used by configure.ac, are copied into the module-specific aclocal.m4 file by an utility called aclocal.
Contact Sofia-SIP development team, if you need changes to these files.
The aclocal.m4 contains the definitions of the autoconf macros used in configure.ac.
This file is generated by aclocal command.
Makefile.am is where you define what programs and libraries should be built, and also what source files are needed to create them. When you run automake, it creates the file Makefile.in.
This file is created by the developer of the module.
When you run configure script, it performs all the checks defined in configure.ac and then replaces all xxx.in files with equivalent xxx files. All @FOO@
variables in the source *.in files are replaced with values found during the configuration process. For instance the variable @srcdir@
in Makefile.in is replaced in Makefile with the source directory path (useful when compiling outside the main source tree).
This file is generated by autoconf command.
This script stores the last parameters given to configre command. If necessary you can rerun the last given configure script (with given parameters) by using command "./config.status -r" or "./config.status --recheck".
This file is generated by configure script.
This file contains results of the various checks that configure script performed. In case the configure script failed, you might try to delete this file and run the configure script again.
This file is generated by configure script.
The Makefile contains the actual rules how to build the target libraries and program. It is used by the make
program. Makefile is generated from Makefile.in when you run autoconf
command. Ensure that "make dist" and "make install" targets work.
This file is generated by config.status and configure scripts.
This file contains C language defines of various confurable issues.
This file is generated by config.status and configure script.
This file contains C language defines describing how the Sofia SIP UA library is configured.
This file is generated by config.status and configure script.