Import libgb from openbsc.git
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..82e43e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,72 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.pc
+aclocal.m4
+acinclude.m4
+aminclude.am
+m4/*.m4
+autom4te.cache
+config.h*
+config.sub
+config.log
+config.status
+config.guess
+configure
+depcomp
+missing
+ltmain.sh
+install-sh
+stamp-h1
+libtool
+libosmocore-*.tar.*
+
+Doxyfile.core
+Doxyfile.gsm
+Doxyfile.vty
+Doxyfile.codec
+
+.tarball-version
+.version
+
+#gnu autotest
+tests/package.m4
+tests/atconfig
+tests/atlocal
+tests/osmo-test
+tests/package.m4
+tests/testsuite
+tests/testsuite.dir/
+tests/testsuite.log
+
+tests/sms/sms_test
+tests/timer/timer_test
+tests/msgfile/msgfile_test
+tests/ussd/ussd_test
+tests/smscb/smscb_test
+tests/bits/bitrev_test
+tests/a5/a5_test
+tests/auth/milenage_test
+tests/conv/conv_test
+tests/lapd/lapd_test
+tests/gsm0808/gsm0808_test
+
+utils/osmo-arfcn
+utils/osmo-auc-gen
+
+doc/codec
+doc/core
+doc/vty
+doc/gsm
+doc/html.tar
+
+src/crc*gen.c
+include/osmocom/core/crc*gen.h
+
+
+# vi files
+*.sw?
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Doxyfile.codec.in b/Doxyfile.codec.in
new file mode 100644
index 0000000..fcd5122
--- /dev/null
+++ b/Doxyfile.codec.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libosmocodec
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Osmocom codec library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc/codec
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = include/osmocom/codec src/codec
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is adviced to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = NO
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/Doxyfile.core.in b/Doxyfile.core.in
new file mode 100644
index 0000000..18eb226
--- /dev/null
+++ b/Doxyfile.core.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libosmocore
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Osmocom core library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc/core
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = include/osmocom/core src
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is adviced to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc/core
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = NO
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/Doxyfile.gsm.in b/Doxyfile.gsm.in
new file mode 100644
index 0000000..ab25b22
--- /dev/null
+++ b/Doxyfile.gsm.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libosmogsm
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Osmocom GSM library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc/gsm
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = include/osmocom/gsm include/osmocom/gsm/protocol src/gsm
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is adviced to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = NO
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/Doxyfile.vty.in b/Doxyfile.vty.in
new file mode 100644
index 0000000..57f19ad
--- /dev/null
+++ b/Doxyfile.vty.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libosmovty
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Osmocom VTY library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc/vty
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = include/osmocom/vty src/vty
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is adviced to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = NO
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..5a157ce
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,60 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+ACLOCAL_AMFLAGS = -I m4
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = include src tests utils
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libosmocore.pc libosmocodec.pc libosmovty.pc libosmogsm.pc
+
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+	echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+	echo $(VERSION) > $(distdir)/.tarball-version
+
+EXTRA_DIST = git-version-gen
+
+if HAVE_DOXYGEN
+
+pkgdocdir=$(docdir)/$(PACKAGE)-$(VERSION)
+doc_htmldir=$(pkgdocdir)/html
+
+doc_html_DATA = $(top_builddir)/doc/html.tar
+
+$(doc_html_DATA): $(top_builddir)/doc/core/html/index.html \
+		  $(top_builddir)/doc/gsm/html/index.html \
+		  $(top_builddir)/doc/vty/html/index.html \
+		  $(top_builddir)/doc/codec/html/index.html
+	cd $(top_builddir)/doc && tar cf html.tar */html
+
+$(top_builddir)/doc/core/html/index.html: $(SOURCES) Doxyfile.core
+	@rm -rf doc/core
+	mkdir -p doc/core
+	$(DOXYGEN) Doxyfile.core
+
+$(top_builddir)/doc/gsm/html/index.html: $(SOURCES) Doxyfile.gsm
+	@rm -rf doc/gsm
+	mkdir -p doc/gsm
+	$(DOXYGEN) Doxyfile.gsm
+
+$(top_builddir)/doc/vty/html/index.html: $(SOURCES) Doxyfile.vty
+	@rm -rf doc/vty
+	mkdir -p doc/vty
+	$(DOXYGEN) Doxyfile.vty
+
+$(top_builddir)/doc/codec/html/index.html: $(SOURCES) Doxyfile.codec
+	@rm -rf doc/codec
+	mkdir -p doc/codec
+	$(DOXYGEN) Doxyfile.codec
+
+install-data-hook:
+	cd $(DESTDIR)$(doc_htmldir) && tar xf html.tar && rm -f html.tar
+
+uninstall-hook:
+	cd $(DESTDIR)$(doc_htmldir) && rm -rf {core,gsm,vty,codec}
+
+DX_CLEAN = doc/{core,gsm,vty,codec}/{html,latex}/* doc/html.tar
+endif
+
+MOSTLYCLEANFILES = $(DX_CLEAN)
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..977eef9
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,178 @@
+AC_INIT([libosmocore],
+	m4_esyscmd([./git-version-gen .tarball-version]),
+	[openbsc@lists.osmocom.org])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+AC_PROG_LIBTOOL
+CHECK_GCC_FVISIBILITY
+
+AC_CONFIG_MACRO_DIR([m4])
+
+dnl checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h)
+# for src/conv.c
+AC_FUNC_ALLOCA
+AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""])
+AC_SUBST(LIBRARY_DL)
+
+AC_PATH_PROG(DOXYGEN,doxygen,false)
+AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false)
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+      [ AC_MSG_RESULT([yes])
+        SYMBOL_VISIBILITY="-fvisibility=hidden"],
+        AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+dnl Generate the output
+AM_CONFIG_HEADER(config.h)
+
+AC_ARG_ENABLE(talloc,
+	[AS_HELP_STRING(
+		[--disable-talloc],
+		[Disable building talloc memory allocator]
+	)],
+	[enable_talloc=$enableval], [enable_talloc="yes"])
+AM_CONDITIONAL(ENABLE_TALLOC, [test x"$enable_talloc" = x"yes"])
+
+AC_ARG_ENABLE(plugin,
+	[AS_HELP_STRING(
+		[--disable-plugin],
+		[Disable support for dlopen plugins],
+	)],
+	[enable_plugin=$enableval], [enable_plugin="yes"])
+AM_CONDITIONAL(ENABLE_PLUGIN, test x"$enable_plugin" = x"yes")
+
+AC_ARG_ENABLE(tests,
+	[AS_HELP_STRING(
+		[--disable-tests],
+		[Disable building test programs]
+	)],
+	[enable_tests=$enableval], [enable_tests="yes"])
+AM_CONDITIONAL(ENABLE_TESTS, test x"$enable_tests" = x"yes")
+
+AC_ARG_ENABLE(vty,
+	[AS_HELP_STRING(
+		[--disable-vty],
+		[Disable building VTY telnet interface]
+	)],
+	[enable_vty=$enableval], [enable_vty="yes"])
+AM_CONDITIONAL(ENABLE_VTY, test x"$enable_vty" = x"yes")
+
+AC_ARG_ENABLE(panic_infloop,
+	[AS_HELP_STRING(
+		[--enable-panic-infloop],
+		[Trigger infinite loop on panic rather than fprintf/abort]
+	)],
+	[panic_infloop=$enableval], [panic_infloop="no"])
+if test x"$panic_infloop" = x"yes"
+then
+	AC_DEFINE([PANIC_INFLOOP],[1],[Use infinite loop on panic rather than fprintf/abort])
+fi
+
+AC_ARG_ENABLE(bsc_fd_check,
+	[AS_HELP_STRING(
+		[--enable-bsc-fd-check],
+		[Instrument bsc_register_fd to check that the fd is registered]
+	)],
+	[fd_check=$enableval], [fd_check="no"])
+if test x"$fd_check" = x"no"
+then
+	AC_DEFINE([BSC_FD_CHECK],[1],[Instrument the bsc_register_fd])
+fi
+
+AC_ARG_ENABLE(msgfile,
+	[AS_HELP_STRING(
+		[--disable-msgfile],
+		[Disable support for the msgfile],
+	)],
+	[enable_msgfile=$enableval], [enable_msgfile="yes"])
+AM_CONDITIONAL(ENABLE_MSGFILE, test x"$enable_msgfile" = x"yes")
+
+AC_ARG_ENABLE(serial,
+	[AS_HELP_STRING(
+		[--disable-serial],
+		[Disable support for the serial helpers],
+	)],
+	[enable_serial=$enableval], [enable_serial="yes"])
+AM_CONDITIONAL(ENABLE_SERIAL, test x"$enable_serial" = x"yes")
+
+AC_ARG_ENABLE(utilities,
+        [AS_HELP_STRING(
+                [--disable-utilities],
+                [Disable building utility programs],
+        )],
+        [enable_utilities=$enableval], [enable_utilities="yes"])
+AM_CONDITIONAL(ENABLE_UTILITIES, test x"$enable_utilities" = x"yes")
+
+AC_ARG_ENABLE(embedded,
+	[AS_HELP_STRING(
+		[--enable-embedded],
+		[Enable building for embedded use and disable unsupported features]
+	)],
+	[embedded=$enableval], [embedded="no"])
+if test x"$embedded" = x"yes"
+then
+	AC_DEFINE([EMBEDDED],[1],[Select building for embedded use])
+	AM_CONDITIONAL(ENABLE_TESTS, false)
+	AM_CONDITIONAL(ENABLE_PLUGIN, false)
+	AM_CONDITIONAL(ENABLE_MSGFILE, false)
+	AM_CONDITIONAL(ENABLE_SERIAL, false)
+	AM_CONDITIONAL(ENABLE_VTY, false)
+	AM_CONDITIONAL(ENABLE_TALLOC, false)
+	AM_CONDITIONAL(ENABLE_UTILITIES, false)
+	AC_DEFINE([PANIC_INFLOOP],[1],[Use infinite loop on panic rather than fprintf/abort])
+fi
+
+
+AC_OUTPUT(
+	libosmocore.pc
+	libosmocodec.pc
+	libosmovty.pc
+	libosmogsm.pc
+	include/osmocom/Makefile
+	include/osmocom/vty/Makefile
+	include/osmocom/codec/Makefile
+	include/osmocom/crypt/Makefile
+	include/osmocom/gsm/Makefile
+	include/osmocom/gsm/protocol/Makefile
+	include/osmocom/core/Makefile
+	include/Makefile
+	src/Makefile
+	src/vty/Makefile
+	src/codec/Makefile
+	src/gsm/Makefile
+	tests/Makefile
+	tests/timer/Makefile
+	tests/sms/Makefile
+	tests/msgfile/Makefile
+	tests/ussd/Makefile
+	tests/smscb/Makefile
+	tests/bits/Makefile
+	tests/a5/Makefile
+	tests/auth/Makefile
+	tests/conv/Makefile
+	tests/lapd/Makefile
+	tests/gsm0808/Makefile
+	utils/Makefile
+	Doxyfile.core
+	Doxyfile.gsm
+	Doxyfile.vty
+	Doxyfile.codec
+	Makefile)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..20a0493
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,17 @@
+libosmocore (0.3.0) natty; urgency=low
+
+  * New upstream version of libosmocore
+
+ -- Harald Welte <laforge@gnumonks.org>  Tue, 10 May 2011 17:28:24 +0200
+
+libosmocore (0.1.27) natty; urgency=low
+
+  * New upstream version of libosmocore.
+
+ -- Holger Hans Peter Freyther <holger@freyther.de>  Thu, 13 Jan 2011 18:07:36 +0800
+
+libosmocore (0.1.17-1) unstable; urgency=low
+
+  * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
+
+ -- Harald Welte <laforge@gnumonks.org>  Tue, 24 Aug 2010 10:55:04 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..3f92875
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,27 @@
+Source: libosmocore
+Section: libs
+Priority: optional
+Maintainer: Harald Welte <laforge@gnumonks.org>
+Build-Depends: debhelper (>= 7.0.50~), autotools-dev, autoconf, automake, libtool, dh-autoreconf
+Standards-Version: 3.8.4
+Homepage: http://bb.osmocom.org/trac/wiki/libosmocore
+Vcs-Git: git://git.osmocom.org/libosmocore.git
+Vcs-Browser: http://git.osmocom.org/gitweb?p=libosmocore.git;a=summary
+
+Package: libosmocore
+Section: libs
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Open Source MObile COMmunications CORE library
+
+Package: libosmocore-dev
+Section: libdevel
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, libosmocore
+Description: Development headers for Open Source MObile COMmunications CORE library
+
+#Package: libosmocore-dbg
+#Section: libdevel
+#Architecture: any
+#Depends: ${shlibs:Depends}, ${misc:Depends}
+#Description: Debug symbols for Open Source MObile COMmunications CORE library
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..c450be5
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,54 @@
+This work was packaged for Debian by:
+
+    Harald Welte <laforge@gnumonks.org> on Tue, 24 Aug 2010 10:55:04 +0200
+
+It was downloaded from:
+
+    git://git.osmocom.org/libosmocore.git
+
+Upstream Author(s):
+
+    Harald Welte <laforge@gnumonks.org>
+    Holger Hans Peter Freyther <zecke@selfish.org>
+    Sylvain Munaut <tnt@246tNt.com>
+    Daniel Willmann <daniel@totalueberwachung.de>
+    Golde <nico@ngolde.de>
+	For src/talloc.c and include/osmocore/talloc.h:
+    Andrew Tridgell
+    Stefan Metzmacher
+	For src/vty/* and include/osmocom/vty/*
+    Kunihiro Ishiguro
+
+Copyright:
+
+    Copyright (C) 2008-2010 Harald Welte <laforge@gnumonks.org>
+    Copyright (C) 2008-2010 Holger Hans Peter Freyther <zecke@selfish.org>
+    Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
+    Copyright (C) 2009-2010 On-Waves
+    Copyright (C) 2008 Daniel Willmann <daniel@totalueberwachung.de>
+    Copyright (C) 2010 Nico Golde <nico@ngolde.de>
+	For src/talloc.c and include/osmocore/talloc.h:
+    Copyright (C) 2004 Andrew Tridgell
+    Copyright (C) 2006 Stefan Metzmacher
+	For src/vty/* and include/osmocom/vty/*
+    Copyright (C) 1998 Kunihiro Ishiguro
+
+License:
+
+    GNU General Public License, Version 2 or later
+
+The Debian packaging is:
+
+    Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>
+
+# Please chose a license for your packaging work. If the program you package
+# uses a mainstream license, using the same license is the safest choice.
+# Please avoid to pick license terms that are more restrictive than the
+# packaged work, as it may make Debian's contributions unacceptable upstream.
+# If you just want it to be GPL version 3, leave the following lines in.
+
+and is licensed under the GPL version 3,
+see "/usr/share/common-licenses/GPL-3".
+
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/debian/docs
diff --git a/debian/libosmocore-dbg.debhelper.log b/debian/libosmocore-dbg.debhelper.log
new file mode 100644
index 0000000..2742cb4
--- /dev/null
+++ b/debian/libosmocore-dbg.debhelper.log
@@ -0,0 +1,6 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
diff --git a/debian/libosmocore-dbg.dirs b/debian/libosmocore-dbg.dirs
new file mode 100644
index 0000000..af59b0a
--- /dev/null
+++ b/debian/libosmocore-dbg.dirs
@@ -0,0 +1 @@
+usr/lib/debug/lib
diff --git a/debian/libosmocore-dbg.install b/debian/libosmocore-dbg.install
new file mode 100644
index 0000000..7ce0212
--- /dev/null
+++ b/debian/libosmocore-dbg.install
@@ -0,0 +1 @@
+usr/lib/debug/lib/*
diff --git a/debian/libosmocore-dev.dirs b/debian/libosmocore-dev.dirs
new file mode 100644
index 0000000..94090a3
--- /dev/null
+++ b/debian/libosmocore-dev.dirs
@@ -0,0 +1,8 @@
+usr/lib
+usr/include
+usr/include/osmocom
+usr/include/osmocom/codec
+usr/include/osmocom/core
+usr/include/osmocom/crypt
+usr/include/osmocom/gsm
+usr/include/osmocom/vty
diff --git a/debian/libosmocore-dev.install b/debian/libosmocore-dev.install
new file mode 100644
index 0000000..eec0e15
--- /dev/null
+++ b/debian/libosmocore-dev.install
@@ -0,0 +1,5 @@
+usr/include/*
+usr/lib/lib*.a
+usr/lib/lib*.so
+usr/lib/lib*.la
+usr/lib/pkgconfig/*
diff --git a/debian/libosmocore.dirs b/debian/libosmocore.dirs
new file mode 100644
index 0000000..94090a3
--- /dev/null
+++ b/debian/libosmocore.dirs
@@ -0,0 +1,8 @@
+usr/lib
+usr/include
+usr/include/osmocom
+usr/include/osmocom/codec
+usr/include/osmocom/core
+usr/include/osmocom/crypt
+usr/include/osmocom/gsm
+usr/include/osmocom/vty
diff --git a/debian/libosmocore.install b/debian/libosmocore.install
new file mode 100644
index 0000000..d0dbfd1
--- /dev/null
+++ b/debian/libosmocore.install
@@ -0,0 +1 @@
+usr/lib/lib*.so.*
diff --git a/debian/patches/debian-changes-0.1.17-1 b/debian/patches/debian-changes-0.1.17-1
new file mode 100644
index 0000000..c0a54bd
--- /dev/null
+++ b/debian/patches/debian-changes-0.1.17-1
@@ -0,0 +1,46 @@
+Description: Upstream changes introduced in version 0.1.17-1
+ This patch has been created by dpkg-source during the package build.
+ Here's the last changelog entry, hopefully it gives details on why
+ those changes were made:
+ .
+ libosmocore (0.1.17-1) unstable; urgency=low
+ .
+   * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>
+ .
+ The person named in the Author field signed this changelog entry.
+Author: Harald Welte <laforge@gnumonks.org>
+
+---
+The information above should follow the Patch Tagging Guidelines, please
+checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
+are templates for supplementary fields that you might want to add:
+
+Origin: <vendor|upstream|other>, <url of original patch>
+Bug: <url in upstream bugtracker>
+Bug-Debian: http://bugs.debian.org/<bugnumber>
+Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
+Forwarded: <no|not-needed|url proving that it has been forwarded>
+Reviewed-By: <name and email of someone who approved the patch>
+Last-Update: <YYYY-MM-DD>
+
+--- /dev/null
++++ libosmocore-0.1.17/.version
+@@ -0,0 +1 @@
++0.1.17
+--- /dev/null
++++ libosmocore-0.1.17/copyright
+@@ -0,0 +1,14 @@
++Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
++Name: libosmocore
++Maintainer: Harald Welte <laforge@gnumonks.org>
++Source: git://git.osmocom.org/libosmocore.git
++
++Copyright: 2008-2010 Harald Welte <laforge@gnumonks.org>
++License: GPL-2+
++
++Files: src/talloc.c include/osmocore/talloc.h
++Copyright: 2004 Andrew Tridgell
++License: LGPL-3+
++
++Files: include/osmocore/linuxlist.h
++License: GPL-2
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..0ca407b
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+debian-changes-0.1.17-1
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..15f78f2
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,19 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+CFLAGS = -Wall -g
+
+%:
+	dh --with autoreconf $@ 
+
+#override_dh_strip:
+#	dh_strip --dbg-package=libosmocore-dbg
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..af745b3
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (git)
diff --git a/doc/.empty b/doc/.empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/.empty
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..42cf3d2
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#	echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#	echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+	*$nl*) v= ;; # reject multi-line output
+	[0-9]*) ;;
+	*) v= ;;
+    esac
+    test -z "$v" \
+	&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+elif
+       v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+	  || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && case $v in
+	 [0-9]*) ;;
+	 v[0-9]*) ;;
+	 *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    case $v in
+	*-*-*) : git describe is okay three part flavor ;;
+	*-*)
+	    : git describe is older two part flavor
+	    # Recreate the number of commits and rewrite such that the
+	    # result is the same as if we were using the newer version
+	    # of git describe.
+	    vtag=`echo "$v" | sed 's/-.*//'`
+	    numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+	    v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+	    ;;
+    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+    v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+	case $v in
+	  *-dirty) ;;
+	  *) v="$v-dirty" ;;
+	esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..3578a80
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = osmocom
diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am
new file mode 100644
index 0000000..21f4f2d
--- /dev/null
+++ b/include/osmocom/Makefile.am
@@ -0,0 +1,5 @@
+if ENABLE_VTY
+SUBDIRS = vty codec crypt gsm core
+else
+SUBDIRS = codec crypt gsm core
+endif
diff --git a/include/osmocom/codec/Makefile.am b/include/osmocom/codec/Makefile.am
new file mode 100644
index 0000000..c213602
--- /dev/null
+++ b/include/osmocom/codec/Makefile.am
@@ -0,0 +1,3 @@
+osmocodec_HEADERS = codec.h
+
+osmocodecdir = $(includedir)/osmocom/codec
diff --git a/include/osmocom/codec/codec.h b/include/osmocom/codec/codec.h
new file mode 100644
index 0000000..6f9ffea
--- /dev/null
+++ b/include/osmocom/codec/codec.h
@@ -0,0 +1,20 @@
+#ifndef _OSMOCOM_CODEC_H
+#define _OSMOCOM_CODEC_H
+
+#include <stdint.h>
+
+extern uint16_t gsm610_bitorder[];	/* FR */
+extern uint16_t gsm620_unvoiced_bitorder[]; /* HR unvoiced */
+extern uint16_t gsm620_voiced_bitorder[];   /* HR voiced */
+extern uint16_t gsm660_bitorder[];	/* EFR */
+
+extern uint16_t gsm690_12_2_bitorder[];	/* AMR 12.2  kbits */
+extern uint16_t gsm690_10_2_bitorder[];	/* AMR 10.2  kbits */
+extern uint16_t gsm690_7_95_bitorder[];	/* AMR  7.95 kbits */
+extern uint16_t gsm690_7_4_bitorder[];	/* AMR  7.4  kbits */
+extern uint16_t gsm690_6_7_bitorder[];	/* AMR  6.7  kbits */
+extern uint16_t gsm690_5_9_bitorder[];	/* AMR  5.9  kbits */
+extern uint16_t gsm690_5_15_bitorder[];	/* AMR  5.15 kbits */
+extern uint16_t gsm690_4_75_bitorder[];	/* AMR  4.75 kbits */
+
+#endif /* _OSMOCOM_CODEC_H */
diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am
new file mode 100644
index 0000000..1df111a
--- /dev/null
+++ b/include/osmocom/core/Makefile.am
@@ -0,0 +1,31 @@
+osmocore_HEADERS = signal.h linuxlist.h timer.h select.h msgb.h bits.h \
+		   bitvec.h statistics.h utils.h socket.h \
+		   gsmtap.h write_queue.h prim.h \
+		   logging.h rate_ctr.h gsmtap_util.h \
+		   crc16.h panic.h process.h linuxrbtree.h \
+		   backtrace.h conv.h application.h \
+		   crcgen.h crc8gen.h crc16gen.h crc32gen.h crc64gen.h
+
+noinst_HEADERS = timer_compat.h
+
+if ENABLE_PLUGIN
+osmocore_HEADERS += plugin.h
+endif
+
+if ENABLE_TALLOC
+osmocore_HEADERS += talloc.h
+endif
+
+if ENABLE_MSGFILE
+osmocore_HEADERS += msgfile.h
+endif
+
+if ENABLE_SERIAL
+osmocore_HEADERS += serial.h
+endif
+
+osmocoredir = $(includedir)/osmocom/core
+
+crc%gen.h: crcXXgen.h.tpl
+	@echo "  SED    $< -> $@"
+	@sed -e's/XX/$*/g' $< > $@
diff --git a/include/osmocom/core/application.h b/include/osmocom/core/application.h
new file mode 100644
index 0000000..3457169
--- /dev/null
+++ b/include/osmocom/core/application.h
@@ -0,0 +1,23 @@
+#ifndef OSMO_APPLICATION_H
+#define OSMO_APPLICATION_H
+
+/*!
+ * \file application.h
+ * \brief Routines for helping with the osmocom application setup.
+ */
+
+/*! \brief information containing the available logging subsystems */
+struct log_info;
+
+/*! \brief one instance of a logging target (file, stderr, ...) */
+struct log_target;
+
+/*! \brief the default logging target, logging to stderr */
+extern struct log_target *osmo_stderr_target;
+
+void osmo_init_ignore_signals(void);
+int osmo_init_logging(const struct log_info *);
+
+int osmo_daemonize(void);
+
+#endif
diff --git a/include/osmocom/core/backtrace.h b/include/osmocom/core/backtrace.h
new file mode 100644
index 0000000..1ed089a
--- /dev/null
+++ b/include/osmocom/core/backtrace.h
@@ -0,0 +1,6 @@
+#ifndef _OSMO_BACKTRACE_H_
+#define _OSMO_BACKTRACE_H_
+
+void osmo_generate_backtrace(void);
+
+#endif
diff --git a/include/osmocom/core/bits.h b/include/osmocom/core/bits.h
new file mode 100644
index 0000000..4c68532
--- /dev/null
+++ b/include/osmocom/core/bits.h
@@ -0,0 +1,78 @@
+#ifndef _OSMO_BITS_H
+#define _OSMO_BITS_H
+
+#include <stdint.h>
+
+/*! \defgroup bits soft, unpacked and packed bits
+ *  @{
+ */
+
+/*! \file bits.h
+ *  \brief Osmocom bit level support code
+ */
+
+typedef int8_t  sbit_t;		/*!< \brief soft bit (-127...127) */
+typedef uint8_t ubit_t;		/*!< \brief unpacked bit (0 or 1) */
+typedef uint8_t pbit_t;		/*!< \brief packed bis (8 bits in a byte) */
+
+/*
+   NOTE on the endianess of pbit_t:
+   Bits in a pbit_t are ordered MSB first, i.e. 0x80 is the first bit.
+   Bit i in a pbit_t array is array[i/8] & (1<<(7-i%8))
+*/
+
+/*! \brief determine how many bytes we would need for \a num_bits packed bits
+ *  \param[in] num_bits Number of packed bits
+ */
+static inline unsigned int osmo_pbit_bytesize(unsigned int num_bits)
+{
+	unsigned int pbit_bytesize = num_bits / 8;
+
+	if (num_bits % 8)
+		pbit_bytesize++;
+
+	return pbit_bytesize;
+}
+
+int osmo_ubit2pbit(pbit_t *out, const ubit_t *in, unsigned int num_bits);
+
+int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits);
+
+int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
+                       const ubit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode);
+
+int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
+                       const pbit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode);
+
+
+/* BIT REVERSAL */
+
+/*! \brief bit-reversal mode for osmo_bit_reversal() */
+enum osmo_br_mode {
+	/*! \brief reverse all bits in a 32bit dword */
+	OSMO_BR_BITS_IN_DWORD	= 31,
+	/*! \brief reverse byte order in a 32bit dword */
+	OSMO_BR_BYTES_IN_DWORD	= 24,
+	/*! \brief reverse bits of each byte in a 32bit dword */
+	OSMO_BR_BITS_IN_BYTE	= 7,
+	/*! \brief swap the two 16bit words in a 32bit dword */
+	OSMO_BR_WORD_SWAP	= 16,
+};
+
+/*! \brief generic bit reversal function */
+uint32_t osmo_bit_reversal(uint32_t x, enum osmo_br_mode k);
+
+/* \brief reverse the bits within each byte of a 32bit word */
+uint32_t osmo_revbytebits_32(uint32_t x);
+
+/* \brief reverse the bits within a byte */
+uint32_t osmo_revbytebits_8(uint8_t x);
+
+/* \brief reverse the bits of each byte in a given buffer */
+void osmo_revbytebits_buf(uint8_t *buf, int len);
+
+/*! @} */
+
+#endif /* _OSMO_BITS_H */
diff --git a/include/osmocom/core/bitvec.h b/include/osmocom/core/bitvec.h
new file mode 100644
index 0000000..9c000d0
--- /dev/null
+++ b/include/osmocom/core/bitvec.h
@@ -0,0 +1,70 @@
+#ifndef _BITVEC_H
+#define _BITVEC_H
+
+/* bit vector utility routines */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \defgroup bitvec Bit vectors
+ *  @{
+ */
+
+/*! \file bitvec.h
+ *  \brief Osmocom bit vector abstraction
+ */
+
+#include <stdint.h>
+
+/*! \brief A single GSM bit
+ *
+ * In GSM mac blocks, every bit can be 0 or 1, or L or H.  L/H are
+ * defined relative to the 0x2b padding pattern */
+enum bit_value {
+	ZERO	= 0, 	/*!< \brief A zero (0) bit */
+	ONE	= 1,	/*!< \brief A one (1) bit */
+	L	= 2,	/*!< \brief A CSN.1 "L" bit */
+	H	= 3,	/*!< \brief A CSN.1 "H" bit */
+};
+
+/*! \brief structure describing a bit vector */
+struct bitvec {
+	unsigned int cur_bit;	/*!< \brief curser to the next unused bit */
+	unsigned int data_len;	/*!< \brief length of data array in bytes */
+	uint8_t *data;		/*!< \brief pointer to data array */
+};
+
+enum bit_value bitvec_get_bit_pos(const struct bitvec *bv, unsigned int bitnr);
+enum bit_value bitvec_get_bit_pos_high(const struct bitvec *bv,
+					unsigned int bitnr);
+unsigned int bitvec_get_nth_set_bit(const struct bitvec *bv, unsigned int n);
+int bitvec_set_bit_pos(struct bitvec *bv, unsigned int bitnum,
+			enum bit_value bit);
+int bitvec_set_bit(struct bitvec *bv, enum bit_value bit);
+int bitvec_get_bit_high(struct bitvec *bv);
+int bitvec_set_bits(struct bitvec *bv, enum bit_value *bits, int count);
+int bitvec_set_uint(struct bitvec *bv, unsigned int in, int count);
+int bitvec_get_uint(struct bitvec *bv, int num_bits);
+int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n, enum bit_value val);
+int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit);
+
+/*! @} */
+
+#endif /* _BITVEC_H */
diff --git a/include/osmocom/core/conv.h b/include/osmocom/core/conv.h
new file mode 100644
index 0000000..e5b2a97
--- /dev/null
+++ b/include/osmocom/core/conv.h
@@ -0,0 +1,146 @@
+/*
+ * conv.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \defgroup conv Convolutional encoding and decoding routines
+ *  @{
+ */
+
+/*! \file conv.h
+ *  \file Osmocom convolutional encoder and decoder
+ */
+
+#ifndef __OSMO_CONV_H__
+#define __OSMO_CONV_H__
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+
+/*! \brief possibe termination types
+ *
+ *  The termination type will determine which state the encoder/decoder
+ *  can start/end with. This is mostly taken care of in the high level API
+ *  call. So if you use the low level API, you must take care of making the
+ *  proper calls yourself.
+ */
+enum osmo_conv_term {
+	CONV_TERM_FLUSH = 0,	/*!< \brief Flush encoder state */
+	CONV_TERM_TRUNCATION,	/*!< \brief Direct truncation */
+	CONV_TERM_TAIL_BITING,	/*!< \brief Tail biting */
+};
+
+/*! \brief structure describing a given convolutional code
+ *
+ *  The only required fields are N,K and the next_output/next_state arrays. The
+ *  other can be left to default value of zero depending on what the code does.
+ *  If 'len' is left at 0 then only the low level API can be used.
+ */
+struct osmo_conv_code {
+	int N;				/*!< \brief Inverse of code rate */
+	int K;				/*!< \brief Constraint length */
+	int len;			/*!< \brief # of data bits */
+
+	enum osmo_conv_term term;	/*!< \brief Termination type */
+
+	const uint8_t (*next_output)[2];/*!< \brief Next output array */
+	const uint8_t (*next_state)[2];	/*!< \brief Next state array  */
+
+	const uint8_t *next_term_output;/*!< \brief Flush termination output */
+	const uint8_t *next_term_state;	/*!< \brief Flush termination state  */
+
+	const int *puncture;		/*!< \brief Punctured bits indexes */
+};
+
+
+/* Common */
+
+int osmo_conv_get_input_length(const struct osmo_conv_code *code, int len);
+int osmo_conv_get_output_length(const struct osmo_conv_code *code, int len);
+
+
+/* Encoding */
+
+	/* Low level API */
+
+/*! \brief convolutional encoder state */
+struct osmo_conv_encoder {
+	const struct osmo_conv_code *code; /*!< \brief for which code? */
+	int i_idx;	/*!< \brief Next input bit index */
+	int p_idx;	/*!< \brief Current puncture index */
+	uint8_t state;	/*!< \brief Current state */
+};
+
+void osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
+                           const struct osmo_conv_code *code);
+void osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
+                                 const ubit_t *input);
+int  osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
+                          const ubit_t *input, ubit_t *output, int n);
+int  osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, ubit_t *output);
+
+	/* All-in-one */
+int  osmo_conv_encode(const struct osmo_conv_code *code,
+                      const ubit_t *input, ubit_t *output);
+
+
+/* Decoding */
+
+	/* Low level API */
+
+/*! \brief convolutional decoder state */
+struct osmo_conv_decoder {
+	const struct osmo_conv_code *code; /*!< \brief for which code? */
+
+	int n_states;		/*!< \brief number of states */
+
+	int len;		/*!< \brief Max o_idx (excl. termination) */
+
+	int o_idx;		/*!< \brief output index */
+	int p_idx;		/*!< \brief puncture index */
+
+	unsigned int *ae;	/*!< \brief accumulated error */
+	unsigned int *ae_next;	/*!< \brief next accumulated error (tmp in scan) */
+	uint8_t *state_history;	/*!< \brief state history [len][n_states] */
+};
+
+void osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
+                           const struct osmo_conv_code *code,
+                           int len, int start_state);
+void osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state);
+void osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder);
+void osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder);
+
+int osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
+                          const sbit_t *input, int n);
+int osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
+                           const sbit_t *input);
+int osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+                                ubit_t *output, int has_flush, int end_state);
+
+	/* All-in-one */
+int osmo_conv_decode(const struct osmo_conv_code *code,
+                     const sbit_t *input, ubit_t *output);
+
+
+/*! @} */
+
+#endif /* __OSMO_CONV_H__ */
diff --git a/include/osmocom/core/crc16.h b/include/osmocom/core/crc16.h
new file mode 100644
index 0000000..0e52417
--- /dev/null
+++ b/include/osmocom/core/crc16.h
@@ -0,0 +1,34 @@
+/*
+ * This was copied from the linux kernel and adjusted for our types.
+ */
+/*
+ *	crc16.h - CRC-16 routine
+ *
+ * Implements the standard CRC-16:
+ *   Width 16
+ *   Poly  0x8005 (x^16 + x^15 + x^2 + 1)
+ *   Init  0
+ *
+ * Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com>
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2. See the file COPYING for more details.
+ */
+
+#ifndef __CRC16_H
+#define __CRC16_H
+
+#include <stdint.h>
+
+#include <sys/types.h>
+
+extern uint16_t const osmo_crc16_table[256];
+
+extern uint16_t osmo_crc16(uint16_t crc, const uint8_t *buffer, size_t len);
+
+static inline uint16_t osmo_crc16_byte(uint16_t crc, const uint8_t data)
+{
+	return (crc >> 8) ^ osmo_crc16_table[(crc ^ data) & 0xff];
+}
+
+#endif /* __CRC16_H */
diff --git a/include/osmocom/core/crcXXgen.h.tpl b/include/osmocom/core/crcXXgen.h.tpl
new file mode 100644
index 0000000..89d083a
--- /dev/null
+++ b/include/osmocom/core/crcXXgen.h.tpl
@@ -0,0 +1,59 @@
+/*
+ * crcXXgen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __OSMO_CRCXXGEN_H__
+#define __OSMO_CRCXXGEN_H__
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crcXXgen.h
+ *  \file Osmocom generic CRC routines (for max XX bits poly) header
+ */
+
+
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+
+
+/*! \brief structure describing a given CRC code of max XX bits */
+struct osmo_crcXXgen_code {
+	int bits;           /*!< \brief Actual number of bits of the CRC */
+	uintXX_t poly;      /*!< \brief Polynom (normal representation, MSB omitted */
+	uintXX_t init;      /*!< \brief Initialization value of the CRC state */
+	uintXX_t remainder; /*!< \brief Remainder of the CRC (final XOR) */
+};
+
+uintXX_t osmo_crcXXgen_compute_bits(const struct osmo_crcXXgen_code *code,
+                                    const ubit_t *in, int len);
+int osmo_crcXXgen_check_bits(const struct osmo_crcXXgen_code *code,
+                             const ubit_t *in, int len, const ubit_t *crc_bits);
+void osmo_crcXXgen_set_bits(const struct osmo_crcXXgen_code *code,
+                            const ubit_t *in, int len, ubit_t *crc_bits);
+
+
+/*! @} */
+
+#endif /* __OSMO_CRCXXGEN_H__ */
+
+/* vim: set syntax=c: */
diff --git a/include/osmocom/core/crcgen.h b/include/osmocom/core/crcgen.h
new file mode 100644
index 0000000..8e208a7
--- /dev/null
+++ b/include/osmocom/core/crcgen.h
@@ -0,0 +1,41 @@
+/*
+ * crcgen.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __OSMO_CRCGEN_H__
+#define __OSMO_CRCGEN_H__
+
+/*! \defgroup crcgen Osmocom generic CRC routines
+ *  @{
+ */
+
+/*! \file crcgen.h
+ *  \file Osmocom generic CRC routines global header
+ */
+
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/crc16gen.h>
+#include <osmocom/core/crc32gen.h>
+#include <osmocom/core/crc64gen.h>
+
+/*! @} */
+
+#endif /* __OSMO_CRCGEN_H__ */
diff --git a/include/osmocom/core/gsmtap.h b/include/osmocom/core/gsmtap.h
new file mode 100644
index 0000000..a4e5d42
--- /dev/null
+++ b/include/osmocom/core/gsmtap.h
@@ -0,0 +1,159 @@
+#ifndef _GSMTAP_H
+#define _GSMTAP_H
+
+/* gsmtap header, pseudo-header in front of the actua GSM payload */
+
+/* GSMTAP is a generic header format for GSM protocol captures,
+ * it uses the IANA-assigned UDP port number 4729 and carries
+ * payload in various formats of GSM interfaces such as Um MAC
+ * blocks or Um bursts.
+ *
+ * Example programs generating GSMTAP data are airprobe
+ * (http://airprobe.org/) or OsmocomBB (http://bb.osmocom.org/)
+ */
+
+#include <stdint.h>
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* The GSMTAP format definition is maintained in libosmocore,
+ * specifically the latest version can always be obtained from
+ * http://cgit.osmocom.org/cgit/libosmocore/tree/include/osmocom/core/gsmtap.h
+ *
+ * If you want to introduce new protocol/burst/channel types or extend
+ * GSMTAP in any way, please contact the GSMTAP maintainer at either the
+ * public openbsc@lists.osmocom.org mailing list, or privately at
+ * Harald Welte <laforge@gnumonks.org>.
+ *
+ * Your cooperation ensures that all projects will use the same GSMTAP
+ * definitions and remain compatible with each other.
+ */
+
+#define GSMTAP_VERSION		0x02
+
+#define GSMTAP_TYPE_UM		0x01
+#define GSMTAP_TYPE_ABIS	0x02
+#define GSMTAP_TYPE_UM_BURST	0x03	/* raw burst bits */
+#define GSMTAP_TYPE_SIM		0x04
+#define GSMTAP_TYPE_TETRA_I1		0x05	/* tetra air interface */
+#define GSMTAP_TYPE_TETRA_I1_BURST	0x06	/* tetra air interface */
+#define GSMTAP_TYPE_WMX_BURST	0x07    /* WiMAX burst */
+#define GSMTAP_TYPE_GB_LLC	0x08 /* GPRS Gb interface: LLC */
+#define GSMTAP_TYPE_GB_SNDCP	0x09 /* GPRS Gb interface: SNDCP */
+#define GSMTAP_TYPE_GMR1_UM	0x0a	/* GMR-1 L2 packets */
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* sub-types for TYPE_UM_BURST */
+#define GSMTAP_BURST_UNKNOWN		0x00
+#define GSMTAP_BURST_FCCH		0x01
+#define GSMTAP_BURST_PARTIAL_SCH	0x02
+#define GSMTAP_BURST_SCH		0x03
+#define GSMTAP_BURST_CTS_SCH		0x04
+#define GSMTAP_BURST_COMPACT_SCH	0x05
+#define GSMTAP_BURST_NORMAL		0x06
+#define GSMTAP_BURST_DUMMY		0x07
+#define GSMTAP_BURST_ACCESS		0x08
+#define GSMTAP_BURST_NONE		0x09
+/* WiMAX bursts */
+#define GSMTAP_BURST_CDMA_CODE		0x10	/* WiMAX CDMA Code Attribute burst */
+#define GSMTAP_BURST_FCH		0x11	/* WiMAX FCH burst */
+#define GSMTAP_BURST_FFB		0x12	/* WiMAX Fast Feedback burst */
+#define GSMTAP_BURST_PDU		0x13	/* WiMAX PDU burst */
+#define GSMTAP_BURST_HACK		0x14	/* WiMAX HARQ ACK burst */
+#define GSMTAP_BURST_PHY_ATTRIBUTES	0x15	/* WiMAX PHY Attributes burst */
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* sub-types for TYPE_UM */
+#define GSMTAP_CHANNEL_UNKNOWN	0x00
+#define GSMTAP_CHANNEL_BCCH	0x01
+#define GSMTAP_CHANNEL_CCCH	0x02
+#define GSMTAP_CHANNEL_RACH	0x03
+#define GSMTAP_CHANNEL_AGCH	0x04
+#define GSMTAP_CHANNEL_PCH	0x05
+#define GSMTAP_CHANNEL_SDCCH	0x06
+#define GSMTAP_CHANNEL_SDCCH4	0x07
+#define GSMTAP_CHANNEL_SDCCH8	0x08
+#define GSMTAP_CHANNEL_TCH_F	0x09
+#define GSMTAP_CHANNEL_TCH_H	0x0a
+#define GSMTAP_CHANNEL_PACCH	0x0b
+#define GSMTAP_CHANNEL_CBCH52	0x0c
+#define GSMTAP_CHANNEL_PDCH	0x0d
+#define GSMTAP_CHANNEL_PTCCH	0x0e
+#define GSMTAP_CHANNEL_CBCH51	0x0f
+
+/* GPRS Coding Scheme CS1..4 */
+#define GSMTAP_GPRS_CS_BASE	0x20
+#define GSMTAP_GPRS_CS(N)	(GSMTAP_GPRS_CS_BASE + N)
+/* (E) GPRS Coding Scheme MCS0..9 */
+#define GSMTAP_GPRS_MCS_BASE	0x30
+#define GSMTAP_GPRS_MCS(N)	(GSMTAP_GPRS_MCS_BASE + N)
+
+#define GSMTAP_CHANNEL_ACCH	0x80
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* sub-types for TYPE_TETRA_AIR */
+#define GSMTAP_TETRA_BSCH	0x01
+#define GSMTAP_TETRA_AACH	0x02
+#define GSMTAP_TETRA_SCH_HU	0x03
+#define GSMTAP_TETRA_SCH_HD	0x04
+#define GSMTAP_TETRA_SCH_F	0x05
+#define GSMTAP_TETRA_BNCH	0x06
+#define GSMTAP_TETRA_STCH	0x07
+#define GSMTAP_TETRA_TCH_F	0x08
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* sub-types for TYPE_GMR1_UM */
+#define GSMTAP_GMR1_UNKNOWN	0x00
+#define GSMTAP_GMR1_BCCH	0x01
+#define GSMTAP_GMR1_CCCH	0x02	/* either AGCH or PCH */
+#define GSMTAP_GMR1_PCH		0x03
+#define GSMTAP_GMR1_AGCH	0x04
+#define GSMTAP_GMR1_BACH	0x05
+#define GSMTAP_GMR1_RACH	0x06
+#define GSMTAP_GMR1_CBCH	0x07
+#define GSMTAP_GMR1_SDCCH	0x08
+#define GSMTAP_GMR1_TACCH	0x09
+#define GSMTAP_GMR1_GBCH	0x0a
+
+#define GSMTAP_GMR1_SACCH	0x01	/* to be combined with _TCH{6,9}   */
+#define GSMTAP_GMR1_FACCH	0x02	/* to be combines with _TCH{3,6,9} */
+#define GSMTAP_GMR1_DKAB	0x03	/* to be combined with _TCH3 */
+#define GSMTAP_GMR1_TCH3	0x10
+#define GSMTAP_GMR1_TCH6	0x14
+#define GSMTAP_GMR1_TCH9	0x18
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+
+/* flags for the ARFCN */
+#define GSMTAP_ARFCN_F_PCS	0x8000
+#define GSMTAP_ARFCN_F_UPLINK	0x4000
+#define GSMTAP_ARFCN_MASK	0x3fff
+
+/* IANA-assigned well-known UDP port for GSMTAP messages */
+#define GSMTAP_UDP_PORT			4729
+
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+struct gsmtap_hdr {
+	uint8_t version;	/* version, set to 0x01 currently */
+	uint8_t hdr_len;	/* length in number of 32bit words */
+	uint8_t type;		/* see GSMTAP_TYPE_* */
+	uint8_t timeslot;	/* timeslot (0..7 on Um) */
+
+	uint16_t arfcn;		/* ARFCN (frequency) */
+	int8_t signal_dbm;	/* signal level in dBm */
+	int8_t snr_db;		/* signal/noise ratio in dB */
+
+	uint32_t frame_number;	/* GSM Frame Number (FN) */
+
+	uint8_t sub_type;	/* Type of burst/channel, see above */
+	uint8_t antenna_nr;	/* Antenna Number */
+	uint8_t sub_slot;	/* sub-slot within timeslot */
+	uint8_t res;		/* reserved for future use (RFU) */
+
+} __attribute__((packed));
+
+#endif /* _GSMTAP_H */
diff --git a/include/osmocom/core/gsmtap_util.h b/include/osmocom/core/gsmtap_util.h
new file mode 100644
index 0000000..5609381
--- /dev/null
+++ b/include/osmocom/core/gsmtap_util.h
@@ -0,0 +1,57 @@
+#ifndef _GSMTAP_UTIL_H
+#define _GSMTAP_UTIL_H
+
+#include <stdint.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/select.h>
+
+/*! \defgroup gsmtap GSMTAP
+ *  @{
+ */
+/*! \file gsmtap_util.h */
+
+uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t rsl_link_id);
+
+struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
+			    uint8_t ss, uint32_t fn, int8_t signal_dbm,
+			    uint8_t snr, const uint8_t *data, unsigned int len);
+
+struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
+			    uint8_t ss, uint32_t fn, int8_t signal_dbm,
+			    uint8_t snr, const uint8_t *data, unsigned int len);
+
+/*! \brief one gsmtap instance */
+struct gsmtap_inst {
+	int ofd_wq_mode;	/*!< \brief wait queue mode? */
+	struct osmo_wqueue wq;	/*!< \brief the wait queue */
+	struct osmo_fd sink_ofd;/*!< \brief file descriptor */
+};
+
+/*! \brief obtain the file descriptor associated with a gsmtap instance */
+static inline int gsmtap_inst_fd(struct gsmtap_inst *gti)
+{
+	return gti->wq.bfd.fd;
+}
+
+int gsmtap_source_init_fd(const char *host, uint16_t port);
+
+int gsmtap_source_add_sink_fd(int gsmtap_fd);
+
+struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
+					int ofd_wq_mode);
+
+int gsmtap_source_add_sink(struct gsmtap_inst *gti);
+
+int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg);
+
+int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len);
+
+int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len);
+
+#endif /* _GSMTAP_UTIL_H */
diff --git a/include/osmocom/core/linuxlist.h b/include/osmocom/core/linuxlist.h
new file mode 100644
index 0000000..fb99c5e
--- /dev/null
+++ b/include/osmocom/core/linuxlist.h
@@ -0,0 +1,360 @@
+#ifndef _LINUX_LLIST_H
+#define _LINUX_LLIST_H
+
+#include <stddef.h>
+
+#ifndef inline
+#define inline __inline__
+#endif
+
+static inline void prefetch(const void *x) {;}
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:	the pointer to the member.
+ * @type:	the type of the container struct this is embedded in.
+ * @member:	the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({			\
+        const typeof( ((type *)0)->member ) *__mptr = (typeof( ((type *)0)->member ) *)(ptr);	\
+        (type *)( (char *)__mptr - offsetof(type, member) );})
+
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized llist entries.
+ */
+#define LLIST_POISON1  ((void *) 0x00100100)
+#define LLIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked llist implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole llists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct llist_head {
+	struct llist_head *next, *prev;
+};
+
+#define LLIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LLIST_HEAD(name) \
+	struct llist_head name = LLIST_HEAD_INIT(name)
+
+#define INIT_LLIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal llist manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __llist_add(struct llist_head *_new,
+			      struct llist_head *prev,
+			      struct llist_head *next)
+{
+	next->prev = _new;
+	_new->next = next;
+	_new->prev = prev;
+	prev->next = _new;
+}
+
+/**
+ * llist_add - add a new entry
+ * @new: new entry to be added
+ * @head: llist head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void llist_add(struct llist_head *_new, struct llist_head *head)
+{
+	__llist_add(_new, head, head->next);
+}
+
+/**
+ * llist_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: llist head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void llist_add_tail(struct llist_head *_new, struct llist_head *head)
+{
+	__llist_add(_new, head->prev, head);
+}
+
+/*
+ * Delete a llist entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal llist manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __llist_del(struct llist_head * prev, struct llist_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * llist_del - deletes entry from llist.
+ * @entry: the element to delete from the llist.
+ * Note: llist_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void llist_del(struct llist_head *entry)
+{
+	__llist_del(entry->prev, entry->next);
+	entry->next = (struct llist_head *)LLIST_POISON1;
+	entry->prev = (struct llist_head *)LLIST_POISON2;
+}
+
+/**
+ * llist_del_init - deletes entry from llist and reinitialize it.
+ * @entry: the element to delete from the llist.
+ */
+static inline void llist_del_init(struct llist_head *entry)
+{
+	__llist_del(entry->prev, entry->next);
+	INIT_LLIST_HEAD(entry); 
+}
+
+/**
+ * llist_move - delete from one llist and add as another's head
+ * @llist: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void llist_move(struct llist_head *llist, struct llist_head *head)
+{
+        __llist_del(llist->prev, llist->next);
+        llist_add(llist, head);
+}
+
+/**
+ * llist_move_tail - delete from one llist and add as another's tail
+ * @llist: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void llist_move_tail(struct llist_head *llist,
+				  struct llist_head *head)
+{
+        __llist_del(llist->prev, llist->next);
+        llist_add_tail(llist, head);
+}
+
+/**
+ * llist_empty - tests whether a llist is empty
+ * @head: the llist to test.
+ */
+static inline int llist_empty(const struct llist_head *head)
+{
+	return head->next == head;
+}
+
+static inline void __llist_splice(struct llist_head *llist,
+				 struct llist_head *head)
+{
+	struct llist_head *first = llist->next;
+	struct llist_head *last = llist->prev;
+	struct llist_head *at = head->next;
+
+	first->prev = head;
+	head->next = first;
+
+	last->next = at;
+	at->prev = last;
+}
+
+/**
+ * llist_splice - join two llists
+ * @llist: the new llist to add.
+ * @head: the place to add it in the first llist.
+ */
+static inline void llist_splice(struct llist_head *llist, struct llist_head *head)
+{
+	if (!llist_empty(llist))
+		__llist_splice(llist, head);
+}
+
+/**
+ * llist_splice_init - join two llists and reinitialise the emptied llist.
+ * @llist: the new llist to add.
+ * @head: the place to add it in the first llist.
+ *
+ * The llist at @llist is reinitialised
+ */
+static inline void llist_splice_init(struct llist_head *llist,
+				    struct llist_head *head)
+{
+	if (!llist_empty(llist)) {
+		__llist_splice(llist, head);
+		INIT_LLIST_HEAD(llist);
+	}
+}
+
+/**
+ * llist_entry - get the struct for this entry
+ * @ptr:	the &struct llist_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * llist_for_each	-	iterate over a llist
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, prefetch(pos->next))
+
+/**
+ * __llist_for_each	-	iterate over a llist
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ *
+ * This variant differs from llist_for_each() in that it's the
+ * simplest possible llist iteration code, no prefetching is done.
+ * Use this for code that knows the llist to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __llist_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * llist_for_each_prev	-	iterate over a llist backwards
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_prev(pos, head) \
+	for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+        	pos = pos->prev, prefetch(pos->prev))
+        	
+/**
+ * llist_for_each_safe	-	iterate over a llist safe against removal of llist entry
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @n:		another &struct llist_head to use as temporary storage
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/**
+ * llist_for_each_entry	-	iterate over llist of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry(pos, head, member)				\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/**
+ * llist_for_each_entry_reverse - iterate backwards over llist of given type.
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry_reverse(pos, head, member)			\
+	for (pos = llist_entry((head)->prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.prev, typeof(*pos), member),	\
+		     prefetch(pos->member.prev))
+
+/**
+ * llist_for_each_entry_continue -	iterate over llist of given type
+ *			continuing after existing point
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry_continue(pos, head, member) 		\
+	for (pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head);					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     prefetch(pos->member.next))
+
+/**
+ * llist_for_each_entry_safe - iterate over llist of given type safe against removal of llist entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		n = llist_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = n, n = llist_entry(n->member.next, typeof(*n), member))
+
+/**
+ * llist_for_each_rcu	-	iterate over an rcu-protected llist
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_rcu(pos, head) \
+	for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next))
+        	
+#define __llist_for_each_rcu(pos, head) \
+	for (pos = (head)->next; pos != (head); \
+        	pos = pos->next, ({ smp_read_barrier_depends(); 0;}))
+        	
+/**
+ * llist_for_each_safe_rcu	-	iterate over an rcu-protected llist safe
+ *					against removal of llist entry
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @n:		another &struct llist_head to use as temporary storage
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_safe_rcu(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next)
+
+/**
+ * llist_for_each_entry_rcu	-	iterate over rcu llist of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your llist.
+ * @member:	the name of the llist_struct within the struct.
+ */
+#define llist_for_each_entry_rcu(pos, head, member)			\
+	for (pos = llist_entry((head)->next, typeof(*pos), member),	\
+		     prefetch(pos->member.next);			\
+	     &pos->member != (head); 					\
+	     pos = llist_entry(pos->member.next, typeof(*pos), member),	\
+		     ({ smp_read_barrier_depends(); 0;}),		\
+		     prefetch(pos->member.next))
+
+
+/**
+ * llist_for_each_continue_rcu	-	iterate over an rcu-protected llist 
+ *			continuing after existing point.
+ * @pos:	the &struct llist_head to use as a loop counter.
+ * @head:	the head for your llist.
+ */
+#define llist_for_each_continue_rcu(pos, head) \
+	for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+        	(pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
+
+#endif
diff --git a/include/osmocom/core/linuxrbtree.h b/include/osmocom/core/linuxrbtree.h
new file mode 100644
index 0000000..44e00a1
--- /dev/null
+++ b/include/osmocom/core/linuxrbtree.h
@@ -0,0 +1,160 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/include/linux/rbtree.h
+
+  To use rbtrees you'll have to implement your own insert and search cores.
+  This will avoid us to use callbacks and to drop drammatically performances.
+  I know it's not the cleaner way,  but in C (not in C++) to get
+  performances and genericity...
+
+  Some example of insert and search follows here. The search is a plain
+  normal search over an ordered tree. The insert instead must be implemented
+  int two steps: as first thing the code must insert the element in
+  order as a red leaf in the tree, then the support library function
+  rb_insert_color() must be called. Such function will do the
+  not trivial work to rebalance the rbtree if necessary.
+
+-----------------------------------------------------------------------
+static inline struct page * rb_search_page_cache(struct inode * inode,
+						 unsigned long offset)
+{
+	struct rb_node * n = inode->i_rb_page_cache.rb_node;
+	struct page * page;
+
+	while (n)
+	{
+		page = rb_entry(n, struct page, rb_page_cache);
+
+		if (offset < page->offset)
+			n = n->rb_left;
+		else if (offset > page->offset)
+			n = n->rb_right;
+		else
+			return page;
+	}
+	return NULL;
+}
+
+static inline struct page * __rb_insert_page_cache(struct inode * inode,
+						   unsigned long offset,
+						   struct rb_node * node)
+{
+	struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
+	struct rb_node * parent = NULL;
+	struct page * page;
+
+	while (*p)
+	{
+		parent = *p;
+		page = rb_entry(parent, struct page, rb_page_cache);
+
+		if (offset < page->offset)
+			p = &(*p)->rb_left;
+		else if (offset > page->offset)
+			p = &(*p)->rb_right;
+		else
+			return page;
+	}
+
+	rb_link_node(node, parent, p);
+
+	return NULL;
+}
+
+static inline struct page * rb_insert_page_cache(struct inode * inode,
+						 unsigned long offset,
+						 struct rb_node * node)
+{
+	struct page * ret;
+	if ((ret = __rb_insert_page_cache(inode, offset, node)))
+		goto out;
+	rb_insert_color(node, &inode->i_rb_page_cache);
+ out:
+	return ret;
+}
+-----------------------------------------------------------------------
+*/
+
+#ifndef	_LINUX_RBTREE_H
+#define	_LINUX_RBTREE_H
+
+#include <stdlib.h>
+
+struct rb_node
+{
+	unsigned long  rb_parent_color;
+#define	RB_RED		0
+#define	RB_BLACK	1
+	struct rb_node *rb_right;
+	struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+struct rb_root
+{
+	struct rb_node *rb_node;
+};
+
+
+#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
+#define rb_color(r)   ((r)->rb_parent_color & 1)
+#define rb_is_red(r)   (!rb_color(r))
+#define rb_is_black(r) rb_color(r)
+#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
+#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
+
+static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
+{
+	rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p;
+}
+static inline void rb_set_color(struct rb_node *rb, int color)
+{
+	rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
+}
+
+#define RB_ROOT	{ NULL, }
+#define	rb_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define RB_EMPTY_ROOT(root)	((root)->rb_node == NULL)
+#define RB_EMPTY_NODE(node)	(rb_parent(node) == node)
+#define RB_CLEAR_NODE(node)	(rb_set_parent(node, node))
+
+extern void rb_insert_color(struct rb_node *, struct rb_root *);
+extern void rb_erase(struct rb_node *, struct rb_root *);
+
+/* Find logical next and previous nodes in a tree */
+extern struct rb_node *rb_next(const struct rb_node *);
+extern struct rb_node *rb_prev(const struct rb_node *);
+extern struct rb_node *rb_first(const struct rb_root *);
+extern struct rb_node *rb_last(const struct rb_root *);
+
+/* Fast replacement of a single node without remove/rebalance/add/rebalance */
+extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, 
+			    struct rb_root *root);
+
+static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
+				struct rb_node ** rb_link)
+{
+	node->rb_parent_color = (unsigned long )parent;
+	node->rb_left = node->rb_right = NULL;
+
+	*rb_link = node;
+}
+
+#endif	/* _LINUX_RBTREE_H */
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
new file mode 100644
index 0000000..76be100
--- /dev/null
+++ b/include/osmocom/core/logging.h
@@ -0,0 +1,211 @@
+#ifndef _OSMOCORE_LOGGING_H
+#define _OSMOCORE_LOGGING_H
+
+/*! \defgroup logging Osmocom logging framework
+ *  @{
+ */
+
+/*! \file logging.h */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <osmocom/core/linuxlist.h>
+
+/*! \brief Maximum number of logging contexts */
+#define LOG_MAX_CTX		8
+/*! \brief Maximum number of logging filters */
+#define LOG_MAX_FILTERS	8
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DEBUGP(ss, fmt, args...) logp(ss, __FILE__, __LINE__, 0, fmt, ## args)
+#define DEBUGPC(ss, fmt, args...) logp(ss, __FILE__, __LINE__, 1, fmt, ## args)
+#else
+#define DEBUGP(xss, fmt, args...)
+#define DEBUGPC(ss, fmt, args...)
+#endif
+
+
+void osmo_vlogp(int subsys, int level, char *file, int line,
+		int cont, const char *format, va_list ap);
+
+void logp(int subsys, char *file, int line, int cont, const char *format, ...) __attribute__ ((format (printf, 5, 6)));
+
+/*! \brief Log a new message through the Osmocom logging framework
+ *  \param[in] ss logging subsystem (e.g. \ref DLGLOBAL)
+ *  \param[in] level logging level (e.g. \ref LOGL_NOTICE)
+ *  \param[in] fmt format string
+ *  \param[in] args variable argument list
+ */
+#define LOGP(ss, level, fmt, args...) \
+	logp2(ss, level, __FILE__, __LINE__, 0, fmt, ##args)
+
+/*! \brief Continue a log message through the Osmocom logging framework
+ *  \param[in] ss logging subsystem (e.g. \ref DLGLOBAL)
+ *  \param[in] level logging level (e.g. \ref LOGL_NOTICE)
+ *  \param[in] fmt format string
+ *  \param[in] args variable argument list
+ */
+#define LOGPC(ss, level, fmt, args...) \
+	logp2(ss, level, __FILE__, __LINE__, 1, fmt, ##args)
+
+/*! \brief different log levels */
+#define LOGL_DEBUG	1	/*!< \brief debugging information */
+#define LOGL_INFO	3
+#define LOGL_NOTICE	5	/*!< \brief abnormal/unexpected condition */
+#define LOGL_ERROR	7	/*!< \brief error condition, requires user action */
+#define LOGL_FATAL	8	/*!< \brief fatal, program aborted */
+
+#define LOG_FILTER_ALL	0x0001
+
+/* logging levels defined by the library itself */
+#define DLGLOBAL	-1
+#define DLLAPD		-2
+#define DLINP		-3
+#define DLMUX		-4
+#define DLMI		-5
+#define DLMIB		-6
+#define DLSMS		-7
+#define OSMO_NUM_DLIB	7
+
+struct log_category {
+	uint8_t loglevel;
+	uint8_t enabled;
+};
+
+/*! \brief Information regarding one logging category */
+struct log_info_cat {
+	const char *name;		/*!< name of category */
+	const char *color;		/*!< color string for cateyory */
+	const char *description;	/*!< description text */
+	uint8_t loglevel;		/*!< currently selected log-level */
+	uint8_t enabled;		/*!< is this category enabled or not */
+};
+
+/*! \brief Log context information, passed to filter */
+struct log_context {
+	void *ctx[LOG_MAX_CTX+1];
+};
+
+struct log_target;
+
+/*! \brief Log filter function */
+typedef int log_filter(const struct log_context *ctx,
+		       struct log_target *target);
+
+/*! \brief Logging configuration, passed to \ref log_init */
+struct log_info {
+	/* \brief filter callback function */
+	log_filter *filter_fn;
+
+	/*! \brief per-category information */
+	struct log_info_cat *cat;
+	/*! \brief total number of categories */
+	unsigned int num_cat;
+	/*! \brief total number of user categories (not library) */
+	unsigned int num_cat_user;
+};
+
+/*! \brief Type of logging target */
+enum log_target_type {
+	LOG_TGT_TYPE_VTY,	/*!< \brief VTY logging */
+	LOG_TGT_TYPE_SYSLOG,	/*!< \brief syslog based logging */
+	LOG_TGT_TYPE_FILE,	/*!< \brief text file logging */
+	LOG_TGT_TYPE_STDERR,	/*!< \brief stderr logging */
+};
+
+/*! \brief structure representing a logging target */
+struct log_target {
+        struct llist_head entry;		/*!< \brief linked list */
+
+	/*! \brief Internal data for filtering */
+	int filter_map;
+	/*! \brief Internal data for filtering */
+	void *filter_data[LOG_MAX_FILTERS+1];
+
+	/*! \brief logging categories */
+	struct log_category *categories;
+
+	/*! \brief global log level */
+	uint8_t loglevel;
+	/*! \brief should color be used when printing log messages? */
+	unsigned int use_color:1;
+	/*! \brief should log messages be prefixed with a timestamp? */
+	unsigned int print_timestamp:1;
+
+	/*! \brief the type of this log taget */
+	enum log_target_type type;
+
+	union {
+		struct {
+			FILE *out;
+			const char *fname;
+		} tgt_file;
+
+		struct {
+			int priority;
+			int facility;
+		} tgt_syslog;
+
+		struct {
+			void *vty;
+		} tgt_vty;
+	};
+
+	/*! \brief call-back function to be called when the logging framework
+	 *	   wants to log somethnig.
+	 *  \param[[in] target logging target
+	 *  \param[in] level log level of currnet message
+	 *  \param[in] string the string that is to be written to the log
+	 */
+        void (*output) (struct log_target *target, unsigned int level,
+			const char *string);
+};
+
+/* use the above macros */
+void logp2(int subsys, unsigned int level, char *file,
+	   int line, int cont, const char *format, ...)
+				__attribute__ ((format (printf, 6, 7)));
+int log_init(const struct log_info *inf, void *talloc_ctx);
+
+/* context management */
+void log_reset_context(void);
+int log_set_context(uint8_t ctx, void *value);
+
+/* filter on the targets */
+void log_set_all_filter(struct log_target *target, int);
+
+void log_set_use_color(struct log_target *target, int);
+void log_set_print_timestamp(struct log_target *target, int);
+void log_set_log_level(struct log_target *target, int log_level);
+void log_parse_category_mask(struct log_target *target, const char* mask);
+int log_parse_level(const char *lvl);
+const char *log_level_str(unsigned int lvl);
+int log_parse_category(const char *category);
+void log_set_category_filter(struct log_target *target, int category,
+			       int enable, int level);
+
+/* management of the targets */
+struct log_target *log_target_create(void);
+void log_target_destroy(struct log_target *target);
+struct log_target *log_target_create_stderr(void);
+struct log_target *log_target_create_file(const char *fname);
+struct log_target *log_target_create_syslog(const char *ident, int option,
+					    int facility);
+int log_target_file_reopen(struct log_target *tgt);
+
+void log_add_target(struct log_target *target);
+void log_del_target(struct log_target *target);
+
+/* Generate command string for VTY use */
+const char *log_vty_command_string(const struct log_info *info);
+const char *log_vty_command_description(const struct log_info *info);
+
+struct log_target *log_target_find(int type, const char *fname);
+extern struct llist_head osmo_log_target_list;
+
+/*! @} */
+
+#endif /* _OSMOCORE_LOGGING_H */
diff --git a/include/osmocom/core/msgb.h b/include/osmocom/core/msgb.h
new file mode 100644
index 0000000..5457a07
--- /dev/null
+++ b/include/osmocom/core/msgb.h
@@ -0,0 +1,370 @@
+#ifndef _MSGB_H
+#define _MSGB_H
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+
+/*! \defgroup msgb Message buffers
+ *  @{
+ */
+
+/*! \file msgb.h
+ *  \brief Osmocom message buffers
+ * The Osmocom message buffers are modelled after the 'struct skb'
+ * inside the Linux kernel network stack.  As they exist in userspace,
+ * they are much simplified.  However, terminology such as headroom,
+ * tailroom, push/pull/put etc. remains the same.
+ */
+
+#define MSGB_DEBUG
+
+/*! \brief Osmocom message buffer */
+struct msgb {
+	struct llist_head list; /*!< \brief linked list header */
+
+
+	/* Part of which TRX logical channel we were received / transmitted */
+	/* FIXME: move them into the control buffer */
+	union {
+		void *dst; /*!< \brief reference of origin/destination */
+		struct gsm_bts_trx *trx;
+	};
+	struct gsm_lchan *lchan; /*!< \brief logical channel */
+
+	unsigned char *l1h; /*!< \brief pointer to Layer1 header (if any) */
+	unsigned char *l2h; /*!< \brief pointer to A-bis layer 2 header: OML, RSL(RLL), NS */
+	unsigned char *l3h; /*!< \brief pointer to Layer 3 header. For OML: FOM; RSL: 04.08; GPRS: BSSGP */
+	unsigned char *l4h; /*!< \brief pointer to layer 4 header */
+
+	unsigned long cb[5]; /*!< \brief control buffer */
+
+	uint16_t data_len;   /*!< \brief length of underlying data array */
+	uint16_t len;	     /*!< \brief length of bytes used in msgb */
+
+	unsigned char *head;	/*!< \brief start of underlying memory buffer */
+	unsigned char *tail;	/*!< \brief end of message in buffer */
+	unsigned char *data;	/*!< \brief start of message in buffer */
+	unsigned char _data[0]; /*!< \brief optional immediate data array */
+};
+
+extern struct msgb *msgb_alloc(uint16_t size, const char *name);
+extern void msgb_free(struct msgb *m);
+extern void msgb_enqueue(struct llist_head *queue, struct msgb *msg);
+extern struct msgb *msgb_dequeue(struct llist_head *queue);
+extern void msgb_reset(struct msgb *m);
+
+#ifdef MSGB_DEBUG
+#include <osmocom/core/panic.h>
+#define MSGB_ABORT(msg, fmt, args ...) do {		\
+	osmo_panic("msgb(%p): " fmt, msg, ## args);	\
+	} while(0)
+#else
+#define MSGB_ABORT(msg, fmt, args ...)
+#endif
+
+/*! \brief obtain L1 header of msgb */
+#define msgb_l1(m)	((void *)(m->l1h))
+/*! \brief obtain L2 header of msgb */
+#define msgb_l2(m)	((void *)(m->l2h))
+/*! \brief obtain L3 header of msgb */
+#define msgb_l3(m)	((void *)(m->l3h))
+/*! \brief obtain SMS header of msgb */
+#define msgb_sms(m)	((void *)(m->l4h))
+
+/*! \brief determine length of L1 message
+ *  \param[in] msgb message buffer
+ *  \returns size of L1 message in bytes
+ *
+ * This function computes the number of bytes between the tail of the
+ * message and the layer 1 header.
+ */
+static inline unsigned int msgb_l1len(const struct msgb *msgb)
+{
+	return msgb->tail - (uint8_t *)msgb_l1(msgb);
+}
+
+/*! \brief determine length of L2 message
+ *  \param[in] msgb message buffer
+ *  \returns size of L2 message in bytes
+ *
+ * This function computes the number of bytes between the tail of the
+ * message and the layer 2 header.
+ */
+static inline unsigned int msgb_l2len(const struct msgb *msgb)
+{
+	return msgb->tail - (uint8_t *)msgb_l2(msgb);
+}
+
+/*! \brief determine length of L3 message
+ *  \param[in] msgb message buffer
+ *  \returns size of L3 message in bytes
+ *
+ * This function computes the number of bytes between the tail of the
+ * message and the layer 3 header.
+ */
+static inline unsigned int msgb_l3len(const struct msgb *msgb)
+{
+	return msgb->tail - (uint8_t *)msgb_l3(msgb);
+}
+
+/*! \brief determine the length of the header
+ *  \param[in] msgb message buffer
+ *  \returns number of bytes between start of buffer and start of msg
+ *
+ * This function computes the length difference between the underlying
+ * data buffer and the used section of the \a msgb.
+ */
+static inline unsigned int msgb_headlen(const struct msgb *msgb)
+{
+	return msgb->len - msgb->data_len;
+}
+
+/*! \brief determine how much tail room is left in msgb
+ *  \param[in] msgb message buffer
+ *  \returns number of bytes remaining at end of msgb
+ *
+ * This function computes the amount of octets left in the underlying
+ * data buffer after the end of the message.
+ */
+static inline int msgb_tailroom(const struct msgb *msgb)
+{
+	return (msgb->head + msgb->data_len) - msgb->tail;
+}
+
+/*! \brief determine the amount of headroom in msgb
+ *  \param[in] msgb message buffer
+ *  \returns number of bytes left ahead of message start in msgb
+ *
+ * This function computes the amount of bytes left in the underlying
+ * data buffer before the start of the actual message.
+ */
+static inline int msgb_headroom(const struct msgb *msgb)
+{
+	return (msgb->data - msgb->head);
+}
+
+/*! \brief append data to end of message buffer
+ *  \param[in] msgb message buffer
+ *  \param[in] len number of bytes to append to message
+ *  \returns pointer to start of newly-appended data
+ *
+ * This function will move the \a tail pointer of the message buffer \a
+ * len bytes further, thus enlarging the message by \a len bytes.
+ *
+ * The return value is a pointer to start of the newly added section at
+ * the end of the message and can be used for actually filling/copying
+ * data into it.
+ */
+static inline unsigned char *msgb_put(struct msgb *msgb, unsigned int len)
+{
+	unsigned char *tmp = msgb->tail;
+	if (msgb_tailroom(msgb) < (int) len)
+		MSGB_ABORT(msgb, "Not enough tailroom msgb_push (%u < %u)\n",
+			   msgb_tailroom(msgb), len);
+	msgb->tail += len;
+	msgb->len += len;
+	return tmp;
+}
+
+/*! \brief append a uint8 value to the end of the message
+ *  \param[in] msgb message buffer
+ *  \param[in] word unsigned 8bit byte to be appended
+ */
+static inline void msgb_put_u8(struct msgb *msgb, uint8_t word)
+{
+	uint8_t *space = msgb_put(msgb, 1);
+	space[0] = word & 0xFF;
+}
+
+/*! \brief append a uint16 value to the end of the message
+ *  \param[in] msgb message buffer
+ *  \param[in] word unsigned 16bit byte to be appended
+ */
+static inline void msgb_put_u16(struct msgb *msgb, uint16_t word)
+{
+	uint8_t *space = msgb_put(msgb, 2);
+	space[0] = word >> 8 & 0xFF;
+	space[1] = word & 0xFF;
+}
+
+/*! \brief append a uint32 value to the end of the message
+ *  \param[in] msgb message buffer
+ *  \param[in] word unsigned 32bit byte to be appended
+ */
+static inline void msgb_put_u32(struct msgb *msgb, uint32_t word)
+{
+	uint8_t *space = msgb_put(msgb, 4);
+	space[0] = word >> 24 & 0xFF;
+	space[1] = word >> 16 & 0xFF;
+	space[2] = word >> 8 & 0xFF;
+	space[3] = word & 0xFF;
+}
+
+/*! \brief remove data from end of message
+ *  \param[in] msgb message buffer
+ *  \param[in] len number of bytes to remove from end
+ */
+static inline unsigned char *msgb_get(struct msgb *msgb, unsigned int len)
+{
+	unsigned char *tmp = msgb->data;
+	msgb->data += len;
+	msgb->len -= len;
+	return tmp;
+}
+/*! \brief remove uint8 from end of message
+ *  \param[in] msgb message buffer
+ *  \returns 8bit value taken from end of msgb
+ */
+static inline uint8_t msgb_get_u8(struct msgb *msgb)
+{
+	uint8_t *space = msgb_get(msgb, 1);
+	return space[0];
+}
+/*! \brief remove uint16 from end of message
+ *  \param[in] msgb message buffer
+ *  \returns 16bit value taken from end of msgb
+ */
+static inline uint16_t msgb_get_u16(struct msgb *msgb)
+{
+	uint8_t *space = msgb_get(msgb, 2);
+	return space[0] << 8 | space[1];
+}
+/*! \brief remove uint32 from end of message
+ *  \param[in] msgb message buffer
+ *  \returns 32bit value taken from end of msgb
+ */
+static inline uint32_t msgb_get_u32(struct msgb *msgb)
+{
+	uint8_t *space = msgb_get(msgb, 4);
+	return space[0] << 24 | space[1] << 16 | space[2] << 8 | space[3];
+}
+
+/*! \brief prepend (push) some data to start of message
+ *  \param[in] msgb message buffer
+ *  \param[in] len number of bytes to pre-pend
+ *  \returns pointer to newly added portion at start of \a msgb
+ *
+ * This function moves the \a data pointer of the \ref msgb further
+ * to the front (by \a len bytes), thereby enlarging the message by \a
+ * len bytes.
+ *
+ * The return value is a pointer to the newly added section in the
+ * beginning of the message.  It can be used to fill/copy data into it.
+ */
+static inline unsigned char *msgb_push(struct msgb *msgb, unsigned int len)
+{
+	if (msgb_headroom(msgb) < (int) len)
+		MSGB_ABORT(msgb, "Not enough headroom msgb_push (%u < %u)\n",
+			   msgb_headroom(msgb), len);
+	msgb->data -= len;
+	msgb->len += len;
+	return msgb->data;
+}
+/*! \brief remove (pull) a header from the front of the message buffer
+ *  \param[in] msgb message buffer
+ *  \param[in] len number of octets to be pulled
+ *  \returns pointer to new start of msgb
+ *
+ * This function moves the \a data pointer of the \ref msgb further back
+ * in the message, thereby shrinking the size of the message by \a len
+ * bytes.
+ */
+static inline unsigned char *msgb_pull(struct msgb *msgb, unsigned int len)
+{
+	msgb->len -= len;
+	return msgb->data += len;
+}
+
+/*! \brief Increase headroom of empty msgb, reducing the tailroom
+ *  \param[in] msg message buffer
+ *  \param[in] len amount of extra octets to be reserved as headroom
+ *
+ * This function reserves some memory at the beginning of the underlying
+ * data buffer.  The idea is to reserve space in case further headers
+ * have to be pushed to the \ref msgb during further processing.
+ *
+ * Calling this function leads to undefined reusults if it is called on
+ * a non-empty \ref msgb.
+ */
+static inline void msgb_reserve(struct msgb *msg, int len)
+{
+	msg->data += len;
+	msg->tail += len;
+}
+
+/*! \brief Trim the msgb to a given absolute length
+ *  \param[in] msg message buffer
+ *  \param[in] len new total length of buffer
+ *  \returns 0 in case of success, negative in case of error
+ */
+static inline int msgb_trim(struct msgb *msg, int len)
+{
+	if (len > msg->data_len)
+		return -1;
+
+	msg->len = len;
+	msg->tail = msg->data + len;
+
+	return 0;
+}
+
+/*! \brief Trim the msgb to a given layer3 length
+ *  \pram[in] msg message buffer
+ *  \param[in] l3len new layer3 length
+ *  \returns 0 in case of success, negative in case of error
+ */
+static inline int msgb_l3trim(struct msgb *msg, int l3len)
+{
+	return msgb_trim(msg, (msg->l3h - msg->data) + l3len);
+}
+
+/*! \brief Allocate message buffer with specified headroom
+ *  \param[in] size size in bytes, including headroom
+ *  \param[in] headroom headroom in bytes
+ *  \param[in] name human-readable name
+ *  \returns allocated message buffer with specified headroom
+ *
+ * This function is a convenience wrapper around \ref msgb_alloc
+ * followed by \ref msgb_reserve in order to create a new \ref msgb with
+ * user-specified amount of headroom.
+ */
+static inline struct msgb *msgb_alloc_headroom(int size, int headroom,
+						const char *name)
+{
+	osmo_static_assert(size > headroom, headroom_bigger);
+
+	struct msgb *msg = msgb_alloc(size, name);
+	if (msg)
+		msgb_reserve(msg, headroom);
+	return msg;
+}
+
+/* non inline functions to ease binding */
+
+uint8_t *msgb_data(const struct msgb *msg);
+uint16_t msgb_length(const struct msgb *msg);
+void msgb_set_talloc_ctx(void *ctx);
+
+/*! @} */
+
+#endif /* _MSGB_H */
diff --git a/include/osmocom/core/msgfile.h b/include/osmocom/core/msgfile.h
new file mode 100644
index 0000000..c5e67a4
--- /dev/null
+++ b/include/osmocom/core/msgfile.h
@@ -0,0 +1,49 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef MSG_FILE_H
+#define MSG_FILE_H
+
+#include <osmocom/core/linuxlist.h>
+
+/**
+ * One message in the list.
+ */
+struct osmo_config_entry {
+	struct llist_head list;
+
+	/* number for everyone to use */
+	int nr;
+
+	/* data from the file */
+	char *mcc;
+	char *mnc;
+	char *option;
+	char *text;
+};
+
+struct osmo_config_list {
+	struct llist_head entry;
+};
+
+struct osmo_config_list* osmo_config_list_parse(void *ctx, const char *filename);
+
+#endif
diff --git a/include/osmocom/core/panic.h b/include/osmocom/core/panic.h
new file mode 100644
index 0000000..fd5cf20
--- /dev/null
+++ b/include/osmocom/core/panic.h
@@ -0,0 +1,20 @@
+#ifndef OSMOCORE_PANIC_H
+#define OSMOCORE_PANIC_H
+
+/*! \addtogroup utils
+ *  @{
+ */
+
+/*! \file panic.h */
+
+#include <stdarg.h>
+
+/*! \brief panic handler callback function type */
+typedef void (*osmo_panic_handler_t)(const char *fmt, va_list args);
+
+extern void osmo_panic(const char *fmt, ...);
+extern void osmo_set_panic_handler(osmo_panic_handler_t h);
+
+/*! @} */
+
+#endif /* OSMOCORE_PANIC_H */
diff --git a/include/osmocom/core/plugin.h b/include/osmocom/core/plugin.h
new file mode 100644
index 0000000..6c0eccc
--- /dev/null
+++ b/include/osmocom/core/plugin.h
@@ -0,0 +1,6 @@
+#ifndef _OSMO_PLUGIN_H
+#define _OSMO_PLUGIN_H
+
+int osmo_plugin_load_all(const char *directory);
+
+#endif
diff --git a/include/osmocom/core/prim.h b/include/osmocom/core/prim.h
new file mode 100644
index 0000000..b1026fe
--- /dev/null
+++ b/include/osmocom/core/prim.h
@@ -0,0 +1,58 @@
+#ifndef OSMO_PRIMITIVE_H
+#define OSMO_PRIMITIVE_H
+
+/*! \defgroup prim Osmocom primitives
+ *  @{
+ */
+
+/*! \file prim.c */
+
+#include <stdint.h>
+#include <osmocom/core/msgb.h>
+
+#define OSMO_PRIM(prim, op)	((prim << 8) | (op & 0xFF))
+#define OSMO_PRIM_HDR(oph)	OSMO_PRIM((oph)->primitive, (oph)->operation)
+
+/*! \brief primitive operation */
+enum osmo_prim_operation {
+	PRIM_OP_REQUEST,	/*!< \brief request */
+	PRIM_OP_RESPONSE,	/*!< \brief response */
+	PRIM_OP_INDICATION,	/*!< \brief indication */
+	PRIM_OP_CONFIRM,	/*!< \brief cofirm */
+};
+
+#define _SAP_GSM_SHIFT	24
+
+#define _SAP_GSM_BASE	(0x01 << _SAP_GSM_SHIFT)
+#define _SAP_TETRA_BASE	(0x02 << _SAP_GSM_SHIFT)
+
+/*! \brief primitive header */
+struct osmo_prim_hdr {
+	unsigned int sap;	/*!< \brief Service Access Point */
+	unsigned int primitive;	/*!< \brief Primitive number */
+	enum osmo_prim_operation operation; /*! \brief Primitive Operation */
+	struct msgb *msg;	/*!< \brief \ref msgb containing associated data */
+};
+
+/*! \brief initialize a primitive header
+ *  \param[in,out] oph primitive header
+ *  \param[in] sap Service Access Point
+ *  \param[in] primtive Primitive Number
+ *  \param[in] operation Primitive Operation (REQ/RESP/IND/CONF)
+ *  \param[in] msg Message
+ */
+static inline void
+osmo_prim_init(struct osmo_prim_hdr *oph, unsigned int sap,
+		unsigned int primitive, enum osmo_prim_operation operation,
+		struct msgb *msg)
+{
+	oph->sap = sap;
+	oph->primitive = primitive;
+	oph->operation = operation;
+	oph->msg = msg;
+}
+
+/*! \brief primitive handler callback type */
+typedef int (*osmo_prim_cb)(struct osmo_prim_hdr *oph, void *ctx);
+
+#endif /* OSMO_PRIMITIVE_H */
diff --git a/include/osmocom/core/process.h b/include/osmocom/core/process.h
new file mode 100644
index 0000000..1dde021
--- /dev/null
+++ b/include/osmocom/core/process.h
@@ -0,0 +1,2 @@
+#warning "Update from osmocom/core/process.h to osmocom/core/application.h"
+#include <osmocom/core/application.h>
diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h
new file mode 100644
index 0000000..24577fd
--- /dev/null
+++ b/include/osmocom/core/rate_ctr.h
@@ -0,0 +1,88 @@
+#ifndef _RATE_CTR_H
+#define _RATE_CTR_H
+
+/*! \defgroup rate_ctr Rate counters
+ *  @{
+ */
+
+/*! \file rate_ctr.h */
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+
+/*! \brief Number of rate counter intervals */
+#define RATE_CTR_INTV_NUM	4
+
+/*! \brief Rate counter interval */
+enum rate_ctr_intv {
+	RATE_CTR_INTV_SEC,	/*!< \brief last second */
+	RATE_CTR_INTV_MIN,	/*!< \brief last minute */
+	RATE_CTR_INTV_HOUR,	/*!< \brief last hour */
+	RATE_CTR_INTV_DAY,	/*!< \brief last day */
+};
+
+/*! \brief data we keep for each of the intervals */
+struct rate_ctr_per_intv {
+	uint64_t last;		/*!< \brief counter value in last interval */
+	uint64_t rate;		/*!< \brief counter rate */
+};
+
+/*! \brief data we keep for each actual value */
+struct rate_ctr {
+	uint64_t current;	/*!< \brief current value */
+	/*! \brief per-interval data */
+	struct rate_ctr_per_intv intv[RATE_CTR_INTV_NUM];
+};
+
+/*! \brief rate counter description */
+struct rate_ctr_desc {
+	const char *name;	/*!< \brief name of the counter */
+	const char *description;/*!< \brief description of the counter */
+};
+
+/*! \brief description of a rate counter group */
+struct rate_ctr_group_desc {
+	/*! \brief The prefix to the name of all counters in this group */
+	const char *group_name_prefix;
+	/*! \brief The human-readable description of the group */
+	const char *group_description;
+	/*! \brief The number of counters in this group */
+	const unsigned int num_ctr;
+	/*! \brief Pointer to array of counter names */
+	const struct rate_ctr_desc *ctr_desc;
+};
+
+/*! \brief One instance of a counter group class */
+struct rate_ctr_group {
+	/*! \brief Linked list of all counter groups in the system */
+	struct llist_head list;
+	/*! \brief Pointer to the counter group class */
+	const struct rate_ctr_group_desc *desc;
+	/*! \brief The index of this ctr_group within its class */
+	unsigned int idx;
+	/*! \brief Actual counter structures below */
+	struct rate_ctr ctr[0];
+};
+
+struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
+					    const struct rate_ctr_group_desc *desc,
+					    unsigned int idx);
+
+void rate_ctr_group_free(struct rate_ctr_group *grp);
+
+void rate_ctr_add(struct rate_ctr *ctr, int inc);
+
+/*! \brief Increment the counter by 1 */
+static inline void rate_ctr_inc(struct rate_ctr *ctr)
+{
+	rate_ctr_add(ctr, 1);
+}
+
+int rate_ctr_init(void *tall_ctx);
+
+struct rate_ctr_group *rate_ctr_get_group_by_name_idx(const char *name, const unsigned int idx);
+const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, const char *name);
+
+/*! @} */
+#endif /* RATE_CTR_H */
diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h
new file mode 100644
index 0000000..efdd716
--- /dev/null
+++ b/include/osmocom/core/select.h
@@ -0,0 +1,45 @@
+#ifndef _BSC_SELECT_H
+#define _BSC_SELECT_H
+
+#include <osmocom/core/linuxlist.h>
+
+/*! \defgroup select Select loop abstraction
+ *  @{
+ */
+
+/*! \file select.h
+ *  \brief select loop abstraction
+ */
+
+/*! \brief Indicate interest in reading from the file descriptor */
+#define BSC_FD_READ	0x0001
+/*! \brief Indicate interest in writing to the file descriptor */
+#define BSC_FD_WRITE	0x0002
+/*! \brief Indicate interest in exceptions from the file descriptor */
+#define BSC_FD_EXCEPT	0x0004
+
+/*! \brief Structure representing a file dsecriptor */
+struct osmo_fd {
+	/*! linked list for internal management */
+	struct llist_head list;	
+	/*! actual operating-system level file decriptor */
+	int fd;
+	/*! bit-mask or of \ref BSC_FD_READ, \ref BSC_FD_WRITE and/or
+	 * \ref BSC_FD_EXCEPT */
+	unsigned int when;
+	/*! call-back function to be called once file descriptor becomes
+	 * available */
+	int (*cb)(struct osmo_fd *fd, unsigned int what);
+	/*! data pointer passed through to call-back function */
+	void *data;
+	/*! private number, extending \a data */
+	unsigned int priv_nr;
+};
+
+int osmo_fd_register(struct osmo_fd *fd);
+void osmo_fd_unregister(struct osmo_fd *fd);
+int osmo_select_main(int polling);
+
+/*! @} */
+
+#endif /* _BSC_SELECT_H */
diff --git a/include/osmocom/core/serial.h b/include/osmocom/core/serial.h
new file mode 100644
index 0000000..889bd8a
--- /dev/null
+++ b/include/osmocom/core/serial.h
@@ -0,0 +1,43 @@
+/*
+ * serial.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \defgroup serial Utility functions to deal with serial ports
+ *  @{
+ */
+
+/*! \file serial.h
+ *  \file Osmocom serial port helpers
+ */
+
+#ifndef __OSMO_SERIAL_H__
+#define __OSMO_SERIAL_H__
+
+#include <termios.h>
+
+int osmo_serial_init(const char *dev, speed_t baudrate);
+int osmo_serial_set_baudrate(int fd, speed_t baudrate);
+int osmo_serial_set_custom_baudrate(int fd, int baudrate);
+int osmo_serial_clear_custom_baudrate(int fd);
+
+/*! @} */
+
+#endif /* __OSMO_SERIAL_H__ */
diff --git a/include/osmocom/core/signal.h b/include/osmocom/core/signal.h
new file mode 100644
index 0000000..b3a5aae
--- /dev/null
+++ b/include/osmocom/core/signal.h
@@ -0,0 +1,46 @@
+#ifndef OSMO_SIGNAL_H
+#define OSMO_SIGNAL_H
+
+#include <stdint.h>
+
+/*! \defgroup signal Intra-application signals
+ *  @{
+ */
+/*! \file signal.h */
+
+/* subsystem signaling numbers: we split the numberspace for applications and
+ * libraries: from 0 to UINT_MAX/2 for applications, from UINT_MAX/2 to
+ * UINT_MAX for libraries. */
+#define OSMO_SIGNAL_SS_APPS		0
+#define OSMO_SIGNAL_SS_RESERVED		2147483648u
+
+/*! \brief signal subsystems */
+enum {
+	SS_L_GLOBAL		= OSMO_SIGNAL_SS_RESERVED,
+	SS_L_INPUT,
+	SS_L_NS,
+};
+
+/* application-defined signal types. */
+#define OSMO_SIGNAL_T_APPS		0
+#define OSMO_SIGNAL_T_RESERVED		2147483648u
+
+/*! \brief signal types. */
+enum {
+	S_L_GLOBAL_SHUTDOWN	= OSMO_SIGNAL_T_RESERVED,
+};
+
+/*! signal callback function type */
+typedef int osmo_signal_cbfn(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data);
+
+
+/* Management */
+int osmo_signal_register_handler(unsigned int subsys, osmo_signal_cbfn *cbfn, void *data);
+void osmo_signal_unregister_handler(unsigned int subsys, osmo_signal_cbfn *cbfn, void *data);
+
+/* Dispatch */
+void osmo_signal_dispatch(unsigned int subsys, unsigned int signal, void *signal_data);
+
+/*! @} */
+
+#endif /* OSMO_SIGNAL_H */
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
new file mode 100644
index 0000000..f15a03a
--- /dev/null
+++ b/include/osmocom/core/socket.h
@@ -0,0 +1,35 @@
+#ifndef _OSMOCORE_SOCKET_H
+#define _OSMOCORE_SOCKET_H
+
+/*! \defgroup socket Socket convenience functions
+ *  @{
+ */
+
+/*! \file socket.h
+ *  \brief Osmocom socket convenience functions
+ */
+
+#include <stdint.h>
+
+struct sockaddr;
+struct osmo_fd;
+
+/* flags for osmo_sock_init. */
+#define OSMO_SOCK_F_CONNECT	(1 << 0)
+#define OSMO_SOCK_F_BIND	(1 << 1)
+#define OSMO_SOCK_F_NONBLOCK	(1 << 2)
+
+int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
+		   const char *host, uint16_t port, unsigned int flags);
+
+int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
+			const char *host, uint16_t port, unsigned int flags);
+
+int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
+		      uint8_t proto, unsigned int flags);
+
+int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen);
+
+/*! @} */
+
+#endif /* _OSMOCORE_SOCKET_H */
diff --git a/include/osmocom/core/statistics.h b/include/osmocom/core/statistics.h
new file mode 100644
index 0000000..04816c1
--- /dev/null
+++ b/include/osmocom/core/statistics.h
@@ -0,0 +1,53 @@
+#ifndef _STATISTICS_H
+#define _STATISTICS_H
+
+/*! \file statistics.h
+ *  \brief Common routines regarding statistics */
+
+/*! structure representing a single counter */
+struct osmo_counter {
+	struct llist_head list;		/*!< \brief internal list head */
+	const char *name;		/*!< \brief human-readable name */
+	const char *description;	/*!< \brief humn-readable description */
+	unsigned long value;		/*!< \brief current value */
+};
+
+/*! \brief Increment counter */
+static inline void osmo_counter_inc(struct osmo_counter *ctr)
+{
+	ctr->value++;
+}
+
+/*! \brief Get current value of counter */
+static inline unsigned long osmo_counter_get(struct osmo_counter *ctr)
+{
+	return ctr->value;
+}
+
+/*! \brief Reset current value of counter to 0 */
+static inline void osmo_counter_reset(struct osmo_counter *ctr)
+{
+	ctr->value = 0;
+}
+
+/*! \brief Allocate a new counter */
+struct osmo_counter *osmo_counter_alloc(const char *name);
+
+/*! \brief Free the specified counter
+ *  \param[ctr] Counter
+ */
+void osmo_counter_free(struct osmo_counter *ctr);
+
+/*! \brief Iteate over all counters
+ *  \param[in] handle_counter Call-back function
+ *  \param[in] data Private dtata handed through to \a handle_counter
+ */
+int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *), void *data);
+
+/*! \brief Resolve counter by human-readable name
+ *  \param[in] name human-readable name of counter
+ *  \returns pointer to counter (\ref osmo_counter) or NULL otherwise
+ */
+struct osmo_counter *osmo_counter_get_by_name(const char *name);
+
+#endif /* _STATISTICS_H */
diff --git a/include/osmocom/core/talloc.h b/include/osmocom/core/talloc.h
new file mode 100644
index 0000000..f7f7643
--- /dev/null
+++ b/include/osmocom/core/talloc.h
@@ -0,0 +1,192 @@
+#ifndef _TALLOC_H_
+#define _TALLOC_H_
+/* 
+   Unix SMB/CIFS implementation.
+   Samba temporary memory allocation functions
+
+   Copyright (C) Andrew Tridgell 2004-2005
+   Copyright (C) Stefan Metzmacher 2006
+   
+     ** NOTE! The following LGPL license applies to the talloc
+     ** library. This does NOT imply that all of Samba is released
+     ** under the LGPL
+   
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 3 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#define HAVE_VA_COPY
+
+/* this is only needed for compatibility with the old talloc */
+typedef void TALLOC_CTX;
+
+/*
+  this uses a little trick to allow __LINE__ to be stringified
+*/
+#ifndef __location__
+#define __TALLOC_STRING_LINE1__(s)    #s
+#define __TALLOC_STRING_LINE2__(s)   __TALLOC_STRING_LINE1__(s)
+#define __TALLOC_STRING_LINE3__  __TALLOC_STRING_LINE2__(__LINE__)
+#define __location__ __FILE__ ":" __TALLOC_STRING_LINE3__
+#endif
+
+#ifndef TALLOC_DEPRECATED
+#define TALLOC_DEPRECATED 0
+#endif
+
+#ifndef PRINTF_ATTRIBUTE
+#if (__GNUC__ >= 3)
+/** Use gcc attribute to check printf fns.  a1 is the 1-based index of
+ * the parameter containing the format, and a2 the index of the first
+ * argument. Note that some gcc 2.x versions don't handle this
+ * properly **/
+#define PRINTF_ATTRIBUTE(a1, a2) __attribute__ ((format (__printf__, a1, a2)))
+#else
+#define PRINTF_ATTRIBUTE(a1, a2)
+#endif
+#endif
+
+/* try to make talloc_set_destructor() and talloc_steal() type safe,
+   if we have a recent gcc */
+#if (__GNUC__ >= 3)
+#define _TALLOC_TYPEOF(ptr) __typeof__(ptr)
+#define talloc_set_destructor(ptr, function)				      \
+	do {								      \
+		int (*_talloc_destructor_fn)(_TALLOC_TYPEOF(ptr)) = (function);	      \
+		_talloc_set_destructor((ptr), (int (*)(void *))_talloc_destructor_fn); \
+	} while(0)
+/* this extremely strange macro is to avoid some braindamaged warning
+   stupidity in gcc 4.1.x */
+#define talloc_steal(ctx, ptr) ({ _TALLOC_TYPEOF(ptr) __talloc_steal_ret = (_TALLOC_TYPEOF(ptr))_talloc_steal((ctx),(ptr)); __talloc_steal_ret; })
+#else
+#define talloc_set_destructor(ptr, function) \
+	_talloc_set_destructor((ptr), (int (*)(void *))(function))
+#define _TALLOC_TYPEOF(ptr) void *
+#define talloc_steal(ctx, ptr) (_TALLOC_TYPEOF(ptr))_talloc_steal((ctx),(ptr))
+#endif
+
+#define talloc_reference(ctx, ptr) (_TALLOC_TYPEOF(ptr))_talloc_reference((ctx),(ptr))
+#define talloc_move(ctx, ptr) (_TALLOC_TYPEOF(*(ptr)))_talloc_move((ctx),(void *)(ptr))
+
+/* useful macros for creating type checked pointers */
+#define talloc(ctx, type) (type *)talloc_named_const(ctx, sizeof(type), #type)
+#define talloc_size(ctx, size) talloc_named_const(ctx, size, __location__)
+#define talloc_ptrtype(ctx, ptr) (_TALLOC_TYPEOF(ptr))talloc_size(ctx, sizeof(*(ptr)))
+
+#define talloc_new(ctx) talloc_named_const(ctx, 0, "talloc_new: " __location__)
+
+#define talloc_zero(ctx, type) (type *)_talloc_zero(ctx, sizeof(type), #type)
+#define talloc_zero_size(ctx, size) _talloc_zero(ctx, size, __location__)
+
+#define talloc_zero_array(ctx, type, count) (type *)_talloc_zero_array(ctx, sizeof(type), count, #type)
+#define talloc_array(ctx, type, count) (type *)_talloc_array(ctx, sizeof(type), count, #type)
+#define talloc_array_size(ctx, size, count) _talloc_array(ctx, size, count, __location__)
+#define talloc_array_ptrtype(ctx, ptr, count) (_TALLOC_TYPEOF(ptr))talloc_array_size(ctx, sizeof(*(ptr)), count)
+#define talloc_array_length(ctx) (talloc_get_size(ctx)/sizeof(*ctx))
+
+#define talloc_realloc(ctx, p, type, count) (type *)_talloc_realloc_array(ctx, p, sizeof(type), count, #type)
+#define talloc_realloc_size(ctx, ptr, size) _talloc_realloc(ctx, ptr, size, __location__)
+
+#define talloc_memdup(t, p, size) _talloc_memdup(t, p, size, __location__)
+
+#define talloc_set_type(ptr, type) talloc_set_name_const(ptr, #type)
+#define talloc_get_type(ptr, type) (type *)talloc_check_name(ptr, #type)
+#define talloc_get_type_abort(ptr, type) (type *)_talloc_get_type_abort(ptr, #type, __location__)
+
+#define talloc_find_parent_bytype(ptr, type) (type *)talloc_find_parent_byname(ptr, #type)
+
+#if TALLOC_DEPRECATED
+#define talloc_zero_p(ctx, type) talloc_zero(ctx, type)
+#define talloc_p(ctx, type) talloc(ctx, type)
+#define talloc_array_p(ctx, type, count) talloc_array(ctx, type, count)
+#define talloc_realloc_p(ctx, p, type, count) talloc_realloc(ctx, p, type, count)
+#define talloc_destroy(ctx) talloc_free(ctx)
+#define talloc_append_string(c, s, a) (s?talloc_strdup_append(s,a):talloc_strdup(c, a))
+#endif
+
+#define TALLOC_FREE(ctx) do { talloc_free(ctx); ctx=NULL; } while(0)
+
+/* The following definitions come from talloc.c  */
+void *_talloc(const void *context, size_t size);
+void *talloc_pool(const void *context, size_t size);
+void _talloc_set_destructor(const void *ptr, int (*_destructor)(void *));
+int talloc_increase_ref_count(const void *ptr);
+size_t talloc_reference_count(const void *ptr);
+void *_talloc_reference(const void *context, const void *ptr);
+int talloc_unlink(const void *context, void *ptr);
+const char *talloc_set_name(const void *ptr, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+void talloc_set_name_const(const void *ptr, const char *name);
+void *talloc_named(const void *context, size_t size, 
+		   const char *fmt, ...) PRINTF_ATTRIBUTE(3,4);
+void *talloc_named_const(const void *context, size_t size, const char *name);
+const char *talloc_get_name(const void *ptr);
+void *talloc_check_name(const void *ptr, const char *name);
+void *_talloc_get_type_abort(const void *ptr, const char *name, const char *location);
+void *talloc_parent(const void *ptr);
+const char *talloc_parent_name(const void *ptr);
+void *talloc_init(const char *fmt, ...) PRINTF_ATTRIBUTE(1,2);
+int talloc_free(void *ptr);
+void talloc_free_children(void *ptr);
+void *_talloc_realloc(const void *context, void *ptr, size_t size, const char *name);
+void *_talloc_steal(const void *new_ctx, const void *ptr);
+void *_talloc_move(const void *new_ctx, const void *pptr);
+size_t talloc_total_size(const void *ptr);
+size_t talloc_total_blocks(const void *ptr);
+void talloc_report_depth_cb(const void *ptr, int depth, int max_depth,
+			    void (*callback)(const void *ptr,
+			  		     int depth, int max_depth,
+					     int is_ref,
+					     void *private_data),
+			    void *private_data);
+void talloc_report_depth_file(const void *ptr, int depth, int max_depth, FILE *f);
+void talloc_report_full(const void *ptr, FILE *f);
+void talloc_report(const void *ptr, FILE *f);
+void talloc_enable_null_tracking(void);
+void talloc_disable_null_tracking(void);
+void talloc_enable_leak_report(void);
+void talloc_enable_leak_report_full(void);
+void *_talloc_zero(const void *ctx, size_t size, const char *name);
+void *_talloc_memdup(const void *t, const void *p, size_t size, const char *name);
+void *_talloc_array(const void *ctx, size_t el_size, unsigned count, const char *name);
+void *_talloc_zero_array(const void *ctx, size_t el_size, unsigned count, const char *name);
+void *_talloc_realloc_array(const void *ctx, void *ptr, size_t el_size, unsigned count, const char *name);
+void *talloc_realloc_fn(const void *context, void *ptr, size_t size);
+void *talloc_autofree_context(void);
+size_t talloc_get_size(const void *ctx);
+void *talloc_find_parent_byname(const void *ctx, const char *name);
+void talloc_show_parents(const void *context, FILE *file);
+int talloc_is_parent(const void *context, const void *ptr);
+
+char *talloc_strdup(const void *t, const char *p);
+char *talloc_strdup_append(char *s, const char *a);
+char *talloc_strdup_append_buffer(char *s, const char *a);
+
+char *talloc_strndup(const void *t, const char *p, size_t n);
+char *talloc_strndup_append(char *s, const char *a, size_t n);
+char *talloc_strndup_append_buffer(char *s, const char *a, size_t n);
+
+char *talloc_vasprintf(const void *t, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);
+char *talloc_vasprintf_append(char *s, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);
+char *talloc_vasprintf_append_buffer(char *s, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);
+
+char *talloc_asprintf(const void *t, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+char *talloc_asprintf_append(char *s, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+char *talloc_asprintf_append_buffer(char *s, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+
+void talloc_set_abort_fn(void (*abort_fn)(const char *reason));
+
+#endif
diff --git a/include/osmocom/core/timer.h b/include/osmocom/core/timer.h
new file mode 100644
index 0000000..ecb5001
--- /dev/null
+++ b/include/osmocom/core/timer.h
@@ -0,0 +1,87 @@
+/*
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \defgroup timer Osmocom timers
+ *  @{
+ */
+
+/*! \file timer.h
+ *  \brief Osmocom timer handling routines
+ */
+
+#ifndef TIMER_H
+#define TIMER_H
+
+#include <sys/time.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/linuxrbtree.h>
+
+/**
+ * Timer management:
+ *      - Create a struct osmo_timer_list
+ *      - Fill out timeout and use add_timer or
+ *        use schedule_timer to schedule a timer in
+ *        x seconds and microseconds from now...
+ *      - Use del_timer to remove the timer
+ *
+ *  Internally:
+ *      - We hook into select.c to give a timeval of the
+ *        nearest timer. On already passed timers we give
+ *        it a 0 to immediately fire after the select
+ *      - update_timers will call the callbacks and remove
+ *        the timers.
+ *
+ */
+/*! \brief A structure representing a single instance of a timer */
+struct osmo_timer_list {
+	struct rb_node node;	  /*!< \brief rb-tree node header */
+	struct llist_head list;   /*!< \brief internal list header */
+	struct timeval timeout;   /*!< \brief expiration time */
+	unsigned int active  : 1; /*!< \brief is it active? */
+
+	void (*cb)(void*);	  /*!< \brief call-back called at timeout */
+	void *data;		  /*!< \brief user data for callback */
+};
+
+/**
+ * timer management
+ */
+
+void osmo_timer_add(struct osmo_timer_list *timer);
+
+void osmo_timer_schedule(struct osmo_timer_list *timer, int seconds, int microseconds);
+
+void osmo_timer_del(struct osmo_timer_list *timer);
+
+int osmo_timer_pending(struct osmo_timer_list *timer);
+
+
+/*
+ * internal timer list management
+ */
+struct timeval *osmo_timers_nearest(void);
+void osmo_timers_prepare(void);
+int osmo_timers_update(void);
+int osmo_timers_check(void);
+
+/*! @} */
+
+#endif
diff --git a/include/osmocom/core/timer_compat.h b/include/osmocom/core/timer_compat.h
new file mode 100644
index 0000000..d86c109
--- /dev/null
+++ b/include/osmocom/core/timer_compat.h
@@ -0,0 +1,79 @@
+/*
+ * (C) 2011 Sylvain Munaut <tnt@246tNt.com>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \defgroup timer Osmocom timers
+ *  @{
+ */
+
+/*! \file timer_compat.h
+ *  \brief Compatibility header with some helpers
+ */
+
+#ifndef TIMER_COMPAT_H
+#define TIMER_COMPAT_H
+
+
+/* Convenience macros for operations on timevals.
+   NOTE: `timercmp' does not work for >= or <=.  */
+
+#ifndef timerisset
+# define timerisset(tvp)	((tvp)->tv_sec || (tvp)->tv_usec)
+#endif
+
+#ifndef timerclear
+# define timerclear(tvp)	((tvp)->tv_sec = (tvp)->tv_usec = 0)
+#endif
+
+#ifndef timercmp
+# define timercmp(a, b, CMP) 						      \
+  (((a)->tv_sec == (b)->tv_sec) ? 					      \
+   ((a)->tv_usec CMP (b)->tv_usec) : 					      \
+   ((a)->tv_sec CMP (b)->tv_sec))
+#endif
+
+#ifndef timeradd
+# define timeradd(a, b, result)						      \
+  do {									      \
+    (result)->tv_sec = (a)->tv_sec + (b)->tv_sec;			      \
+    (result)->tv_usec = (a)->tv_usec + (b)->tv_usec;			      \
+    if ((result)->tv_usec >= 1000000)					      \
+      {									      \
+	++(result)->tv_sec;						      \
+	(result)->tv_usec -= 1000000;					      \
+      }									      \
+  } while (0)
+#endif
+
+#ifndef timersub
+# define timersub(a, b, result)						      \
+  do {									      \
+    (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;			      \
+    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;			      \
+    if ((result)->tv_usec < 0) {					      \
+      --(result)->tv_sec;						      \
+      (result)->tv_usec += 1000000;					      \
+    }									      \
+  } while (0)
+#endif
+
+
+/*! @} */
+
+#endif /* TIMER_COMPAT_H */
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
new file mode 100644
index 0000000..78cf186
--- /dev/null
+++ b/include/osmocom/core/utils.h
@@ -0,0 +1,56 @@
+#ifndef OSMOCORE_UTIL_H
+#define OSMOCORE_UTIL_H
+
+/*! \defgroup utils General-purpose utility functions
+ *  @{
+ */
+
+/*! \file utils.h */
+
+/*! \brief Determine number of elements in an array of static size */
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+/*! \brief Return the maximum of two specified values */
+#define OSMO_MAX(a, b) (a) >= (b) ? (a) : (b)
+/*! \brief Return the minimum of two specified values */
+#define OSMO_MIN(a, b) (a) >= (b) ? (b) : (a)
+
+#include <stdint.h>
+
+/*! \brief A mapping between human-readable string and numeric value */
+struct value_string {
+	unsigned int value;	/*!< \brief numeric value */
+	const char *str;	/*!< \brief human-readable string */
+};
+
+const char *get_value_string(const struct value_string *vs, uint32_t val);
+
+int get_string_value(const struct value_string *vs, const char *str);
+
+char osmo_bcd2char(uint8_t bcd);
+/* only works for numbers in ascci */
+uint8_t osmo_char2bcd(char c);
+
+int osmo_hexparse(const char *str, uint8_t *b, int max_len);
+
+char *osmo_ubit_dump(const uint8_t *bits, unsigned int len);
+char *osmo_hexdump(const unsigned char *buf, int len);
+char *osmo_hexdump_nospc(const unsigned char *buf, int len);
+char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len) __attribute__((__deprecated__));
+
+#define osmo_static_assert(exp, name) typedef int dummy##name [(exp) ? 1 : -1];
+
+void osmo_str2lower(char *out, const char *in);
+void osmo_str2upper(char *out, const char *in);
+
+#define OSMO_SNPRINTF_RET(ret, rem, offset, len)		\
+do {								\
+	len += ret;						\
+	if (ret > rem)						\
+		ret = rem;					\
+	offset += ret;						\
+	rem -= ret;						\
+} while (0)
+
+/*! @} */
+
+#endif
diff --git a/include/osmocom/core/write_queue.h b/include/osmocom/core/write_queue.h
new file mode 100644
index 0000000..816c036
--- /dev/null
+++ b/include/osmocom/core/write_queue.h
@@ -0,0 +1,63 @@
+/* Generic write queue implementation */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef OSMO_WQUEUE_H
+#define OSMO_WQUEUE_H
+
+/*! \defgroup write_queue Osmocom msgb write queues
+ *  @{
+ */
+
+/*! \file write_queue.h
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+
+/*! write queue instance */
+struct osmo_wqueue {
+	/*! \brief osmocom file descriptor */
+	struct osmo_fd bfd;
+	/*! \brief maximum length of write queue */
+	unsigned int max_length;
+	/*! \brief current length of write queue */
+	unsigned int current_length;
+
+	/*! \brief actual linked list implementing the queue */
+	struct llist_head msg_queue;
+
+	/*! \brief call-back in case qeueue is readable */
+	int (*read_cb)(struct osmo_fd *fd);
+	/*! \brief call-back in case qeueue is writable */
+	int (*write_cb)(struct osmo_fd *fd, struct msgb *msg);
+	/*! \brief call-back in case qeueue has exceptions */
+	int (*except_cb)(struct osmo_fd *fd);
+};
+
+void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length);
+void osmo_wqueue_clear(struct osmo_wqueue *queue);
+int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data);
+int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what);
+
+/*! @} */
+
+#endif
diff --git a/include/osmocom/crypt/Makefile.am b/include/osmocom/crypt/Makefile.am
new file mode 100644
index 0000000..e4a6e53
--- /dev/null
+++ b/include/osmocom/crypt/Makefile.am
@@ -0,0 +1,3 @@
+osmocrypt_HEADERS = gprs_cipher.h auth.h
+
+osmocryptdir = $(includedir)/osmocom/crypt
diff --git a/include/osmocom/crypt/auth.h b/include/osmocom/crypt/auth.h
new file mode 100644
index 0000000..67b3200
--- /dev/null
+++ b/include/osmocom/crypt/auth.h
@@ -0,0 +1,91 @@
+#ifndef _OSMOCRYPTO_AUTH_H
+#define _OSMOCRYPTO_AUTH_H
+
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+
+/*! \brief Authentication Type */
+enum osmo_sub_auth_type {
+	OSMO_AUTH_TYPE_NONE	= 0x00,
+	OSMO_AUTH_TYPE_GSM	= 0x01,
+	OSMO_AUTH_TYPE_UMTS	= 0x02,
+};
+
+/*! \brief Authentication Algorithm */
+enum osmo_auth_algo {
+	OSMO_AUTH_ALG_NONE,
+	OSMO_AUTH_ALG_COMP128v1,
+	OSMO_AUTH_ALG_COMP128v2,
+	OSMO_AUTH_ALG_COMP128v3,
+	OSMO_AUTH_ALG_XOR,
+	OSMO_AUTH_ALG_MILENAGE,
+	_OSMO_AUTH_ALG_NUM,
+};
+
+/*! \brief permanent (secret) subscriber auth data */
+struct osmo_sub_auth_data {
+	enum osmo_sub_auth_type type;
+	enum osmo_auth_algo algo;
+	union {
+		struct {
+			uint8_t opc[16];
+			uint8_t k[16];
+			uint8_t amf[2];
+			uint64_t sqn;
+			int opc_is_op;
+		} umts;
+		struct {
+			uint8_t ki[16];
+		} gsm;
+	} u;
+};
+
+/* data structure describing a computed auth vector, generated by AuC */
+struct osmo_auth_vector {
+	uint8_t rand[16];
+	uint8_t autn[16];
+	uint8_t ck[16];
+	uint8_t ik[16];
+	uint8_t res[16];
+	uint8_t res_len;
+	uint8_t kc[8];
+	uint8_t sres[4];
+	uint32_t auth_types;	/*!< bitmask of OSMO_AUTH_TYPE_* */
+};
+
+/* \brief An implementation of an authentication algorithm */
+struct osmo_auth_impl {
+	struct llist_head list;
+	enum osmo_auth_algo algo;
+	const char *name;
+	unsigned int priority;
+
+	int (*gen_vec)(struct osmo_auth_vector *vec,
+			struct osmo_sub_auth_data *aud,
+			const uint8_t *_rand);
+
+	int (*gen_vec_auts)(struct osmo_auth_vector *vec,
+			    struct osmo_sub_auth_data *aud,
+			    const uint8_t *rand_auts, const uint8_t *auts,
+			    const uint8_t *_rand);
+};
+
+int osmo_auth_gen_vec(struct osmo_auth_vector *vec,
+		      struct osmo_sub_auth_data *aud, const uint8_t *_rand);
+
+int osmo_auth_gen_vec_auts(struct osmo_auth_vector *vec,
+			   struct osmo_sub_auth_data *aud,
+			   const uint8_t *rand_auts, const uint8_t *auts,
+			   const uint8_t *_rand);
+
+int osmo_auth_register(struct osmo_auth_impl *impl);
+
+int osmo_auth_load(const char *path);
+
+int osmo_auth_supported(enum osmo_auth_algo algo);
+
+const char *osmo_auth_alg_name(enum osmo_auth_algo alg);
+enum osmo_auth_algo osmo_auth_alg_parse(const char *name);
+
+#endif /* _OSMOCRYPTO_AUTH_H */
diff --git a/include/osmocom/crypt/gprs_cipher.h b/include/osmocom/crypt/gprs_cipher.h
new file mode 100644
index 0000000..3051071
--- /dev/null
+++ b/include/osmocom/crypt/gprs_cipher.h
@@ -0,0 +1,54 @@
+#ifndef _GPRS_CIPHER_H
+#define _GPRS_CIPHER_H
+
+#include <osmocom/core/linuxlist.h>
+
+#define GSM0464_CIPH_MAX_BLOCK	1523
+
+enum gprs_ciph_algo {
+	GPRS_ALGO_GEA0,
+	GPRS_ALGO_GEA1,
+	GPRS_ALGO_GEA2,
+	GPRS_ALGO_GEA3,
+	_GPRS_ALGO_NUM
+};
+
+enum gprs_cipher_direction {
+	GPRS_CIPH_MS2SGSN,
+	GPRS_CIPH_SGSN2MS,
+};
+
+/* An implementation of a GPRS cipher */
+struct gprs_cipher_impl {
+	struct llist_head list;
+	enum gprs_ciph_algo algo;
+	const char *name;
+	unsigned int priority;
+
+	/* As specified in 04.64 Annex A.  Uses Kc, IV and direction
+	 * to generate the 1523 bytes cipher stream that need to be
+	 * XORed wit the plaintext for encrypt / ciphertext for decrypt */
+	int (*run)(uint8_t *out, uint16_t len, uint64_t kc, uint32_t iv,
+		   enum gprs_cipher_direction direction);
+};
+
+/* register a cipher with the core (from a plugin) */
+int gprs_cipher_register(struct gprs_cipher_impl *ciph);
+
+/* load all available GPRS cipher plugins */
+int gprs_cipher_load(const char *path);
+
+/* function to be called by core code */
+int gprs_cipher_run(uint8_t *out, uint16_t len, enum gprs_ciph_algo algo,
+		    uint64_t kc, uint32_t iv, enum gprs_cipher_direction dir);
+
+/* Do we have an implementation for this cipher? */
+int gprs_cipher_supported(enum gprs_ciph_algo algo);
+
+/* GSM TS 04.64 / Section A.2.1 : Generation of 'input' */
+uint32_t gprs_cipher_gen_input_ui(uint32_t iov_ui, uint8_t sapi, uint32_t lfn, uint32_t oc);
+
+/* GSM TS 04.64 / Section A.2.1 : Generation of 'input' */
+uint32_t gprs_cipher_gen_input_i(uint32_t iov_i, uint32_t lfn, uint32_t oc);
+
+#endif /* _GPRS_CIPHER_H */
diff --git a/include/osmocom/gsm/Makefile.am b/include/osmocom/gsm/Makefile.am
new file mode 100644
index 0000000..fc1abfe
--- /dev/null
+++ b/include/osmocom/gsm/Makefile.am
@@ -0,0 +1,8 @@
+osmogsm_HEADERS = a5.h comp128.h gsm0808.h gsm48_ie.h mncc.h rxlev_stat.h \
+		  gsm0480.h gsm48.h gsm_utils.h rsl.h tlv.h abis_nm.h \
+		  sysinfo.h prim.h gsm0502.h lapd_core.h lapdm.h \
+		  gsm0411_utils.h gsm0411_smc.h gsm0411_smr.h
+
+SUBDIRS = protocol
+
+osmogsmdir = $(includedir)/osmocom/gsm
diff --git a/include/osmocom/gsm/a5.h b/include/osmocom/gsm/a5.h
new file mode 100644
index 0000000..649dbab
--- /dev/null
+++ b/include/osmocom/gsm/a5.h
@@ -0,0 +1,63 @@
+/*
+ * a5.h
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __OSMO_A5_H__
+#define __OSMO_A5_H__
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+
+/*! \defgroup a5 GSM A5 ciphering algorithm
+ *  @{
+ */
+
+/*! \file gsm/a5.h
+ *  \brief Osmocom GSM A5 ciphering algorithm header
+ */
+
+/*! \brief Converts a frame number into the 22 bit number used in A5/x
+ *  \param[in] fn The true framenumber
+ *  \return 22 bit word
+ */
+static inline uint32_t
+osmo_a5_fn_count(uint32_t fn)
+{
+	int t1 = fn / (26 * 51);
+	int t2 = fn % 26;
+	int t3 = fn % 51;
+	return (t1 << 11) | (t3 << 5) | t2;
+}
+
+	/* Notes:
+	 *  - key must be 8 bytes long (or NULL for A5/0)
+	 *  - the dl and ul pointer must be either NULL or 114 bits long
+	 *  - fn is the _real_ GSM frame number.
+	 *    (converted internally to fn_count)
+	 */
+void osmo_a5(int n, const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul);
+void osmo_a5_1(const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul);
+void osmo_a5_2(const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul);
+
+/*! @} */
+
+#endif /* __OSMO_A5_H__ */
diff --git a/include/osmocom/gsm/abis_nm.h b/include/osmocom/gsm/abis_nm.h
new file mode 100644
index 0000000..cc01765
--- /dev/null
+++ b/include/osmocom/gsm/abis_nm.h
@@ -0,0 +1,40 @@
+#ifndef _OSMO_GSM_ABIS_NM_H
+#define _OSMO_GSM_ABIS_NM_H
+
+/*! \defgroup oml A-bis OML
+ *  @{
+ */
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+/*! \file abis_nm.h */
+
+enum abis_nm_msgtype;
+enum gsm_phys_chan_config;
+
+const enum abis_nm_msgtype abis_nm_reports[4];
+const enum abis_nm_msgtype abis_nm_no_ack_nack[3];
+const enum abis_nm_msgtype abis_nm_sw_load_msgs[9];
+const enum abis_nm_msgtype abis_nm_nacks[33];
+
+extern const struct value_string abis_nm_obj_class_names[];
+extern const struct value_string abis_nm_adm_state_names[];
+
+const char *abis_nm_nack_cause_name(uint8_t cause);
+const char *abis_nm_nack_name(uint8_t nack);
+const char *abis_nm_event_type_name(uint8_t cause);
+const char *abis_nm_severity_name(uint8_t cause);
+const struct tlv_definition abis_nm_att_tlvdef;
+const char *abis_nm_opstate_name(uint8_t os);
+const char *abis_nm_avail_name(uint8_t avail);
+const char *abis_nm_test_name(uint8_t test);
+void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh);
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan);
+enum abis_nm_chan_comb abis_nm_pchan4chcomb(uint8_t chcomb);
+
+/*! @} */
+
+#endif /* _OSMO_GSM_ABIS_NM_H */
diff --git a/include/osmocom/gsm/comp128.h b/include/osmocom/gsm/comp128.h
new file mode 100644
index 0000000..e4587d4
--- /dev/null
+++ b/include/osmocom/gsm/comp128.h
@@ -0,0 +1,22 @@
+/*
+ * COMP128 header
+ *
+ * See comp128.c for details
+ */
+
+#ifndef __COMP128_H__
+#define __COMP128_H__
+
+#include <stdint.h>
+
+/*
+ * Performs the COMP128 algorithm (used as A3/A8)
+ * ki    : uint8_t [16]
+ * srand : uint8_t [16]
+ * sres  : uint8_t [4]
+ * kc    : uint8_t [8]
+ */
+void comp128(const uint8_t *ki, const uint8_t *srand, uint8_t *sres, uint8_t *kc);
+
+#endif /* __COMP128_H__ */
+
diff --git a/include/osmocom/gsm/gsm0411_smc.h b/include/osmocom/gsm/gsm0411_smc.h
new file mode 100644
index 0000000..e1508a2
--- /dev/null
+++ b/include/osmocom/gsm/gsm0411_smc.h
@@ -0,0 +1,62 @@
+#ifndef _GSM0411_SMC_H
+#define _GSM0411_SMC_H
+
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#define GSM411_MMSMS_EST_REQ		0x310
+#define GSM411_MMSMS_EST_IND		0x312
+#define GSM411_MMSMS_EST_CNF		0x311
+#define GSM411_MMSMS_REL_REQ		0x320
+#define GSM411_MMSMS_REL_IND		0x322
+#define GSM411_MMSMS_DATA_REQ		0x330
+#define GSM411_MMSMS_DATA_IND		0x332
+#define GSM411_MMSMS_UNIT_DATA_REQ	0x340
+#define GSM411_MMSMS_UNIT_DATA_IND	0x342
+#define GSM411_MMSMS_ERR_IND		0x372
+
+#define GSM411_MNSMS_ABORT_REQ		0x101
+#define GSM411_MNSMS_DATA_REQ		0x102
+#define GSM411_MNSMS_DATA_IND		0x103
+#define GSM411_MNSMS_EST_REQ		0x104
+#define GSM411_MNSMS_EST_IND		0x105
+#define GSM411_MNSMS_ERROR_IND		0x106
+#define GSM411_MNSMS_REL_REQ		0x107
+
+struct gsm411_smc_inst {
+	int network;		/* is this a MO (0) or MT (1) transfer */
+	int (*mn_recv) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg);
+	int (*mm_send) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg, int cp_msg_type);
+
+	enum gsm411_cp_state cp_state;
+	struct osmo_timer_list cp_timer;
+	struct msgb *cp_msg;	/* store pending message */
+	int cp_rel;		/* store pending release */
+	int cp_retx;		/* retry counter */
+	int cp_max_retr;	/* maximum retry */
+	int cp_tc1;		/* timer value TC1* */
+
+};
+
+extern const struct value_string gsm411_cp_cause_strs[];
+
+/* init a new instance */
+void gsm411_smc_init(struct gsm411_smc_inst *inst, int network,
+	int (*mn_recv) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg),
+	int (*mm_send) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg, int cp_msg_type));
+
+/* clear instance */
+void gsm411_smc_clear(struct gsm411_smc_inst *inst);
+
+/* message from upper layer */
+int gsm411_smc_send(struct gsm411_smc_inst *inst, int msg_type,
+	struct msgb *msg);
+
+/* message from lower layer */
+int gsm411_smc_recv(struct gsm411_smc_inst *inst, int msg_type,
+	struct msgb *msg, int cp_msg_type);
+
+#endif /* _GSM0411_SMC_H */
diff --git a/include/osmocom/gsm/gsm0411_smr.h b/include/osmocom/gsm/gsm0411_smr.h
new file mode 100644
index 0000000..5ea8584
--- /dev/null
+++ b/include/osmocom/gsm/gsm0411_smr.h
@@ -0,0 +1,45 @@
+#ifndef _GSM0411_SMR_H
+#define _GSM0411_SMR_H
+
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#define GSM411_SM_RL_DATA_REQ		0x401
+#define GSM411_SM_RL_DATA_IND		0x402
+#define GSM411_SM_RL_MEM_AVAIL_REQ	0x403
+#define GSM411_SM_RL_MEM_AVAIL_IND	0x404
+#define GSM411_SM_RL_REPORT_REQ		0x405
+#define GSM411_SM_RL_REPORT_IND		0x406
+
+struct gsm411_smr_inst {
+	int network;		/* is this a MO (0) or MT (1) transfer */
+	int (*rl_recv) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg);
+	int (*mn_send) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg);
+
+	enum gsm411_rp_state rp_state;
+	struct osmo_timer_list rp_timer;
+};
+
+extern const struct value_string gsm411_rp_cause_strs[];
+
+/* init a new instance */
+void gsm411_smr_init(struct gsm411_smr_inst *inst, int network,
+	int (*rl_recv) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg),
+	int (*mn_send) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg));
+
+/* clear instance */
+void gsm411_smr_clear(struct gsm411_smr_inst *inst);
+
+/* message from upper layer */
+int gsm411_smr_send(struct gsm411_smr_inst *inst, int msg_type,
+	struct msgb *msg);
+
+/* message from lower layer */
+int gsm411_smr_recv(struct gsm411_smr_inst *inst, int msg_type,
+	struct msgb *msg);
+
+#endif /* _GSM0411_SMR_H */
+
diff --git a/include/osmocom/gsm/gsm0411_utils.h b/include/osmocom/gsm/gsm0411_utils.h
new file mode 100644
index 0000000..d29ca63
--- /dev/null
+++ b/include/osmocom/gsm/gsm0411_utils.h
@@ -0,0 +1,36 @@
+#ifndef _GSM0411_UTILS_H
+#define _GSM0411_UTILS_H
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+uint8_t gsm411_bcdify(uint8_t value);
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+uint8_t gsm411_unbcdify(uint8_t value);
+
+struct msgb *gsm411_msgb_alloc(void);
+
+/* Generate 03.40 TP-SCTS */
+void gsm340_gen_scts(uint8_t *scts, time_t time);
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+time_t gsm340_scts(uint8_t *scts);
+
+/* decode validity period. return minutes */
+unsigned long gsm340_validity_period(uint8_t sms_vpf, uint8_t *sms_vp);
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs);
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type,
+	uint8_t plan, const char *number);
+
+/* Prefix msg with a RP header */
+int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type,
+	uint8_t rp_msg_ref);
+
+/* Prefix msg with a 04.08/04.11 CP header */
+int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans,
+			     uint8_t msg_type);
+
+#endif /* _GSM0411_UTILS_H */
diff --git a/include/osmocom/gsm/gsm0480.h b/include/osmocom/gsm/gsm0480.h
new file mode 100644
index 0000000..f6c3734
--- /dev/null
+++ b/include/osmocom/gsm/gsm0480.h
@@ -0,0 +1,26 @@
+#ifndef gsm0480_h
+#define gsm0480_h
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_04_80.h>
+
+#define MAX_LEN_USSD_STRING	31
+
+struct ussd_request {
+	uint8_t text[MAX_LEN_USSD_STRING + 1];
+	uint8_t transaction_id;
+	uint8_t invoke_id;
+};
+
+int gsm0480_decode_ussd_request(const struct gsm48_hdr *hdr, uint16_t len,
+				struct ussd_request *request);
+
+struct msgb *gsm0480_create_ussd_resp(uint8_t invoke_id, uint8_t trans_id, const char *text);
+struct msgb *gsm0480_create_unstructuredSS_Notify(int alertPattern, const char *text);
+struct msgb *gsm0480_create_notifySS(const char *text);
+
+int gsm0480_wrap_invoke(struct msgb *msg, int op, int link_id);
+int gsm0480_wrap_facility(struct msgb *msg);
+
+#endif
diff --git a/include/osmocom/gsm/gsm0502.h b/include/osmocom/gsm/gsm0502.h
new file mode 100644
index 0000000..46b629e
--- /dev/null
+++ b/include/osmocom/gsm/gsm0502.h
@@ -0,0 +1,38 @@
+#ifndef OSMOCOM_GSM_0502_H
+#define OSMOCOM_GSM_0502_H
+
+#include <stdint.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+/* Table 5 Clause 7 TS 05.02 */
+static inline unsigned int
+gsm0502_get_n_pag_blocks(struct gsm48_control_channel_descr *chan_desc)
+{
+	if (chan_desc->ccch_conf == RSL_BCCH_CCCH_CONF_1_C)
+		return 3 - chan_desc->bs_ag_blks_res;
+	else
+		return 9 - chan_desc->bs_ag_blks_res;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+static inline unsigned int
+gsm0502_get_ccch_group(uint64_t imsi, unsigned int bs_cc_chans,
+			unsigned int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) / n_pag_blocks;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+static inline unsigned int
+gsm0502_get_paging_group(uint64_t imsi, unsigned int bs_cc_chans,
+			 int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) % n_pag_blocks;
+}
+
+unsigned int
+gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi);
+
+#endif
diff --git a/include/osmocom/gsm/gsm0808.h b/include/osmocom/gsm/gsm0808.h
new file mode 100644
index 0000000..5380dd9
--- /dev/null
+++ b/include/osmocom/gsm/gsm0808.h
@@ -0,0 +1,50 @@
+/* (C) 2009,2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009,2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+#ifndef OSMOCORE_GSM0808_H
+#define OSMOCORE_GSM0808_H
+
+#include "tlv.h"
+
+struct msgb;
+
+struct msgb *gsm0808_create_layer3(struct msgb *msg, uint16_t netcode, uint16_t countrycode, int lac, uint16_t ci);
+struct msgb *gsm0808_create_reset(void);
+struct msgb *gsm0808_create_clear_command(uint8_t reason);
+struct msgb *gsm0808_create_clear_complete(void);
+struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id);
+struct msgb *gsm0808_create_cipher_reject(uint8_t cause);
+struct msgb *gsm0808_create_classmark_update(const uint8_t *cm2, uint8_t cm2_len,
+					     const uint8_t *cm3, uint8_t cm3_len);
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id);
+struct msgb *gsm0808_create_assignment_completed(uint8_t rr_cause,
+						 uint8_t chosen_channel, uint8_t encr_alg_id,
+						 uint8_t speech_mode);
+struct msgb *gsm0808_create_assignment_failure(uint8_t cause, uint8_t *rr_cause);
+struct msgb *gsm0808_create_clear_rqst(uint8_t cause);
+
+struct msgb *gsm0808_create_dtap(struct msgb *msg, uint8_t link_id);
+void gsm0808_prepend_dtap_header(struct msgb *msg, uint8_t link_id);
+
+const struct tlv_definition *gsm0808_att_tlvdef(void);
+
+const char *gsm0808_bssmap_name(uint8_t msg_type);
+const char *gsm0808_bssap_name(uint8_t msg_type);
+
+#endif
diff --git a/include/osmocom/gsm/gsm48.h b/include/osmocom/gsm/gsm48.h
new file mode 100644
index 0000000..16a625a
--- /dev/null
+++ b/include/osmocom/gsm/gsm48.h
@@ -0,0 +1,38 @@
+#ifndef _OSMOCORE_GSM48_H
+#define _OSMOCORE_GSM48_H
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48_ie.h>
+
+/* A parsed GPRS routing area */
+struct gprs_ra_id {
+	uint16_t	mnc;
+	uint16_t	mcc;
+	uint16_t	lac;
+	uint8_t		rac;
+};
+
+extern const struct tlv_definition gsm48_att_tlvdef;
+extern const struct tlv_definition gsm48_rr_att_tlvdef;
+extern const struct tlv_definition gsm48_mm_att_tlvdef;
+const char *gsm48_cc_state_name(uint8_t state);
+const char *gsm48_cc_msg_name(uint8_t msgtype);
+const char *rr_cause_name(uint8_t cause);
+
+void gsm48_generate_lai(struct gsm48_loc_area_id *lai48, uint16_t mcc,
+			uint16_t mnc, uint16_t lac);
+int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi);
+int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi);
+
+/* Convert Mobile Identity (10.5.1.4) to string */
+int gsm48_mi_to_string(char *string, const int str_len,
+			const uint8_t *mi, const int mi_len);
+
+/* Parse Routeing Area Identifier */
+void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf);
+int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid);
+
+int gsm48_number_of_paging_subchannels(struct gsm48_control_channel_descr *chan_desc);
+
+#endif
diff --git a/include/osmocom/gsm/gsm48_ie.h b/include/osmocom/gsm/gsm48_ie.h
new file mode 100644
index 0000000..f4fce25
--- /dev/null
+++ b/include/osmocom/gsm/gsm48_ie.h
@@ -0,0 +1,117 @@
+#ifndef _OSMOCORE_GSM48_IE_H
+#define _OSMOCORE_GSM48_IE_H
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+/* decode a 'called/calling/connect party BCD number' as in 10.5.4.7 */
+int gsm48_decode_bcd_number(char *output, int output_len,
+			    const uint8_t *bcd_lv, int h_len);
+
+/* convert a ASCII phone number to 'called/calling/connect party BCD number' */
+int gsm48_encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len,
+			    int h_len, const char *input);
+/* decode 'bearer capability' */
+int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
+			     const uint8_t *lv);
+/* encode 'bearer capability' */
+int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
+			     const struct gsm_mncc_bearer_cap *bcap);
+/* decode 'call control cap' */
+int gsm48_decode_cccap(struct gsm_mncc_cccap *ccap, const uint8_t *lv);
+/* encode 'call control cap' */
+int gsm48_encode_cccap(struct msgb *msg,
+			const struct gsm_mncc_cccap *ccap);
+/* decode 'called party BCD number' */
+int gsm48_decode_called(struct gsm_mncc_number *called,
+			 const uint8_t *lv);
+/* encode 'called party BCD number' */
+int gsm48_encode_called(struct msgb *msg,
+			 const struct gsm_mncc_number *called);
+/* decode callerid of various IEs */
+int gsm48_decode_callerid(struct gsm_mncc_number *callerid,
+			 const uint8_t *lv);
+/* encode callerid of various IEs */
+int gsm48_encode_callerid(struct msgb *msg, int ie, int max_len,
+			   const struct gsm_mncc_number *callerid);
+/* decode 'cause' */
+int gsm48_decode_cause(struct gsm_mncc_cause *cause,
+			const uint8_t *lv);
+/* encode 'cause' */
+int gsm48_encode_cause(struct msgb *msg, int lv_only,
+			const struct gsm_mncc_cause *cause);
+/* decode 'calling number' */
+int gsm48_decode_calling(struct gsm_mncc_number *calling,
+			 const uint8_t *lv);
+/* encode 'calling number' */
+int gsm48_encode_calling(struct msgb *msg, 
+			  const struct gsm_mncc_number *calling);
+/* decode 'connected number' */
+int gsm48_decode_connected(struct gsm_mncc_number *connected,
+			 const uint8_t *lv);
+/* encode 'connected number' */
+int gsm48_encode_connected(struct msgb *msg,
+			    const struct gsm_mncc_number *connected);
+/* decode 'redirecting number' */
+int gsm48_decode_redirecting(struct gsm_mncc_number *redirecting,
+			 const uint8_t *lv);
+/* encode 'redirecting number' */
+int gsm48_encode_redirecting(struct msgb *msg,
+			      const struct gsm_mncc_number *redirecting);
+/* decode 'facility' */
+int gsm48_decode_facility(struct gsm_mncc_facility *facility,
+			   const uint8_t *lv);
+/* encode 'facility' */
+int gsm48_encode_facility(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_facility *facility);
+/* decode 'notify' */
+int gsm48_decode_notify(int *notify, const uint8_t *v);
+/* encode 'notify' */
+int gsm48_encode_notify(struct msgb *msg, int notify);
+/* decode 'signal' */
+int gsm48_decode_signal(int *signal, const uint8_t *v);
+/* encode 'signal' */
+int gsm48_encode_signal(struct msgb *msg, int signal);
+/* decode 'keypad' */
+int gsm48_decode_keypad(int *keypad, const uint8_t *lv);
+/* encode 'keypad' */
+int gsm48_encode_keypad(struct msgb *msg, int keypad);
+/* decode 'progress' */
+int gsm48_decode_progress(struct gsm_mncc_progress *progress,
+			   const uint8_t *lv);
+/* encode 'progress' */
+int gsm48_encode_progress(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_progress *p);
+/* decode 'user-user' */
+int gsm48_decode_useruser(struct gsm_mncc_useruser *uu,
+			   const uint8_t *lv);
+/* encode 'useruser' */
+int gsm48_encode_useruser(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_useruser *uu);
+/* decode 'ss version' */
+int gsm48_decode_ssversion(struct gsm_mncc_ssversion *ssv,
+			    const uint8_t *lv);
+/* encode 'ss version' */
+int gsm48_encode_ssversion(struct msgb *msg,
+			   const struct gsm_mncc_ssversion *ssv);
+/* decode 'more data' does not require a function, because it has no value */
+/* encode 'more data' */
+int gsm48_encode_more(struct msgb *msg);
+
+/* structure of one frequency */
+struct gsm_sysinfo_freq {
+	/* if the frequency included in the sysinfo */
+	uint8_t	mask;
+};
+
+/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */
+int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd,
+			   uint8_t len, uint8_t mask, uint8_t frqt);
+
+#endif
diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h
new file mode 100644
index 0000000..6d31672
--- /dev/null
+++ b/include/osmocom/gsm/gsm_utils.h
@@ -0,0 +1,151 @@
+/* GSM utility functions, e.g. coding and decoding */
+/*
+ * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef GSM_UTILS_H
+#define GSM_UTILS_H
+
+#include <stdint.h>
+
+#define ADD_MODULO(sum, delta, modulo) do {	\
+	if ((sum += delta) >= modulo)		\
+		sum -= modulo;			\
+	} while (0)
+
+#define GSM_MAX_FN	(26*51*2048)
+
+struct gsm_time {
+	uint32_t	fn;	/* FN count */
+	uint16_t	t1;	/* FN div (26*51) */
+	uint8_t		t2;	/* FN modulo 26 */
+	uint8_t		t3;	/* FN modulo 51 */
+	uint8_t		tc;
+};
+
+enum gsm_band {
+	GSM_BAND_850	= 1,
+	GSM_BAND_900	= 2,
+	GSM_BAND_1800	= 4,
+	GSM_BAND_1900	= 8,
+	GSM_BAND_450	= 0x10,
+	GSM_BAND_480	= 0x20,
+	GSM_BAND_750	= 0x40,
+	GSM_BAND_810	= 0x80,
+};
+
+const char *gsm_band_name(enum gsm_band band);
+enum gsm_band gsm_band_parse(const char *mhz);
+
+int gsm_7bit_decode(char *decoded, const uint8_t *user_data, uint8_t length);
+int gsm_7bit_decode_hdr(char *decoded, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind);
+int gsm_7bit_encode(uint8_t *result, const char *data);
+
+int gsm_septets2octets(uint8_t *result, uint8_t *rdata, uint8_t septet_len, uint8_t padding);
+int gsm_septet_encode(uint8_t *result, const char *data);
+uint8_t gsm_get_octet_len(const uint8_t sept_len);
+
+unsigned int ms_class_gmsk_dbm(enum gsm_band band, int ms_class);
+
+int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm);
+int ms_pwr_dbm(enum gsm_band band, uint8_t lvl);
+
+/* According to TS 08.05 Chapter 8.1.4 */
+int rxlev2dbm(uint8_t rxlev);
+uint8_t dbm2rxlev(int dbm);
+
+/* According to GSM 04.08 Chapter 10.5.1.6 */
+static inline int ms_cm2_a5n_support(uint8_t *cm2, int n) {
+	switch (n) {
+		case 0: return 1;
+		case 1: return (cm2[0] & (1<<3)) ? 0 : 1;
+		case 2: return (cm2[2] & (1<<0)) ? 1 : 0;
+		case 3: return (cm2[2] & (1<<1)) ? 1 : 0;
+		default:
+			return 0;
+	}
+}
+
+/* According to GSM 04.08 Chapter 10.5.2.29 */
+static inline int rach_max_trans_val2raw(int val) { return (val >> 1) & 3; }
+static inline int rach_max_trans_raw2val(int raw) {
+	const int tbl[4] = { 1, 2, 4, 7 };
+	return tbl[raw & 3];
+}
+
+#define	ARFCN_PCS	0x8000
+#define	ARFCN_UPLINK	0x4000
+#define	ARFCN_FLAG_MASK	0xf000	/* Reserve the upper 5 bits for flags */
+
+enum gsm_band gsm_arfcn2band(uint16_t arfcn);
+
+/* Convert an ARFCN to the frequency in MHz * 10 */
+uint16_t gsm_arfcn2freq10(uint16_t arfcn, int uplink);
+
+/* Convert from frame number to GSM time */
+void gsm_fn2gsmtime(struct gsm_time *time, uint32_t fn);
+
+/* Convert from GSM time to frame number */
+uint32_t gsm_gsmtime2fn(struct gsm_time *time);
+
+/* GSM TS 03.03 Chapter 2.6 */
+enum gprs_tlli_type {
+	TLLI_LOCAL,
+	TLLI_FOREIGN,
+	TLLI_RANDOM,
+	TLLI_AUXILIARY,
+	TLLI_RESERVED,
+};
+
+/* TS 03.03 Chapter 2.6 */
+int gprs_tlli_type(uint32_t tlli);
+
+uint32_t gprs_tmsi2tlli(uint32_t p_tmsi, enum gprs_tlli_type type);
+
+/* Osmocom internal, not part of any gsm spec */
+enum gsm_phys_chan_config {
+	GSM_PCHAN_NONE,
+	GSM_PCHAN_CCCH,
+	GSM_PCHAN_CCCH_SDCCH4,
+	GSM_PCHAN_TCH_F,
+	GSM_PCHAN_TCH_H,
+	GSM_PCHAN_SDCCH8_SACCH8C,
+	GSM_PCHAN_PDCH,		/* GPRS PDCH */
+	GSM_PCHAN_TCH_F_PDCH,	/* TCH/F if used, PDCH otherwise */
+	GSM_PCHAN_UNKNOWN,
+	_GSM_PCHAN_MAX
+};
+
+/* Osmocom internal, not part of any gsm spec */
+enum gsm_chan_t {
+	GSM_LCHAN_NONE,
+	GSM_LCHAN_SDCCH,
+	GSM_LCHAN_TCH_F,
+	GSM_LCHAN_TCH_H,
+	GSM_LCHAN_UNKNOWN,
+	GSM_LCHAN_CCCH,
+	GSM_LCHAN_PDTCH,
+	_GSM_LCHAN_MAX
+};
+
+
+#endif
diff --git a/include/osmocom/gsm/lapd_core.h b/include/osmocom/gsm/lapd_core.h
new file mode 100644
index 0000000..0f4e889
--- /dev/null
+++ b/include/osmocom/gsm/lapd_core.h
@@ -0,0 +1,171 @@
+#ifndef _OSMOCOM_LAPD_H
+#define _OSMOCOM_LAPD_H
+
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/prim.h>
+
+/*! \defgroup lapd LAPD implementation common part
+ *  @{
+ */
+
+/*! \file lapd.h */
+
+/* primitive related sutff */
+
+/*! \brief LAPD related primitives (L2<->L3 SAP)*/
+enum osmo_dl_prim {
+	PRIM_DL_UNIT_DATA,	/*!< \brief DL-UNIT-DATA */
+	PRIM_DL_DATA,		/*!< \brief DL-DATA */
+	PRIM_DL_EST,		/*!< \brief DL-ESTABLISH */
+	PRIM_DL_REL,		/*!< \brief DL-RLEEASE */
+	PRIM_DL_SUSP,		/*!< \brief DL-SUSPEND */
+	PRIM_DL_RES,		/*!< \brief DL-RESUME */
+	PRIM_DL_RECON,		/*!< \brief DL-RECONNECT */
+	PRIM_MDL_ERROR,		/*!< \brief MDL-ERROR */
+};
+
+/* Uses the same values as RLL, so no conversion for GSM is required. */
+#define MDL_CAUSE_T200_EXPIRED		0x01
+#define MDL_CAUSE_REEST_REQ		0x02
+#define MDL_CAUSE_UNSOL_UA_RESP		0x03
+#define MDL_CAUSE_UNSOL_DM_RESP		0x04
+#define MDL_CAUSE_UNSOL_DM_RESP_MF	0x05
+#define MDL_CAUSE_UNSOL_SPRV_RESP	0x06
+#define MDL_CAUSE_SEQ_ERR		0x07
+#define MDL_CAUSE_UFRM_INC_PARAM	0x08
+#define MDL_CAUSE_SFRM_INC_PARAM	0x09
+#define MDL_CAUSE_IFRM_INC_MBITS	0x0a
+#define MDL_CAUSE_IFRM_INC_LEN		0x0b
+#define MDL_CAUSE_FRM_UNIMPL		0x0c
+#define MDL_CAUSE_SABM_MF		0x0d
+#define MDL_CAUSE_SABM_INFO_NOTALL	0x0e
+#define MDL_CAUSE_FRMR			0x0f
+
+/*! \brief for MDL-ERROR.ind */
+struct mdl_error_ind_param {
+	uint8_t cause;		/*!< \brief generic cause value */
+};
+
+/*! \brief for DL-REL.req */
+struct dl_rel_req_param {
+	uint8_t mode;		/*!< \brief release mode */
+};
+
+/*! \brief primitive header for LAPD DL-SAP primitives */
+struct osmo_dlsap_prim {
+	struct osmo_prim_hdr oph; /*!< \brief generic primitive header */
+	union {
+		struct mdl_error_ind_param error_ind;
+		struct dl_rel_req_param rel_req;
+	} u;			/*!< \brief request-specific data */
+};
+
+/*! \brief LAPD mode/role */
+enum lapd_mode {
+	LAPD_MODE_USER,		/*!< \brief behave like user */
+	LAPD_MODE_NETWORK,	/*!< \brief behave like network */
+};
+
+/*! \brief LAPD state (Figure B.2/Q.921)*/
+enum lapd_state {
+	LAPD_STATE_NULL = 0,
+	LAPD_STATE_TEI_UNASS,
+	LAPD_STATE_ASS_TEI_WAIT,
+	LAPD_STATE_EST_TEI_WAIT,
+	LAPD_STATE_IDLE,
+	LAPD_STATE_SABM_SENT,
+	LAPD_STATE_DISC_SENT,
+	LAPD_STATE_MF_EST,
+	LAPD_STATE_TIMER_RECOV,
+};
+
+/*! \brief LAPD message format (I / S / U) */
+enum lapd_format {
+	LAPD_FORM_UKN = 0,
+	LAPD_FORM_I,
+	LAPD_FORM_S,
+	LAPD_FORM_U,
+};
+
+/*! \brief LAPD message context */
+struct lapd_msg_ctx {
+	struct lapd_datalink *dl;
+	int n201;
+	/* address */
+	uint8_t cr;
+	uint8_t sapi;
+	uint8_t tei;
+	uint8_t lpd;
+	/* control */
+	uint8_t format;
+	uint8_t p_f; /* poll / final bit */
+	uint8_t n_send;
+	uint8_t n_recv;
+	uint8_t s_u; /* S or repectivly U function bits */
+	/* length */
+	int	length;
+	uint8_t	more;
+};
+
+struct lapd_cr_ent {
+	uint8_t cmd;
+	uint8_t resp;
+};
+
+struct lapd_history {
+	struct msgb *msg; /* message to be sent / NULL, if histoy is empty */
+	int	more; /* if message is fragmented */
+};
+
+/*! \brief LAPD datalink */
+struct lapd_datalink {
+	int (*send_dlsap)(struct osmo_dlsap_prim *dp,
+	        struct lapd_msg_ctx *lctx);
+	int (*send_ph_data_req)(struct lapd_msg_ctx *lctx, struct msgb *msg);
+	struct {
+		/*! \brief filled-in once we set the lapd_mode above */
+		struct lapd_cr_ent loc2rem;
+		struct lapd_cr_ent rem2loc;
+	} cr;
+	enum lapd_mode mode; /*!< \brief current mode of link */
+	int use_sabme; /*!< \brief use SABME instead of SABM */
+	int reestablish; /*!< \brief enable reestablish support */
+	int n200, n200_est_rel; /*!< \brief number of retranmissions */
+	struct lapd_msg_ctx lctx; /*!< \brief LAPD context */
+	int maxf; /*!< \brief maximum frame size (after defragmentation) */ 
+	uint8_t k; /*!< \brief maximum number of unacknowledged frames */
+	uint8_t v_range; /*!< \brief range of sequence numbers */
+	uint8_t v_send;	/*!< \brief seq nr of next I frame to be transmitted */
+	uint8_t v_ack;	/*!< \brief last frame ACKed by peer */
+	uint8_t v_recv;	/*!< \brief seq nr of next I frame expected to be received */
+	uint32_t state; /*!< \brief LAPD state (\ref lapd_state) */
+	int seq_err_cond; /*!< \brief condition of sequence error */
+	uint8_t own_busy; /*!< \brief receiver busy on our side */
+	uint8_t peer_busy; /*!< \brief receiver busy on remote side */
+	int t200_sec, t200_usec; /*!< \brief retry timer (default 1 sec) */
+	int t203_sec, t203_usec; /*!< \brief retry timer (default 10 secs) */
+	struct osmo_timer_list t200; /*!< \brief T200 timer */
+	struct osmo_timer_list t203; /*!< \brief T203 timer */
+	uint8_t retrans_ctr; /*!< \brief re-transmission counter */
+	struct llist_head tx_queue; /*!< \brief frames to L1 */
+	struct llist_head send_queue; /*!< \brief frames from L3 */
+	struct msgb *send_buffer; /*!< \brief current frame transmitting */
+	int send_out; /*!< \brief how much was sent from send_buffer */
+	struct lapd_history *tx_hist; /*!< \brief tx history structure array */
+	uint8_t range_hist; /*!< \brief range of history buffer 2..2^n */
+	struct msgb *rcv_buffer; /*!< \brief buffer to assemble the received message */
+	struct msgb *cont_res; /*!< \brief buffer to store content resolution data on network side, to detect multiple phones on same channel */
+};
+
+void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range,
+	int maxf);
+void lapd_dl_exit(struct lapd_datalink *dl);
+void lapd_dl_reset(struct lapd_datalink *dl);
+int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode);
+int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx);
+int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
+
+#endif /* _OSMOCOM_LAPD_H */
diff --git a/include/osmocom/gsm/lapdm.h b/include/osmocom/gsm/lapdm.h
new file mode 100644
index 0000000..571fd46
--- /dev/null
+++ b/include/osmocom/gsm/lapdm.h
@@ -0,0 +1,162 @@
+#ifndef _OSMOCOM_LAPDM_H
+#define _OSMOCOM_LAPDM_H
+
+#include <osmocom/gsm/lapd_core.h>
+
+/*! \defgroup lapdm LAPDm implementation according to GSM TS 04.06
+ *  @{
+ */
+
+/*! \file lapdm.h */
+
+/* primitive related sutff */
+
+/*! \brief LAPDm related primitives (L1<->L2 SAP) */
+enum osmo_ph_prim {
+	PRIM_PH_DATA,		/*!< \brief PH-DATA */
+	PRIM_PH_RACH,		/*!< \brief PH-RANDOM_ACCESS */
+	PRIM_PH_CONN,		/*!< \brief PH-CONNECT */
+	PRIM_PH_EMPTY_FRAME,	/*!< \brief PH-EMPTY_FRAME */
+	PRIM_PH_RTS,		/*!< \brief PH-RTS */
+};
+
+/*! \brief for PH-RANDOM_ACCESS.req */
+struct ph_rach_req_param {
+	uint8_t ra;		/*!< \brief Random Access */
+	uint8_t ta;		/*!< \brief Timing Advance */
+	uint8_t tx_power;	/*!< \brief Transmit Power */
+	uint8_t is_combined_ccch;/*!< \brief Are we using a combined CCCH? */
+	uint16_t offset;	/*!< \brief Timing Offset */
+};
+
+/*! \brief for PH-RANDOM_ACCESS.ind */
+struct ph_rach_ind_param {
+	uint8_t ra;		/*!< \brief Random Access */
+	uint8_t acc_delay;	/*!< \brief Delay in bit periods */
+	uint32_t fn;		/*!< \brief GSM Frame Number at time of RA */
+};
+
+/*! \brief for PH-[UNIT]DATA.{req,ind} */
+struct ph_data_param {
+	uint8_t link_id;	/*!< \brief Link Identifier (Like RSL) */
+	uint8_t chan_nr;	/*!< \brief Channel Number (Like RSL) */
+};
+
+/*! \brief for PH-CONN.ind */
+struct ph_conn_ind_param {
+	uint32_t fn;		/*!< \brief GSM Frame Number */
+};
+
+/*! \brief primitive header for LAPDm PH-SAP primitives */
+struct osmo_phsap_prim {
+	struct osmo_prim_hdr oph; /*!< \brief generic primitive header */
+	union {
+		struct ph_data_param data;
+		struct ph_rach_req_param rach_req;
+		struct ph_rach_ind_param rach_ind;
+		struct ph_conn_ind_param conn_ind;
+	} u;			/*!< \brief request-specific data */
+};
+
+/*! \brief LAPDm mode/role */
+enum lapdm_mode {
+	LAPDM_MODE_MS,		/*!< \brief behave like a MS (mobile phone) */
+	LAPDM_MODE_BTS,		/*!< \brief behave like a BTS (network) */
+};
+
+struct lapdm_entity;
+
+/*! \brief LAPDm message context */
+struct lapdm_msg_ctx {
+	struct lapdm_datalink *dl;
+	int lapdm_fmt;
+	uint8_t chan_nr;
+	uint8_t link_id;
+	uint8_t ta_ind;		/* TA indicated by network */
+	uint8_t tx_power_ind;	/* MS power indicated by network */
+};
+
+/*! \brief LAPDm datalink like TS 04.06 / Section 3.5.2 */
+struct lapdm_datalink {
+	struct lapd_datalink dl; /* \brief common LAPD */
+	struct lapdm_msg_ctx mctx; /*!< \brief context of established connection */
+
+	struct lapdm_entity *entity; /*!< \brief LAPDm entity we are part of */
+};
+
+/*! \brief LAPDm datalink SAPIs */
+enum lapdm_dl_sapi {
+	DL_SAPI0	= 0,	/*!< \brief SAPI 0 */
+	DL_SAPI3	= 1,	/*!< \brief SAPI 1 */
+	_NR_DL_SAPI
+};
+
+typedef int (*lapdm_cb_t)(struct msgb *msg, struct lapdm_entity *le, void *ctx);
+
+#define LAPDM_ENT_F_EMPTY_FRAME		0x0001
+#define LAPDM_ENT_F_POLLING_ONLY	0x0002
+
+/*! \brief a LAPDm Entity */
+struct lapdm_entity {
+	/*! \brief the SAPIs of the LAPDm entity */
+	struct lapdm_datalink datalink[_NR_DL_SAPI];
+	int last_tx_dequeue; /*!< \brief last entity that was dequeued */
+	int tx_pending; /*!< \brief currently a pending frame not confirmed by L1 */
+	enum lapdm_mode mode; /*!< \brief are we in BTS mode or MS mode */
+	unsigned int flags;
+
+	void *l1_ctx;	/*!< \brief context for layer1 instance */
+	void *l3_ctx;	/*!< \brief context for layer3 instance */
+
+	osmo_prim_cb l1_prim_cb;/*!< \brief callback for sending prims to L1 */
+	lapdm_cb_t l3_cb;	/*!< \brief callback for sending stuff to L3 */
+
+	/*! \brief pointer to \ref lapdm_channel of which we're part */
+	struct lapdm_channel *lapdm_ch;
+
+	uint8_t ta;		/* TA used and indicated to network */
+	uint8_t tx_power;	/* MS power used and indicated to network */
+};
+
+/*! \brief the two lapdm_entities that form a GSM logical channel (ACCH + DCCH) */
+struct lapdm_channel {
+	struct llist_head list;		/*!< \brief internal linked list */
+	char *name;			/*!< \brief human-readable name */
+	struct lapdm_entity lapdm_acch;	/*!< \brief Associated Control Channel */
+	struct lapdm_entity lapdm_dcch;	/*!< \brief Dedicated Control Channel */
+};
+
+const char *get_rsl_name(int value);
+extern const char *lapdm_state_names[];
+
+/* initialize a LAPDm entity */
+void lapdm_entity_init(struct lapdm_entity *le, enum lapdm_mode mode, int t200);
+void lapdm_channel_init(struct lapdm_channel *lc, enum lapdm_mode mode);
+
+/* deinitialize a LAPDm entity */
+void lapdm_entity_exit(struct lapdm_entity *le);
+void lapdm_channel_exit(struct lapdm_channel *lc);
+
+/* input into layer2 (from layer 1) */
+int lapdm_phsap_up(struct osmo_prim_hdr *oph, struct lapdm_entity *le);
+
+/* input into layer2 (from layer 3) */
+int lapdm_rslms_recvmsg(struct msgb *msg, struct lapdm_channel *lc);
+
+void lapdm_channel_set_l3(struct lapdm_channel *lc, lapdm_cb_t cb, void *ctx);
+void lapdm_channel_set_l1(struct lapdm_channel *lc, osmo_prim_cb cb, void *ctx);
+
+int lapdm_entity_set_mode(struct lapdm_entity *le, enum lapdm_mode mode);
+int lapdm_channel_set_mode(struct lapdm_channel *lc, enum lapdm_mode mode);
+
+void lapdm_entity_reset(struct lapdm_entity *le);
+void lapdm_channel_reset(struct lapdm_channel *lc);
+
+void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags);
+void lapdm_channel_set_flags(struct lapdm_channel *lc, unsigned int flags);
+
+int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp);
+
+/*! @} */
+
+#endif /* _OSMOCOM_LAPDM_H */
diff --git a/include/osmocom/gsm/mncc.h b/include/osmocom/gsm/mncc.h
new file mode 100644
index 0000000..a094bb9
--- /dev/null
+++ b/include/osmocom/gsm/mncc.h
@@ -0,0 +1,71 @@
+#ifndef _OSMOCORE_MNCC_H
+#define _OSMOCORE_MNCC_H
+
+#define GSM_MAX_FACILITY       128
+#define GSM_MAX_SSVERSION      128
+#define GSM_MAX_USERUSER       128
+
+/* Expanded fields from GSM TS 04.08, Table 10.5.102 */
+struct gsm_mncc_bearer_cap {
+	int		transfer;	/* Information Transfer Capability */
+	int 		mode;		/* Transfer Mode */
+	int		coding;		/* Coding Standard */
+	int		radio;		/* Radio Channel Requirement */
+	int		speech_ctm;	/* CTM text telephony indication */
+	int		speech_ver[8];	/* Speech version indication */
+};
+
+struct gsm_mncc_number {
+	int 		type;
+	int 		plan;
+	int		present;
+	int		screen;
+	char		number[33];
+};
+
+struct gsm_mncc_cause {
+	int		location;
+	int		coding;
+	int		rec;
+	int		rec_val;
+	int		value;
+	int		diag_len;
+	char		diag[32];
+};
+
+struct gsm_mncc_useruser {
+	int		proto;
+	char		info[GSM_MAX_USERUSER + 1]; /* + termination char */
+};
+
+struct gsm_mncc_progress {
+	int		coding;
+	int		location;
+	int 		descr;
+};
+
+struct gsm_mncc_facility {
+	int		len;
+	char		info[GSM_MAX_FACILITY];
+};
+
+struct gsm_mncc_ssversion {
+	int		len;
+	char		info[GSM_MAX_SSVERSION];
+};
+
+struct gsm_mncc_cccap {
+	int		dtmf;
+	int		pcp;
+};
+
+enum {
+	GSM_MNCC_BCAP_SPEECH	= 0,
+	GSM_MNCC_BCAP_UNR_DIG	= 1,
+	GSM_MNCC_BCAP_AUDIO	= 2,
+	GSM_MNCC_BCAP_FAX_G3	= 3,
+	GSM_MNCC_BCAP_OTHER_ITC = 5,
+	GSM_MNCC_BCAP_RESERVED	= 7,
+};
+
+#endif
diff --git a/include/osmocom/gsm/prim.h b/include/osmocom/gsm/prim.h
new file mode 100644
index 0000000..95cbb12
--- /dev/null
+++ b/include/osmocom/gsm/prim.h
@@ -0,0 +1,13 @@
+#ifndef OSMO_GSM_PRIM_H
+#define OSMO_GSM_PRIM_H
+
+#include <osmocom/core/prim.h>
+
+/* enumeration of GSM related SAPs */
+enum osmo_gsm_sap {
+	SAP_GSM_PH	= _SAP_GSM_BASE,
+	SAP_GSM_DL,
+	SAP_GSM_MDL,
+};
+
+#endif
diff --git a/include/osmocom/gsm/protocol/Makefile.am b/include/osmocom/gsm/protocol/Makefile.am
new file mode 100644
index 0000000..6ed55e4
--- /dev/null
+++ b/include/osmocom/gsm/protocol/Makefile.am
@@ -0,0 +1,6 @@
+osmogsm_proto_HEADERS = gsm_03_41.h \
+			gsm_04_08.h gsm_04_11.h gsm_04_12.h gsm_04_80.h \
+			gsm_08_08.h gsm_08_58.h gsm_44_318.h \
+			gsm_12_21.h ipaccess.h
+
+osmogsm_protodir = $(includedir)/osmocom/gsm/protocol
diff --git a/include/osmocom/gsm/protocol/gsm_03_41.h b/include/osmocom/gsm/protocol/gsm_03_41.h
new file mode 100644
index 0000000..54365cb
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_03_41.h
@@ -0,0 +1,51 @@
+#ifndef PROTO_GSM_03_41_H
+#define PROTO_GSM_03_41_H
+
+#include <stdint.h>
+
+/* GSM TS 03.41 definitions also TS 23.041*/
+
+/* Chapter 9.3.2 */
+struct gsm341_ms_message {
+	struct {
+		uint8_t code_hi:6;
+		uint8_t gs:2;
+		uint8_t update:4;
+		uint8_t code_lo:4;
+	} serial;
+	uint16_t msg_id;
+	struct {
+		uint8_t language:4;
+		uint8_t group:4;
+	} dcs;
+	struct {
+		uint8_t total:4;
+		uint8_t current:4;
+	} page;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.4.1.3 */
+struct gsm341_etws_message {
+	struct {
+		uint8_t code_hi:4;
+		uint8_t popup:1;
+		uint8_t alert:1;
+		uint8_t gs:2;
+		uint8_t update:4;
+		uint8_t code_lo:4;
+	} serial;
+	uint16_t msg_id;
+	uint16_t warning_type;
+	uint8_t data[0];
+} __attribute__((packed));
+
+#define GSM341_MSG_CODE(ms) ((ms)->serial.code_lo | ((ms)->serial.code_hi << 4))
+
+/* Section 9.3.2.1 - Geographical Scope */
+#define GSM341_GS_CELL_WIDE_IMMED	0
+#define GSM341_GS_PLMN_WIDE		1
+#define GSM341_GS_LA_WIDE		2
+#define GSM341_GS_CELL_WIDE		3
+
+#endif /* PROTO_GSM_03_41_H */
diff --git a/include/osmocom/gsm/protocol/gsm_04_08.h b/include/osmocom/gsm/protocol/gsm_04_08.h
new file mode 100644
index 0000000..5057ada
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_04_08.h
@@ -0,0 +1,1265 @@
+#ifndef PROTO_GSM_04_08_H
+#define PROTO_GSM_04_08_H
+
+#include <stdint.h>
+
+/* GSM TS 04.08  definitions */
+struct gsm_lchan;
+
+/* Chapter 10.5.1.5 */
+struct gsm48_classmark1 {
+	uint8_t pwr_lev:3,
+		 a5_1:1,
+		 es_ind:1,
+		 rev_lev:2,
+		 spare:1;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.1.6 */
+struct gsm48_classmark2 {
+	uint8_t pwr_lev:3,
+		 a5_1:1,
+		 es_ind:1,
+		 rev_lev:2,
+		 spare:1;
+	uint8_t	fc:1,
+		 vgcs:1,
+		 vbs:1,
+		 sm_cap:1,
+		 ss_scr:2,
+		 ps_cap:1,
+		 spare2:1;
+	uint8_t	a5_2:1,
+		 a5_3:1,
+		 cmsp:1,
+		 solsa:1,
+		 spare3:1,
+		 lcsva_cap:1,
+		 spare4:1,
+		 cm3:1;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.1b.3 */
+struct gsm48_range_1024 {
+	uint8_t	w1_hi:2,
+		 f0:1,
+		 form_id:5;
+	uint8_t	w1_lo;
+	uint8_t	w2_hi;
+	uint8_t	w3_hi:7,
+		 w2_lo:1;
+	uint8_t	w4_hi:6,
+		 w3_lo:2;
+	uint8_t	w5_hi:6,
+		 w4_lo:2;
+	uint8_t	w6_hi:6,
+		 w5_lo:2;
+	uint8_t	w7_hi:6,
+		 w6_lo:2;
+	uint8_t	w8_hi:6,
+		 w7_lo:2;
+	uint8_t	w9:7,
+		 w8_lo:1;
+	uint8_t	w11_hi:1,
+		 w10:7;
+	uint8_t	w12_hi:2,
+		 w11_lo:6;
+	uint8_t	w13_hi:3,
+		 w12_lo:5;
+	uint8_t	w14_hi:4,
+		 w13_lo:4;
+	uint8_t	w15_hi:5,
+		 w14_lo:3;
+	uint8_t	w16:6,
+		 w15_lo:2;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.1b.4 */
+struct gsm48_range_512 {
+	uint8_t	orig_arfcn_hi:1,
+		 form_id:7;
+	uint8_t	orig_arfcn_mid;
+	uint8_t	w1_hi:7,
+		 orig_arfcn_lo:1;
+	uint8_t	w2_hi:6,
+		 w1_lo:2;
+	uint8_t	w3_hi:6,
+		 w2_lo:2;
+	uint8_t	w4_hi:6,
+		 w3_lo:2;
+	uint8_t	w5:7,
+		 w4_lo:1;
+	uint8_t	w7_hi:1,
+		 w6:7;
+	uint8_t	w8_hi:2,
+		 w7_lo:6;
+	uint8_t	w9_hi:4,
+		 w8_lo:4;
+	uint8_t	w10:6,
+		 w9_lo:2;
+	uint8_t	w12_hi:2,
+		 w11:6;
+	uint8_t	w13_hi:4,
+		 w12_lo:4;
+	uint8_t	w14:6,
+		 w13_lo:2;
+	uint8_t	w16_hi:2,
+		 w15:6;
+	uint8_t	w17:5,
+		 w16_lo:3;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.1b.5 */
+struct gsm48_range_256 {
+	uint8_t	orig_arfcn_hi:1,
+		 form_id:7;
+	uint8_t	orig_arfcn_mid;
+	uint8_t	w1_hi:7,
+		 orig_arfcn_lo:1;
+	uint8_t	w2:7,
+		 w1_lo:1;
+	uint8_t	w4_hi:1,
+		 w3:7;
+	uint8_t	w5_hi:3,
+		 w4_lo:5;
+	uint8_t	w6_hi:5,
+		 w5_lo:3;
+	uint8_t	w8_hi:1,
+		 w7:6,
+		 w6_lo:1;
+	uint8_t	w9_hi:4,
+		 w8_lo:4;
+	uint8_t	w11_hi:2,
+		 w10:5,
+		 w9_lo:1;
+	uint8_t	w12:5,
+		 w11_lo:3;
+	uint8_t	w14_hi:3,
+		 w13:5;
+	uint8_t	w16_hi:1,
+		 w15:5,
+		 w14_lo:2;
+	uint8_t	w18_hi:1,
+		 w17:4,
+		 w16_lo:3;
+	uint8_t	w20_hi:1,
+		 w19:4,
+		 w18_lo:3;
+	uint8_t	spare:1,
+		 w21:4,
+		 w20_lo:3;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.1b.6 */
+struct gsm48_range_128 {
+	uint8_t	orig_arfcn_hi:1,
+		 form_id:7;
+	uint8_t	orig_arfcn_mid;
+	uint8_t	w1:7,
+		 orig_arfcn_lo:1;
+	uint8_t	w3_hi:2,
+		 w2:6;
+	uint8_t	w4_hi:4,
+		 w3_lo:4;
+	uint8_t	w6_hi:2,
+		 w5:5,
+		 w4_lo:1;
+	uint8_t	w7:5,
+		 w6_lo:3;
+	uint8_t	w9:4,
+		 w8:4;
+	uint8_t	w11:4,
+		 w10:4;
+	uint8_t	w13:4,
+		 w12:4;
+	uint8_t	w15:4,
+		 w14:4;
+	uint8_t	w18_hi:2,
+		 w17:3,
+		 w16:3;
+	uint8_t	w21_hi:1,
+		 w20:3,
+		 w19:3,
+		 w18_lo:1;
+	uint8_t	w23:3,
+		 w22:3,
+		 w21_lo:2;
+	uint8_t	w26_hi:2,
+		 w25:3,
+		 w24:3;
+	uint8_t	spare:1,
+		 w28:3,
+		 w27:3,
+		 w26_lo:1;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.1b.7 */
+struct gsm48_var_bit {
+	uint8_t	orig_arfcn_hi:1,
+		 form_id:7;
+	uint8_t	orig_arfcn_mid;
+	uint8_t	rrfcn1_7:7,
+		 orig_arfcn_lo:1;
+	uint8_t rrfcn8_111[13];
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.5 */
+struct gsm48_chan_desc {
+	uint8_t chan_nr;
+	union {
+		struct {
+			uint8_t maio_high:4,
+				 h:1,
+				 tsc:3;
+			uint8_t hsn:6,
+				 maio_low:2;
+		} __attribute__ ((packed)) h1;
+		struct {
+			uint8_t arfcn_high:2,
+				 spare:2,
+				 h:1,
+				 tsc:3;
+			uint8_t arfcn_low;
+		} __attribute__ ((packed)) h0;
+	} __attribute__ ((packed));
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.20 */
+struct gsm48_meas_res {
+	uint8_t	rxlev_full:6,
+		 dtx_used:1,
+		 ba_used:1;
+	uint8_t	rxlev_sub:6,
+		 meas_valid:1,
+		 spare:1;
+	uint8_t	no_nc_n_hi:1,
+		 rxqual_sub:3,
+		 rxqual_full:3,
+		 spare2:1;
+	uint8_t	rxlev_nc1:6,
+		 no_nc_n_lo:2;
+	uint8_t	bsic_nc1_hi:3,
+		 bcch_f_nc1:5;
+	uint8_t	rxlev_nc2_hi:5,
+		 bsic_nc1_lo:3;
+	uint8_t	bsic_nc2_hi:2,
+		 bcch_f_nc2:5,
+		 rxlev_nc2_lo:1;
+	uint8_t	rxlev_nc3_hi:4,
+		 bsic_nc2_lo:4;
+	uint8_t	bsic_nc3_hi:1,
+		 bcch_f_nc3:5,
+		 rxlev_nc3_lo:2;
+	uint8_t	rxlev_nc4_hi:3,
+		 bsic_nc3_lo:5;
+	uint8_t	bcch_f_nc4:5,
+		 rxlev_nc4_lo:3;
+	uint8_t	rxlev_nc5_hi:2,
+		 bsic_nc4:6;
+	uint8_t	bcch_f_nc5_hi:4,
+		 rxlev_nc5_lo:4;
+	uint8_t	rxlev_nc6_hi:1,
+		 bsic_nc5:6,
+		 bcch_f_nc5_lo:1;
+	uint8_t	bcch_f_nc6_hi:3,
+		 rxlev_nc6_lo:5;
+	uint8_t	bsic_nc6:6,
+		 bcch_f_nc6_lo:2;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.21aa */
+struct gsm48_multi_rate_conf {
+	uint8_t smod : 2,
+		 spare: 1,
+		 icmi : 1,
+		 nscb : 1,
+		 ver : 3;
+	uint8_t m4_75 : 1,
+		 m5_15 : 1,
+		 m5_90 : 1,
+		 m6_70 : 1,
+		 m7_40 : 1,
+		 m7_95 : 1,
+		 m10_2 : 1,
+		 m12_2 : 1;
+} __attribute__((packed));
+
+/* Chapter 10.5.2.28(a) */
+struct gsm48_power_cmd {
+	uint8_t power_level:5,
+		 spare:2,
+		 atc:1;
+} __attribute__((packed));
+
+/* Chapter 10.5.2.29 */
+struct gsm48_rach_control {
+	uint8_t re :1,
+		 cell_bar :1,
+		 tx_integer :4,
+		 max_trans :2;
+	uint8_t t2;
+	uint8_t t3;
+} __attribute__ ((packed));
+
+
+/* Chapter 10.5.2.30 */
+struct gsm48_req_ref {
+	uint8_t ra;
+	uint8_t t3_high:3,
+		 t1:5;
+	uint8_t t2:5,
+		 t3_low:3;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.38 */
+struct gsm48_start_time {
+	uint8_t t3_high:3,
+		 t1:5;
+	uint8_t t2:5,
+		 t3_low:3;
+} __attribute__ ((packed));
+
+/* Chapter 10.5.2.39 */
+struct gsm48_sync_ind {
+	uint8_t si:2,
+		 rot:1,
+		 nci:1,
+		 sync_ie:4;
+} __attribute__((packed));
+
+/*
+ * Chapter 9.1.5/9.1.6
+ *
+ * For 9.1.6 the chan_desc has the meaning of 10.5.2.5a
+ */
+struct gsm48_chan_mode_modify {
+	struct gsm48_chan_desc chan_desc;
+	uint8_t mode;
+} __attribute__ ((packed));
+
+enum gsm48_chan_mode {
+	GSM48_CMODE_SIGN	= 0x00,
+	GSM48_CMODE_SPEECH_V1	= 0x01,
+	GSM48_CMODE_SPEECH_EFR	= 0x21,
+	GSM48_CMODE_SPEECH_AMR	= 0x41,
+	GSM48_CMODE_DATA_14k5	= 0x0f,
+	GSM48_CMODE_DATA_12k0	= 0x03,
+	GSM48_CMODE_DATA_6k0	= 0x0b,
+	GSM48_CMODE_DATA_3k6	= 0x23,
+};
+
+/* Chapter 9.1.2 */
+struct gsm48_ass_cmd {
+	/* Semantic is from 10.5.2.5a */
+	struct gsm48_chan_desc chan_desc;
+	uint8_t power_command;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.1.13 */
+struct gsm48_frq_redef {
+	/* Semantic is from 10.5.2.5a */
+	struct gsm48_chan_desc chan_desc;
+	uint8_t mob_alloc_len;
+	uint8_t mob_alloc[0];
+} __attribute__((packed));
+
+/* Chapter 10.5.2.2 */
+struct gsm48_cell_desc {
+	uint8_t bcc:3,
+		 ncc:3,
+		 arfcn_hi:2;
+	uint8_t arfcn_lo;
+} __attribute__((packed));
+
+/* Chapter 9.1.15 */
+struct gsm48_ho_cmd {
+	struct gsm48_cell_desc cell_desc;
+	struct gsm48_chan_desc chan_desc;
+	uint8_t ho_ref;
+	uint8_t power_command;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Chapter 9.1.18 */
+struct gsm48_imm_ass {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t page_mode;
+	struct gsm48_chan_desc chan_desc;
+	struct gsm48_req_ref req_ref;
+	uint8_t timing_advance;
+	uint8_t mob_alloc_len;
+	uint8_t mob_alloc[0];
+} __attribute__ ((packed));
+
+/* Chapter 9.1.25 */
+struct gsm48_pag_resp {
+	uint8_t spare:4,
+		 key_seq:4;
+	uint32_t classmark2;
+	uint8_t mi_len;
+	uint8_t mi[0];
+} __attribute__ ((packed));
+
+/* Chapter 10.5.1.3 */
+struct gsm48_loc_area_id {
+	uint8_t digits[3];	/* BCD! */
+	uint16_t lac;
+} __attribute__ ((packed));
+
+/* Section 9.2.2 */
+struct gsm48_auth_req {
+	uint8_t key_seq:4,
+	         spare:4;
+	uint8_t rand[16];
+} __attribute__ ((packed));
+
+/* Section 9.2.3 */
+struct gsm48_auth_resp {
+	uint8_t sres[4];
+} __attribute__ ((packed));
+
+/* Section 9.2.15 */
+struct gsm48_loc_upd_req {
+	uint8_t type:4,
+		 key_seq:4;
+	struct gsm48_loc_area_id lai;
+	struct gsm48_classmark1 classmark1;
+	uint8_t mi_len;
+	uint8_t mi[0];
+} __attribute__ ((packed));
+
+/* Section 10.1 */
+struct gsm48_hdr {
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.3x System information Type header */
+struct gsm48_system_information_type_header {
+	uint8_t l2_plen;
+	uint8_t rr_protocol_discriminator :4,
+		skip_indicator:4; 
+	uint8_t system_information;
+} __attribute__ ((packed));
+
+/* Section 10.5.2.4 Cell Selection Parameters */
+struct gsm48_cell_sel_par {
+	uint8_t ms_txpwr_max_ccch:5,	/* GSM 05.08 MS-TXPWR-MAX-CCCH */
+		 cell_resel_hyst:3;	/* GSM 05.08 CELL-RESELECT-HYSTERESIS */
+	uint8_t rxlev_acc_min:6,	/* GSM 05.08 RXLEV-ACCESS-MIN */
+		 neci:1,
+		 acs:1;
+} __attribute__ ((packed));
+
+/* Section 10.5.2.11 Control Channel Description , Figure 10.5.33 */
+struct gsm48_control_channel_descr {
+	uint8_t ccch_conf :3,
+		bs_ag_blks_res :3,
+		att :1,
+		spare1 :1;
+	uint8_t bs_pa_mfrms : 3,
+		spare2 :5;
+	uint8_t t3212;
+} __attribute__ ((packed));
+
+struct gsm48_cell_options {
+	uint8_t radio_link_timeout:4,
+		 dtx:2,
+		 pwrc:1,
+		 spare:1;
+} __attribute__ ((packed));
+
+/* Section 9.2.9 CM service request */
+struct gsm48_service_request {
+	uint8_t cm_service_type : 4,
+		 cipher_key_seq  : 4;
+	/* length + 3 bytes */
+	uint32_t classmark;
+	uint8_t mi_len;
+	uint8_t mi[0];
+	/* optional priority level */
+} __attribute__ ((packed));
+
+/* Section 9.1.31 System information Type 1 */
+struct gsm48_system_information_type_1 {
+	struct gsm48_system_information_type_header header;
+	uint8_t cell_channel_description[16];
+	struct gsm48_rach_control rach_control;
+	uint8_t rest_octets[0]; /* NCH position on the CCCH */
+} __attribute__ ((packed));
+
+/* Section 9.1.32 System information Type 2 */
+struct gsm48_system_information_type_2 {
+	struct gsm48_system_information_type_header header;
+	uint8_t bcch_frequency_list[16];
+	uint8_t ncc_permitted;
+	struct gsm48_rach_control rach_control;
+} __attribute__ ((packed));
+
+/* Section 9.1.33 System information Type 2bis */
+struct gsm48_system_information_type_2bis {
+	struct gsm48_system_information_type_header header;
+	uint8_t bcch_frequency_list[16];
+	struct gsm48_rach_control rach_control;
+	uint8_t rest_octets[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.34 System information Type 2ter */
+struct gsm48_system_information_type_2ter {
+	struct gsm48_system_information_type_header header;
+	uint8_t ext_bcch_frequency_list[16];
+	uint8_t rest_octets[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.35 System information Type 3 */
+struct gsm48_system_information_type_3 {
+	struct gsm48_system_information_type_header header;
+	uint16_t cell_identity;
+	struct gsm48_loc_area_id lai;
+	struct gsm48_control_channel_descr control_channel_desc;
+	struct gsm48_cell_options cell_options;
+	struct gsm48_cell_sel_par cell_sel_par;
+	struct gsm48_rach_control rach_control;
+	uint8_t rest_octets[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.36 System information Type 4 */
+struct gsm48_system_information_type_4 {
+	struct gsm48_system_information_type_header header;
+	struct gsm48_loc_area_id lai;
+	struct gsm48_cell_sel_par cell_sel_par;
+	struct gsm48_rach_control rach_control;
+	/*	optional CBCH conditional CBCH... followed by
+		mandantory SI 4 Reset Octets
+	 */
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.37 System information Type 5 */
+struct gsm48_system_information_type_5 {
+	uint8_t rr_protocol_discriminator :4,
+		skip_indicator:4; 
+	uint8_t system_information;
+	uint8_t bcch_frequency_list[16];
+} __attribute__ ((packed));
+
+/* Section 9.1.38 System information Type 5bis */
+struct gsm48_system_information_type_5bis {
+        uint8_t rr_protocol_discriminator :4,
+		 skip_indicator:4;
+	uint8_t system_information;
+	uint8_t bcch_frequency_list[16];
+} __attribute__ ((packed));
+
+/* Section 9.1.39 System information Type 5ter */
+struct gsm48_system_information_type_5ter {
+        uint8_t rr_protocol_discriminator :4,
+		 skip_indicator:4;
+	uint8_t system_information;
+	uint8_t bcch_frequency_list[16];
+} __attribute__ ((packed));
+
+/* Section 9.1.40 System information Type 6 */
+struct gsm48_system_information_type_6 {
+	uint8_t rr_protocol_discriminator :4,
+		skip_indicator:4; 
+	uint8_t system_information;
+	uint16_t cell_identity;
+	struct gsm48_loc_area_id lai;
+	struct gsm48_cell_options cell_options;
+	uint8_t ncc_permitted;
+	uint8_t rest_octets[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.43a System Information type 13 */
+struct gsm48_system_information_type_13 {
+	struct gsm48_system_information_type_header header;
+	uint8_t rest_octets[0];
+} __attribute__ ((packed));
+
+/* Section 9.2.12 IMSI Detach Indication */
+struct gsm48_imsi_detach_ind {
+	struct gsm48_classmark1 classmark1;
+	uint8_t mi_len;
+	uint8_t mi[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.1 */
+struct gsm48_add_ass {
+	/* Semantic is from 10.5.2.5 */
+	struct gsm48_chan_desc chan_desc;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.3 */
+struct gsm48_ass_cpl {
+	uint8_t rr_cause;
+} __attribute__((packed));
+
+/* Section 9.1.4 */
+struct gsm48_ass_fail {
+	uint8_t rr_cause;
+} __attribute__((packed));
+
+/* Section 9.1.3 */
+struct gsm48_ho_cpl {
+	uint8_t rr_cause;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.4 */
+struct gsm48_ho_fail {
+	uint8_t rr_cause;
+} __attribute__((packed));
+
+/* Section 9.1.7 */
+struct gsm48_chan_rel {
+	uint8_t rr_cause;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.9 */
+struct gsm48_cip_mode_cmd {
+	uint8_t sc:1,
+		 alg_id:3,
+		 cr:1,
+		 spare:3;
+} __attribute__((packed));
+
+/* Section 9.1.11 */
+struct gsm48_cm_change {
+	uint8_t cm2_len;
+	struct gsm48_classmark2 cm2;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.19 */
+struct gsm48_imm_ass_ext {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t page_mode;
+	struct gsm48_chan_desc chan_desc1;
+	struct gsm48_req_ref req_ref1;
+	uint8_t timing_advance1;
+	struct gsm48_chan_desc chan_desc2;
+	struct gsm48_req_ref req_ref2;
+	uint8_t timing_advance2;
+	uint8_t mob_alloc_len;
+	uint8_t mob_alloc[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.20 */
+struct gsm48_imm_ass_rej {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t page_mode;
+	struct gsm48_req_ref req_ref1;
+	uint8_t wait_ind1;
+	struct gsm48_req_ref req_ref2;
+	uint8_t wait_ind2;
+	struct gsm48_req_ref req_ref3;
+	uint8_t wait_ind3;
+	struct gsm48_req_ref req_ref4;
+	uint8_t wait_ind4;
+	uint8_t rest[0];
+} __attribute__ ((packed));
+
+/* Section 9.1.22 */
+struct gsm48_paging1 {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t pag_mode:2,
+		 spare:2,
+		 cneed1:2,
+		 cneed2:2;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.23 */
+struct gsm48_paging2 {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t pag_mode:2,
+		 spare:2,
+		 cneed1:2,
+		 cneed2:2;
+	uint32_t tmsi1;
+	uint32_t tmsi2;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.24 */
+struct gsm48_paging3 {
+	uint8_t l2_plen;
+	uint8_t proto_discr;
+	uint8_t msg_type;
+	uint8_t pag_mode:2,
+		 spare:2,
+		 cneed1:2,
+		 cneed2:2;
+	uint32_t tmsi1;
+	uint32_t tmsi2;
+	uint32_t tmsi3;
+	uint32_t tmsi4;
+	uint8_t cneed3:2,
+		 cneed4:2,
+		 spare2:4;
+	uint8_t rest[0];
+} __attribute__((packed));
+
+/* Section 9.1.25 */
+struct gsm48_pag_rsp {
+	uint8_t key_seq:3,
+		 spare:5;
+	uint8_t cm2_len;
+	struct gsm48_classmark2 cm2;
+	uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.29 */
+struct gsm48_rr_status {
+	uint8_t rr_cause;
+} __attribute__((packed));
+
+/* Section 10.2 + GSM 04.07 12.2.3.1.1 */
+#define GSM48_PDISC_GROUP_CC	0x00
+#define GSM48_PDISC_BCAST_CC	0x01
+#define GSM48_PDISC_PDSS1	0x02
+#define GSM48_PDISC_CC		0x03
+#define GSM48_PDISC_PDSS2	0x04
+#define GSM48_PDISC_MM		0x05
+#define GSM48_PDISC_RR		0x06
+#define GSM48_PDISC_MM_GPRS	0x08
+#define GSM48_PDISC_SMS		0x09
+#define GSM48_PDISC_SM_GPRS	0x0a
+#define GSM48_PDISC_NC_SS	0x0b
+#define GSM48_PDISC_LOC		0x0c
+#define GSM48_PDISC_MASK	0x0f
+#define GSM48_PDISC_USSD	0x11
+
+/* Section 10.4 */
+#define GSM48_MT_RR_INIT_REQ		0x3c
+#define GSM48_MT_RR_ADD_ASS		0x3b
+#define GSM48_MT_RR_IMM_ASS		0x3f
+#define GSM48_MT_RR_IMM_ASS_EXT		0x39
+#define GSM48_MT_RR_IMM_ASS_REJ		0x3a
+
+#define GSM48_MT_RR_CIPH_M_CMD		0x35
+#define GSM48_MT_RR_CIPH_M_COMPL	0x32
+
+#define GSM48_MT_RR_CFG_CHG_CMD		0x30
+#define GSM48_MT_RR_CFG_CHG_ACK		0x31
+#define GSM48_MT_RR_CFG_CHG_REJ		0x33
+
+#define GSM48_MT_RR_ASS_CMD		0x2e
+#define GSM48_MT_RR_ASS_COMPL		0x29
+#define GSM48_MT_RR_ASS_FAIL		0x2f
+#define GSM48_MT_RR_HANDO_CMD		0x2b
+#define GSM48_MT_RR_HANDO_COMPL		0x2c
+#define GSM48_MT_RR_HANDO_FAIL		0x28
+#define GSM48_MT_RR_HANDO_INFO		0x2d
+
+#define GSM48_MT_RR_CELL_CHG_ORDER	0x08
+#define GSM48_MT_RR_PDCH_ASS_CMD	0x23
+
+#define GSM48_MT_RR_CHAN_REL		0x0d
+#define GSM48_MT_RR_PART_REL		0x0a
+#define GSM48_MT_RR_PART_REL_COMP	0x0f
+
+#define GSM48_MT_RR_PAG_REQ_1		0x21
+#define GSM48_MT_RR_PAG_REQ_2		0x22
+#define GSM48_MT_RR_PAG_REQ_3		0x24
+#define GSM48_MT_RR_PAG_RESP		0x27
+#define GSM48_MT_RR_NOTIF_NCH		0x20
+#define GSM48_MT_RR_NOTIF_FACCH		0x25
+#define GSM48_MT_RR_NOTIF_RESP		0x26
+
+#define GSM48_MT_RR_SYSINFO_8		0x18
+#define GSM48_MT_RR_SYSINFO_1		0x19
+#define GSM48_MT_RR_SYSINFO_2		0x1a
+#define GSM48_MT_RR_SYSINFO_3		0x1b
+#define GSM48_MT_RR_SYSINFO_4		0x1c
+#define GSM48_MT_RR_SYSINFO_5		0x1d
+#define GSM48_MT_RR_SYSINFO_6		0x1e
+#define GSM48_MT_RR_SYSINFO_7		0x1f
+
+#define GSM48_MT_RR_SYSINFO_2bis	0x02
+#define GSM48_MT_RR_SYSINFO_2ter	0x03
+#define GSM48_MT_RR_SYSINFO_5bis	0x05
+#define GSM48_MT_RR_SYSINFO_5ter	0x06
+#define GSM48_MT_RR_SYSINFO_9		0x04
+#define GSM48_MT_RR_SYSINFO_13		0x00
+
+#define GSM48_MT_RR_SYSINFO_16		0x3d
+#define GSM48_MT_RR_SYSINFO_17		0x3e
+
+#define GSM48_MT_RR_CHAN_MODE_MODIF	0x10
+#define GSM48_MT_RR_STATUS		0x12
+#define GSM48_MT_RR_CHAN_MODE_MODIF_ACK	0x17
+#define GSM48_MT_RR_FREQ_REDEF		0x14
+#define GSM48_MT_RR_MEAS_REP		0x15
+#define GSM48_MT_RR_CLSM_CHG		0x16
+#define GSM48_MT_RR_CLSM_ENQ		0x13
+#define GSM48_MT_RR_EXT_MEAS_REP	0x36
+#define GSM48_MT_RR_EXT_MEAS_REP_ORD	0x37
+#define GSM48_MT_RR_GPRS_SUSP_REQ	0x34
+
+#define GSM48_MT_RR_VGCS_UPL_GRANT	0x08
+#define GSM48_MT_RR_UPLINK_RELEASE	0x0e
+#define GSM48_MT_RR_UPLINK_FREE		0x0c
+#define GSM48_MT_RR_UPLINK_BUSY		0x2a
+#define GSM48_MT_RR_TALKER_IND		0x11
+
+#define GSM48_MT_RR_APP_INFO		0x38
+
+/* Table 10.2/3GPP TS 04.08 */
+#define GSM48_MT_MM_IMSI_DETACH_IND	0x01
+#define GSM48_MT_MM_LOC_UPD_ACCEPT	0x02
+#define GSM48_MT_MM_LOC_UPD_REJECT	0x04
+#define GSM48_MT_MM_LOC_UPD_REQUEST	0x08
+
+#define GSM48_MT_MM_AUTH_REJ		0x11
+#define GSM48_MT_MM_AUTH_REQ		0x12
+#define GSM48_MT_MM_AUTH_RESP		0x14
+#define GSM48_MT_MM_ID_REQ		0x18
+#define GSM48_MT_MM_ID_RESP		0x19
+#define GSM48_MT_MM_TMSI_REALL_CMD	0x1a
+#define GSM48_MT_MM_TMSI_REALL_COMPL	0x1b
+
+#define GSM48_MT_MM_CM_SERV_ACC		0x21
+#define GSM48_MT_MM_CM_SERV_REJ		0x22
+#define GSM48_MT_MM_CM_SERV_ABORT	0x23
+#define GSM48_MT_MM_CM_SERV_REQ		0x24
+#define GSM48_MT_MM_CM_SERV_PROMPT	0x25
+#define GSM48_MT_MM_CM_REEST_REQ	0x28
+#define GSM48_MT_MM_ABORT		0x29
+
+#define GSM48_MT_MM_NULL		0x30
+#define GSM48_MT_MM_STATUS		0x31
+#define GSM48_MT_MM_INFO		0x32
+
+/* Table 10.3/3GPP TS 04.08 */
+#define GSM48_MT_CC_ALERTING		0x01
+#define GSM48_MT_CC_CALL_CONF		0x08
+#define GSM48_MT_CC_CALL_PROC		0x02
+#define GSM48_MT_CC_CONNECT		0x07
+#define GSM48_MT_CC_CONNECT_ACK		0x0f
+#define GSM48_MT_CC_EMERG_SETUP		0x0e
+#define GSM48_MT_CC_PROGRESS		0x03
+#define GSM48_MT_CC_ESTAB		0x04
+#define GSM48_MT_CC_ESTAB_CONF		0x06
+#define GSM48_MT_CC_RECALL		0x0b
+#define GSM48_MT_CC_START_CC		0x09
+#define GSM48_MT_CC_SETUP		0x05
+
+#define GSM48_MT_CC_MODIFY		0x17
+#define GSM48_MT_CC_MODIFY_COMPL	0x1f
+#define GSM48_MT_CC_MODIFY_REJECT	0x13
+#define GSM48_MT_CC_USER_INFO		0x10
+#define GSM48_MT_CC_HOLD		0x18
+#define GSM48_MT_CC_HOLD_ACK		0x19
+#define GSM48_MT_CC_HOLD_REJ		0x1a
+#define GSM48_MT_CC_RETR		0x1c
+#define GSM48_MT_CC_RETR_ACK		0x1d
+#define GSM48_MT_CC_RETR_REJ		0x1e
+
+#define GSM48_MT_CC_DISCONNECT		0x25
+#define GSM48_MT_CC_RELEASE		0x2d
+#define GSM48_MT_CC_RELEASE_COMPL	0x2a
+
+#define GSM48_MT_CC_CONG_CTRL		0x39
+#define GSM48_MT_CC_NOTIFY		0x3e
+#define GSM48_MT_CC_STATUS		0x3d
+#define GSM48_MT_CC_STATUS_ENQ		0x34
+#define GSM48_MT_CC_START_DTMF		0x35
+#define GSM48_MT_CC_STOP_DTMF		0x31
+#define GSM48_MT_CC_STOP_DTMF_ACK	0x32
+#define GSM48_MT_CC_START_DTMF_ACK	0x36
+#define GSM48_MT_CC_START_DTMF_REJ	0x37
+#define GSM48_MT_CC_FACILITY		0x3a
+
+/* FIXME: Table 10.4 / 10.4a (GPRS) */
+
+/* Section 10.5.3.3 CM service type */
+#define GSM48_CMSERV_MO_CALL_PACKET	1
+#define GSM48_CMSERV_EMERGENCY		2
+#define GSM48_CMSERV_SMS		4
+#define GSM48_CMSERV_SUP_SERV		8
+#define GSM48_CMSERV_VGCS		9
+#define GSM48_CMSERV_VBS		10
+#define GSM48_CMSERV_LOC_SERV		11
+
+/* Section 10.5.2.26, Table 10.5.64 */
+#define GSM48_PM_MASK		0x03
+#define GSM48_PM_NORMAL		0x00
+#define GSM48_PM_EXTENDED	0x01
+#define GSM48_PM_REORG		0x02
+#define GSM48_PM_SAME		0x03
+
+/* Chapter 10.5.3.5 / Table 10.5.93 */
+#define GSM48_LUPD_NORMAL	0x0
+#define GSM48_LUPD_PERIODIC	0x1
+#define GSM48_LUPD_IMSI_ATT	0x2
+#define GSM48_LUPD_RESERVED	0x3
+
+/* Table 10.5.4 */
+#define GSM_MI_TYPE_MASK	0x07
+#define GSM_MI_TYPE_NONE	0x00
+#define GSM_MI_TYPE_IMSI	0x01
+#define GSM_MI_TYPE_IMEI	0x02
+#define GSM_MI_TYPE_IMEISV	0x03
+#define GSM_MI_TYPE_TMSI	0x04
+#define GSM_MI_ODD		0x08
+
+#define GSM48_IE_MOBILE_ID	0x17	/* 10.5.1.4 */
+#define GSM48_IE_NAME_LONG	0x43	/* 10.5.3.5a */
+#define GSM48_IE_NAME_SHORT	0x45	/* 10.5.3.5a */
+#define GSM48_IE_UTC		0x46	/* 10.5.3.8 */
+#define GSM48_IE_NET_TIME_TZ	0x47	/* 10.5.3.9 */
+#define GSM48_IE_LSA_IDENT	0x48	/* 10.5.3.11 */
+
+#define GSM48_IE_BEARER_CAP	0x04	/* 10.5.4.5 */
+#define GSM48_IE_CAUSE		0x08	/* 10.5.4.11 */
+#define GSM48_IE_CC_CAP		0x15	/* 10.5.4.5a */
+#define GSM48_IE_ALERT		0x19	/* 10.5.4.26 */
+#define GSM48_IE_FACILITY	0x1c	/* 10.5.4.15 */
+#define GSM48_IE_PROGR_IND	0x1e	/* 10.5.4.21 */
+#define GSM48_IE_AUX_STATUS	0x24	/* 10.5.4.4 */
+#define GSM48_IE_NOTIFY		0x27	/* 10.5.4.20 */
+#define GSM48_IE_KPD_FACILITY	0x2c	/* 10.5.4.17 */
+#define GSM48_IE_SIGNAL		0x34	/* 10.5.4.23 */
+#define GSM48_IE_CONN_BCD	0x4c	/* 10.5.4.13 */
+#define GSM48_IE_CONN_SUB	0x4d	/* 10.5.4.14 */
+#define GSM48_IE_CALLING_BCD	0x5c	/* 10.5.4.9 */
+#define GSM48_IE_CALLING_SUB	0x5d	/* 10.5.4.10 */
+#define GSM48_IE_CALLED_BCD	0x5e	/* 10.5.4.7 */
+#define GSM48_IE_CALLED_SUB	0x6d	/* 10.5.4.8 */
+#define GSM48_IE_REDIR_BCD	0x74	/* 10.5.4.21a */
+#define GSM48_IE_REDIR_SUB	0x75	/* 10.5.4.21b */
+#define GSM48_IE_LOWL_COMPAT	0x7c	/* 10.5.4.18 */
+#define GSM48_IE_HIGHL_COMPAT	0x7d	/* 10.5.4.16 */
+#define GSM48_IE_USER_USER	0x7e	/* 10.5.4.25 */
+#define GSM48_IE_SS_VERS	0x7f	/* 10.5.4.24 */
+#define GSM48_IE_MORE_DATA	0xa0	/* 10.5.4.19 */
+#define GSM48_IE_CLIR_SUPP	0xa1	/* 10.5.4.11a */
+#define GSM48_IE_CLIR_INVOC	0xa2	/* 10.5.4.11b */
+#define GSM48_IE_REV_C_SETUP	0xa3	/* 10.5.4.22a */
+#define GSM48_IE_REPEAT_CIR	0xd1	/* 10.5.4.22 */
+#define GSM48_IE_REPEAT_SEQ	0xd3	/* 10.5.4.22 */
+
+/* Section 10.5.4.11 / Table 10.5.122 */
+#define GSM48_CAUSE_CS_GSM	0x60
+
+/* Section 9.1.2 / Table 9.3 */
+/* RR elements */
+#define GSM48_IE_VGCS_TARGET	0x01
+//#define GSM48_IE_VGCS_T_MODE_I	0x01
+#define GSM48_IE_FRQSHORT_AFTER	0x02
+#define GSM48_IE_MUL_RATE_CFG	0x03	/* 10.5.2.21aa */
+#define GSM48_IE_FREQ_L_AFTER	0x05
+#define GSM48_IE_MSLOT_DESC	0x10
+#define GSM48_IE_CHANMODE_2	0x11
+#define GSM48_IE_FRQSHORT_BEFORE 0x12
+//#define GSM48_IE_FRQSHORT_BEFOR 0x12
+#define GSM48_IE_CHANMODE_3	0x13
+#define GSM48_IE_CHANMODE_4	0x14
+#define GSM48_IE_CHANMODE_5	0x15
+#define GSM48_IE_CHANMODE_6	0x16
+#define GSM48_IE_CHANMODE_7	0x17
+#define GSM48_IE_CHANMODE_8	0x18
+#define GSM48_IE_CHANDESC_2	0x64
+#define GSM48_IE_MA_AFTER	0x72
+#define GSM48_IE_START_TIME	0x7c
+#define GSM48_IE_FREQ_L_BEFORE	0x19
+//#define GSM48_IE_FRQLIST_BEFORE	0x19
+#define GSM48_IE_CH_DESC_1_BEFORE	0x1c
+//#define GSM48_IE_CHDES_1_BEFORE 0x1c
+#define GSM48_IE_CH_DESC_2_BEFORE	0x1d
+//#define GSM48_IE_CHDES_2_BEFORE	0x1d
+#define GSM48_IE_F_CH_SEQ_BEFORE	0x1e
+//#define GSM48_IE_FRQSEQ_BEFORE	0x1e
+#define GSM48_IE_CLASSMARK3	0x20
+#define GSM48_IE_MA_BEFORE	0x21
+#define GSM48_IE_RR_PACKET_UL	0x22
+#define GSM48_IE_RR_PACKET_DL	0x23
+#define GSM48_IE_CELL_CH_DESC	0x62
+#define GSM48_IE_CHANMODE_1	0x63
+#define GSM48_IE_CHDES_2_AFTER	0x64
+#define GSM48_IE_MODE_SEC_CH	0x66
+#define GSM48_IE_F_CH_SEQ_AFTER	0x69
+#define GSM48_IE_MA_AFTER	0x72
+#define GSM48_IE_BA_RANGE	0x73
+#define GSM48_IE_GROUP_CHDES	0x74
+#define GSM48_IE_BA_LIST_PREF	0x75
+#define GSM48_IE_MOB_OVSERV_DIF	0x77
+#define GSM48_IE_REALTIME_DIFF	0x7b
+#define GSM48_IE_START_TIME	0x7c
+#define GSM48_IE_TIMING_ADVANCE	0x7d
+#define GSM48_IE_GROUP_CIP_SEQ	0x80
+#define GSM48_IE_CIP_MODE_SET	0x90
+#define GSM48_IE_GPRS_RESUMPT	0xc0
+#define GSM48_IE_SYNC_IND	0xd0
+/* System Information 4 (types are equal IEs above) */
+#define GSM48_IE_CBCH_CHAN_DESC	0x64
+#define GSM48_IE_CBCH_MOB_AL	0x72
+
+/* Additional MM elements */
+#define GSM48_IE_LOCATION_AREA	0x13
+#define GSM48_IE_PRIORITY_LEV	0x80
+#define GSM48_IE_FOLLOW_ON_PROC	0xa1
+#define GSM48_IE_CTS_PERMISSION	0xa2
+
+/* Section 10.5.4.23 / Table 10.5.130 */
+enum gsm48_signal_val {
+	GSM48_SIGNAL_DIALTONE	= 0x00,
+	GSM48_SIGNAL_RINGBACK	= 0x01,
+	GSM48_SIGNAL_INTERCEPT	= 0x02,
+	GSM48_SIGNAL_NET_CONG	= 0x03,
+	GSM48_SIGNAL_BUSY	= 0x04,
+	GSM48_SIGNAL_CONFIRM	= 0x05,
+	GSM48_SIGNAL_ANSWER	= 0x06,
+	GSM48_SIGNAL_CALL_WAIT	= 0x07,
+	GSM48_SIGNAL_OFF_HOOK	= 0x08,
+	GSM48_SIGNAL_OFF	= 0x3f,
+	GSM48_SIGNAL_ALERT_OFF	= 0x4f,
+};
+
+enum gsm48_cause_loc {
+	GSM48_CAUSE_LOC_USER		= 0x00,
+	GSM48_CAUSE_LOC_PRN_S_LU	= 0x01,
+	GSM48_CAUSE_LOC_PUN_S_LU	= 0x02,
+	GSM48_CAUSE_LOC_TRANS_NET	= 0x03,
+	GSM48_CAUSE_LOC_PUN_S_RU	= 0x04,
+	GSM48_CAUSE_LOC_PRN_S_RU	= 0x05,
+	/* not defined */
+	GSM48_CAUSE_LOC_INN_NET		= 0x07,
+	GSM48_CAUSE_LOC_NET_BEYOND	= 0x0a,
+};
+
+/* Section 10.5.2.31 RR Cause / Table 10.5.70 */
+enum gsm48_rr_cause {
+	GSM48_RR_CAUSE_NORMAL		= 0x00,
+	GSM48_RR_CAUSE_ABNORMAL_UNSPEC	= 0x01,
+	GSM48_RR_CAUSE_ABNORMAL_UNACCT	= 0x02,
+	GSM48_RR_CAUSE_ABNORMAL_TIMER	= 0x03,
+	GSM48_RR_CAUSE_ABNORMAL_NOACT	= 0x04,
+	GSM48_RR_CAUSE_PREMPTIVE_REL	= 0x05,
+	GSM48_RR_CAUSE_HNDOVER_IMP	= 0x06,
+	GSM48_RR_CAUSE_CHAN_MODE_UNACCT	= 0x07,
+	GSM48_RR_CAUSE_FREQ_NOT_IMPL	= 0x08,
+	GSM48_RR_CAUSE_CALL_CLEARED	= 0x41,
+	GSM48_RR_CAUSE_SEMANT_INCORR	= 0x5f,
+	GSM48_RR_CAUSE_INVALID_MAND_INF = 0x60,
+	GSM48_RR_CAUSE_MSG_TYPE_N	= 0x61,
+	GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT= 0x62,
+	GSM48_RR_CAUSE_COND_IE_ERROR	= 0x64,
+	GSM48_RR_CAUSE_NO_CELL_ALLOC_A	= 0x65,
+	GSM48_RR_CAUSE_PROT_ERROR_UNSPC = 0x6f,
+};
+
+/* Section 10.5.4.11 CC Cause / Table 10.5.123 */
+enum gsm48_cc_cause {
+	GSM48_CC_CAUSE_UNASSIGNED_NR	= 1,
+	GSM48_CC_CAUSE_NO_ROUTE		= 3,
+	GSM48_CC_CAUSE_CHAN_UNACCEPT	= 6,
+	GSM48_CC_CAUSE_OP_DET_BARRING	= 8,
+	GSM48_CC_CAUSE_NORM_CALL_CLEAR	= 16,
+	GSM48_CC_CAUSE_USER_BUSY	= 17,
+	GSM48_CC_CAUSE_USER_NOTRESPOND	= 18,
+	GSM48_CC_CAUSE_USER_ALERTING_NA	= 19,
+	GSM48_CC_CAUSE_CALL_REJECTED	= 21,
+	GSM48_CC_CAUSE_NUMBER_CHANGED	= 22,
+	GSM48_CC_CAUSE_PRE_EMPTION	= 25,
+	GSM48_CC_CAUSE_NONSE_USER_CLR	= 26,
+	GSM48_CC_CAUSE_DEST_OOO		= 27,
+	GSM48_CC_CAUSE_INV_NR_FORMAT	= 28,
+	GSM48_CC_CAUSE_FACILITY_REJ	= 29,
+	GSM48_CC_CAUSE_RESP_STATUS_INQ	= 30,
+	GSM48_CC_CAUSE_NORMAL_UNSPEC	= 31,
+	GSM48_CC_CAUSE_NO_CIRCUIT_CHAN	= 34,
+	GSM48_CC_CAUSE_NETWORK_OOO	= 38,
+	GSM48_CC_CAUSE_TEMP_FAILURE	= 41,
+	GSM48_CC_CAUSE_SWITCH_CONG	= 42,
+	GSM48_CC_CAUSE_ACC_INF_DISCARD	= 43,
+	GSM48_CC_CAUSE_REQ_CHAN_UNAVAIL	= 44,
+	GSM48_CC_CAUSE_RESOURCE_UNAVAIL	= 47,
+	GSM48_CC_CAUSE_QOS_UNAVAIL	= 49,
+	GSM48_CC_CAUSE_REQ_FAC_NOT_SUBSC= 50,
+	GSM48_CC_CAUSE_INC_BARRED_CUG	= 55,
+	GSM48_CC_CAUSE_BEARER_CAP_UNAUTH= 57,
+	GSM48_CC_CAUSE_BEARER_CA_UNAVAIL= 58,
+	GSM48_CC_CAUSE_SERV_OPT_UNAVAIL	= 63,
+	GSM48_CC_CAUSE_BEARERSERV_UNIMPL= 65,
+	GSM48_CC_CAUSE_ACM_GE_ACM_MAX	= 68,
+	GSM48_CC_CAUSE_REQ_FAC_NOTIMPL	= 69,
+	GSM48_CC_CAUSE_RESTR_BCAP_AVAIL	= 70,
+	GSM48_CC_CAUSE_SERV_OPT_UNIMPL	= 79,
+	GSM48_CC_CAUSE_INVAL_TRANS_ID	= 81,
+	GSM48_CC_CAUSE_USER_NOT_IN_CUG	= 87,
+	GSM48_CC_CAUSE_INCOMPAT_DEST	= 88,
+	GSM48_CC_CAUSE_INVAL_TRANS_NET	= 91,
+	GSM48_CC_CAUSE_SEMANTIC_INCORR	= 95,
+	GSM48_CC_CAUSE_INVAL_MAND_INF	= 96,
+	GSM48_CC_CAUSE_MSGTYPE_NOTEXIST	= 97,
+	GSM48_CC_CAUSE_MSGTYPE_INCOMPAT	= 98,
+	GSM48_CC_CAUSE_IE_NOTEXIST	= 99,
+	GSM48_CC_CAUSE_COND_IE_ERR	= 100,
+	GSM48_CC_CAUSE_MSG_INCOMP_STATE	= 101,
+	GSM48_CC_CAUSE_RECOVERY_TIMER	= 102,
+	GSM48_CC_CAUSE_PROTO_ERR	= 111,
+	GSM48_CC_CAUSE_INTERWORKING	= 127,
+};
+
+/* Annex G, GSM specific cause values for mobility management */
+enum gsm48_reject_value {
+	GSM48_REJECT_IMSI_UNKNOWN_IN_HLR	= 2,
+	GSM48_REJECT_ILLEGAL_MS			= 3,
+	GSM48_REJECT_IMSI_UNKNOWN_IN_VLR	= 4,
+	GSM48_REJECT_IMEI_NOT_ACCEPTED		= 5,
+	GSM48_REJECT_ILLEGAL_ME			= 6,
+	GSM48_REJECT_PLMN_NOT_ALLOWED		= 11,
+	GSM48_REJECT_LOC_NOT_ALLOWED		= 12,
+	GSM48_REJECT_ROAMING_NOT_ALLOWED	= 13,
+	GSM48_REJECT_NETWORK_FAILURE		= 17,
+	GSM48_REJECT_CONGESTION			= 22,
+	GSM48_REJECT_SRV_OPT_NOT_SUPPORTED	= 32,
+	GSM48_REJECT_RQD_SRV_OPT_NOT_SUPPORTED	= 33,
+	GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER	= 34,
+	GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED	= 38,
+	GSM48_REJECT_INCORRECT_MESSAGE		= 95,
+	GSM48_REJECT_INVALID_MANDANTORY_INF	= 96,
+	GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED	= 97,
+	GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE	= 98,
+	GSM48_REJECT_INF_ELEME_NOT_IMPLEMENTED	= 99,
+	GSM48_REJECT_CONDTIONAL_IE_ERROR	= 100,
+	GSM48_REJECT_MSG_NOT_COMPATIBLE		= 101,
+	GSM48_REJECT_PROTOCOL_ERROR		= 111,
+
+	/* according to G.6 Additional cause codes for GMM */
+	GSM48_REJECT_GPRS_NOT_ALLOWED		= 7,
+	GSM48_REJECT_SERVICES_NOT_ALLOWED	= 8,
+	GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE = 9,
+	GSM48_REJECT_IMPLICITLY_DETACHED	= 10,
+	GSM48_REJECT_GPRS_NOT_ALLOWED_IN_PLMN	= 14,
+	GSM48_REJECT_MSC_TMP_NOT_REACHABLE	= 16,
+};
+
+enum chreq_type {
+	CHREQ_T_EMERG_CALL,
+	CHREQ_T_CALL_REEST_TCH_F,
+	CHREQ_T_CALL_REEST_TCH_H,
+	CHREQ_T_CALL_REEST_TCH_H_DBL,
+	CHREQ_T_SDCCH,
+	CHREQ_T_TCH_F,
+	CHREQ_T_VOICE_CALL_TCH_H,
+	CHREQ_T_DATA_CALL_TCH_H,
+	CHREQ_T_LOCATION_UPD,
+	CHREQ_T_PAG_R_ANY_NECI0,
+	CHREQ_T_PAG_R_ANY_NECI1,
+	CHREQ_T_PAG_R_TCH_F,
+	CHREQ_T_PAG_R_TCH_FH,
+	CHREQ_T_LMU,
+	CHREQ_T_RESERVED_SDCCH,
+	CHREQ_T_RESERVED_IGNORE,
+};
+
+/* Chapter 11.3 */
+#define GSM48_T301	180, 0
+#define GSM48_T303	30, 0
+#define GSM48_T305	30, 0
+#define GSM48_T306	30, 0
+#define GSM48_T308	10, 0
+#define GSM48_T310	180, 0
+#define GSM48_T313	30, 0
+#define GSM48_T323	30, 0
+#define GSM48_T331	30, 0
+#define GSM48_T333	30, 0
+#define GSM48_T334	25, 0 /* min 15 */
+#define GSM48_T338	30, 0
+#define GSM48_T303_MS	30, 0
+#define GSM48_T305_MS	30, 0
+#define GSM48_T308_MS	30, 0
+#define GSM48_T310_MS	30, 0
+#define GSM48_T313_MS	30, 0
+#define GSM48_T323_MS	30, 0
+#define GSM48_T332_MS	30, 0
+#define GSM48_T335_MS	30, 0
+
+/* Chapter 5.1.2.2 */
+#define	GSM_CSTATE_NULL			0
+#define	GSM_CSTATE_INITIATED		1
+#define	GSM_CSTATE_MM_CONNECTION_PEND	2 /* see 10.5.4.6 */
+#define	GSM_CSTATE_MO_CALL_PROC		3
+#define	GSM_CSTATE_CALL_DELIVERED	4
+#define	GSM_CSTATE_CALL_PRESENT		6
+#define	GSM_CSTATE_CALL_RECEIVED	7
+#define	GSM_CSTATE_CONNECT_REQUEST	8
+#define	GSM_CSTATE_MO_TERM_CALL_CONF	9
+#define	GSM_CSTATE_ACTIVE		10
+#define	GSM_CSTATE_DISCONNECT_REQ	12
+#define	GSM_CSTATE_DISCONNECT_IND	12
+#define	GSM_CSTATE_RELEASE_REQ		19
+#define	GSM_CSTATE_MO_ORIG_MODIFY	26
+#define	GSM_CSTATE_MO_TERM_MODIFY	27
+#define	GSM_CSTATE_CONNECT_IND		28
+
+#define SBIT(a) (1 << a)
+#define ALL_STATES 0xffffffff
+
+/* Table 10.5.3/3GPP TS 04.08: Location Area Identification information element */
+#define GSM_LAC_RESERVED_DETACHED       0x0
+#define GSM_LAC_RESERVED_ALL_BTS        0xfffe
+
+/* GSM 04.08 Bearer Capability: Information Transfer Capability */
+enum gsm48_bcap_itcap {
+	GSM48_BCAP_ITCAP_SPEECH		= 0,
+	GSM48_BCAP_ITCAP_UNR_DIG_INF	= 1,
+	GSM48_BCAP_ITCAP_3k1_AUDIO	= 2,
+	GSM48_BCAP_ITCAP_FAX_G3		= 3,
+	GSM48_BCAP_ITCAP_OTHER		= 5,
+	GSM48_BCAP_ITCAP_RESERVED	= 7,
+};
+
+/* GSM 04.08 Bearer Capability: Transfer Mode */
+enum gsm48_bcap_tmod {
+	GSM48_BCAP_TMOD_CIRCUIT		= 0,
+	GSM48_BCAP_TMOD_PACKET		= 1,
+};
+
+/* GSM 04.08 Bearer Capability: Coding Standard */
+enum gsm48_bcap_coding {
+	GSM48_BCAP_CODING_GSM_STD	= 0,
+};
+
+/* GSM 04.08 Bearer Capability: Radio Channel Requirements */
+enum gsm48_bcap_rrq {
+	GSM48_BCAP_RRQ_FR_ONLY	= 1,
+	GSM48_BCAP_RRQ_DUAL_HR	= 2,
+	GSM48_BCAP_RRQ_DUAL_FR	= 3,
+};
+
+#define GSM48_TMSI_LEN	5
+#define GSM48_MID_TMSI_LEN	(GSM48_TMSI_LEN + 2)
+#define GSM48_MI_SIZE 32
+
+/* Chapter 10.4.4.15 */
+struct gsm48_ra_id {
+	uint8_t digits[3];	/* MCC + MNC BCD digits */
+	uint16_t lac;		/* Location Area Code */
+	uint8_t rac;		/* Routing Area Code */
+} __attribute__ ((packed));
+
+#define GSM48_CELL_CHAN_DESC_SIZE	16
+
+#define GSM_MACBLOCK_LEN	23
+#define GSM_MACBLOCK_PADDING	0x2b
+
+#endif /* PROTO_GSM_04_08_H */
diff --git a/include/osmocom/gsm/protocol/gsm_04_11.h b/include/osmocom/gsm/protocol/gsm_04_11.h
new file mode 100644
index 0000000..f37152f
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_04_11.h
@@ -0,0 +1,190 @@
+#ifndef PROTO_GSM_04_11_H
+#define PROTO_GSM_04_11_H
+
+#include <stdint.h>
+
+/* GSM TS 04.11  definitions */
+
+/* Chapter 5.2.3: SMC-CS states at the user/network side */
+enum gsm411_cp_state {
+	GSM411_CPS_IDLE 		= 0,
+	GSM411_CPS_MM_CONN_PENDING	= 1,	/* only MT ! */
+	GSM411_CPS_WAIT_CP_ACK		= 2,
+	GSM411_CPS_MM_ESTABLISHED	= 3,
+};
+
+/* Chapter 6.2.2: SMR states at the user/network side */
+enum gsm411_rp_state {
+	GSM411_RPS_IDLE			= 0,
+	GSM411_RPS_WAIT_FOR_RP_ACK	= 1,
+	GSM411_RPS_WAIT_TO_TX_RP_ACK	= 3,
+	GSM411_RPS_WAIT_FOR_RETRANS_T	= 4,
+};
+
+/* Chapter 8.1.2 (refers to GSM 04.07 Chapter 11.2.3.1.1 */
+#define GSM411_PDISC_SMS	0x09
+
+/* Chapter 8.1.3 */
+#define GSM411_MT_CP_DATA	0x01
+#define GSM411_MT_CP_ACK	0x04
+#define GSM411_MT_CP_ERROR	0x10
+
+enum gsm411_cp_ie {
+	GSM411_CP_IE_USER_DATA		= 0x01,	/* 8.1.4.1 */
+	GSM411_CP_IE_CAUSE		= 0x02,	/* 8.1.4.2. */
+};
+
+/* Section 8.1.4.2 / Table 8.2 */
+enum gsm411_cp_cause {
+	GSM411_CP_CAUSE_NET_FAIL	= 17,
+	GSM411_CP_CAUSE_CONGESTION	= 22,
+	GSM411_CP_CAUSE_INV_TRANS_ID	= 81,
+	GSM411_CP_CAUSE_SEMANT_INC_MSG	= 95,
+	GSM411_CP_CAUSE_INV_MAND_INF	= 96,
+	GSM411_CP_CAUSE_MSGTYPE_NOTEXIST= 97,
+	GSM411_CP_CAUSE_MSG_INCOMP_STATE= 98,
+	GSM411_CP_CAUSE_IE_NOTEXIST	= 99,
+	GSM411_CP_CAUSE_PROTOCOL_ERR	= 111,
+};
+
+/* Chapter 8.2.2 */
+#define GSM411_MT_RP_DATA_MO	0x00
+#define GSM411_MT_RP_DATA_MT	0x01
+#define GSM411_MT_RP_ACK_MO	0x02
+#define GSM411_MT_RP_ACK_MT	0x03
+#define GSM411_MT_RP_ERROR_MO	0x04
+#define GSM411_MT_RP_ERROR_MT	0x05
+#define GSM411_MT_RP_SMMA_MO	0x06
+
+enum gsm411_rp_ie {
+	GSM411_IE_RP_USER_DATA		= 0x41,	/* 8.2.5.3 */
+	GSM411_IE_RP_CAUSE		= 0x42,	/* 8.2.5.4 */
+};
+
+/* Chapter 8.2.5.4 Table 8.4 */
+enum gsm411_rp_cause {
+	/* valid only for MO */
+	GSM411_RP_CAUSE_MO_NUM_UNASSIGNED	= 1,
+	GSM411_RP_CAUSE_MO_OP_DET_BARR		= 8,
+	GSM411_RP_CAUSE_MO_CALL_BARRED		= 10,
+	GSM411_RP_CAUSE_MO_SMS_REJECTED		= 21,
+	GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER	= 27,
+	GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR	= 28,
+	GSM411_RP_CAUSE_MO_FACILITY_REJ		= 29,
+	GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR	= 30,
+	GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER	= 38,
+	GSM411_RP_CAUSE_MO_TEMP_FAIL		= 41,
+	GSM411_RP_CAUSE_MO_CONGESTION		= 42,
+	GSM411_RP_CAUSE_MO_RES_UNAVAIL		= 47,
+	GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR	= 50,
+	GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL	= 69,
+	GSM411_RP_CAUSE_MO_INTERWORKING		= 127,
+	/* valid only for MT */
+	GSM411_RP_CAUSE_MT_MEM_EXCEEDED		= 22,
+	/* valid for both directions */
+	GSM411_RP_CAUSE_INV_TRANS_REF		= 81,
+	GSM411_RP_CAUSE_SEMANT_INC_MSG		= 95,
+	GSM411_RP_CAUSE_INV_MAND_INF		= 96,
+	GSM411_RP_CAUSE_MSGTYPE_NOTEXIST	= 97,
+	GSM411_RP_CAUSE_MSG_INCOMP_STATE	= 98,
+	GSM411_RP_CAUSE_IE_NOTEXIST		= 99,
+	GSM411_RP_CAUSE_PROTOCOL_ERR		= 111,
+};
+
+/* Chapter 10: Timers */
+#define GSM411_TMR_TR1M		40, 0	/* 35 < x < 45 seconds */
+#define GSM411_TMR_TRAM		30, 0	/* 25 < x < 35 seconds */
+#define GSM411_TMR_TR2M		15, 0	/* 12 < x < 20 seconds */
+
+#define GSM411_TMR_TC1A		30, 0	/* TR1M - 10 */
+#define GSM411_TMR_TC1A_SEC	30	/* TR1M - 10 */
+
+/* Chapter 8.2.1 */
+struct gsm411_rp_hdr {
+	uint8_t len;
+	uint8_t msg_type;
+	uint8_t msg_ref;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+/* our own enum, not related to on-air protocol */
+enum sms_alphabet {
+	DCS_NONE,
+	DCS_7BIT_DEFAULT,
+	DCS_UCS2,
+	DCS_8BIT_DATA,
+};
+
+/* GSM 03.40 / Chapter 9.2.3.1: TP-Message-Type-Indicator */
+#define GSM340_SMS_DELIVER_SC2MS	0x00
+#define GSM340_SMS_DELIVER_REP_MS2SC	0x00
+#define GSM340_SMS_STATUS_REP_SC2MS	0x02
+#define GSM340_SMS_COMMAND_MS2SC	0x02
+#define GSM340_SMS_SUBMIT_MS2SC		0x01
+#define GSM340_SMS_SUBMIT_REP_SC2MS	0x01
+#define GSM340_SMS_RESSERVED		0x03
+
+/* GSM 03.40 / Chapter 9.2.3.2: TP-More-Messages-to-Send */
+#define GSM340_TP_MMS_MORE		0
+#define GSM340_TP_MMS_NO_MORE		1
+
+/* GSM 03.40 / Chapter 9.2.3.3: TP-Validity-Period-Format */
+#define GSM340_TP_VPF_NONE		0
+#define GSM340_TP_VPF_RELATIVE		2
+#define GSM340_TP_VPF_ENHANCED		1
+#define GSM340_TP_VPF_ABSOLUTE		3
+
+/* GSM 03.40 / Chapter 9.2.3.4: TP-Status-Report-Indication */
+#define GSM340_TP_SRI_NONE		0
+#define GSM340_TP_SRI_PRESENT		1
+
+/* GSM 03.40 / Chapter 9.2.3.5: TP-Status-Report-Request */
+#define GSM340_TP_SRR_NONE		0
+#define GSM340_TP_SRR_REQUESTED		1
+
+/* GSM 03.40 / Chapter 9.2.3.9: TP-Protocol-Identifier */
+/* telematic interworking (001 or 111 in bits 7-5) */
+#define GSM340_TP_PID_IMPLICIT		0x00
+#define GSM340_TP_PID_TELEX		0x01
+#define GSM340_TP_PID_FAX_G3		0x02
+#define GSM340_TP_PID_FAX_G4		0x03
+#define GSM340_TP_PID_VOICE		0x04
+#define GSM430_TP_PID_ERMES		0x05
+#define GSM430_TP_PID_NATIONAL_PAGING	0x06
+#define GSM430_TP_PID_VIDEOTEX		0x07
+#define GSM430_TP_PID_TELETEX_UNSPEC	0x08
+#define GSM430_TP_PID_TELETEX_PSPDN	0x09
+#define GSM430_TP_PID_TELETEX_CSPDN	0x0a
+#define GSM430_TP_PID_TELETEX_PSTN	0x0b
+#define GSM430_TP_PID_TELETEX_ISDN	0x0c
+#define GSM430_TP_PID_TELETEX_UCI	0x0d
+#define GSM430_TP_PID_MSG_HANDLING	0x10
+#define GSM430_TP_PID_MSG_X400		0x11
+#define GSM430_TP_PID_EMAIL		0x12
+#define GSM430_TP_PID_GSM_MS		0x1f
+/* if bit 7 = 0 and bit 6 = 1 */
+#define GSM430_TP_PID_SMS_TYPE_0	0
+#define GSM430_TP_PID_SMS_TYPE_1	1
+#define GSM430_TP_PID_SMS_TYPE_2	2
+#define GSM430_TP_PID_SMS_TYPE_3	3
+#define GSM430_TP_PID_SMS_TYPE_4	4
+#define GSM430_TP_PID_SMS_TYPE_5	5
+#define GSM430_TP_PID_SMS_TYPE_6	6
+#define GSM430_TP_PID_SMS_TYPE_7	7
+#define GSM430_TP_PID_RETURN_CALL_MSG	0x1f
+#define GSM430_TP_PID_ME_DATA_DNLOAD	0x3d
+#define GSM430_TP_PID_ME_DE_PERSONAL	0x3e
+#define GSM430_TP_PID_ME_SIM_DNLOAD	0x3f
+
+/* GSM 03.38 Chapter 4: SMS Data Coding Scheme */
+#define GSM338_DCS_00_
+
+#define GSM338_DCS_1110_7BIT		(0 << 2)
+#define GSM338_DCS_1111_7BIT		(0 << 2)
+#define GSM338_DCS_1111_8BIT_DATA	(1 << 2)
+#define GSM338_DCS_1111_CLASS0		0
+#define GSM338_DCS_1111_CLASS1_ME	1
+#define GSM338_DCS_1111_CLASS2_SIM	2
+#define GSM338_DCS_1111_CLASS3_TE	3	/* See TS 07.05 */
+
+#endif /* PROTO_GSM_04_11_H */
diff --git a/include/osmocom/gsm/protocol/gsm_04_12.h b/include/osmocom/gsm/protocol/gsm_04_12.h
new file mode 100644
index 0000000..9b1538a
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_04_12.h
@@ -0,0 +1,31 @@
+#ifndef PROTO_GSM_04_12_H
+#define PROTO_GSM_04_12_H
+
+#include <stdint.h>
+
+/* GSM TS 04.12 definitions for Short Message Service Cell Broadcast */
+
+#define GSM412_SEQ_FST_BLOCK		0x0
+#define GSM412_SEQ_SND_BLOCK		0x1
+#define GSM412_SEQ_TRD_BLOCK		0x2
+#define GSM412_SEQ_FTH_BLOCK		0x3
+#define GSM412_SEQ_FST_SCHED_BLOCK	0x8
+#define GSM412_SEQ_NULL_MSG		0xf
+
+struct gsm412_block_type {
+	uint8_t	seq_nr : 4,
+		lb : 1,
+		lpd : 2,
+		spare : 1;
+} __attribute__((packed));
+
+struct gsm412_sched_msg {
+	uint8_t beg_slot_nr : 6,
+		type : 2;
+	uint8_t end_slot_nr : 6,
+		spare1 : 1, spare2: 1;
+	uint8_t cbsms_msg_map[6];
+	uint8_t data[0];
+} __attribute__((packed));
+
+#endif
diff --git a/include/osmocom/gsm/protocol/gsm_04_80.h b/include/osmocom/gsm/protocol/gsm_04_80.h
new file mode 100644
index 0000000..fa5c945
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_04_80.h
@@ -0,0 +1,126 @@
+#ifndef PROTO_GSM_04_80_H
+#define PROTO_GSM_04_80_H
+
+/* GSM TS 04.80  definitions (Supplementary Services Specification, Formats and Coding) */
+
+/* Section 3.4 */
+#define GSM0480_MTYPE_RELEASE_COMPLETE	0x2A
+#define GSM0480_MTYPE_FACILITY			0x3A
+#define GSM0480_MTYPE_REGISTER			0x3B
+
+/* Section 3.5 */
+#define GSM0480_IE_FACILITY			0x1C
+#define GSM0480_IE_SS_VERSION			0x7F
+
+/* Section 3.6.2 */
+#define GSM0480_CTYPE_INVOKE			0xA1
+#define GSM0480_CTYPE_RETURN_RESULT		0xA2
+#define GSM0480_CTYPE_RETURN_ERROR		0xA3
+#define GSM0480_CTYPE_REJECT			0xA4
+
+/* Section 3.6.3 */
+#define GSM0480_COMPIDTAG_INVOKE_ID		0x02
+#define GSM0480_COMPIDTAG_LINKED_ID		0x80
+
+/* Section 3.6.4 */
+#define GSM0480_OPERATION_CODE			0x02
+
+/* Section 3.6.5 */
+#define GSM_0480_SEQUENCE_TAG			0x30
+#define GSM_0480_SET_TAG			0x31
+
+/* Section 3.6.6 */
+#define GSM_0480_ERROR_CODE_TAG			0x02
+
+/* Section 3.6.7 */
+/* Table 3.13 */
+#define GSM_0480_PROBLEM_CODE_TAG_GENERAL	0x80
+#define GSM_0480_PROBLEM_CODE_TAG_INVOKE	0x81
+#define GSM_0480_PROBLEM_CODE_TAG_RETURN_RESULT	0x82
+#define GSM_0480_PROBLEM_CODE_TAG_RETURN_ERROR	0x83
+
+/* Table 3.14 */
+#define GSM_0480_GEN_PROB_CODE_UNRECOGNISED	0x00
+#define GSM_0480_GEN_PROB_CODE_MISTYPED		0x01
+#define GSM_0480_GEN_PROB_CODE_BAD_STRUCTURE	0x02
+
+/* Table 3.15 */
+#define GSM_0480_INVOKE_PROB_CODE_DUPLICATE_INVOKE_ID		0x00
+#define GSM_0480_INVOKE_PROB_CODE_UNRECOGNISED_OPERATION	0x01
+#define GSM_0480_INVOKE_PROB_CODE_MISTYPED_PARAMETER		0x02
+#define GSM_0480_INVOKE_PROB_CODE_RESOURCE_LIMITATION		0x03
+#define GSM_0480_INVOKE_PROB_CODE_INITIATING_RELEASE		0x04
+#define GSM_0480_INVOKE_PROB_CODE_UNRECOGNISED_LINKED_ID	0x05
+#define GSM_0480_INVOKE_PROB_CODE_UNEXPECTED_LINKED_RESPONSE	0x06
+#define GSM_0480_INVOKE_PROB_CODE_UNEXPECTED_LINKED_OPERATION	0x07
+
+/* Table 3.16 */
+#define GSM_0480_RESULT_PROB_CODE_UNRECOGNISED_INVOKE_ID	0x00
+#define GSM_0480_RESULT_PROB_CODE_RETURN_RESULT_UNEXPECTED	0x01
+#define GSM_0480_RESULT_PROB_CODE_MISTYPED_PARAMETER		0x02
+
+/* Table 3.17 */
+#define GSM_0480_ERROR_PROB_CODE_UNRECOGNISED_INVOKE_ID		0x00
+#define GSM_0480_ERROR_PROB_CODE_RETURN_ERROR_UNEXPECTED	0x01
+#define GSM_0480_ERROR_PROB_CODE_UNRECOGNISED_ERROR		0x02
+#define GSM_0480_ERROR_PROB_CODE_UNEXPECTED_ERROR		0x03
+#define GSM_0480_ERROR_PROB_CODE_MISTYPED_PARAMETER		0x04
+
+/* Section 4.5 */
+#define GSM0480_OP_CODE_REGISTER_SS		0x0A
+#define GSM0480_OP_CODE_ERASE_SS		0x0B
+#define GSM0480_OP_CODE_ACTIVATE_SS		0x0C
+#define GSM0480_OP_CODE_DEACTIVATE_SS		0x0D
+#define GSM0480_OP_CODE_INTERROGATE_SS		0x0E
+#define GSM0480_OP_CODE_NOTIFY_SS		0x10
+#define GSM0480_OP_CODE_REGISTER_PASSWORD	0x11
+#define GSM0480_OP_CODE_GET_PASSWORD		0x12
+#define GSM0480_OP_CODE_PROCESS_USS_DATA	0x13
+#define GSM0480_OP_CODE_FORWARD_CHECK_SS_IND	0x26
+#define GSM0480_OP_CODE_PROCESS_USS_REQ		0x3B
+#define GSM0480_OP_CODE_USS_REQUEST		0x3C
+#define GSM0480_OP_CODE_USS_NOTIFY		0x3D
+#define GSM0480_OP_CODE_FORWARD_CUG_INFO	0x78
+#define GSM0480_OP_CODE_SPLIT_MPTY		0x79
+#define GSM0480_OP_CODE_RETRIEVE_MPTY		0x7A
+#define GSM0480_OP_CODE_HOLD_MPTY		0x7B
+#define GSM0480_OP_CODE_BUILD_MPTY		0x7C
+#define GSM0480_OP_CODE_FORWARD_CHARGE_ADVICE	0x7D
+
+#define GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER			0x01
+#define GSM0480_ERR_CODE_ILLEGAL_SUBSCRIBER			0x09
+#define GSM0480_ERR_CODE_BEARER_SERVICE_NOT_PROVISIONED		0x0A
+#define GSM0480_ERR_CODE_TELESERVICE_NOT_PROVISIONED		0x0B
+#define GSM0480_ERR_CODE_ILLEGAL_EQUIPMENT			0x0C
+#define GSM0480_ERR_CODE_CALL_BARRED				0x0D
+#define GSM0480_ERR_CODE_ILLEGAL_SS_OPERATION			0x10
+#define GSM0480_ERR_CODE_SS_ERROR_STATUS			0x11
+#define GSM0480_ERR_CODE_SS_NOT_AVAILABLE			0x12
+#define GSM0480_ERR_CODE_SS_SUBSCRIPTION_VIOLATION		0x13
+#define GSM0480_ERR_CODE_SS_INCOMPATIBILITY			0x14
+#define GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED			0x15
+#define GSM0480_ERR_CODE_ABSENT_SUBSCRIBER			0x1B
+#define GSM0480_ERR_CODE_SYSTEM_FAILURE				0x22
+#define GSM0480_ERR_CODE_DATA_MISSING				0x23
+#define GSM0480_ERR_CODE_UNEXPECTED_DATA_VALUE			0x24
+#define GSM0480_ERR_CODE_PW_REGISTRATION_FAILURE		0x25
+#define GSM0480_ERR_CODE_NEGATIVE_PW_CHECK			0x26
+#define GSM0480_ERR_CODE_NUM_PW_ATTEMPTS_VIOLATION		0x2B
+#define GSM0480_ERR_CODE_UNKNOWN_ALPHABET			0x47
+#define GSM0480_ERR_CODE_USSD_BUSY				0x48
+#define GSM0480_ERR_CODE_MAX_MPTY_PARTICIPANTS			0x7E
+#define GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE		0x7F
+
+/* ASN.1 type-tags */
+#define ASN1_BOOLEAN_TAG		0x01
+#define ASN1_INTEGER_TAG		0x02
+#define ASN1_BIT_STRING_TAG		0x03
+#define ASN1_OCTET_STRING_TAG		0x04
+#define ASN1_NULL_TYPE_TAG		0x05
+#define ASN1_OBJECT_ID_TAG		0x06
+#define ASN1_UTF8_STRING_TAG		0x0C
+#define ASN1_PRINTABLE_STRING_TAG	0x13
+#define ASN1_IA5_STRING_TAG		0x16
+#define ASN1_UNICODE_STRING_TAG		0x1E
+
+#endif /* PROTO_GSM_04_80_H */
diff --git a/include/osmocom/gsm/protocol/gsm_08_08.h b/include/osmocom/gsm/protocol/gsm_08_08.h
new file mode 100644
index 0000000..6b8f935
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -0,0 +1,303 @@
+/* From GSM08.08 */
+
+#ifndef GSM_0808_H
+#define GSM_0808_H
+
+#include <stdlib.h>
+
+/*
+ * this is from GSM 03.03 CGI but is copied in GSM 08.08
+ * in § 3.2.2.27 for Cell Identifier List
+ */
+enum CELL_IDENT {
+	CELL_IDENT_WHOLE_GLOBAL		= 0,
+	CELL_IDENT_LAC_AND_CI		= 1,
+	CELL_IDENT_CI			= 2,
+	CELL_IDENT_NO_CELL		= 3,
+	CELL_IDENT_LAI_AND_LAC		= 4,
+	CELL_IDENT_LAC			= 5,
+	CELL_IDENT_BSS			= 6,
+	CELL_IDENT_UTRAN_PLMN_LAC_RNC	= 8,
+	CELL_IDENT_UTRAN_RNC		= 9,
+	CELL_IDENT_UTRAN_LAC_RNC	= 10,
+};
+
+
+/* GSM 08.06 § 6.3 */
+enum BSSAP_MSG_TYPE {
+	BSSAP_MSG_BSS_MANAGEMENT    = 0x0,
+	BSSAP_MSG_DTAP		    = 0x1,
+};
+
+struct bssmap_header {
+	uint8_t type;
+	uint8_t length;
+} __attribute__((packed));
+
+struct dtap_header {
+	uint8_t type;
+	uint8_t link_id;
+	uint8_t length;
+} __attribute__((packed));
+
+
+enum BSS_MAP_MSG_TYPE {
+	BSS_MAP_MSG_RESERVED_0		= 0,
+
+	/* ASSIGNMENT MESSAGES */
+	BSS_MAP_MSG_ASSIGMENT_RQST	= 1,
+	BSS_MAP_MSG_ASSIGMENT_COMPLETE	= 2,
+	BSS_MAP_MSG_ASSIGMENT_FAILURE	= 3,
+
+	/*  HANDOVER MESSAGES */
+	BSS_MAP_MSG_HANDOVER_RQST		= 16,
+	BSS_MAP_MSG_HANDOVER_REQUIRED		= 17,
+	BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE= 18,
+	BSS_MAP_MSG_HANDOVER_CMD		= 19,
+	BSS_MAP_MSG_HANDOVER_COMPLETE		= 20,
+	BSS_MAP_MSG_HANDOVER_SUCCEEDED		= 21,
+	BSS_MAP_MSG_HANDOVER_FAILURE		= 22,
+	BSS_MAP_MSG_HANDOVER_PERFORMED		= 23,
+	BSS_MAP_MSG_HANDOVER_CANDIDATE_ENQUIRE	= 24,
+	BSS_MAP_MSG_HANDOVER_CANDIDATE_RESPONSE	= 25,
+	BSS_MAP_MSG_HANDOVER_REQUIRED_REJECT	= 26,
+	BSS_MAP_MSG_HANDOVER_DETECT		= 27,
+
+	/* RELEASE MESSAGES */
+	BSS_MAP_MSG_CLEAR_CMD		= 32,
+	BSS_MAP_MSG_CLEAR_COMPLETE		= 33,
+	BSS_MAP_MSG_CLEAR_RQST		= 34,
+	BSS_MAP_MSG_RESERVED_1			= 35,
+	BSS_MAP_MSG_RESERVED_2			= 36,
+	BSS_MAP_MSG_SAPI_N_REJECT		= 37,
+	BSS_MAP_MSG_CONFUSION			= 38,
+
+	/* OTHER CONNECTION RELATED MESSAGES */
+	BSS_MAP_MSG_SUSPEND			= 40,
+	BSS_MAP_MSG_RESUME			= 41,
+	BSS_MAP_MSG_CONNECTION_ORIENTED_INFORMATION = 42,
+	BSS_MAP_MSG_PERFORM_LOCATION_RQST	= 43,
+	BSS_MAP_MSG_LSA_INFORMATION		= 44,
+	BSS_MAP_MSG_PERFORM_LOCATION_RESPONSE	= 45,
+	BSS_MAP_MSG_PERFORM_LOCATION_ABORT	= 46,
+	BSS_MAP_MSG_COMMON_ID			= 47,
+
+	/* GENERAL MESSAGES */
+	BSS_MAP_MSG_RESET			= 48,
+	BSS_MAP_MSG_RESET_ACKNOWLEDGE		= 49,
+	BSS_MAP_MSG_OVERLOAD			= 50,
+	BSS_MAP_MSG_RESERVED_3			= 51,
+	BSS_MAP_MSG_RESET_CIRCUIT		= 52,
+	BSS_MAP_MSG_RESET_CIRCUIT_ACKNOWLEDGE	= 53,
+	BSS_MAP_MSG_MSC_INVOKE_TRACE		= 54,
+	BSS_MAP_MSG_BSS_INVOKE_TRACE		= 55,
+	BSS_MAP_MSG_CONNECTIONLESS_INFORMATION	= 58,
+
+	/* TERRESTRIAL RESOURCE MESSAGES */
+	BSS_MAP_MSG_BLOCK			= 64,
+	BSS_MAP_MSG_BLOCKING_ACKNOWLEDGE	= 65,
+	BSS_MAP_MSG_UNBLOCK			= 66,
+	BSS_MAP_MSG_UNBLOCKING_ACKNOWLEDGE	= 67,
+	BSS_MAP_MSG_CIRCUIT_GROUP_BLOCK		= 68,
+	BSS_MAP_MSG_CIRCUIT_GROUP_BLOCKING_ACKNOWLEDGE	= 69,
+	BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCK	= 70,
+	BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCKING_ACKNOWLEDGE = 71,
+	BSS_MAP_MSG_UNEQUIPPED_CIRCUIT		= 72,
+	BSS_MAP_MSG_CHANGE_CIRCUIT		= 78,
+	BSS_MAP_MSG_CHANGE_CIRCUIT_ACKNOWLEDGE	= 79,
+
+	/* RADIO RESOURCE MESSAGES */
+	BSS_MAP_MSG_RESOURCE_RQST		= 80,
+	BSS_MAP_MSG_RESOURCE_INDICATION		= 81,
+	BSS_MAP_MSG_PAGING			= 82,
+	BSS_MAP_MSG_CIPHER_MODE_CMD		= 83,
+	BSS_MAP_MSG_CLASSMARK_UPDATE		= 84,
+	BSS_MAP_MSG_CIPHER_MODE_COMPLETE	= 85,
+	BSS_MAP_MSG_QUEUING_INDICATION		= 86,
+	BSS_MAP_MSG_COMPLETE_LAYER_3		= 87,
+	BSS_MAP_MSG_CLASSMARK_RQST		= 88,
+	BSS_MAP_MSG_CIPHER_MODE_REJECT		= 89,
+	BSS_MAP_MSG_LOAD_INDICATION		= 90,
+
+	/* VGCS/VBS */
+	BSS_MAP_MSG_VGCS_VBS_SETUP		= 4,
+	BSS_MAP_MSG_VGCS_VBS_SETUP_ACK		= 5,
+	BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE	= 6,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST	= 7,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT	= 28,
+	BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE	= 29,
+	BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION	= 30,
+	BSS_MAP_MSG_UPLINK_RQST		= 31,
+	BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE	= 39,
+	BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION	= 73,
+	BSS_MAP_MSG_UPLINK_RELEASE_INDICATION	= 74,
+	BSS_MAP_MSG_UPLINK_REJECT_CMD	= 75,
+	BSS_MAP_MSG_UPLINK_RELEASE_CMD	= 76,
+	BSS_MAP_MSG_UPLINK_SEIZED_CMD	= 77,
+};
+
+enum GSM0808_IE_CODING {
+	GSM0808_IE_CIRCUIT_IDENTITY_CODE	= 1,
+	GSM0808_IE_RESERVED_0			= 2,
+	GSM0808_IE_RESOURCE_AVAILABLE		= 3,
+	GSM0808_IE_CAUSE			= 4,
+	GSM0808_IE_CELL_IDENTIFIER		= 5,
+	GSM0808_IE_PRIORITY			= 6,
+	GSM0808_IE_LAYER_3_HEADER_INFORMATION	= 7,
+	GSM0808_IE_IMSI				= 8,
+	GSM0808_IE_TMSI				= 9,
+	GSM0808_IE_ENCRYPTION_INFORMATION	= 10,
+	GSM0808_IE_CHANNEL_TYPE			= 11,
+	GSM0808_IE_PERIODICITY			= 12,
+	GSM0808_IE_EXTENDED_RESOURCE_INDICATOR	= 13,
+	GSM0808_IE_NUMBER_OF_MSS		= 14,
+	GSM0808_IE_RESERVED_1			= 15,
+	GSM0808_IE_RESERVED_2			= 16,
+	GSM0808_IE_RESERVED_3			= 17,
+	GSM0808_IE_CLASSMARK_INFORMATION_T2	= 18,
+	GSM0808_IE_CLASSMARK_INFORMATION_T3	= 19,
+	GSM0808_IE_INTERFERENCE_BAND_TO_USE	= 20,
+	GSM0808_IE_RR_CAUSE			= 21,
+	GSM0808_IE_RESERVED_4			= 22,
+	GSM0808_IE_LAYER_3_INFORMATION		= 23,
+	GSM0808_IE_DLCI				= 24,
+	GSM0808_IE_DOWNLINK_DTX_FLAG		= 25,
+	GSM0808_IE_CELL_IDENTIFIER_LIST		= 26,
+	GSM0808_IE_RESPONSE_RQST		= 27,
+	GSM0808_IE_RESOURCE_INDICATION_METHOD	= 28,
+	GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1	= 29,
+	GSM0808_IE_CIRCUIT_IDENTITY_CODE_LIST	= 30,
+	GSM0808_IE_DIAGNOSTIC			= 31,
+	GSM0808_IE_LAYER_3_MESSAGE_CONTENTS	= 32,
+	GSM0808_IE_CHOSEN_CHANNEL		= 33,
+	GSM0808_IE_TOTAL_RESOURCE_ACCESSIBLE	= 34,
+	GSM0808_IE_CIPHER_RESPONSE_MODE		= 35,
+	GSM0808_IE_CHANNEL_NEEDED		= 36,
+	GSM0808_IE_TRACE_TYPE			= 37,
+	GSM0808_IE_TRIGGERID			= 38,
+	GSM0808_IE_TRACE_REFERENCE		= 39,
+	GSM0808_IE_TRANSACTIONID		= 40,
+	GSM0808_IE_MOBILE_IDENTITY		= 41,
+	GSM0808_IE_OMCID			= 42,
+	GSM0808_IE_FORWARD_INDICATOR		= 43,
+	GSM0808_IE_CHOSEN_ENCR_ALG		= 44,
+	GSM0808_IE_CIRCUIT_POOL			= 45,
+	GSM0808_IE_CIRCUIT_POOL_LIST		= 46,
+	GSM0808_IE_TIME_INDICATION		= 47,
+	GSM0808_IE_RESOURCE_SITUATION		= 48,
+	GSM0808_IE_CURRENT_CHANNEL_TYPE_1	= 49,
+	GSM0808_IE_QUEUEING_INDICATOR		= 50,
+	GSM0808_IE_SPEECH_VERSION		= 64,
+	GSM0808_IE_ASSIGNMENT_REQUIREMENT	= 51,
+	GSM0808_IE_TALKER_FLAG			= 53,
+	GSM0808_IE_CONNECTION_RELEASE_RQSTED	= 54,
+	GSM0808_IE_GROUP_CALL_REFERENCE		= 55,
+	GSM0808_IE_EMLPP_PRIORITY		= 56,
+	GSM0808_IE_CONFIG_EVO_INDI		= 57,
+	GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION	= 58,
+	GSM0808_IE_LSA_IDENTIFIER		= 59,
+	GSM0808_IE_LSA_IDENTIFIER_LIST		= 60,
+	GSM0808_IE_LSA_INFORMATION		= 61,
+	GSM0808_IE_LCS_QOS			= 62,
+	GSM0808_IE_LSA_ACCESS_CTRL_SUPPR	= 63,
+	GSM0808_IE_LCS_PRIORITY			= 67,
+	GSM0808_IE_LOCATION_TYPE		= 68,
+	GSM0808_IE_LOCATION_ESTIMATE		= 69,
+	GSM0808_IE_POSITIONING_DATA		= 70,
+	GSM0808_IE_LCS_CAUSE			= 71,
+	GSM0808_IE_LCS_CLIENT_TYPE		= 72,
+	GSM0808_IE_APDU				= 73,
+	GSM0808_IE_NETWORK_ELEMENT_IDENTITY	= 74,
+	GSM0808_IE_GPS_ASSISTANCE_DATA		= 75,
+	GSM0808_IE_DECIPHERING_KEYS		= 76,
+	GSM0808_IE_RETURN_ERROR_RQST		= 77,
+	GSM0808_IE_RETURN_ERROR_CAUSE		= 78,
+	GSM0808_IE_SEGMENTATION			= 79,
+	GSM0808_IE_SERVICE_HANDOVER		= 80,
+	GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_UMTS	= 81,
+	GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_CDMA2000= 82,
+	GSM0808_IE_RESERVED_5			= 65,
+	GSM0808_IE_RESERVED_6			= 66,
+};
+
+enum gsm0808_cause {
+	GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE			= 0,
+	GSM0808_CAUSE_RADIO_INTERFACE_FAILURE				= 1,
+	GSM0808_CAUSE_UPLINK_QUALITY					= 2,
+	GSM0808_CAUSE_UPLINK_STRENGTH					= 3,
+	GSM0808_CAUSE_DOWNLINK_QUALITY					= 4,
+	GSM0808_CAUSE_DOWNLINK_STRENGTH					= 5,
+	GSM0808_CAUSE_DISTANCE						= 6,
+	GSM0808_CAUSE_O_AND_M_INTERVENTION				= 7,
+	GSM0808_CAUSE_RESPONSE_TO_MSC_INVOCATION			= 8,
+	GSM0808_CAUSE_CALL_CONTROL					= 9,
+	GSM0808_CAUSE_RADIO_INTERFACE_FAILURE_REVERSION			= 10,
+	GSM0808_CAUSE_HANDOVER_SUCCESSFUL				= 11,
+	GSM0808_CAUSE_BETTER_CELL					= 12,
+	GSM0808_CAUSE_DIRECTED_RETRY					= 13,
+	GSM0808_CAUSE_JOINED_GROUP_CALL_CHANNEL				= 14,
+	GSM0808_CAUSE_TRAFFIC						= 15,
+	GSM0808_CAUSE_EQUIPMENT_FAILURE					= 32,
+	GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE			= 33,
+	GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE	= 34,
+	GSM0808_CAUSE_CCCH_OVERLOAD					= 35,
+	GSM0808_CAUSE_PROCESSOR_OVERLOAD				= 36,
+	GSM0808_CAUSE_BSS_NOT_EQUIPPED					= 37,
+	GSM0808_CAUSE_MS_NOT_EQUIPPED					= 38,
+	GSM0808_CAUSE_INVALID_CELL					= 39,
+	GSM0808_CAUSE_TRAFFIC_LOAD					= 40,
+	GSM0808_CAUSE_PREEMPTION					= 41,
+	GSM0808_CAUSE_RQSTED_TRANSCODING_RATE_ADAPTION_UNAVAILABLE	= 48,
+	GSM0808_CAUSE_CIRCUIT_POOL_MISMATCH				= 49,
+	GSM0808_CAUSE_SWITCH_CIRCUIT_POOL				= 50,
+	GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE		= 51,
+	GSM0808_CAUSE_LSA_NOT_ALLOWED					= 52,
+	GSM0808_CAUSE_CIPHERING_ALGORITHM_NOT_SUPPORTED			= 64,
+	GSM0808_CAUSE_TERRESTRIAL_CIRCUIT_ALREADY_ALLOCATED		= 80,
+	GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS				= 81,
+	GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING		= 82,
+	GSM0808_CAUSE_INCORRECT_VALUE					= 83,
+	GSM0808_CAUSE_UNKNOWN_MESSAGE_TYPE				= 84,
+	GSM0808_CAUSE_UNKNOWN_INFORMATION_ELEMENT			= 85,
+	GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC		= 96,
+};
+
+/* GSM 08.08 3.2.2.11 Channel Type */
+enum gsm0808_chan_indicator {
+	GSM0808_CHAN_SPEECH = 1,
+	GSM0808_CHAN_DATA   = 2,
+	GSM0808_CHAN_SIGN   = 3,
+};
+
+enum gsm0808_chan_rate_type_data {
+	GSM0808_DATA_FULL_BM	= 0x8,
+	GSM0808_DATA_HALF_LM	= 0x9,
+	GSM0808_DATA_FULL_RPREF	= 0xa,
+	GSM0808_DATA_HALF_PREF	= 0xb,
+	GSM0808_DATA_FULL_PREF_NO_CHANGE	= 0x1a,
+	GSM0808_DATA_HALF_PREF_NO_CHANGE	= 0x1b,
+	GSM0808_DATA_MULTI_MASK	= 0x20,
+	GSM0808_DATA_MULTI_MASK_NO_CHANGE	= 0x30,
+};
+
+enum gsm0808_chan_rate_type_speech {
+	GSM0808_SPEECH_FULL_BM	= 0x8,
+	GSM0808_SPEECH_HALF_LM	= 0x9,
+	GSM0808_SPEECH_FULL_PREF= 0xa,
+	GSM0808_SPEECH_HALF_PREF= 0xb,
+	GSM0808_SPEECH_FULL_PREF_NO_CHANGE	= 0x1a,
+	GSM0808_SPEECH_HALF_PREF_NO_CHANGE	= 0x1b,
+	GSM0808_SPEECH_PERM	= 0xf,
+	GSM0808_SPEECH_PERM_NO_CHANGE = 0x1f,
+};
+
+enum gsm0808_permitted_speech {
+	GSM0808_PERM_FR1	= 0x01,
+	GSM0808_PERM_FR2	= 0x11,
+	GSM0808_PERM_FR3	= 0x21,
+	GSM0808_PERM_HR1	= GSM0808_PERM_FR1 | 0x4,
+	GSM0808_PERM_HR2	= GSM0808_PERM_FR2 | 0x4,
+	GSM0808_PERM_HR3	= GSM0808_PERM_FR3 | 0x4,
+};
+
+#endif
diff --git a/include/osmocom/gsm/protocol/gsm_08_58.h b/include/osmocom/gsm/protocol/gsm_08_58.h
new file mode 100644
index 0000000..10c201d
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_08_58.h
@@ -0,0 +1,561 @@
+#ifndef PROTO_GSM_08_58_H
+#define PROTO_GSM_08_58_H
+
+/* GSM Radio Signalling Link messages on the A-bis interface 
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+/*! \addtogroup rsl
+ *  @{
+ */
+
+/*! \file gsm_08_58.h */
+
+/*! \brief RSL common header */
+struct abis_rsl_common_hdr {
+	uint8_t	msg_discr;	/*!< \brief message discriminator (ABIS_RSL_MDISC_*) */
+	uint8_t	msg_type;	/*!< \brief message type (\ref abis_rsl_msgtype) */
+	uint8_t	data[0];	/*!< \brief actual payload data */
+} __attribute__ ((packed));
+
+/* \brief RSL RLL header (Chapter 8.3) */
+struct abis_rsl_rll_hdr {
+	struct abis_rsl_common_hdr c;
+	uint8_t	ie_chan;	/*!< \brief \ref RSL_IE_CHAN_NR (tag) */
+	uint8_t	chan_nr;	/*!< \brief RSL channel number (value) */
+	uint8_t	ie_link_id;	/*!< \brief \ref RSL_IE_LINK_IDENT (tag) */
+	uint8_t	link_id;	/*!< \brief RSL link identifier (value) */
+	uint8_t	data[0];	/*!< \brief message payload data */
+} __attribute__ ((packed));
+
+/* \brief RSL Dedicated Channel header (Chapter 8.3 and 8.4) */
+struct abis_rsl_dchan_hdr {
+	struct abis_rsl_common_hdr c;
+	uint8_t	ie_chan;	/*!< \brief \ref RSL_IE_CHAN_NR (tag) */
+	uint8_t	chan_nr;	/*!< \brief RSL channel number (value) */
+	uint8_t	data[0];	/*!< \brief message payload data */
+} __attribute__ ((packed));
+
+/* \brief RSL Common Channel header (Chapter 8.5) */
+struct abis_rsl_cchan_hdr {
+	struct abis_rsl_common_hdr c;
+	uint8_t	ie_chan;	/*!< \brief \ref RSL_IE_CHAN_NR (tag) */
+	uint8_t	chan_nr;	/*!< \brief RSL channel number (value) */
+	uint8_t	data[0];	/*!< \brief message payload data */
+} __attribute__ ((packed));
+
+
+/* Chapter 9.1 */
+/* \brief RSL Message Discriminator: RLL */
+#define ABIS_RSL_MDISC_RLL		0x02
+/* \brief RSL Message Discriminator: Dedicated Channel */
+#define ABIS_RSL_MDISC_DED_CHAN		0x08
+/* \brief RSL Message Discriminator: Common Channel */
+#define ABIS_RSL_MDISC_COM_CHAN		0x0c
+/* \brief RSL Message Discriminator: TRX Management */
+#define ABIS_RSL_MDISC_TRX		0x10
+/* \brief RSL Message Discriminator: Location Service */
+#define ABIS_RSL_MDISC_LOC		0x20
+/* \brief RSL Message Discriminator: ip.access */
+#define ABIS_RSL_MDISC_IPACCESS		0x7e
+#define ABIS_RSL_MDISC_TRANSP		0x01
+
+/* \brief Check if given RSL message discriminator is transparent */
+#define ABIS_RSL_MDISC_IS_TRANSP(x)	(x & 0x01)
+
+/* \brief RSL Message Tyoe (Chapter 9.1) */
+enum abis_rsl_msgtype {
+	/* Radio Link Layer Management */
+	RSL_MT_DATA_REQ			= 0x01,
+	RSL_MT_DATA_IND,
+	RSL_MT_ERROR_IND,
+	RSL_MT_EST_REQ,
+	RSL_MT_EST_CONF,
+	RSL_MT_EST_IND,
+	RSL_MT_REL_REQ,
+	RSL_MT_REL_CONF,
+	RSL_MT_REL_IND,
+	RSL_MT_UNIT_DATA_REQ,
+	RSL_MT_UNIT_DATA_IND,		/* 0x0b */
+	RSL_MT_SUSP_REQ,		/* non-standard elements */
+	RSL_MT_SUSP_CONF,
+	RSL_MT_RES_REQ,
+	RSL_MT_RECON_REQ,		/* 0x0f */
+
+	/* Common Channel Management / TRX Management */
+	RSL_MT_BCCH_INFO			= 0x11,
+	RSL_MT_CCCH_LOAD_IND,
+	RSL_MT_CHAN_RQD,
+	RSL_MT_DELETE_IND,
+	RSL_MT_PAGING_CMD,
+	RSL_MT_IMMEDIATE_ASSIGN_CMD,
+	RSL_MT_SMS_BC_REQ,
+	RSL_MT_CHAN_CONF,		/* non-standard element */
+	/* empty */
+	RSL_MT_RF_RES_IND			= 0x19,
+	RSL_MT_SACCH_FILL,
+	RSL_MT_OVERLOAD,
+	RSL_MT_ERROR_REPORT,
+	RSL_MT_SMS_BC_CMD,
+	RSL_MT_CBCH_LOAD_IND,
+	RSL_MT_NOT_CMD,			/* 0x1f */
+
+	/* Dedicate Channel Management */
+	RSL_MT_CHAN_ACTIV			= 0x21,
+	RSL_MT_CHAN_ACTIV_ACK,
+	RSL_MT_CHAN_ACTIV_NACK,
+	RSL_MT_CONN_FAIL,
+	RSL_MT_DEACTIVATE_SACCH,
+	RSL_MT_ENCR_CMD,
+	RSL_MT_HANDO_DET,
+	RSL_MT_MEAS_RES,
+	RSL_MT_MODE_MODIFY_REQ,
+	RSL_MT_MODE_MODIFY_ACK,
+	RSL_MT_MODE_MODIFY_NACK,
+	RSL_MT_PHY_CONTEXT_REQ,
+	RSL_MT_PHY_CONTEXT_CONF,
+	RSL_MT_RF_CHAN_REL,
+	RSL_MT_MS_POWER_CONTROL,
+	RSL_MT_BS_POWER_CONTROL,		/* 0x30 */
+	RSL_MT_PREPROC_CONFIG,
+	RSL_MT_PREPROC_MEAS_RES,
+	RSL_MT_RF_CHAN_REL_ACK,
+	RSL_MT_SACCH_INFO_MODIFY,
+	RSL_MT_TALKER_DET,
+	RSL_MT_LISTENER_DET,
+	RSL_MT_REMOTE_CODEC_CONF_REP,
+	RSL_MT_RTD_REP,
+	RSL_MT_PRE_HANDO_NOTIF,
+	RSL_MT_MR_CODEC_MOD_REQ,
+	RSL_MT_MR_CODEC_MOD_ACK,
+	RSL_MT_MR_CODEC_MOD_NACK,
+	RSL_MT_MR_CODEC_MOD_PER,
+	RSL_MT_TFO_REP,
+	RSL_MT_TFO_MOD_REQ,		/* 0x3f */
+	RSL_MT_LOCATION_INFO		= 0x41,
+
+	/* ip.access specific RSL message types */
+	RSL_MT_IPAC_DIR_RETR_ENQ	= 0x40,
+	RSL_MT_IPAC_PDCH_ACT		= 0x48,
+	RSL_MT_IPAC_PDCH_ACT_ACK,
+	RSL_MT_IPAC_PDCH_ACT_NACK,
+	RSL_MT_IPAC_PDCH_DEACT		= 0x4b,
+	RSL_MT_IPAC_PDCH_DEACT_ACK,
+	RSL_MT_IPAC_PDCH_DEACT_NACK,
+	RSL_MT_IPAC_CONNECT_MUX		= 0x50,
+	RSL_MT_IPAC_CONNECT_MUX_ACK,
+	RSL_MT_IPAC_CONNECT_MUX_NACK,
+	RSL_MT_IPAC_BIND_MUX		= 0x53,
+	RSL_MT_IPAC_BIND_MUX_ACK,
+	RSL_MT_IPAC_BIND_MUX_NACK,
+	RSL_MT_IPAC_DISC_MUX		= 0x56,
+	RSL_MT_IPAC_DISC_MUX_ACK,
+	RSL_MT_IPAC_DISC_MUX_NACK,
+	RSL_MT_IPAC_CRCX		= 0x70,		/* Bind to local BTS RTP port */
+	RSL_MT_IPAC_CRCX_ACK,
+	RSL_MT_IPAC_CRCX_NACK,
+	RSL_MT_IPAC_MDCX		= 0x73,
+	RSL_MT_IPAC_MDCX_ACK,
+	RSL_MT_IPAC_MDCX_NACK,
+	RSL_MT_IPAC_DLCX_IND		= 0x76,
+	RSL_MT_IPAC_DLCX		= 0x77,
+	RSL_MT_IPAC_DLCX_ACK,
+	RSL_MT_IPAC_DLCX_NACK,
+};
+
+/*! \brief Siemens vendor-specific RSL message types */
+enum abis_rsl_msgtype_siemens {
+	RSL_MT_SIEMENS_MRPCI		= 0x41,
+	RSL_MT_SIEMENS_INTRAC_HO_COND_IND = 0x42,
+	RSL_MT_SIEMENS_INTERC_HO_COND_IND = 0x43,
+	RSL_MT_SIEMENS_FORCED_HO_REQ	= 0x44,
+	RSL_MT_SIEMENS_PREF_AREA_REQ	= 0x45,
+	RSL_MT_SIEMENS_PREF_AREA	= 0x46,
+	RSL_MT_SIEMENS_START_TRACE	= 0x47,
+	RSL_MT_SIEMENS_START_TRACE_ACK	= 0x48,
+	RSL_MT_SIEMENS_STOP_TRACE	= 0x49,
+	RSL_MT_SIEMENS_TRMR		= 0x4a,
+	RSL_MT_SIEMENS_HO_FAIL_IND	= 0x4b,
+	RSL_MT_SIEMENS_STOP_TRACE_ACK	= 0x4c,
+	RSL_MT_SIEMENS_UPLF		= 0x4d,
+	RSL_MT_SIEMENS_UPLB		= 0x4e,
+	RSL_MT_SIEMENS_SET_SYS_INFO_10	= 0x4f,
+	RSL_MT_SIEMENS_MODIF_COND_IND	= 0x50,
+};
+
+/*! \brief RSL Information Element Identifiers (Chapter 9.3) */
+enum abis_rsl_ie {
+	RSL_IE_CHAN_NR			= 0x01,
+	RSL_IE_LINK_IDENT,
+	RSL_IE_ACT_TYPE,
+	RSL_IE_BS_POWER,
+	RSL_IE_CHAN_IDENT,
+	RSL_IE_CHAN_MODE,
+	RSL_IE_ENCR_INFO,
+	RSL_IE_FRAME_NUMBER,
+	RSL_IE_HANDO_REF,
+	RSL_IE_L1_INFO,
+	RSL_IE_L3_INFO,
+	RSL_IE_MS_IDENTITY,
+	RSL_IE_MS_POWER,
+	RSL_IE_PAGING_GROUP,
+	RSL_IE_PAGING_LOAD,
+	RSL_IE_PYHS_CONTEXT		= 0x10,
+	RSL_IE_ACCESS_DELAY,
+	RSL_IE_RACH_LOAD,
+	RSL_IE_REQ_REFERENCE,
+	RSL_IE_RELEASE_MODE,
+	RSL_IE_RESOURCE_INFO,
+	RSL_IE_RLM_CAUSE,
+	RSL_IE_STARTNG_TIME,
+	RSL_IE_TIMING_ADVANCE,
+	RSL_IE_UPLINK_MEAS,
+	RSL_IE_CAUSE,
+	RSL_IE_MEAS_RES_NR,
+	RSL_IE_MSG_ID,
+	/* reserved */
+	RSL_IE_SYSINFO_TYPE		= 0x1e,
+	RSL_IE_MS_POWER_PARAM,
+	RSL_IE_BS_POWER_PARAM,
+	RSL_IE_PREPROC_PARAM,
+	RSL_IE_PREPROC_MEAS,
+	RSL_IE_IMM_ASS_INFO,		/* Phase 1 (3.6.0), later Full below */
+	RSL_IE_SMSCB_INFO		= 0x24,
+	RSL_IE_MS_TIMING_OFFSET,
+	RSL_IE_ERR_MSG,
+	RSL_IE_FULL_BCCH_INFO,
+	RSL_IE_CHAN_NEEDED,
+	RSL_IE_CB_CMD_TYPE,
+	RSL_IE_SMSCB_MSG,
+	RSL_IE_FULL_IMM_ASS_INFO,
+	RSL_IE_SACCH_INFO,
+	RSL_IE_CBCH_LOAD_INFO,
+	RSL_IE_SMSCB_CHAN_INDICATOR,
+	RSL_IE_GROUP_CALL_REF,
+	RSL_IE_CHAN_DESC		= 0x30,
+	RSL_IE_NCH_DRX_INFO,
+	RSL_IE_CMD_INDICATOR,
+	RSL_IE_EMLPP_PRIO,
+	RSL_IE_UIC,
+	RSL_IE_MAIN_CHAN_REF,
+	RSL_IE_MR_CONFIG,
+	RSL_IE_MR_CONTROL,
+	RSL_IE_SUP_CODEC_TYPES,
+	RSL_IE_CODEC_CONFIG,
+	RSL_IE_RTD,
+	RSL_IE_TFO_STATUS,
+	RSL_IE_LLP_APDU,
+	/* Siemens vendor-specific */
+	RSL_IE_SIEMENS_MRPCI		= 0x40,
+	RSL_IE_SIEMENS_PREF_AREA_TYPE	= 0x43,
+	RSL_IE_SIEMENS_ININ_CELL_HO_PAR	= 0x45,
+	RSL_IE_SIEMENS_TRACE_REF_NR	= 0x46,
+	RSL_IE_SIEMENS_INT_TRACE_IDX	= 0x47,
+	RSL_IE_SIEMENS_L2_HDR_INFO	= 0x48,
+	RSL_IE_SIEMENS_HIGHEST_RATE	= 0x4e,
+	RSL_IE_SIEMENS_SUGGESTED_RATE	= 0x4f,
+
+	/* ip.access */
+	RSL_IE_IPAC_SRTP_CONFIG	= 0xe0,
+	RSL_IE_IPAC_PROXY_UDP	= 0xe1,
+	RSL_IE_IPAC_BSCMPL_TOUT	= 0xe2,
+	RSL_IE_IPAC_REMOTE_IP	= 0xf0,
+	RSL_IE_IPAC_REMOTE_PORT	= 0xf1,
+	RSL_IE_IPAC_RTP_PAYLOAD	= 0xf2,
+	RSL_IE_IPAC_LOCAL_PORT	= 0xf3,
+	RSL_IE_IPAC_SPEECH_MODE	= 0xf4,
+	RSL_IE_IPAC_LOCAL_IP	= 0xf5,
+	RSL_IE_IPAC_CONN_STAT	= 0xf6,
+	RSL_IE_IPAC_HO_C_PARMS	= 0xf7,
+	RSL_IE_IPAC_CONN_ID	= 0xf8,
+	RSL_IE_IPAC_RTP_CSD_FMT	= 0xf9,
+	RSL_IE_IPAC_RTP_JIT_BUF	= 0xfa,
+	RSL_IE_IPAC_RTP_COMPR	= 0xfb,
+	RSL_IE_IPAC_RTP_PAYLOAD2= 0xfc,
+	RSL_IE_IPAC_RTP_MPLEX	= 0xfd,
+	RSL_IE_IPAC_RTP_MPLEX_ID= 0xfe,
+};
+
+/* Chapter 9.3.1 */
+#define RSL_CHAN_NR_MASK	0xf8
+#define RSL_CHAN_Bm_ACCHs	0x08
+#define RSL_CHAN_Lm_ACCHs	0x10	/* .. 0x18 */
+#define RSL_CHAN_SDCCH4_ACCH	0x20	/* .. 0x38 */
+#define RSL_CHAN_SDCCH8_ACCH	0x40	/* ...0x78 */
+#define RSL_CHAN_BCCH		0x80
+#define RSL_CHAN_RACH		0x88
+#define RSL_CHAN_PCH_AGCH	0x90
+
+/* Chapter 9.3.3 */
+#define RSL_ACT_TYPE_INITIAL	0x00
+#define RSL_ACT_TYPE_REACT	0x80
+#define RSL_ACT_INTRA_IMM_ASS	0x00
+#define RSL_ACT_INTRA_NORM_ASS	0x01
+#define RSL_ACT_INTER_ASYNC	0x02
+#define RSL_ACT_INTER_SYNC	0x03
+#define RSL_ACT_SECOND_ADD	0x04
+#define RSL_ACT_SECOND_MULTI	0x05
+
+/*! \brief RSL Channel Mode IF (Chapter 9.3.6) */
+struct rsl_ie_chan_mode {
+	uint8_t dtx_dtu;
+	uint8_t spd_ind;
+	uint8_t chan_rt;
+	uint8_t chan_rate;
+} __attribute__ ((packed));
+#define RSL_CMOD_DTXu		0x01	/* uplink */
+#define RSL_CMOD_DTXd		0x02	/* downlink */
+enum rsl_cmod_spd {
+	RSL_CMOD_SPD_SPEECH	= 0x01,
+	RSL_CMOD_SPD_DATA	= 0x02,
+	RSL_CMOD_SPD_SIGN	= 0x03,
+};
+#define RSL_CMOD_CRT_SDCCH	0x01
+#define RSL_CMOD_CRT_TCH_Bm	0x08	/* full-rate */
+#define RSL_CMOD_CRT_TCH_Lm	0x09	/* half-rate */
+/* FIXME: More CRT types */
+/* Speech */
+#define RSL_CMOD_SP_GSM1	0x01
+#define RSL_CMOD_SP_GSM2	0x11
+#define RSL_CMOD_SP_GSM3	0x21
+/* Data */
+#define RSL_CMOD_SP_NT_14k5	0x58
+#define RSL_CMOD_SP_NT_12k0	0x50
+#define RSL_CMOD_SP_NT_6k0	0x51
+
+/*! \brief RSL Channel Identification IE (Chapter 9.3.5) */
+struct rsl_ie_chan_ident {
+	/* GSM 04.08 10.5.2.5 */
+	struct {
+		uint8_t iei;
+		uint8_t chan_nr;	/* enc_chan_nr */
+		uint8_t oct3;
+		uint8_t oct4;
+	} chan_desc;
+#if 0	/* spec says we need this but Abissim doesn't use it */
+	struct {
+		uint8_t tag;
+		uint8_t len;
+	} mobile_alloc;
+#endif
+} __attribute__ ((packed));
+
+/* Chapter 9.3.22 */
+#define RLL_CAUSE_T200_EXPIRED		0x01
+#define RLL_CAUSE_REEST_REQ		0x02
+#define RLL_CAUSE_UNSOL_UA_RESP		0x03
+#define RLL_CAUSE_UNSOL_DM_RESP		0x04
+#define RLL_CAUSE_UNSOL_DM_RESP_MF	0x05
+#define RLL_CAUSE_UNSOL_SPRV_RESP	0x06
+#define RLL_CAUSE_SEQ_ERR		0x07
+#define RLL_CAUSE_UFRM_INC_PARAM	0x08
+#define RLL_CAUSE_SFRM_INC_PARAM	0x09
+#define RLL_CAUSE_IFRM_INC_MBITS	0x0a
+#define RLL_CAUSE_IFRM_INC_LEN		0x0b
+#define RLL_CAUSE_FRM_UNIMPL		0x0c
+#define RLL_CAUSE_SABM_MF		0x0d
+#define RLL_CAUSE_SABM_INFO_NOTALL	0x0e
+
+/* Chapter 9.3.26 */
+#define RSL_ERRCLS_NORMAL		0x00
+#define RSL_ERRCLS_RESOURCE_UNAVAIL	0x20
+#define RSL_ERRCLS_SERVICE_UNAVAIL	0x30
+#define RSL_ERRCLS_SERVICE_UNIMPL	0x40
+#define RSL_ERRCLS_INVAL_MSG		0x50
+#define RSL_ERRCLS_PROTO_ERROR		0x60
+#define RSL_ERRCLS_INTERWORKING		0x70
+
+/* normal event */
+#define RSL_ERR_RADIO_IF_FAIL		0x00
+#define RSL_ERR_RADIO_LINK_FAIL		0x01
+#define RSL_ERR_HANDOVER_ACC_FAIL	0x02
+#define RSL_ERR_TALKER_ACC_FAIL		0x03
+#define RSL_ERR_OM_INTERVENTION		0x07
+#define RSL_ERR_NORMAL_UNSPEC		0x0f
+#define RSL_ERR_T_MSRFPCI_EXP		0x18
+/* resource unavailable */
+#define RSL_ERR_EQUIPMENT_FAIL		0x20
+#define RSL_ERR_RR_UNAVAIL		0x21
+#define RSL_ERR_TERR_CH_FAIL		0x22
+#define RSL_ERR_CCCH_OVERLOAD		0x23
+#define RSL_ERR_ACCH_OVERLOAD		0x24
+#define RSL_ERR_PROCESSOR_OVERLOAD	0x25
+#define RSL_ERR_RES_UNAVAIL		0x2f
+/* service or option not available */
+#define RSL_ERR_TRANSC_UNAVAIL		0x30
+#define RSL_ERR_SERV_OPT_UNAVAIL	0x3f
+/* service or option not implemented */
+#define RSL_ERR_ENCR_UNIMPL		0x40
+#define RSL_ERR_SERV_OPT_UNIMPL		0x4f
+/* invalid message */
+#define RSL_ERR_RCH_ALR_ACTV_ALLOC	0x50
+#define RSL_ERR_INVALID_MESSAGE		0x5f
+/* protocol error */
+#define RSL_ERR_MSG_DISCR		0x60
+#define RSL_ERR_MSG_TYPE		0x61
+#define RSL_ERR_MSG_SEQ			0x62
+#define RSL_ERR_IE_ERROR		0x63
+#define RSL_ERR_MAND_IE_ERROR		0x64
+#define RSL_ERR_OPT_IE_ERROR		0x65
+#define RSL_ERR_IE_NONEXIST		0x66
+#define RSL_ERR_IE_LENGTH		0x67
+#define RSL_ERR_IE_CONTENT		0x68
+#define RSL_ERR_PROTO			0x6f
+/* interworking */
+#define RSL_ERR_INTERWORKING		0x7f
+
+/* Chapter 9.3.30 */
+#define RSL_SYSTEM_INFO_8	0x00
+#define RSL_SYSTEM_INFO_1	0x01
+#define RSL_SYSTEM_INFO_2	0x02
+#define RSL_SYSTEM_INFO_3	0x03
+#define RSL_SYSTEM_INFO_4	0x04
+#define RSL_SYSTEM_INFO_5	0x05
+#define RSL_SYSTEM_INFO_6	0x06
+#define RSL_SYSTEM_INFO_7	0x07
+#define RSL_SYSTEM_INFO_16	0x08
+#define RSL_SYSTEM_INFO_17	0x09
+#define RSL_SYSTEM_INFO_2bis	0x0a
+#define RSL_SYSTEM_INFO_2ter	0x0b
+#define RSL_SYSTEM_INFO_5bis	0x0d
+#define RSL_SYSTEM_INFO_5ter	0x0e
+#define RSL_SYSTEM_INFO_10	0x0f
+#define RSL_EXT_MEAS_ORDER	0x47
+#define RSL_MEAS_INFO		0x48
+#define RSL_SYSTEM_INFO_13	0x28
+#define RSL_SYSTEM_INFO_2quater	0x29
+#define RSL_SYSTEM_INFO_9	0x2a
+#define RSL_SYSTEM_INFO_18	0x2b
+#define RSL_SYSTEM_INFO_19	0x2c
+#define RSL_SYSTEM_INFO_20	0x2d
+
+/* Chapter 9.3.40 */
+#define RSL_CHANNEED_ANY	0x00
+#define RSL_CHANNEED_SDCCH	0x01
+#define RSL_CHANNEED_TCH_F	0x02
+#define RSL_CHANNEED_TCH_ForH	0x03
+
+/*! \brief RSL Cell Broadcast Command (Chapter 9.3.45) */
+struct rsl_ie_cb_cmd_type {
+	uint8_t last_block:2;
+	uint8_t spare:1;
+	uint8_t def_bcast:1;
+	uint8_t command:4;
+} __attribute__ ((packed));
+/* ->command */
+#define RSL_CB_CMD_TYPE_NORMAL		0x00
+#define RSL_CB_CMD_TYPE_SCHEDULE	0x08
+#define RSL_CB_CMD_TYPE_DEFAULT		0x0e
+#define RSL_CB_CMD_TYPE_NULL		0x0f
+/* ->def_bcast */
+#define RSL_CB_CMD_DEFBCAST_NORMAL	0
+#define RSL_CB_CMD_DEFBCAST_NULL	1
+/* ->last_block */
+#define RSL_CB_CMD_LASTBLOCK_4		0
+#define RSL_CB_CMD_LASTBLOCK_1		1
+#define RSL_CB_CMD_LASTBLOCK_2		2
+#define RSL_CB_CMD_LASTBLOCK_3		3
+
+/* Chapter 3.3.2.3 Brocast control channel */
+/* CCCH-CONF, NC is not combined */
+#define RSL_BCCH_CCCH_CONF_1_NC	0x00
+#define RSL_BCCH_CCCH_CONF_1_C	0x01
+#define RSL_BCCH_CCCH_CONF_2_NC	0x02
+#define RSL_BCCH_CCCH_CONF_3_NC	0x04
+#define RSL_BCCH_CCCH_CONF_4_NC	0x06
+
+/* BS-PA-MFRMS */
+#define RSL_BS_PA_MFRMS_2	0x00
+#define RSL_BS_PA_MFRMS_3	0x01
+#define RSL_BS_PA_MFRMS_4	0x02
+#define RSL_BS_PA_MFRMS_5	0x03
+#define RSL_BS_PA_MFRMS_6	0x04
+#define RSL_BS_PA_MFRMS_7	0x05
+#define RSL_BS_PA_MFRMS_8	0x06
+#define RSL_BS_PA_MFRMS_9	0x07
+
+/* RSL_IE_IPAC_RTP_PAYLOAD[2] */
+enum rsl_ipac_rtp_payload {
+	RSL_IPAC_RTP_GSM	= 1,
+	RSL_IPAC_RTP_EFR,
+	RSL_IPAC_RTP_AMR,
+	RSL_IPAC_RTP_CSD,
+	RSL_IPAC_RTP_MUX,
+};
+
+/* RSL_IE_IPAC_SPEECH_MODE, lower four bits */
+enum rsl_ipac_speech_mode_s {
+	RSL_IPAC_SPEECH_GSM_FR = 0,	/* GSM FR (Type 1, FS) */
+	RSL_IPAC_SPEECH_GSM_EFR = 1,	/* GSM EFR (Type 2, FS) */
+	RSL_IPAC_SPEECH_GSM_AMR_FR = 2,	/* GSM AMR/FR (Type 3, FS) */
+	RSL_IPAC_SPEECH_GSM_HR = 3,	/* GSM HR (Type 1, HS) */
+	RSL_IPAC_SPEECH_GSM_AMR_HR = 5,	/* GSM AMR/hr (Type 3, HS) */
+	RSL_IPAC_SPEECH_AS_RTP = 0xf,	/* As specified by RTP Payload IE */
+};
+/* RSL_IE_IPAC_SPEECH_MODE, upper four bits */
+enum rsl_ipac_speech_mode_m {
+	RSL_IPAC_SPEECH_M_RXTX = 0,	/* Send and Receive */
+	RSL_IPAC_SPEECH_M_RX = 1,	/* Receive only */
+	RSL_IPAC_SPEECH_M_TX = 2,	/* Send only */
+};
+
+/* RSL_IE_IPAC_RTP_CSD_FMT, lower four bits */
+enum rsl_ipac_rtp_csd_format_d {
+	RSL_IPAC_RTP_CSD_EXT_TRAU = 0,
+	RSL_IPAC_RTP_CSD_NON_TRAU = 1,
+	RSL_IPAC_RTP_CSD_TRAU_BTS = 2,
+	RSL_IPAC_RTP_CSD_IWF_FREE = 3,
+};
+/* RSL_IE_IPAC_RTP_CSD_FMT, upper four bits */
+enum rsl_ipac_rtp_csd_format_ir {
+	RSL_IPAC_RTP_CSD_IR_8k = 0,
+	RSL_IPAC_RTP_CSD_IR_16k = 1,
+	RSL_IPAC_RTP_CSD_IR_32k = 2,
+	RSL_IPAC_RTP_CSD_IR_64k = 3,
+};
+
+/* Siemens vendor-specific RSL extensions */
+struct rsl_mrpci {
+	uint8_t power_class:3,
+		 vgcs_capable:1,
+		 vbs_capable:1,
+		 gsm_phase:2;
+} __attribute__ ((packed));
+
+enum rsl_mrpci_pwrclass {
+	RSL_MRPCI_PWRC_1	= 0,
+	RSL_MRPCI_PWRC_2	= 1,
+	RSL_MRPCI_PWRC_3	= 2,
+	RSL_MRPCI_PWRC_4	= 3,
+	RSL_MRPCI_PWRC_5	= 4,
+};
+enum rsl_mrpci_phase {
+	RSL_MRPCI_PHASE_1	= 0,
+	/* reserved */
+	RSL_MRPCI_PHASE_2	= 2,
+	RSL_MRPCI_PHASE_2PLUS	= 3,
+};
+
+/*! @} */
+
+#endif /* PROTO_GSM_08_58_H */
diff --git a/include/osmocom/gsm/protocol/gsm_12_21.h b/include/osmocom/gsm/protocol/gsm_12_21.h
new file mode 100644
index 0000000..694df93
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_12_21.h
@@ -0,0 +1,748 @@
+#ifndef PROTO_GSM_12_21_H
+#define PROTO_GSM_12_21_H
+
+/* GSM Network Management messages on the A-bis interface 
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup oml
+ *  @{
+ */
+
+/*! \file gsm_12_21.h */
+
+#include <stdint.h>
+#include <osmocom/gsm/tlv.h>
+
+/*! \brief generic header in front of every OML message according to TS 08.59 */
+struct abis_om_hdr {
+	/*! \brief Message Discriminator \ref abis_oml_mdisc */
+	uint8_t	mdisc;
+	/*! \brief Placement (like \ref ABIS_OM_PLACEMENT_ONLY) */
+	uint8_t	placement;
+	/*! \brief Sequence Number (if \ref ABIS_OM_PLACEMENT_MIDDLE) */
+	uint8_t	sequence;
+	/*! \brief Length in octets */
+	uint8_t	length;
+	/*! \brief actual payload data */
+	uint8_t	data[0];
+} __attribute__ ((packed));
+
+/*! \brief Message Discriminator for Formatted Object Messages */
+#define ABIS_OM_MDISC_FOM		0x80
+/*! \brief Message Discriminator for Man Machine Interface */
+#define ABIS_OM_MDISC_MMI		0x40
+/*! \brief Message Discriminator for TRAU management */
+#define ABIS_OM_MDISC_TRAU		0x20
+/*! \brief Message Discriminator for Manufacturer Specific Messages */
+#define ABIS_OM_MDISC_MANUF		0x10
+
+/*! \brief Entire OML message is in the L2 frame */
+#define ABIS_OM_PLACEMENT_ONLY		0x80
+/*! \brief First fragment of OML message is in this L2 frame */
+#define ABIS_OM_PLACEMENT_FIRST 	0x40
+/*! \brief Middle fragment of OML message is in this L2 frame */
+#define ABIS_OM_PLACEMENT_MIDDLE	0x20
+/*! \brief Last fragment of OML message is in this L2 frame */
+#define ABIS_OM_PLACEMENT_LAST		0x10
+
+/*! \brief OML Object Instance */
+struct abis_om_obj_inst {
+	uint8_t	bts_nr;	/*!< \brief BTS Number */
+	uint8_t	trx_nr;	/*!< \brief TRX Number */
+	uint8_t	ts_nr;	/*!< \brief Timeslot Number */
+} __attribute__ ((packed));
+
+/*! \brief OML Object Instance */
+struct abis_om_fom_hdr {
+	uint8_t	msg_type;	/*!< \brief Message Type (\ref abis_nm_msgtype) */
+	uint8_t	obj_class;	/*!< \brief Object Class (\ref abis_nm_obj_class) */
+	struct abis_om_obj_inst	obj_inst; /*!< \brief Object Instance */
+	uint8_t	data[0];	/*!< \brief Data */
+} __attribute__ ((packed));
+
+/*! \brief Size of the OML FOM header in octets */
+#define ABIS_OM_FOM_HDR_SIZE	(sizeof(struct abis_om_hdr) + sizeof(struct abis_om_fom_hdr))
+
+/*! \brief OML Message Type (Section 9.1) */
+enum abis_nm_msgtype {
+	/* SW Download Management Messages */
+	NM_MT_LOAD_INIT			= 0x01,
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ,		/* BTS->BSC */
+	NM_MT_SW_ACT_REQ_ACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW,		/* BSC->BTS */
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,		/* 0x10 */
+	/* A-bis Interface Management Messages */
+	NM_MT_ESTABLISH_TEI		= 0x21,
+	NM_MT_ESTABLISH_TEI_ACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN,
+	NM_MT_CONN_TERR_SIGN_ACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN,
+	NM_MT_DISC_TERR_SIGN_ACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF,
+	NM_MT_CONN_TERR_TRAF_ACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF,
+	NM_MT_DISC_TERR_TRAF_ACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	/* Transmission Management Messages */
+	NM_MT_CONN_MDROP_LINK		= 0x31,
+	NM_MT_CONN_MDROP_LINK_ACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK,
+	NM_MT_DISC_MDROP_LINK_ACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	/* Air Interface Management Messages */
+	NM_MT_SET_BTS_ATTR		= 0x41,
+	NM_MT_SET_BTS_ATTR_ACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR,
+	NM_MT_SET_RADIO_ATTR_ACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR,
+	NM_MT_SET_CHAN_ATTR_ACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	/* Test Management Messages */
+	NM_MT_PERF_TEST			= 0x51,
+	NM_MT_PERF_TEST_ACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_TEST_REP,
+	NM_MT_SEND_TEST_REP,
+	NM_MT_SEND_TEST_REP_ACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST,
+	NM_MT_STOP_TEST_ACK,
+	NM_MT_STOP_TEST_NACK,
+	/* State Management and Event Report Messages */
+	NM_MT_STATECHG_EVENT_REP	= 0x61,
+	NM_MT_FAILURE_EVENT_REP,
+	NM_MT_STOP_EVENT_REP,
+	NM_MT_STOP_EVENT_REP_ACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP,
+	NM_MT_REST_EVENT_REP_ACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE,
+	NM_MT_CHG_ADM_STATE_ACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ,
+	NM_MT_CHG_ADM_STATE_REQ_ACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS		= 0x93,
+	NM_MT_REP_OUTST_ALARMS_ACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	/* Equipment Management Messages */
+	NM_MT_CHANGEOVER		= 0x71,
+	NM_MT_CHANGEOVER_ACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART,
+	NM_MT_OPSTART_ACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT,
+	NM_MT_REINIT_ACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT,		/* BS11: get alarm ?!? */
+	NM_MT_SET_SITE_OUT_ACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF		= 0x90,
+	NM_MT_CHG_HW_CONF_ACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	/* Measurement Management Messages */
+	NM_MT_MEAS_RES_REQ		= 0x8a,
+	NM_MT_MEAS_RES_RESP,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+	/* Other Messages */
+	NM_MT_GET_ATTR			= 0x81,
+	NM_MT_GET_ATTR_RESP,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES,
+	NM_MT_SET_ALARM_THRES_ACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+};
+
+/*! \brief Siemens specific OML Message Types */
+enum abis_nm_msgtype_bs11 {
+	NM_MT_BS11_RESET_RESOURCE	= 0x74,
+
+	NM_MT_BS11_BEGIN_DB_TX		= 0xa3,
+	NM_MT_BS11_BEGIN_DB_TX_ACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX		= 0xa6,
+	NM_MT_BS11_END_DB_TX_ACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ		= 0xa9,
+	NM_MT_BS11_CREATE_OBJ_ACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ		= 0xac,
+	NM_MT_BS11_DELETE_OBJ_ACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+
+	NM_MT_BS11_SET_ATTR		= 0xd0,
+	NM_MT_BS11_SET_ATTR_ACK,
+	NM_MT_BS11_SET_ATTR_NACK,
+	NM_MT_BS11_LMT_SESSION		= 0xdc,
+
+	NM_MT_BS11_GET_STATE		= 0xe3,
+	NM_MT_BS11_GET_STATE_ACK,
+	NM_MT_BS11_LMT_LOGON		= 0xe5,
+	NM_MT_BS11_LMT_LOGON_ACK,
+	NM_MT_BS11_RESTART		= 0xe7,
+	NM_MT_BS11_RESTART_ACK,
+	NM_MT_BS11_DISCONNECT		= 0xe9,
+	NM_MT_BS11_DISCONNECT_ACK,
+	NM_MT_BS11_LMT_LOGOFF		= 0xec,
+	NM_MT_BS11_LMT_LOGOFF_ACK,
+	NM_MT_BS11_RECONNECT		= 0xf1,
+	NM_MT_BS11_RECONNECT_ACK,
+};
+
+/*! \brief ip.access specific OML Message Types */
+enum abis_nm_msgtype_ipacc {
+	NM_MT_IPACC_RESTART		= 0x87,
+	NM_MT_IPACC_RESTART_ACK,
+	NM_MT_IPACC_RESTART_NACK,
+	NM_MT_IPACC_RSL_CONNECT		= 0xe0,
+	NM_MT_IPACC_RSL_CONNECT_ACK,
+	NM_MT_IPACC_RSL_CONNECT_NACK,
+	NM_MT_IPACC_RSL_DISCONNECT	= 0xe3,
+	NM_MT_IPACC_RSL_DISCONNECT_ACK,
+	NM_MT_IPACC_RSL_DISCONNECT_NACK,
+	NM_MT_IPACC_CONN_TRAF		= 0xe6,
+	NM_MT_IPACC_CONN_TRAF_ACK,
+	NM_MT_IPACC_CONN_TRAF_NACK,
+	NM_MT_IPACC_DEF_BOOT_SW		= 0xec,
+	NM_MT_IPACC_DEF_BOOT_SW_ACK,
+	MN_MT_IPACC_DEF_BOOT_SW_NACK,
+	NM_MT_IPACC_SET_NVATTR		= 0xef,
+	NM_MT_IPACC_SET_NVATTR_ACK,
+	NM_MT_IPACC_SET_NVATTR_NACK,
+	NM_MT_IPACC_GET_NVATTR		= 0xf2,
+	NM_MT_IPACC_GET_NVATTR_ACK,
+	NM_MT_IPACC_GET_NVATTR_NACK,
+	NM_MT_IPACC_SET_ATTR		= 0xf5,
+	NM_MT_IPACC_SET_ATTR_ACK,
+	NM_MT_IPACC_SET_ATTR_NACK,
+};
+
+enum abis_nm_bs11_cell_alloc {
+	NM_BS11_CANR_GSM	= 0x00,
+	NM_BS11_CANR_DCS1800	= 0x01,
+};
+
+/*! \brief OML Object Class (Section 9.2) */
+enum abis_nm_obj_class {
+	NM_OC_SITE_MANAGER		= 0x00,
+	NM_OC_BTS,
+	NM_OC_RADIO_CARRIER,
+	NM_OC_CHANNEL,
+	NM_OC_BASEB_TRANSC,
+	/* RFU: 05-FE */
+
+	NM_OC_IPAC_E1_TRUNK		= 0x0e,
+	NM_OC_IPAC_E1_PORT		= 0x0f,
+	NM_OC_IPAC_E1_CHAN		= 0x10,
+	NM_OC_IPAC_CLK_MODULE		= 0x22,
+
+	NM_OC_BS11_ADJC			= 0xa0,
+	NM_OC_BS11_HANDOVER		= 0xa1,
+	NM_OC_BS11_PWR_CTRL		= 0xa2,
+	NM_OC_BS11_BTSE			= 0xa3,		/* LMT? */
+	NM_OC_BS11_RACK			= 0xa4,
+	NM_OC_BS11			= 0xa5,		/* 01: ALCO */
+	NM_OC_BS11_TEST			= 0xa6,
+	NM_OC_BS11_ENVABTSE		= 0xa8,
+	NM_OC_BS11_BPORT		= 0xa9,
+
+	NM_OC_GPRS_NSE			= 0xf0,
+	NM_OC_GPRS_CELL			= 0xf1,
+	NM_OC_GPRS_NSVC			= 0xf2,
+
+	NM_OC_NULL			= 0xff,
+};
+
+/*! \brief OML Attributes / IEs (Section 9.4) */
+enum abis_nm_attr {
+	NM_ATT_ABIS_CHANNEL	= 0x01,
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_ADM_STATE,
+	NM_ATT_ARFCN_LIST,
+	NM_ATT_AUTON_REPORT,
+	NM_ATT_AVAIL_STATUS,
+	NM_ATT_BCCH_ARFCN,
+	NM_ATT_BSIC,
+	NM_ATT_BTS_AIR_TIMER,
+	NM_ATT_CCCH_L_I_P,
+	NM_ATT_CCCH_L_T,
+	NM_ATT_CHAN_COMB,
+	NM_ATT_CONN_FAIL_CRIT,
+	NM_ATT_DEST,
+	/* res */
+	NM_ATT_EVENT_TYPE	= 0x11, /* BS11: file data ?!? */
+	NM_ATT_FILE_ID,
+	NM_ATT_FILE_VERSION,
+	NM_ATT_GSM_TIME,
+	NM_ATT_HSN,
+	NM_ATT_HW_CONFIG,
+	NM_ATT_HW_DESC,
+	NM_ATT_INTAVE_PARAM,
+	NM_ATT_INTERF_BOUND,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MAIO,
+	NM_ATT_MANUF_STATE,
+	NM_ATT_MANUF_THRESH,
+	NM_ATT_MANUF_ID,
+	NM_ATT_MAX_TA,
+	NM_ATT_MDROP_LINK,	/* 0x20 */
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_NY1,
+	NM_ATT_OPER_STATE,
+	NM_ATT_OVERL_PERIOD,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_POWER_CLASS,
+	NM_ATT_POWER_THRESH,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RACH_B_THRESH,
+	NM_ATT_LDAVG_SLOTS,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_RF_MAXPOWR_R,
+	NM_ATT_SITE_INPUTS,
+	NM_ATT_SITE_OUTPUTS,
+	NM_ATT_SOURCE,		/* 0x30 */
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_T200,
+	NM_ATT_TEI,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_VSWR_THRESH,
+	NM_ATT_WINDOW_SIZE,
+	/* Res  */
+	NM_ATT_BS11_RSSI_OFFS	= 0x3d,
+	NM_ATT_BS11_TXPWR	= 0x3e,
+	NM_ATT_BS11_DIVERSITY	= 0x3f,
+	/* Res  */
+	NM_ATT_TSC		= 0x40,
+	NM_ATT_SW_CONFIG,
+	NM_ATT_SW_DESCR,
+	NM_ATT_SEVERITY,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_FILE_DATA,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+
+	NM_ATT_BS11_ESN_FW_CODE_NO	= 0x4c,
+	NM_ATT_BS11_ESN_HW_CODE_NO	= 0x4f,
+
+	NM_ATT_BS11_ESN_PCB_SERIAL	= 0x55,
+	NM_ATT_BS11_EXCESSIVE_DISTANCE	= 0x58,
+
+	NM_ATT_BS11_ALL_TEST_CATG	= 0x60,
+	NM_ATT_BS11_BTSLS_HOPPING,
+	NM_ATT_BS11_CELL_ALLOC_NR,
+	NM_ATT_BS11_CELL_GLOBAL_ID,
+	NM_ATT_BS11_ENA_INTERF_CLASS	= 0x66,
+	NM_ATT_BS11_ENA_INT_INTEC_HANDO	= 0x67,
+	NM_ATT_BS11_ENA_INT_INTRC_HANDO	= 0x68,
+	NM_ATT_BS11_ENA_MS_PWR_CTRL	= 0x69,
+	NM_ATT_BS11_ENA_PWR_BDGT_HO	= 0x6a,
+	NM_ATT_BS11_ENA_PWR_CTRL_RLFW	= 0x6b,
+	NM_ATT_BS11_ENA_RXLEV_HO	= 0x6c,
+	NM_ATT_BS11_ENA_RXQUAL_HO	= 0x6d,
+	NM_ATT_BS11_FACCH_QUAL		= 0x6e,
+
+	NM_ATT_IPACC_DST_IP		= 0x80,
+	NM_ATT_IPACC_DST_IP_PORT	= 0x81,
+	NM_ATT_IPACC_SSRC		= 0x82,
+	NM_ATT_IPACC_RTP_PAYLD_TYPE	= 0x83,
+	NM_ATT_IPACC_BASEB_ID		= 0x84,
+	NM_ATT_IPACC_STREAM_ID		= 0x85,
+	NM_ATT_IPACC_NV_FLAGS		= 0x86,
+	NM_ATT_IPACC_FREQ_CTRL		= 0x87,
+	NM_ATT_IPACC_PRIM_OML_CFG	= 0x88,
+	NM_ATT_IPACC_SEC_OML_CFG	= 0x89,
+	NM_ATT_IPACC_IP_IF_CFG		= 0x8a,		/* IP interface */
+	NM_ATT_IPACC_IP_GW_CFG		= 0x8b,		/* IP gateway */
+	NM_ATT_IPACC_IN_SERV_TIME	= 0x8c,
+	NM_ATT_IPACC_TRX_BTS_ASS	= 0x8d,
+	NM_ATT_IPACC_LOCATION		= 0x8e,		/* string describing location */
+	NM_ATT_IPACC_PAGING_CFG		= 0x8f,
+	NM_ATT_IPACC_FILE_DATA		= 0x90,
+	NM_ATT_IPACC_UNIT_ID		= 0x91,		/* Site/BTS/TRX */
+	NM_ATT_IPACC_PARENT_UNIT_ID	= 0x92,
+	NM_ATT_IPACC_UNIT_NAME		= 0x93,		/* default: nbts-<mac-as-string> */
+	NM_ATT_IPACC_SNMP_CFG		= 0x94,
+	NM_ATT_IPACC_PRIM_OML_CFG_LIST	= 0x95,
+	NM_ATT_IPACC_PRIM_OML_FB_TOUT	= 0x96,
+	NM_ATT_IPACC_CUR_SW_CFG		= 0x97,
+	NM_ATT_IPACC_TIMING_BUS		= 0x98,
+	NM_ATT_IPACC_CGI		= 0x99,
+	NM_ATT_IPACC_RAC		= 0x9a,
+	NM_ATT_IPACC_OBJ_VERSION	= 0x9b,
+	NM_ATT_IPACC_GPRS_PAGING_CFG	= 0x9c,
+	NM_ATT_IPACC_NSEI		= 0x9d,
+	NM_ATT_IPACC_BVCI		= 0x9e,
+	NM_ATT_IPACC_NSVCI		= 0x9f,
+	NM_ATT_IPACC_NS_CFG		= 0xa0,
+	NM_ATT_IPACC_BSSGP_CFG		= 0xa1,
+	NM_ATT_IPACC_NS_LINK_CFG	= 0xa2,
+	NM_ATT_IPACC_RLC_CFG		= 0xa3,	
+	NM_ATT_IPACC_ALM_THRESH_LIST	= 0xa4,
+	NM_ATT_IPACC_MONIT_VAL_LIST	= 0xa5,
+	NM_ATT_IPACC_TIB_CONTROL	= 0xa6,
+	NM_ATT_IPACC_SUPP_FEATURES	= 0xa7,
+	NM_ATT_IPACC_CODING_SCHEMES	= 0xa8,
+	NM_ATT_IPACC_RLC_CFG_2		= 0xa9,
+	NM_ATT_IPACC_HEARTB_TOUT	= 0xaa,
+	NM_ATT_IPACC_UPTIME		= 0xab,
+	NM_ATT_IPACC_RLC_CFG_3		= 0xac,
+	NM_ATT_IPACC_SSL_CFG		= 0xad,
+	NM_ATT_IPACC_SEC_POSSIBLE	= 0xae,
+	NM_ATT_IPACC_IML_SSL_STATE	= 0xaf,
+	NM_ATT_IPACC_REVOC_DATE		= 0xb0,
+
+
+	NM_ATT_BS11_RF_RES_IND_PER	= 0x8f,
+	
+	NM_ATT_BS11_RX_LEV_MIN_CELL	= 0x90,
+	NM_ATT_BS11_ABIS_EXT_TIME	= 0x91,
+	NM_ATT_BS11_TIMER_HO_REQUEST	= 0x92,
+	NM_ATT_BS11_TIMER_NCELL		= 0x93,
+	NM_ATT_BS11_TSYNC		= 0x94,
+	NM_ATT_BS11_TTRAU		= 0x95,
+	NM_ATT_BS11_EMRG_CFG_MEMBER	= 0x9b,
+	NM_ATT_BS11_TRX_AREA		= 0x9f,
+
+	NM_ATT_BS11_BCCH_RECONF		= 0xd7,
+	NM_ATT_BS11_BIT_ERR_THESH	= 0xa0,
+	NM_ATT_BS11_BOOT_SW_VERS	= 0xa1,
+	NM_ATT_BS11_CCLK_ACCURACY	= 0xa3,
+	NM_ATT_BS11_CCLK_TYPE		= 0xa4,
+	NM_ATT_BS11_INP_IMPEDANCE	= 0xaa,
+	NM_ATT_BS11_L1_PROT_TYPE	= 0xab,
+	NM_ATT_BS11_LINE_CFG		= 0xac,
+	NM_ATT_BS11_LI_PORT_1		= 0xad,
+	NM_ATT_BS11_LI_PORT_2		= 0xae,
+
+	NM_ATT_BS11_L1_REM_ALM_TYPE	= 0xb0,
+	NM_ATT_BS11_SW_LOAD_INTENDED	= 0xbb,
+	NM_ATT_BS11_SW_LOAD_SAFETY	= 0xbc,
+	NM_ATT_BS11_SW_LOAD_STORED	= 0xbd,
+
+	NM_ATT_BS11_VENDOR_NAME		= 0xc1,
+	NM_ATT_BS11_HOPPING_MODE	= 0xc5,
+	NM_ATT_BS11_LMT_LOGON_SESSION	= 0xc6,
+	NM_ATT_BS11_LMT_LOGIN_TIME	= 0xc7,
+	NM_ATT_BS11_LMT_USER_ACC_LEV	= 0xc8,
+	NM_ATT_BS11_LMT_USER_NAME	= 0xc9,
+
+	NM_ATT_BS11_L1_CONTROL_TS	= 0xd8,
+	NM_ATT_BS11_RADIO_MEAS_GRAN	= 0xdc,	/* in SACCH multiframes */
+	NM_ATT_BS11_RADIO_MEAS_REP	= 0xdd,
+
+	NM_ATT_BS11_SH_LAPD_INT_TIMER	= 0xe8,
+
+	NM_ATT_BS11_BTS_STATE		= 0xf0,
+	NM_ATT_BS11_E1_STATE		= 0xf1,
+	NM_ATT_BS11_PLL			= 0xf2,
+	NM_ATT_BS11_RX_OFFSET		= 0xf3,
+	NM_ATT_BS11_ANT_TYPE		= 0xf4,
+	NM_ATT_BS11_PLL_MODE		= 0xfc,
+	NM_ATT_BS11_PASSWORD		= 0xfd,
+};
+#define NM_ATT_BS11_FILE_DATA	NM_ATT_EVENT_TYPE
+
+/*! \brief OML Administrative State (Section 9.4.4) */
+enum abis_nm_adm_state {
+	NM_STATE_LOCKED		= 0x01,
+	NM_STATE_UNLOCKED	= 0x02,
+	NM_STATE_SHUTDOWN	= 0x03,
+	NM_STATE_NULL		= 0xff,
+};
+
+/*! \brief OML Availability State (Section 9.4.7) */
+enum abis_nm_avail_state {
+	NM_AVSTATE_IN_TEST	= 1,
+	NM_AVSTATE_POWER_OFF	= 2,
+	NM_AVSTATE_OFF_LINE	= 3,
+	NM_AVSTATE_DEPENDENCY	= 5,
+	NM_AVSTATE_DEGRADED	= 6,
+	NM_AVSTATE_NOT_INSTALLED= 7,
+	NM_AVSTATE_OK		= 0xff,
+};
+
+/*! \brief OML Operational State */
+enum abis_nm_op_state {
+	NM_OPSTATE_DISABLED	= 1,
+	NM_OPSTATE_ENABLED	= 2,
+	NM_OPSTATE_NULL		= 0xff,
+};
+
+/* \brief Channel Combination (Section 9.4.13) */
+enum abis_nm_chan_comb {
+	NM_CHANC_TCHFull	= 0x00,	/* TCH/F + TCH/H + SACCH/TF */
+	NM_CHANC_TCHHalf	= 0x01, /* TCH/H(0,1) + FACCH/H(0,1) +
+					   SACCH/TH(0,1) */
+	NM_CHANC_TCHHalf2	= 0x02, /* TCH/H(0) + FACCH/H(0) + SACCH/TH(0) +
+					   TCH/H(1) */
+	NM_CHANC_SDCCH		= 0x03,	/* SDCCH/8 + SACCH/8 */
+	NM_CHANC_mainBCCH	= 0x04,	/* FCCH + SCH + BCCH + CCCH */
+	NM_CHANC_BCCHComb	= 0x05,	/* FCCH + SCH + BCCH + CCCH + SDCCH/4 +
+					   SACCH/C4 */
+	NM_CHANC_BCCH		= 0x06,	/* BCCH + CCCH */
+	NM_CHANC_BCCH_CBCH	= 0x07,	/* CHANC_BCCHComb + CBCH */
+	NM_CHANC_SDCCH_CBCH	= 0x08,	/* CHANC_SDCCH8 + CBCH */
+	/* ip.access */
+	NM_CHANC_IPAC_bPDCH	= 0x0b,	/* PBCCH + PCCCH + PDTCH/F + PACCH/F +
+					   PTCCH/F */
+	NM_CHANC_IPAC_cPDCH	= 0x0c, /* PBCCH + PDTCH/F + PACCH/F + PTCCH/F */
+	NM_CHANC_IPAC_PDCH	= 0x0d,	/* PDTCH/F + PACCH/F + PTCCH/F */
+	NM_CHANC_IPAC_TCHFull_PDCH = 0x80,
+	NM_CHANC_IPAC_TCHFull_TCHHalf = 0x81,
+};
+
+/*! \brief Event Type (Section 9.4.16) */
+enum abis_nm_event_type {
+	NM_EVT_COMM_FAIL	= 0x00,
+	NM_EVT_QOS_FAIL		= 0x01,
+	NM_EVT_PROC_FAIL	= 0x02,
+	NM_EVT_EQUIP_FAIL	= 0x03,
+	NM_EVT_ENV_FAIL		= 0x04,
+};
+
+/*! \brief Perceived Severity (Section: 9.4.63) */
+enum abis_nm_severity {
+	NM_SEVER_CEASED		= 0x00,
+	NM_SEVER_CRITICAL	= 0x01,
+	NM_SEVER_MAJOR		= 0x02,
+	NM_SEVER_MINOR		= 0x03,
+	NM_SEVER_WARNING	= 0x04,
+	NM_SEVER_INDETERMINATE	= 0x05,
+};
+
+/*! \brief Probable Cause Type (Section 9.4.43) */
+enum abis_nm_pcause_type {
+	NM_PCAUSE_T_X721	= 0x01,
+	NM_PCAUSE_T_GSM		= 0x02,
+	NM_PCAUSE_T_MANUF	= 0x03,
+};
+
+/*! \brief NACK causes (Section 9.4.36) */
+enum abis_nm_nack_cause {
+	/* General Nack Causes */
+	NM_NACK_INCORR_STRUCT		= 0x01,
+	NM_NACK_MSGTYPE_INVAL		= 0x02,
+	NM_NACK_OBJCLASS_INVAL		= 0x05,
+	NM_NACK_OBJCLASS_NOTSUPP	= 0x06,
+	NM_NACK_BTSNR_UNKN		= 0x07,
+	NM_NACK_TRXNR_UNKN		= 0x08,
+	NM_NACK_OBJINST_UNKN		= 0x09,
+	NM_NACK_ATTRID_INVAL		= 0x0c,
+	NM_NACK_ATTRID_NOTSUPP		= 0x0d,
+	NM_NACK_PARAM_RANGE		= 0x0e,
+	NM_NACK_ATTRLIST_INCONSISTENT	= 0x0f,
+	NM_NACK_SPEC_IMPL_NOTSUPP	= 0x10,
+	NM_NACK_CANT_PERFORM		= 0x11,
+	/* Specific Nack Causes */
+	NM_NACK_RES_NOTIMPL		= 0x19,
+	NM_NACK_RES_NOTAVAIL		= 0x1a,
+	NM_NACK_FREQ_NOTAVAIL		= 0x1b,
+	NM_NACK_TEST_NOTSUPP		= 0x1c,
+	NM_NACK_CAPACITY_RESTR		= 0x1d,
+	NM_NACK_PHYSCFG_NOTPERFORM	= 0x1e,
+	NM_NACK_TEST_NOTINIT		= 0x1f,
+	NM_NACK_PHYSCFG_NOTRESTORE	= 0x20,
+	NM_NACK_TEST_NOSUCH		= 0x21,
+	NM_NACK_TEST_NOSTOP		= 0x22,
+	NM_NACK_MSGINCONSIST_PHYSCFG	= 0x23,
+	NM_NACK_FILE_INCOMPLETE		= 0x25,
+	NM_NACK_FILE_NOTAVAIL		= 0x26,
+	NM_NACK_FILE_NOTACTIVATE	= 0x27,
+	NM_NACK_REQ_NOT_GRANT		= 0x28,
+	NM_NACK_WAIT			= 0x29,
+	NM_NACK_NOTH_REPORT_EXIST	= 0x2a,
+	NM_NACK_MEAS_NOTSUPP		= 0x2b,
+	NM_NACK_MEAS_NOTSTART		= 0x2c,
+};
+
+/*! \brief Abis OML Channel (Section 9.4.1) */
+struct abis_nm_channel {
+	uint8_t	attrib;
+	uint8_t	bts_port;	/*!< \brief BTS port number */
+	uint8_t	timeslot;	/*!< \brief E1 timeslot */
+	uint8_t	subslot;	/*!< \brief E1 sub-slot */
+} __attribute__ ((packed));
+
+/*! \brief Siemens BS-11 specific objects in the SienemsHW (0xA5) object class */
+enum abis_bs11_objtype {
+	BS11_OBJ_ALCO		= 0x01,
+	BS11_OBJ_BBSIG		= 0x02,	/* obj_class: 0,1 */
+	BS11_OBJ_TRX1		= 0x03,	/* only DEACTIVATE TRX1 */
+	BS11_OBJ_CCLK		= 0x04,
+	BS11_OBJ_GPSU		= 0x06,
+	BS11_OBJ_LI		= 0x07,
+	BS11_OBJ_PA		= 0x09,	/* obj_class: 0, 1*/
+};
+
+/*! \brief Siemens BS11 TRX power */
+enum abis_bs11_trx_power {
+	BS11_TRX_POWER_GSM_2W	= 0x06,
+	BS11_TRX_POWER_GSM_250mW= 0x07,
+	BS11_TRX_POWER_GSM_80mW	= 0x08,
+	BS11_TRX_POWER_GSM_30mW	= 0x09,
+	BS11_TRX_POWER_DCS_3W	= 0x0a,
+	BS11_TRX_POWER_DCS_1W6	= 0x0b,
+	BS11_TRX_POWER_DCS_500mW= 0x0c,
+	BS11_TRX_POWER_DCS_160mW= 0x0d,
+};
+
+/*! \brief Siemens BS11 PLL mode */
+enum abis_bs11_li_pll_mode {
+	BS11_LI_PLL_LOCKED	= 2,
+	BS11_LI_PLL_STANDALONE	= 3,
+};
+
+/*! \brief Siemens BS11 E1 line configuration */
+enum abis_bs11_line_cfg {
+	BS11_LINE_CFG_STAR	= 0x00,
+	BS11_LINE_CFG_MULTIDROP	= 0x01,
+	BS11_LINE_CFG_LOOP	= 0x02,
+};
+
+/*! \brief Siemens BS11 boot phase */
+enum abis_bs11_phase {
+	BS11_STATE_SOFTWARE_RQD		= 0x01,
+	BS11_STATE_LOAD_SMU_INTENDED	= 0x11,
+	BS11_STATE_LOAD_SMU_SAFETY	= 0x21,
+	BS11_STATE_LOAD_FAILED		= 0x31,
+	BS11_STATE_LOAD_DIAGNOSTIC	= 0x41,
+	BS11_STATE_WARM_UP		= 0x51,
+	BS11_STATE_WARM_UP_2		= 0x52,
+	BS11_STATE_WAIT_MIN_CFG		= 0x62,
+	BS11_STATE_MAINTENANCE		= 0x72,
+	BS11_STATE_LOAD_MBCCU		= 0x92,
+	BS11_STATE_WAIT_MIN_CFG_2	= 0xA2,
+	BS11_STATE_NORMAL		= 0x03,
+	BS11_STATE_ABIS_LOAD		= 0x13,
+};
+
+/*! \brief ip.access test number */
+enum abis_nm_ipacc_test_no {
+	NM_IPACC_TESTNO_RLOOP_ANT	= 0x01,
+	NM_IPACC_TESTNO_RLOOP_XCVR	= 0x02,
+	NM_IPACC_TESTNO_FUNC_OBJ	= 0x03,
+	NM_IPACC_TESTNO_CHAN_USAGE	= 0x40,
+	NM_IPACC_TESTNO_BCCH_CHAN_USAGE	= 0x41,
+	NM_IPACC_TESTNO_FREQ_SYNC	= 0x42,
+	NM_IPACC_TESTNO_BCCH_INFO	= 0x43,
+	NM_IPACC_TESTNO_TX_BEACON	= 0x44,
+	NM_IPACC_TESTNO_SYSINFO_MONITOR	= 0x45,
+	NM_IPACC_TESTNO_BCCCH_MONITOR	= 0x46,
+};
+
+/*! \brief first byte after length inside NM_ATT_TEST_REPORT */
+enum abis_nm_ipacc_test_res {
+	NM_IPACC_TESTRES_SUCCESS	= 0,
+	NM_IPACC_TESTRES_TIMEOUT	= 1,
+	NM_IPACC_TESTRES_NO_CHANS	= 2,
+	NM_IPACC_TESTRES_PARTIAL	= 3,
+	NM_IPACC_TESTRES_STOPPED	= 4,
+};
+
+/*! \brief internal IE inside NM_ATT_TEST_REPORT */
+enum abis_nm_ipacc_testres_ie {
+	NM_IPACC_TR_IE_FREQ_ERR_LIST	= 3,
+	NM_IPACC_TR_IE_CHAN_USAGE	= 4,
+	NM_IPACC_TR_IE_BCCH_INFO	= 6,
+	NM_IPACC_TR_IE_RESULT_DETAILS	= 8,
+	NM_IPACC_TR_IE_FREQ_ERR		= 18,
+};
+
+/*! \brief ip.access IEI */
+enum ipac_eie {
+	NM_IPAC_EIE_ARFCN_WHITE		= 0x01,
+	NM_IPAC_EIE_ARFCH_BLACK		= 0x02,
+	NM_IPAC_EIE_FREQ_ERR_LIST	= 0x03,
+	NM_IPAC_EIE_CHAN_USE_LIST	= 0x04,
+	NM_IPAC_EIE_BCCH_INFO_TYPE	= 0x05,
+	NM_IPAC_EIE_BCCH_INFO		= 0x06,
+	NM_IPAC_EIE_CONFIG		= 0x07,
+	NM_IPAC_EIE_RES_DETAILS		= 0x08,
+	NM_IPAC_EIE_RXLEV_THRESH	= 0x09,
+	NM_IPAC_EIE_FREQ_SYNC_OPTS	= 0x0a,
+	NM_IPAC_EIE_MAC_ADDR		= 0x0b,
+	NM_IPAC_EIE_HW_SW_COMPAT_NR	= 0x0c,
+	NM_IPAC_EIE_MANUF_SER_NR	= 0x0d,
+	NM_IPAC_EIE_OEM_ID		= 0x0e,
+	NM_IPAC_EIE_DATE_TIME_MANUF	= 0x0f,
+	NM_IPAC_EIE_DATE_TIME_CALIB	= 0x10,
+	NM_IPAC_EIE_BEACON_INFO		= 0x11,
+	NM_IPAC_EIE_FREQ_ERR		= 0x12,
+	/* FIXME */
+	NM_IPAC_EIE_FREQ_BANDS		= 0x1c,
+	NM_IPAC_EIE_MAX_TA		= 0x1d,
+	NM_IPAC_EIE_CIPH_ALGOS		= 0x1e,
+	NM_IPAC_EIE_CHAN_TYPES		= 0x1f,
+	NM_IPAC_EIE_CHAN_MODES		= 0x20,
+	NM_IPAC_EIE_GPRS_CODING		= 0x21,
+	NM_IPAC_EIE_RTP_FEATURES	= 0x22,
+	NM_IPAC_EIE_RSL_FEATURES	= 0x23,
+	NM_IPAC_EIE_BTS_HW_CLASS	= 0x24,
+	NM_IPAC_EIE_BTS_ID		= 0x25,
+};
+
+/*! \brief ip.access NWL BCCH information type */
+enum ipac_bcch_info_type {
+	IPAC_BINF_RXLEV			= (1 << 8),
+	IPAC_BINF_RXQUAL		= (1 << 9),
+	IPAC_BINF_FREQ_ERR_QUAL		= (1 << 10),
+	IPAC_BINF_FRAME_OFFSET		= (1 << 11),
+	IPAC_BINF_FRAME_NR_OFFSET	= (1 << 12),
+	IPAC_BINF_BSIC			= (1 << 13),
+	IPAC_BINF_CGI			= (1 << 14),
+	IPAC_BINF_NEIGH_BA_SI2		= (1 << 15),
+	IPAC_BINF_NEIGH_BA_SI2bis	= (1 << 0),
+	IPAC_BINF_NEIGH_BA_SI2ter	= (1 << 1),
+	IPAC_BINF_CELL_ALLOC		= (1 << 2),
+};
+
+/*! @} */
+
+#endif /* PROTO_GSM_12_21_H */
diff --git a/include/osmocom/gsm/protocol/gsm_44_318.h b/include/osmocom/gsm/protocol/gsm_44_318.h
new file mode 100644
index 0000000..31c0ea7
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_44_318.h
@@ -0,0 +1,153 @@
+#ifndef PROTO_GSM_44_318_H
+#define PROTO_GSM_44_318_H
+
+#include <stdint.h>
+
+/* Definitions according to 3GPP TS 44.318 6.8.0 Release 6 */
+
+/* Table 11.1.1.4.1: Message types for URR */
+
+enum gan_msg_type {
+	GA_MT_RC_DISCOVERY_REQUEST	= 0x01,
+	GA_MT_RC_DISCOVERY_ACCEPT	= 0x02,
+	GA_MT_RC_DISCOVERY_REJECT	= 0x03,
+
+	GA_MT_RC_REGISTER_REQUEST	= 0x10,
+	GA_MT_RC_REGISTER_ACCEPT	= 0x11,
+	GA_MT_RC_REGISTER_REDIRECT	= 0x12,
+	GA_MT_RC_REGISTER_REJECT	= 0x13,
+	GA_MT_RC_DEREGISTER		= 0x14,
+	GA_MT_RC_REGISTER_UPDATE_UL	= 0x15,
+	GA_MT_RC_REGISTER_UPDATE_DL	= 0x16,
+	GA_MT_RC_CELL_BCAST_INFO	= 0x17,
+
+	GA_MT_CSR_CIPH_MODE_CMD		= 0x20,
+	GA_MT_CSR_CIPH_MODE_COMPL	= 0x21,
+
+	GA_MT_CSR_ACT_CHAN		= 0x30,
+	GA_MT_CSR_ACT_CHAN_ACK		= 0x31,
+	GA_MT_CSR_ACT_CHAN_COMPL	= 0x32,
+	GA_MT_CSR_ACT_CHAN_FAIL		= 0x33,
+	GA_MT_CSR_CHAN_MODE_MOD		= 0x34,
+	GA_MT_CSR_CHAN_MODE_MOD_ACK	= 0x35,
+
+	GA_MT_CSR_RELEASE		= 0x40,
+	GA_MT_CSR_RELEASE_COMPL		= 0x41,
+	GA_MT_CSR_CLEAR_REQ		= 0x42,
+
+	GA_MT_CSR_HO_ACCESS		= 0x50,
+	GA_MT_CSR_HO_COMPL		= 0x51,
+	GA_MT_CSR_UL_QUAL_IND		= 0x52,
+	GA_MT_CSR_HO_INFO		= 0x53,
+	GA_MT_CSR_HO_CMD		= 0x54,
+	GA_MT_CSR_HO_FAIL		= 0x55,
+
+	GA_MT_CSR_PAGING_REQ		= 0x60,
+	GA_MT_CSR_PAGING_RESP		= 0x61,
+
+	GA_MT_CSR_UL_DIRECT_XFER	= 0x70,
+	GA_MT_CSR_DL_DIRECT_XFER	= 0x72,
+	GA_MT_CSR_STATUS		= 0x73,
+	GA_MT_RC_KEEPALIVE		= 0x74,
+	GA_MT_CSR_CM_ENQ		= 0x75,
+	GA_MT_CSR_CM_CHANGE		= 0x76,
+
+	GA_MT_CSR_REQUEST		= 0x80,
+	GA_MT_CSR_REQUEST_ACCEPT	= 0x81,
+	GA_MT_CSR_REQUEST_REJECT	= 0x82,
+};
+
+/* All tables in 10.1.x and 10.2.x / Table 11.2.1 */
+enum gan_iei {
+	GA_IE_MI		= 1,
+	GA_IE_GAN_RELEASE_IND	= 2,
+	GA_IE_RADIO_IE		= 3,
+	GA_IE_GERAN_CELL_ID	= 4,
+	GA_IE_LAC		= 5,
+	GA_IE_GERAN_COV_IND	= 6,
+	GA_IE_GAN_CM		= 7,
+	GA_IE_GEO_LOC		= 8,
+	GA_IE_DEF_SEGW_IP	= 9,
+	GA_IE_DEF_SEGW_FQDN	= 10,
+	GA_IE_REDIR_CTR		= 11,
+	GA_IE_DISCOV_REJ_CAUSE	= 12,
+	GA_IE_GANC_CELL_DESC	= 13,
+	GA_IE_GANC_CTRL_CH_DESC	= 14,
+	GA_IE_GERAN_CELL_ID_LIST= 15,
+	GA_IE_TU3907_TIMER	= 16,
+	GA_IE_RR_STATE		= 17,
+	GA_IE_RAI		= 18,
+	GA_IE_GAN_BAND		= 19,
+	GA_IE_GARC_GACSR_STATE	= 20,
+	GA_IE_REG_REJ_CAUSE	= 21,
+	GA_IE_TU3906_TIMER	= 22,
+	GA_IE_TU3910_TIMER	= 23,
+	GA_IE_TU3902_TIMER	= 24,
+	GA_IE_L3_MSG		= 26,
+	GA_IE_CHAN_MODE		= 27,
+	GA_IE_MS_CLASSMARK2	= 28,
+	GA_IE_RR_CAUSE		= 29,
+	GA_EI_CIPH_MODE_SET	= 30,
+	GA_IE_GPRS_RESUMPTION	= 31,
+	GA_IE_HO_FROM_GAN_CMD	= 32,
+	GA_IE_UL_QUAL_IND	= 33,
+	GA_IE_TLLI		= 34,
+	GA_IE_PFI		= 35,
+	GA_IE_SUSP_CAUSE	= 36,
+	GA_IE_TU3820_TIMER	= 37,
+	GA_IE_REQD_QOS		= 38,
+	GA_IE_P_DEACT_CAUSE	= 39
+	GA_IE_REQD_UL_RATE	= 40,
+	GA_IE_RAC		= 41,
+	GA_IE_AP_LOCATION	= 42,
+	GA_IE_TU4001_TIMER	= 43,
+	GA_IE_LOC_STATUS	= 44,
+	GA_IE_CIPH_RESP		= 45,
+	GA_IE_CIPH_RAND		= 46,
+	GA_IE_CIPH_MAC		= 47,
+	GA_IE_CKSN		= 48,
+	GA_IE_SAPI_ID		= 49,
+	GA_IE_EST_CAUSE		= 50,
+	GA_IE_CHAN_NEEDED	= 51,
+	GA_IE_PDU_IN_ERROR	= 52,
+	GA_IE_SAMPLE_SIZE	= 53,
+	GA_IE_PAYLOAD_TYPE	= 54,
+	GA_IE_MULTIRATE_CONF	= 55,
+	GA_IE_MS_CLASSMARK3	= 56,
+	GA_IE_LLC_PDU		= 57,
+	GA_IE_LOC_BLACKL_IND	= 58,
+	GA_IE_RESET_IND		= 59,
+	GA_IE_TU4003_TIMER	= 60,
+	GA_IE_AP_SERV_NAME	= 61,
+	GA_IE_SERV_ZONE_INFO	= 62,
+	GA_IE_RTP_RED_CONF	= 63,
+	GA_IE_UTRAN_CLASSMARK	= 64,
+	GA_IE_CM_ENQ_MASK	= 65,
+	GA_IE_UTRAN_CELLID_LIST = 66,
+	GA_IE_SERV_GANC_TBL_IND	= 67,
+	GA_IE_AP_REG_IND	= 68,
+	GA_IE_GAN_PLMN_LIST	= 69,
+	GA_IE_REQD_GAN_SERV	= 71,
+	GA_IE_BCAST_CONTAINER	= 72,
+	GA_IE_3G_CELL_ID	= 73,
+	GA_IE_MS_RADIO_ID	= 96,
+	GA_IE_DEF_GANC_IP	= 97,
+	GA_IE_DEF_GANC_FQDN	= 98,
+	GA_IE_GPRS_IP_ADDR	= 99,
+	GA_IE_GPRS_UDP_PORT	= 100
+	GA_IE_GANC_TCP_PORT	= 103,
+	GA_IE_RTP_UDP_PORT	= 104,
+	GA_IE_RTCP_UDP_PORT	= 105,
+	GA_IE_GERAN_RCV_SIGL_LIST = 106,
+	GA_IE_UTRAN_RCV_SIGL_LIST = 107,
+};
+
+/* 11.1.1 GA-RC and GA-CSR Message header IE */
+struct gan_rc_csr_hdr {
+	uint16_t len;
+	uint8_t pdisc:4,
+		skip_ind:4;
+	uint8_t msg_type;
+} __attribute__((packed));
+
+#endif /* PROTO_GSM_44_318_H */
diff --git a/include/osmocom/gsm/protocol/ipaccess.h b/include/osmocom/gsm/protocol/ipaccess.h
new file mode 100644
index 0000000..5d98a21
--- /dev/null
+++ b/include/osmocom/gsm/protocol/ipaccess.h
@@ -0,0 +1,94 @@
+#ifndef _OSMO_PROTO_IPACCESS_H
+#define _OSMO_PROTO_IPACCESS_H
+
+#include <stdint.h>
+
+#define IPA_TCP_PORT_OML	3002
+#define IPA_TCP_PORT_RSL	3003
+
+struct ipaccess_head {
+	uint16_t len;	/* network byte order */
+	uint8_t proto;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+struct ipaccess_head_ext {
+	uint8_t proto;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+enum ipaccess_proto {
+	IPAC_PROTO_RSL		= 0x00,
+	IPAC_PROTO_IPACCESS	= 0xfe,
+	IPAC_PROTO_SCCP		= 0xfd,
+	IPAC_PROTO_OML		= 0xff,
+
+
+	/* OpenBSC extensions */
+	IPAC_PROTO_OSMO		= 0xee,
+	IPAC_PROTO_MGCP_OLD	= 0xfc,
+};
+
+enum ipaccess_proto_ext {
+	IPAC_PROTO_EXT_CTRL	= 0x00,
+	IPAC_PROTO_EXT_MGCP	= 0x01,
+	IPAC_PROTO_EXT_LAC	= 0x02,
+	IPAC_PROTO_EXT_SMSC	= 0x03,
+};
+
+enum ipaccess_msgtype {
+	IPAC_MSGT_PING		= 0x00,
+	IPAC_MSGT_PONG		= 0x01,
+	IPAC_MSGT_ID_GET	= 0x04,
+	IPAC_MSGT_ID_RESP	= 0x05,
+	IPAC_MSGT_ID_ACK	= 0x06,
+
+	/* OpenBSC extension */
+	IPAC_MSGT_SCCP_OLD	= 0xff,
+};
+
+enum ipaccess_id_tags {
+	IPAC_IDTAG_SERNR		= 0x00,
+	IPAC_IDTAG_UNITNAME		= 0x01,
+	IPAC_IDTAG_LOCATION1		= 0x02,
+	IPAC_IDTAG_LOCATION2		= 0x03,
+	IPAC_IDTAG_EQUIPVERS		= 0x04,
+	IPAC_IDTAG_SWVERSION		= 0x05,
+	IPAC_IDTAG_IPADDR		= 0x06,
+	IPAC_IDTAG_MACADDR		= 0x07,
+	IPAC_IDTAG_UNIT			= 0x08,
+};
+
+/*
+ * Firmware specific header
+ */
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[2];
+	uint16_t more_more_magic;
+	uint32_t header_length;
+	uint32_t file_length;
+	char sw_part[20];
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	uint16_t table_offset;
+	/* stuff i don't know */
+} __attribute__((packed));
+
+struct sdp_header_entry {
+	uint16_t something1;
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	uint32_t length;
+	uint32_t addr1;
+	uint32_t addr2;
+	uint32_t start;
+} __attribute__((packed));
+
+#endif /* _OSMO_PROTO_IPACCESS_H */
diff --git a/include/osmocom/gsm/rsl.h b/include/osmocom/gsm/rsl.h
new file mode 100644
index 0000000..b8e4157
--- /dev/null
+++ b/include/osmocom/gsm/rsl.h
@@ -0,0 +1,55 @@
+#ifndef _OSMOCORE_RSL_H
+#define _OSMOCORE_RSL_H
+
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+/*! \defgroup rsl RSL
+ *  @{
+ */
+
+/*! \file rsl.h */
+
+void rsl_init_rll_hdr(struct abis_rsl_rll_hdr *dh, uint8_t msg_type);
+
+void rsl_init_cchan_hdr(struct abis_rsl_cchan_hdr *ch, uint8_t msg_type);
+
+extern const struct tlv_definition rsl_att_tlvdef;
+
+/*! \brief Parse RSL TLV structure using \ref tlv_parse */
+#define rsl_tlv_parse(dec, buf, len)     \
+			tlv_parse(dec, &rsl_att_tlvdef, buf, len, 0, 0)
+
+/* encode channel number as per Section 9.3.1 */
+uint8_t rsl_enc_chan_nr(uint8_t type, uint8_t subch, uint8_t timeslot);
+/* decode channel number as per Section 9.3.1 */
+int rsl_dec_chan_nr(uint8_t chan_nr, uint8_t *type, uint8_t *subch, uint8_t *timeslot);
+/* Turns channel number into a string */
+const char *rsl_chan_nr_str(uint8_t chan_nr);
+
+
+const char *rsl_err_name(uint8_t err);
+const char *rsl_rlm_cause_name(uint8_t err);
+const char *rsl_msg_name(uint8_t err);
+const char *rsl_ipac_msg_name(uint8_t msg_type);
+
+/* Section 3.3.2.3 TS 05.02. I think this looks like a table */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf);
+
+/* Push a RSL RLL header */
+void rsl_rll_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr,
+		      uint8_t link_id, int transparent);
+
+/* Push a RSL RLL header with L3_INFO IE */
+void rsl_rll_push_l3(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr,
+		     uint8_t link_id, int transparent);
+
+/* Allocate msgb and fill with simple RSL RLL header */
+struct msgb *rsl_rll_simple(uint8_t msg_type, uint8_t chan_nr,
+			    uint8_t link_id, int transparent);
+
+/*! @} */
+
+#endif /* _OSMOCORE_RSL_H */
diff --git a/include/osmocom/gsm/rxlev_stat.h b/include/osmocom/gsm/rxlev_stat.h
new file mode 100644
index 0000000..415509d
--- /dev/null
+++ b/include/osmocom/gsm/rxlev_stat.h
@@ -0,0 +1,22 @@
+#ifndef _OSMOCORE_RXLEV_STATS_H
+#define _OSMOCORE_RXLEV_STATS_H
+
+#define NUM_RXLEVS 32
+#define NUM_ARFCNS 1024
+
+struct rxlev_stats {
+	/* the maximum number of ARFCN's is 1024, and there are 32 RxLevels,
+	 * so in we keep one 1024bit-bitvec for each RxLev */
+	uint8_t rxlev_buckets[NUM_RXLEVS][NUM_ARFCNS/8];
+};
+
+void rxlev_stat_input(struct rxlev_stats *st, uint16_t arfcn, uint8_t rxlev);
+
+/* get the next ARFCN that has the specified Rxlev */
+int16_t rxlev_stat_get_next(const struct rxlev_stats *st, uint8_t rxlev, int16_t arfcn);
+
+void rxlev_stat_reset(struct rxlev_stats *st);
+
+void rxlev_stat_dump(const struct rxlev_stats *st);
+
+#endif /* _OSMOCORE_RXLEV_STATS_H */
diff --git a/include/osmocom/gsm/sysinfo.h b/include/osmocom/gsm/sysinfo.h
new file mode 100644
index 0000000..06feb1d
--- /dev/null
+++ b/include/osmocom/gsm/sysinfo.h
@@ -0,0 +1,43 @@
+#ifndef _OSMO_GSM_SYSINFO_H
+#define _OSMO_GSM_SYSINFO_H
+
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+enum osmo_sysinfo_type {
+	SYSINFO_TYPE_NONE,
+	SYSINFO_TYPE_1,
+	SYSINFO_TYPE_2,
+	SYSINFO_TYPE_3,
+	SYSINFO_TYPE_4,
+	SYSINFO_TYPE_5,
+	SYSINFO_TYPE_6,
+	SYSINFO_TYPE_7,
+	SYSINFO_TYPE_8,
+	SYSINFO_TYPE_9,
+	SYSINFO_TYPE_10,
+	SYSINFO_TYPE_13,
+	SYSINFO_TYPE_16,
+	SYSINFO_TYPE_17,
+	SYSINFO_TYPE_18,
+	SYSINFO_TYPE_19,
+	SYSINFO_TYPE_20,
+	SYSINFO_TYPE_2bis,
+	SYSINFO_TYPE_2ter,
+	SYSINFO_TYPE_2quater,
+	SYSINFO_TYPE_5bis,
+	SYSINFO_TYPE_5ter,
+	SYSINFO_TYPE_EMO,
+	SYSINFO_TYPE_MEAS_INFO,
+	/* FIXME all the various bis and ter */
+	_MAX_SYSINFO_TYPE
+};
+
+typedef uint8_t sysinfo_buf_t[GSM_MACBLOCK_LEN];
+
+extern const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE];
+
+uint8_t osmo_sitype2rsl(enum osmo_sysinfo_type si_type);
+enum osmo_sysinfo_type osmo_rsl2sitype(uint8_t rsl_si);
+
+#endif /* _OSMO_GSM_SYSINFO_H */
diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h
new file mode 100644
index 0000000..d1efc55
--- /dev/null
+++ b/include/osmocom/gsm/tlv.h
@@ -0,0 +1,319 @@
+#ifndef _TLV_H
+#define _TLV_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/msgb.h>
+
+/*! \defgroup tlv GSM L3 compatible TLV parser
+ *  @{
+ */
+/*! \file tlv.h */
+
+/* Terminology / wording
+		tag	length		value	(in bits)
+
+	    V	-	-		8
+	   LV	-	8		N * 8
+	  TLV	8	8		N * 8
+	TL16V	8	16		N * 8
+	TLV16	8	8		N * 16
+	 TvLV	8	8/16		N * 8
+
+*/
+
+/*! \brief gross length of a LV type field */
+#define LV_GROSS_LEN(x)		(x+1)
+/*! \brief gross length of a TLV type field */
+#define TLV_GROSS_LEN(x)	(x+2)
+/*! \brief gross length of a TLV16 type field */
+#define TLV16_GROSS_LEN(x)	((2*x)+2)
+/*! \brief gross length of a TL16V type field */
+#define TL16V_GROSS_LEN(x)	(x+3)
+/*! \brief gross length of a L16TV type field */
+#define L16TV_GROSS_LEN(x)	(x+3)
+
+/*! \brief maximum length of TLV of one byte length */
+#define TVLV_MAX_ONEBYTE	0x7f
+
+/*! \brief gross length of a TVLV type field */
+static inline uint16_t TVLV_GROSS_LEN(uint16_t len)
+{
+	if (len <= TVLV_MAX_ONEBYTE)
+		return TLV_GROSS_LEN(len);
+	else
+		return TL16V_GROSS_LEN(len);
+}
+
+/* TLV generation */
+
+/*! \brief put (append) a LV field */
+static inline uint8_t *lv_put(uint8_t *buf, uint8_t len,
+				const uint8_t *val)
+{
+	*buf++ = len;
+	memcpy(buf, val, len);
+	return buf + len;
+}
+
+/*! \brief put (append) a TLV field */
+static inline uint8_t *tlv_put(uint8_t *buf, uint8_t tag, uint8_t len,
+				const uint8_t *val)
+{
+	*buf++ = tag;
+	*buf++ = len;
+	memcpy(buf, val, len);
+	return buf + len;
+}
+
+/*! \brief put (append) a TLV16 field */
+static inline uint8_t *tlv16_put(uint8_t *buf, uint8_t tag, uint8_t len,
+				const uint16_t *val)
+{
+	*buf++ = tag;
+	*buf++ = len;
+	memcpy(buf, val, len*2);
+	return buf + len*2;
+}
+
+/*! \brief put (append) a TL16V field */
+static inline uint8_t *tl16v_put(uint8_t *buf, uint8_t tag, uint16_t len,
+				const uint8_t *val)
+{
+	*buf++ = tag;
+	*buf++ = len >> 8;
+	*buf++ = len & 0xff;
+	memcpy(buf, val, len);
+	return buf + len*2;
+}
+
+/*! \brief put (append) a TvLV field */
+static inline uint8_t *tvlv_put(uint8_t *buf, uint8_t tag, uint16_t len,
+				 const uint8_t *val)
+{
+	uint8_t *ret;
+
+	if (len <= TVLV_MAX_ONEBYTE) {
+		ret = tlv_put(buf, tag, len, val);
+		buf[1] |= 0x80;
+	} else
+		ret = tl16v_put(buf, tag, len, val);
+
+	return ret;
+}
+
+/*! \brief put (append) a TLV16 field to \ref msgb */
+static inline uint8_t *msgb_tlv16_put(struct msgb *msg, uint8_t tag, uint8_t len, const uint16_t *val)
+{
+	uint8_t *buf = msgb_put(msg, TLV16_GROSS_LEN(len));
+	return tlv16_put(buf, tag, len, val);
+}
+
+/*! \brief put (append) a TL16V field to \ref msgb */
+static inline uint8_t *msgb_tl16v_put(struct msgb *msg, uint8_t tag, uint16_t len,
+					const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, TL16V_GROSS_LEN(len));
+	return tl16v_put(buf, tag, len, val);
+}
+
+/*! \brief put (append) a TvLV field to \ref msgb */
+static inline uint8_t *msgb_tvlv_put(struct msgb *msg, uint8_t tag, uint16_t len,
+				      const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, TVLV_GROSS_LEN(len));
+	return tvlv_put(buf, tag, len, val);
+}
+
+/*! \brief put (append) a L16TV field to \ref msgb */
+static inline uint8_t *msgb_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+                                       const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, L16TV_GROSS_LEN(len));
+
+	*buf++ = len >> 8;
+	*buf++ = len & 0xff;
+	*buf++ = tag;
+	memcpy(buf, val, len);
+	return buf + len;
+}
+
+/*! \brief put (append) a V field */
+static inline uint8_t *v_put(uint8_t *buf, uint8_t val)
+{
+	*buf++ = val;
+	return buf;
+}
+
+/*! \brief put (append) a TV field */
+static inline uint8_t *tv_put(uint8_t *buf, uint8_t tag, 
+				uint8_t val)
+{
+	*buf++ = tag;
+	*buf++ = val;
+	return buf;
+}
+
+/*! \brief put (append) a TVfixed field */
+static inline uint8_t *tv_fixed_put(uint8_t *buf, uint8_t tag,
+				    unsigned int len, const uint8_t *val)
+{
+	*buf++ = tag;
+	memcpy(buf, val, len);
+	return buf + len;
+}
+
+/*! \brief put (append) a TV16 field
+ *  \param[in,out] buf data buffer
+ *  \param[in] tag Tag value
+ *  \param[in] val Value (in host byte order!)
+ */
+static inline uint8_t *tv16_put(uint8_t *buf, uint8_t tag, 
+				 uint16_t val)
+{
+	*buf++ = tag;
+	*buf++ = val >> 8;
+	*buf++ = val & 0xff;
+	return buf;
+}
+
+/*! \brief put (append) a LV field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_lv_put(struct msgb *msg, uint8_t len, const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, LV_GROSS_LEN(len));
+	return lv_put(buf, len, val);
+}
+
+/*! \brief put (append) a TLV field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_tlv_put(struct msgb *msg, uint8_t tag, uint8_t len, const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, TLV_GROSS_LEN(len));
+	return tlv_put(buf, tag, len, val);
+}
+
+/*! \brief put (append) a TV field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_tv_put(struct msgb *msg, uint8_t tag, uint8_t val)
+{
+	uint8_t *buf = msgb_put(msg, 2);
+	return tv_put(buf, tag, val);
+}
+
+/*! \brief put (append) a TVfixed field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_tv_fixed_put(struct msgb *msg, uint8_t tag,
+					unsigned int len, const uint8_t *val)
+{
+	uint8_t *buf = msgb_put(msg, 1+len);
+	return tv_fixed_put(buf, tag, len, val);
+}
+
+/*! \brief put (append) a V field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_v_put(struct msgb *msg, uint8_t val)
+{
+	uint8_t *buf = msgb_put(msg, 1);
+	return v_put(buf, val);
+}
+
+/*! \brief put (append) a TV16 field to a \ref msgb
+ *  \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_tv16_put(struct msgb *msg, uint8_t tag, uint16_t val)
+{
+	uint8_t *buf = msgb_put(msg, 3);
+	return tv16_put(buf, tag, val);
+}
+
+/*! \brief push (prepend) a TLV field to a \ref msgb
+ *  \returns pointer to first byte of newly-pushed information */
+static inline uint8_t *msgb_tlv_push(struct msgb *msg, uint8_t tag, uint8_t len, const uint8_t *val)
+{
+	uint8_t *buf = msgb_push(msg, TLV_GROSS_LEN(len));
+	tlv_put(buf, tag, len, val);
+	return buf;
+}
+
+/*! \brief push (prepend) a TV field to a \ref msgb
+ *  \returns pointer to first byte of newly-pushed information */
+static inline uint8_t *msgb_tv_push(struct msgb *msg, uint8_t tag, uint8_t val)
+{
+	uint8_t *buf = msgb_push(msg, 2);
+	tv_put(buf, tag, val);
+	return buf;
+}
+
+/*! \brief push (prepend) a TV16 field to a \ref msgb
+ *  \returns pointer to first byte of newly-pushed information */
+static inline uint8_t *msgb_tv16_push(struct msgb *msg, uint8_t tag, uint16_t val)
+{
+	uint8_t *buf = msgb_push(msg, 3);
+	tv16_put(buf, tag, val);
+	return buf;
+}
+
+/*! \brief push (prepend) a TvLV field to a \ref msgb
+ *  \returns pointer to first byte of newly-pushed information */
+static inline uint8_t *msgb_tvlv_push(struct msgb *msg, uint8_t tag, uint16_t len,
+				      const uint8_t *val)
+{
+	uint8_t *buf = msgb_push(msg, TVLV_GROSS_LEN(len));
+	tvlv_put(buf, tag, len, val);
+	return buf;
+}
+
+/* TLV parsing */
+
+/*! \brief Entry in a TLV parser array */
+struct tlv_p_entry {
+	uint16_t len;		/*!< \brief length */
+	const uint8_t *val;	/*!< \brief pointer to value */
+};
+
+/*! \brief TLV type */
+enum tlv_type {
+	TLV_TYPE_NONE,		/*!< \brief no type */
+	TLV_TYPE_FIXED,		/*!< \brief fixed-length value-only */
+	TLV_TYPE_T,		/*!< \brief tag-only */
+	TLV_TYPE_TV,		/*!< \brief tag-value (8bit) */
+	TLV_TYPE_TLV,		/*!< \brief tag-length-value */
+	TLV_TYPE_TL16V,		/*!< \brief tag, 16 bit length, value */
+	TLV_TYPE_TvLV,		/*!< \brief tag, variable length, value */
+	TLV_TYPE_SINGLE_TV	/*!< \brief tag and value (both 4 bit) in 1 byte */
+};
+
+/*! \brief Definition of a single IE (Information Element) */
+struct tlv_def {
+	enum tlv_type type;	/*!< \brief TLV type */
+	uint8_t fixed_len;	/*!< \brief length in case of \ref TLV_TYPE_FIXED */
+};
+
+/*! \brief Definition of All 256 IE / TLV */
+struct tlv_definition {
+	struct tlv_def def[256];
+};
+
+/*! \brief result of the TLV parser */
+struct tlv_parsed {
+	struct tlv_p_entry lv[256];
+};
+
+extern struct tlv_definition tvlv_att_def;
+
+int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
+                  const struct tlv_definition *def,
+                  const uint8_t *buf, int buf_len);
+int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def,
+	      const uint8_t *buf, int buf_len, uint8_t lv_tag, uint8_t lv_tag2);
+/* take a master (src) tlvdev and fill up all empty slots in 'dst' */
+void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src);
+
+#define TLVP_PRESENT(x, y)	((x)->lv[y].val)
+#define TLVP_LEN(x, y)		(x)->lv[y].len
+#define TLVP_VAL(x, y)		(x)->lv[y].val
+
+/*! @} */
+
+#endif /* _TLV_H */
diff --git a/include/osmocom/vty/Makefile.am b/include/osmocom/vty/Makefile.am
new file mode 100644
index 0000000..83d0010
--- /dev/null
+++ b/include/osmocom/vty/Makefile.am
@@ -0,0 +1,4 @@
+osmovty_HEADERS = buffer.h command.h vector.h vty.h \
+	telnet_interface.h logging.h misc.h
+
+osmovtydir = $(includedir)/osmocom/vty
diff --git a/include/osmocom/vty/buffer.h b/include/osmocom/vty/buffer.h
new file mode 100644
index 0000000..c9467a9
--- /dev/null
+++ b/include/osmocom/vty/buffer.h
@@ -0,0 +1,102 @@
+/*
+ * Buffering to output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_BUFFER_H
+#define _ZEBRA_BUFFER_H
+
+#include <sys/types.h>
+
+/* Create a new buffer.  Memory will be allocated in chunks of the given
+   size.  If the argument is 0, the library will supply a reasonable
+   default size suitable for buffering socket I/O. */
+struct buffer *buffer_new(void *ctx, size_t);
+
+/* Free all data in the buffer. */
+void buffer_reset(struct buffer *);
+
+/* This function first calls buffer_reset to release all buffered data.
+   Then it frees the struct buffer itself. */
+void buffer_free(struct buffer *);
+
+/* Add the given data to the end of the buffer. */
+extern void buffer_put(struct buffer *, const void *, size_t);
+/* Add a single character to the end of the buffer. */
+extern void buffer_putc(struct buffer *, u_char);
+/* Add a NUL-terminated string to the end of the buffer. */
+extern void buffer_putstr(struct buffer *, const char *);
+
+/* Combine all accumulated (and unflushed) data inside the buffer into a
+   single NUL-terminated string allocated using XMALLOC(MTYPE_TMP).  Note
+   that this function does not alter the state of the buffer, so the data
+   is still inside waiting to be flushed. */
+char *buffer_getstr(struct buffer *);
+
+/* Returns 1 if there is no pending data in the buffer.  Otherwise returns 0. */
+int buffer_empty(struct buffer *);
+
+typedef enum {
+	/* An I/O error occurred.  The buffer should be destroyed and the
+	   file descriptor should be closed. */
+	BUFFER_ERROR = -1,
+
+	/* The data was written successfully, and the buffer is now empty
+	   (there is no pending data waiting to be flushed). */
+	BUFFER_EMPTY = 0,
+
+	/* There is pending data in the buffer waiting to be flushed.  Please
+	   try flushing the buffer when select indicates that the file descriptor
+	   is writeable. */
+	BUFFER_PENDING = 1
+} buffer_status_t;
+
+/* Try to write this data to the file descriptor.  Any data that cannot
+   be written immediately is added to the buffer queue. */
+extern buffer_status_t buffer_write(struct buffer *, int fd,
+				    const void *, size_t);
+
+/* This function attempts to flush some (but perhaps not all) of
+   the queued data to the given file descriptor. */
+extern buffer_status_t buffer_flush_available(struct buffer *, int fd);
+
+/* The following 2 functions (buffer_flush_all and buffer_flush_window)
+   are for use in lib/vty.c only.  They should not be used elsewhere. */
+
+/* Call buffer_flush_available repeatedly until either all data has been
+   flushed, or an I/O error has been encountered, or the operation would
+   block. */
+extern buffer_status_t buffer_flush_all(struct buffer *, int fd);
+
+/* Attempt to write enough data to the given fd to fill a window of the
+   given width and height (and remove the data written from the buffer).
+
+   If !no_more, then a message saying " --More-- " is appended.
+   If erase is true, then first overwrite the previous " --More-- " message
+   with spaces.
+
+   Any write error (including EAGAIN or EINTR) will cause this function
+   to return -1 (because the logic for handling the erase and more features
+   is too complicated to retry the write later).
+*/
+extern buffer_status_t buffer_flush_window(struct buffer *, int fd, int width,
+					   int height, int erase, int no_more);
+
+#endif				/* _ZEBRA_BUFFER_H */
diff --git a/include/osmocom/vty/command.h b/include/osmocom/vty/command.h
new file mode 100644
index 0000000..c8cea7c
--- /dev/null
+++ b/include/osmocom/vty/command.h
@@ -0,0 +1,372 @@
+/*
+ * Zebra configuration command interface routine
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_COMMAND_H
+#define _ZEBRA_COMMAND_H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "vector.h"
+#include "vty.h"
+
+/*! \defgroup command VTY Command
+ *  @{
+ */
+/*! \file command.h */
+
+/*! \brief Host configuration variable */
+struct host {
+	/*! \brief Host name of this router. */
+	char *name;
+
+	/*! \brief Password for vty interface. */
+	char *password;
+	char *password_encrypt;
+
+	/*! \brief Enable password */
+	char *enable;
+	char *enable_encrypt;
+
+	/*! \brief System wide terminal lines. */
+	int lines;
+
+	/*! \brief Log filename. */
+	char *logfile;
+
+	/*! \brief config file name of this host */
+	char *config;
+
+	/*! \brief Flags for services */
+	int advanced;
+	int encrypt;
+
+	/*! \brief Banner configuration. */
+	const char *motd;
+	char *motdfile;
+
+	/*! \brief VTY application information */
+	const struct vty_app_info *app_info;
+};
+
+/*! \brief There are some command levels which called from command node. */
+enum node_type {
+	AUTH_NODE,		/*!< \brief Authentication mode of vty interface. */
+	VIEW_NODE,		/*!< \brief View node. Default mode of vty interface. */
+	AUTH_ENABLE_NODE,	/*!< \brief Authentication mode for change enable. */
+	ENABLE_NODE,		/*!< \brief Enable node. */
+	CONFIG_NODE,		/*!< \brief Config node. Default mode of config file. */
+	SERVICE_NODE,		/*!< \brief Service node. */
+	DEBUG_NODE,		/*!< \brief Debug node. */
+	CFG_LOG_NODE,		/*!< \brief Configure the logging */
+
+	VTY_NODE,		/*!< \brief Vty node. */
+
+	L_E1INP_NODE,		/*!< \brief E1 line in libosmo-abis. */
+	L_IPA_NODE,		/*!< \brief IPA proxying commands in libosmo-abis. */
+	L_NS_NODE,		/*!< \brief NS node in libosmo-gb. */
+	L_BSSGP_NODE,		/*!< \brief BSSGP node in libosmo-gb. */
+
+	_LAST_OSMOVTY_NODE
+};
+
+/*! \brief Node which has some commands and prompt string and
+ * configuration function pointer . */
+struct cmd_node {
+	/*! \brief Node index */
+	enum node_type node;
+
+	/*! \brief Prompt character at vty interface. */
+	const char *prompt;
+
+	/*! \brief Is this node's configuration goes to vtysh ? */
+	int vtysh;
+
+	/*! \brief Node's configuration write function */
+	int (*func) (struct vty *);
+
+	/*! \brief Vector of this node's command list. */
+	vector cmd_vector;
+};
+
+enum {
+	CMD_ATTR_DEPRECATED = 1,
+	CMD_ATTR_HIDDEN,
+};
+
+/*! \brief Structure of a command element */
+struct cmd_element {
+	const char *string;	/*!< \brief Command specification by string. */
+	int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
+	const char *doc;	/*!< \brief Documentation of this command. */
+	int daemon;		/*!< \brief Daemon to which this command belong. */
+	vector strvec;		/*!< \brief Pointing out each description vector. */
+	unsigned int cmdsize;	/*!< \brief Command index count. */
+	char *config;		/*!< \brief Configuration string */
+	vector subconfig;	/*!< \brief Sub configuration string */
+	u_char attr;		/*!< \brief Command attributes */
+};
+
+/*! \brief Command description structure. */
+struct desc {
+	const char *cmd;	/*!< \brief Command string. */
+	const char *str;	/*!< \brief Command's description. */
+};
+
+/*! \brief Return value of the commands. */
+#define CMD_SUCCESS              0
+#define CMD_WARNING              1
+#define CMD_ERR_NO_MATCH         2
+#define CMD_ERR_AMBIGUOUS        3
+#define CMD_ERR_INCOMPLETE       4
+#define CMD_ERR_EXEED_ARGC_MAX   5
+#define CMD_ERR_NOTHING_TODO     6
+#define CMD_COMPLETE_FULL_MATCH  7
+#define CMD_COMPLETE_MATCH       8
+#define CMD_COMPLETE_LIST_MATCH  9
+#define CMD_SUCCESS_DAEMON      10
+
+/* Argc max counts. */
+#define CMD_ARGC_MAX   256
+
+/* Turn off these macros when uisng cpp with extract.pl */
+#ifndef VTYSH_EXTRACT_PL
+
+/* helper defines for end-user DEFUN* macros */
+#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+  static struct cmd_element cmdname = \
+  { \
+    .string = cmdstr, \
+    .func = funcname, \
+    .doc = helpstr, \
+    .attr = attrs, \
+    .daemon = dnum, \
+  };
+
+/* global (non static) cmd_element */
+#define gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \
+  struct cmd_element cmdname = \
+  { \
+    .string = cmdstr, \
+    .func = funcname, \
+    .doc = helpstr, \
+    .attr = attrs, \
+    .daemon = dnum, \
+  };
+
+#define DEFUN_CMD_FUNC_DECL(funcname) \
+  static int funcname (struct cmd_element *, struct vty *, int, const char *[]); \
+
+#define DEFUN_CMD_FUNC_TEXT(funcname) \
+  static int funcname \
+    (struct cmd_element *self, struct vty *vty, int argc, const char *argv[])
+
+/*! \brief Macro for defining a VTY node and function
+ *  \param[in] funcname Name of the function implementing the node
+ *  \param[in] cmdname Name of the command node
+ *  \param[in] cmdstr String with syntax of node
+ *  \param[in] helpstr String with help message of node
+ */
+#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+/*! \brief Macro for defining a non-static (global) VTY node and function
+ *  \param[in] funcname Name of the function implementing the node
+ *  \param[in] cmdname Name of the command node
+ *  \param[in] cmdstr String with syntax of node
+ *  \param[in] helpstr String with help message of node
+ */
+#define gDEFUN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) \
+
+/* DEFUN_NOSH for commands that vtysh should ignore */
+#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN(funcname, cmdname, cmdstr, helpstr)
+
+/* DEFSH for vtysh. */
+#define DEFSH(daemon, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) \
+
+/* DEFUN + DEFSH */
+#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+/* DEFUN + DEFSH with attributes */
+#define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_FUNC_DECL(funcname) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \
+  DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
+
+#define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED)
+
+/* ALIAS macro which define existing command's alias. */
+#define ALIAS(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
+/* global (non static) cmd_element */
+#define gALIAS(funcname, cmdname, cmdstr, helpstr) \
+  gDEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)
+
+#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)
+
+#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0)
+
+#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0)
+
+#define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon)
+
+#define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon)
+
+#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
+  DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon)
+
+#endif				/* VTYSH_EXTRACT_PL */
+
+/* Some macroes */
+#define CMD_OPTION(S)   ((S[0]) == '[')
+#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<'))
+#define CMD_VARARG(S)   ((S[0]) == '.')
+#define CMD_RANGE(S)	((S[0] == '<'))
+
+#define CMD_IPV4(S)	   ((strcmp ((S), "A.B.C.D") == 0))
+#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0))
+#define CMD_IPV6(S)        ((strcmp ((S), "X:X::X:X") == 0))
+#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0))
+
+/* Common descriptions. */
+#define SHOW_STR "Show running system information\n"
+#define IP_STR "IP information\n"
+#define IPV6_STR "IPv6 information\n"
+#define NO_STR "Negate a command or set its defaults\n"
+#define CLEAR_STR "Reset functions\n"
+#define RIP_STR "RIP information\n"
+#define BGP_STR "BGP information\n"
+#define OSPF_STR "OSPF information\n"
+#define NEIGHBOR_STR "Specify neighbor router\n"
+#define DEBUG_STR "Debugging functions (see also 'undebug')\n"
+#define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n"
+#define ROUTER_STR "Enable a routing process\n"
+#define AS_STR "AS number\n"
+#define MBGP_STR "MBGP information\n"
+#define MATCH_STR "Match values from routing table\n"
+#define SET_STR "Set values in destination routing protocol\n"
+#define OUT_STR "Filter outgoing routing updates\n"
+#define IN_STR  "Filter incoming routing updates\n"
+#define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n"
+#define OSPF6_NUMBER_STR "Specify by number\n"
+#define INTERFACE_STR "Interface infomation\n"
+#define IFNAME_STR "Interface name(e.g. ep0)\n"
+#define IP6_STR "IPv6 Information\n"
+#define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n"
+#define OSPF6_ROUTER_STR "Enable a routing process\n"
+#define OSPF6_INSTANCE_STR "<1-65535> Instance ID\n"
+#define SECONDS_STR "<1-65535> Seconds\n"
+#define ROUTE_STR "Routing Table\n"
+#define PREFIX_LIST_STR "Build a prefix list\n"
+#define OSPF6_DUMP_TYPE_LIST \
+"(neighbor|interface|area|lsa|zebra|config|dbex|spf|route|lsdb|redistribute|hook|asbr|prefix|abr)"
+#define ISIS_STR "IS-IS information\n"
+#define AREA_TAG_STR "[area tag]\n"
+
+#define CONF_BACKUP_EXT ".sav"
+
+/* IPv4 only machine should not accept IPv6 address for peer's IP
+   address.  So we replace VTY command string like below. */
+#ifdef HAVE_IPV6
+#define NEIGHBOR_CMD       "neighbor (A.B.C.D|X:X::X:X) "
+#define NO_NEIGHBOR_CMD    "no neighbor (A.B.C.D|X:X::X:X) "
+#define NEIGHBOR_ADDR_STR  "Neighbor address\nIPv6 address\n"
+#define NEIGHBOR_CMD2      "neighbor (A.B.C.D|X:X::X:X|WORD) "
+#define NO_NEIGHBOR_CMD2   "no neighbor (A.B.C.D|X:X::X:X|WORD) "
+#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor IPv6 address\nNeighbor tag\n"
+#else
+#define NEIGHBOR_CMD       "neighbor A.B.C.D "
+#define NO_NEIGHBOR_CMD    "no neighbor A.B.C.D "
+#define NEIGHBOR_ADDR_STR  "Neighbor address\n"
+#define NEIGHBOR_CMD2      "neighbor (A.B.C.D|WORD) "
+#define NO_NEIGHBOR_CMD2   "no neighbor (A.B.C.D|WORD) "
+#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor tag\n"
+#endif				/* HAVE_IPV6 */
+
+/* Prototypes. */
+void install_node(struct cmd_node *, int (*)(struct vty *));
+void install_default(enum node_type);
+void install_element(enum node_type, struct cmd_element *);
+void install_element_ve(struct cmd_element *cmd);
+void sort_node(void);
+
+/* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
+   string with a space between each element (allocated using
+   XMALLOC(MTYPE_TMP)).  Returns NULL if shift >= argc. */
+char *argv_concat(const char **argv, int argc, int shift);
+
+vector cmd_make_strvec(const char *);
+void cmd_free_strvec(vector);
+vector cmd_describe_command();
+char **cmd_complete_command();
+const char *cmd_prompt(enum node_type);
+int config_from_file(struct vty *, FILE *);
+enum node_type node_parent(enum node_type);
+int cmd_execute_command(vector, struct vty *, struct cmd_element **, int);
+int cmd_execute_command_strict(vector, struct vty *, struct cmd_element **);
+void config_replace_string(struct cmd_element *, char *, ...);
+void cmd_init(int);
+
+/* Export typical functions. */
+extern struct cmd_element config_exit_cmd;
+extern struct cmd_element config_help_cmd;
+extern struct cmd_element config_list_cmd;
+char *host_config_file();
+void host_config_set(const char *);
+
+/* This is called from main when a daemon is invoked with -v or --version. */
+void print_version(int print_copyright);
+
+extern void *tall_vty_cmd_ctx;
+
+/*! @} */
+#endif				/* _ZEBRA_COMMAND_H */
diff --git a/include/osmocom/vty/logging.h b/include/osmocom/vty/logging.h
new file mode 100644
index 0000000..e0011bf
--- /dev/null
+++ b/include/osmocom/vty/logging.h
@@ -0,0 +1,12 @@
+#ifndef _VTY_LOGGING_H
+#define _VTY_LOGGING_H
+
+#define LOGGING_STR	"Configure log message to this terminal\n"
+#define FILTER_STR	"Filter log messages\n"
+
+struct log_info;
+void logging_vty_add_cmds(const struct log_info *cat);
+struct vty;
+struct log_target *osmo_log_vty2tgt(struct vty *vty);
+
+#endif /* _VTY_LOGGING_H */
diff --git a/include/osmocom/vty/misc.h b/include/osmocom/vty/misc.h
new file mode 100644
index 0000000..8923473
--- /dev/null
+++ b/include/osmocom/vty/misc.h
@@ -0,0 +1,13 @@
+#ifndef OSMO_VTY_MISC_H
+#define OSMO_VTY_MISC_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/core/rate_ctr.h>
+
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+                            struct rate_ctr_group *ctrg);
+
+int osmo_vty_write_config_file(const char *filename);
+int osmo_vty_save_config_file(void);
+
+#endif
diff --git a/include/osmocom/vty/telnet_interface.h b/include/osmocom/vty/telnet_interface.h
new file mode 100644
index 0000000..3c22201
--- /dev/null
+++ b/include/osmocom/vty/telnet_interface.h
@@ -0,0 +1,56 @@
+/* minimalistic telnet/network interface it might turn into a wire interface */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef TELNET_INTERFACE_H
+#define TELNET_INTERFACE_H
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/vty/vty.h>
+
+/*! \defgroup telnet_interface Telnet Interface
+ *  @{
+ */
+
+/*! \file telnet_interface.h */
+
+/*! \brief A telnet connection */
+struct telnet_connection {
+	/*! \brief linked list header for internal management */
+	struct llist_head entry;
+	/*! \brief private data pointer passed through */
+	void *priv;
+	/*! \brief filedsecriptor (socket ) */
+	struct osmo_fd fd;
+	/*! \brief VTY instance associated with telnet connection */
+	struct vty *vty;
+	/*! \brief logging target associated with this telnet connection */
+	struct log_target *dbg;
+};
+
+int telnet_init(void *tall_ctx, void *priv, int port);
+int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port);
+
+void telnet_exit(void);
+
+/*! @} */
+
+#endif /* TELNET_INTERFACE_H */
diff --git a/include/osmocom/vty/vector.h b/include/osmocom/vty/vector.h
new file mode 100644
index 0000000..22a184d
--- /dev/null
+++ b/include/osmocom/vty/vector.h
@@ -0,0 +1,64 @@
+/*
+ * Generic vector interface header.
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VECTOR_H
+#define _ZEBRA_VECTOR_H
+
+/* struct for vector */
+struct _vector {
+	unsigned int active;	/* number of active slots */
+	unsigned int alloced;	/* number of allocated slot */
+	void **index;		/* index to data */
+};
+typedef struct _vector *vector;
+
+#define VECTOR_MIN_SIZE 1
+
+/* (Sometimes) usefull macros.  This macro convert index expression to
+ array expression. */
+/* Reference slot at given index, caller must ensure slot is active */
+#define vector_slot(V,I)  ((V)->index[(I)])
+/* Number of active slots.
+ * Note that this differs from vector_count() as it the count returned
+ * will include any empty slots
+ */
+#define vector_active(V) ((V)->active)
+
+/* Prototypes. */
+vector vector_init(unsigned int size);
+void vector_ensure(vector v, unsigned int num);
+int vector_empty_slot(vector v);
+int vector_set(vector v, void *val);
+int vector_set_index(vector v, unsigned int i, void *val);
+void vector_unset(vector v, unsigned int i);
+unsigned int vector_count(vector v);
+void vector_only_wrapper_free(vector v);
+void vector_only_index_free(void *index);
+void vector_free(vector v);
+vector vector_copy(vector v);
+
+void *vector_lookup(vector, unsigned int);
+void *vector_lookup_ensure(vector, unsigned int);
+
+extern void *tall_vty_vec_ctx;
+
+#endif				/* _ZEBRA_VECTOR_H */
diff --git a/include/osmocom/vty/vty.h b/include/osmocom/vty/vty.h
new file mode 100644
index 0000000..1518894
--- /dev/null
+++ b/include/osmocom/vty/vty.h
@@ -0,0 +1,188 @@
+#ifndef _VTY_H
+#define _VTY_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+/*! \defgroup vty VTY (Virtual TTY) interface
+ *  @{
+ */
+/*! \file vty.h */
+
+/* GCC have printf type attribute check.  */
+#ifdef __GNUC__
+#define VTY_PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
+#else
+#define VTY_PRINTF_ATTRIBUTE(a,b)
+#endif				/* __GNUC__ */
+
+/* Does the I/O error indicate that the operation should be retried later? */
+#define ERRNO_IO_RETRY(EN) \
+	(((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR))
+
+/* Vty read buffer size. */
+#define VTY_READ_BUFSIZ 512
+
+#define VTY_BUFSIZ 512
+#define VTY_MAXHIST 20
+
+/*! \brief VTY events */
+enum event {
+	VTY_SERV,
+	VTY_READ,
+	VTY_WRITE,
+	VTY_CLOSED,
+	VTY_TIMEOUT_RESET,
+#ifdef VTYSH
+	VTYSH_SERV,
+	VTYSH_READ,
+	VTYSH_WRITE
+#endif				/* VTYSH */
+};
+
+/*! Internal representation of a single VTY */
+struct vty {
+	/*! \brief underlying file (if any) */
+	FILE *file;
+
+	/*! \brief private data, specified by creator */
+	void *priv;
+
+	/*! \brief File descripter of this vty. */
+	int fd;
+
+	/*! \brief Is this vty connect to file or not */
+	enum { VTY_TERM, VTY_FILE, VTY_SHELL, VTY_SHELL_SERV } type;
+
+	/*! \brief Node status of this vty */
+	int node;
+
+	/*! \brief Failure count */
+	int fail;
+
+	/*! \brief Output buffer. */
+	struct buffer *obuf;
+
+	/*! \brief Command input buffer */
+	char *buf;
+
+	/*! \brief Command cursor point */
+	int cp;
+
+	/*! \brief Command length */
+	int length;
+
+	/*! \brief Command max length. */
+	int max;
+
+	/*! \brief Histry of command */
+	char *hist[VTY_MAXHIST];
+
+	/*! \brief History lookup current point */
+	int hp;
+
+	/*! \brief History insert end point */
+	int hindex;
+
+	/*! \brief For current referencing point of interface, route-map,
+	   access-list etc... */
+	void *index;
+
+	/*! \brief For multiple level index treatment such as key chain and key. */
+	void *index_sub;
+
+	/*! \brief For escape character. */
+	unsigned char escape;
+
+	/*! \brief Current vty status. */
+	enum { VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE } status;
+
+	/*! \brief IAC handling
+	 *
+	 * IAC handling: was the last character received the IAC
+	 * (interpret-as-command) escape character (and therefore the next
+	 * character will be the command code)?  Refer to Telnet RFC 854. */
+	unsigned char iac;
+
+	/*! \brief IAC SB (option subnegotiation) handling */
+	unsigned char iac_sb_in_progress;
+	/* At the moment, we care only about the NAWS (window size) negotiation,
+	 * and that requires just a 5-character buffer (RFC 1073):
+	 * <NAWS char> <16-bit width> <16-bit height> */
+#define TELNET_NAWS_SB_LEN 5
+	/*! \brief sub-negotiation buffer */
+	unsigned char sb_buf[TELNET_NAWS_SB_LEN];
+	/*! \brief How many subnegotiation characters have we received?  
+	 *
+	 * We just drop those that do not fit in the buffer. */
+	size_t sb_len;
+
+	/*! \brief Window width */
+	int width;
+	/*! \brief Widnow height */
+	int height;
+
+	/*! \brief Configure lines. */
+	int lines;
+
+	int monitor;
+
+	/*! \brief In configure mode. */
+	int config;
+};
+
+/* Small macro to determine newline is newline only or linefeed needed. */
+#define VTY_NEWLINE  ((vty->type == VTY_TERM) ? "\r\n" : "\n")
+
+static inline char *vty_newline(struct vty *vty)
+{
+	return VTY_NEWLINE;
+}
+
+/*! Information an application registers with the VTY */
+struct vty_app_info {
+	/*! \brief name of the application */
+	const char *name;
+	/*! \brief version string of the application */
+	const char *version;
+	/*! \brief copyright string of the application */
+	const char *copyright;
+	/*! \brief \ref talloc context */
+	void *tall_ctx;
+	/*! \brief call-back for returning to parent n ode */
+	enum node_type (*go_parent_cb)(struct vty *vty);
+	/*! \brief call-back to determine if node is config node */
+	int (*is_config_node)(struct vty *vty, int node);
+};
+
+/* Prototypes. */
+void vty_init(struct vty_app_info *app_info);
+int vty_read_config_file(const char *file_name, void *priv);
+void vty_init_vtysh (void);
+void vty_reset (void);
+struct vty *vty_new (void);
+struct vty *vty_create (int vty_sock, void *priv);
+int vty_out (struct vty *, const char *, ...) VTY_PRINTF_ATTRIBUTE(2, 3);
+int vty_out_newline(struct vty *);
+int vty_read(struct vty *vty);
+//void vty_time_print (struct vty *, int);
+void vty_close (struct vty *);
+char *vty_get_cwd (void);
+void vty_log (const char *level, const char *proto, const char *fmt, va_list);
+int vty_config_lock (struct vty *);
+int vty_config_unlock (struct vty *);
+int vty_shell (struct vty *);
+int vty_shell_serv (struct vty *);
+void vty_hello (struct vty *);
+void *vty_current_index(struct vty *);
+int vty_current_node(struct vty *vty);
+enum node_type vty_go_parent(struct vty *vty);
+
+extern void *tall_vty_ctx;
+
+extern struct cmd_element cfg_description_cmd;
+extern struct cmd_element cfg_no_description_cmd;
+
+/*! @} */
+
+#endif
diff --git a/libosmocodec.pc.in b/libosmocodec.pc.in
new file mode 100644
index 0000000..3030230
--- /dev/null
+++ b/libosmocodec.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom Codec related utilities Library
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} -losmocodec
+Cflags: -I${includedir}/
+
diff --git a/libosmocore.pc.in b/libosmocore.pc.in
new file mode 100644
index 0000000..7c29869
--- /dev/null
+++ b/libosmocore.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom Core Library
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} -losmocore
+Cflags: -I${includedir}/
+
diff --git a/libosmogsm.pc.in b/libosmogsm.pc.in
new file mode 100644
index 0000000..753bb3a
--- /dev/null
+++ b/libosmogsm.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom GSM Core Library
+Description: GSM Core Library
+Version: @VERSION@
+Libs: -L${libdir} -losmogsm
+Cflags: -I${includedir}/
+
diff --git a/libosmovty.pc.in b/libosmovty.pc.in
new file mode 100644
index 0000000..2cc0b5f
--- /dev/null
+++ b/libosmovty.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom VTY Interface Library
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} -losmovty
+Cflags: -I${includedir}/
+
diff --git a/m4/DUMMY b/m4/DUMMY
new file mode 100644
index 0000000..fda557a
--- /dev/null
+++ b/m4/DUMMY
@@ -0,0 +1 @@
+Dummply placeholder.
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..079d0b4
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,43 @@
+SUBDIRS=. vty codec gsm
+
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
+LIBVERSION=4:0:0
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CFLAGS = -fPIC -Wall
+
+lib_LTLIBRARIES = libosmocore.la
+
+libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \
+			 bitvec.c statistics.c \
+			 write_queue.c utils.c socket.c \
+			 logging.c logging_syslog.c rate_ctr.c \
+			 gsmtap_util.c crc16.c panic.c backtrace.c \
+			 conv.c application.c rbtree.c \
+			 crc8gen.c crc16gen.c crc32gen.c crc64gen.c
+
+if ENABLE_PLUGIN
+libosmocore_la_SOURCES += plugin.c
+libosmocore_la_LDFLAGS = -version-info $(LIBVERSION) $(LIBRARY_DL)
+else
+libosmocore_la_LDFLAGS = -version-info $(LIBVERSION)
+endif
+
+if ENABLE_TALLOC
+libosmocore_la_SOURCES += talloc.c
+else
+libosmocore_la_LIBADD = -ltalloc
+endif
+
+if ENABLE_MSGFILE
+libosmocore_la_SOURCES += msgfile.c
+endif
+
+if ENABLE_SERIAL
+libosmocore_la_SOURCES += serial.c
+endif
+
+crc%gen.c: crcXXgen.c.tpl
+	@echo "  SED    $< -> $@"
+	@sed -e's/XX/$*/g' $< > $@
diff --git a/src/application.c b/src/application.c
new file mode 100644
index 0000000..e0d989e
--- /dev/null
+++ b/src/application.c
@@ -0,0 +1,154 @@
+/* Utility functions to setup applications */
+/*
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \file application.c
+ *  \brief Routines for helping with the osmocom application setup.
+ */
+
+/*! \mainpage libosmocore Documentation
+ * \section sec_intro Introduction
+ * This library is a collection of common code used in various
+ * sub-projects inside the Osmocom family of projects.  It includes a
+ * logging framework, select() loop abstraction, timers with callbacks,
+ * bit vectors, bit packing/unpacking, convolutional decoding, GSMTAP, a
+ * generic plugin interface, statistics counters, memory allocator,
+ * socket abstraction, message buffers, etc.
+ * \n\n
+ * Please note that C language projects inside Osmocom are typically
+ * single-threaded event-loop state machine designs.  As such,
+ * routines in libosmocore are not thread-safe.  If you must use them in
+ * a multi-threaded context, you have to add your own locking.
+ *
+ * \section sec_copyright Copyright and License
+ * Copyright © 2008-2011 - Harald Welte, Holger Freyther and contributors\n
+ * All rights reserved. \n\n
+ * The source code of libosmocore is licensed under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License, or (at your option) any later
+ * version.\n
+ * See <http://www.gnu.org/licenses/> or COPYING included in the source
+ * code package istelf.\n
+ * The information detailed here is provided AS IS with NO WARRANTY OF
+ * ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ * \n\n
+ *
+ * \section sec_contact Contact and Support
+ * Community-based support is available at the OpenBSC mailing list
+ * <http://lists.osmocom.org/mailman/listinfo/openbsc>\n
+ * Commercial support options available upon request from
+ * <http://sysmocom.de/>
+ */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+struct log_target *osmo_stderr_target;
+
+/*! \brief Ignore \ref SIGPIPE, \ref SIGALRM, \ref SIGHUP and \ref SIGIO */
+void osmo_init_ignore_signals(void)
+{
+	/* Signals that by default would terminate */
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGALRM, SIG_IGN);
+	signal(SIGHUP, SIG_IGN);
+	signal(SIGIO, SIG_IGN);
+}
+
+/*! \brief Initialize the osmocom logging framework
+ *  \param[in] log_info Array of available logging sub-systems
+ *  \returns 0 on success, -1 in case of error
+ *
+ * This function initializes the osmocom logging systems.  It also
+ * creates the default (stderr) logging target.
+ */
+int osmo_init_logging(const struct log_info *log_info)
+{
+	log_init(log_info, NULL);
+	osmo_stderr_target = log_target_create_stderr();
+	if (!osmo_stderr_target)
+		return -1;
+
+	log_add_target(osmo_stderr_target);
+	log_set_all_filter(osmo_stderr_target, 1);
+	return 0;
+}
+
+/*! \brief Turn the current process into a background daemon
+ *
+ * This function will fork the process, exit the parent and set umask,
+ * create a new session, close stdin/stdout/stderr and chdir to /tmp
+ */
+int osmo_daemonize(void)
+{
+	int rc;
+	pid_t pid, sid;
+
+	/* Check if parent PID == init, in which case we are already a daemon */
+	if (getppid() == 1)
+		return -EEXIST;
+
+	/* Fork from the parent process */
+	pid = fork();
+	if (pid < 0) {
+		/* some error happened */
+		return pid;
+	}
+
+	if (pid > 0) {
+		/* if we have received a positive PID, then we are the parent
+		 * and can exit */
+		exit(0);
+	}
+
+	/* FIXME: do we really want this? */
+	umask(0);
+
+	/* Create a new session and set process group ID */
+	sid = setsid();
+	if (sid < 0)
+		return sid;
+
+	/* Change to the /tmp directory, which prevents the CWD from being locked
+	 * and unable to remove it */
+	rc = chdir("/tmp");
+	if (rc < 0)
+		return rc;
+
+	/* Redirect stdio to /dev/null */
+/* since C89/C99 says stderr is a macro, we can safely do this! */
+#ifdef stderr
+	freopen("/dev/null", "r", stdin);
+	freopen("/dev/null", "w", stdout);
+	freopen("/dev/null", "w", stderr);
+#endif
+
+	return 0;
+}
diff --git a/src/backtrace.c b/src/backtrace.c
new file mode 100644
index 0000000..023671c
--- /dev/null
+++ b/src/backtrace.c
@@ -0,0 +1,64 @@
+/*
+ * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Nico Golde <nico@ngolde.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \file backtrace.c
+ *  \brief Routines realted to generating call back traces
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+#include "config.h"
+
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+
+/*! \brief Generate and print a call back-trace
+ *
+ * This function will generate a function call back-trace of the
+ * current process and print it to stdout
+ */
+void osmo_generate_backtrace(void)
+{
+	int i, nptrs;
+	void *buffer[100];
+	char **strings;
+
+	nptrs = backtrace(buffer, ARRAY_SIZE(buffer));
+	printf("backtrace() returned %d addresses\n", nptrs);
+
+	strings = backtrace_symbols(buffer, nptrs);
+	if (!strings)
+		return;
+
+	for (i = 1; i < nptrs; i++)
+		printf("%s\n", strings[i]);
+
+	free(strings);
+}
+#else
+void osmo_generate_backtrace(void)
+{
+}
+#endif
diff --git a/src/bits.c b/src/bits.c
new file mode 100644
index 0000000..4c67bdd
--- /dev/null
+++ b/src/bits.c
@@ -0,0 +1,188 @@
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+
+/*! \addtogroup bits
+ *  @{
+ */
+
+/*! \file bits.c
+ *  \brief Osmocom bit level support code
+ */
+
+
+/*! \brief convert unpacked bits to packed bits, return length in bytes
+ *  \param[out] out output buffer of packed bits
+ *  \param[in] in input buffer of unpacked bits
+ *  \param[in] num_bits number of bits
+ */
+int osmo_ubit2pbit(pbit_t *out, const ubit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	uint8_t curbyte = 0;
+	pbit_t *outptr = out;
+
+	for (i = 0; i < num_bits; i++) {
+		uint8_t bitnum = 7 - (i % 8);
+
+		curbyte |= (in[i] << bitnum);
+
+		if(i % 8 == 7){
+			*outptr++ = curbyte;
+			curbyte = 0;
+		}
+	}
+	/* we have a non-modulo-8 bitcount */
+	if (i % 8)
+		*outptr++ = curbyte;
+
+	return outptr - out;
+}
+
+/*! \brief convert packed bits to unpacked bits, return length in bytes
+ *  \param[out] out output buffer of unpacked bits
+ *  \param[in] in input buffer of packed bits
+ *  \param[in] num_bits number of bits
+ */
+int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits)
+{
+	unsigned int i;
+	ubit_t *cur = out;
+	ubit_t *limit = out + num_bits;
+
+	for (i = 0; i < (num_bits/8)+1; i++) {
+		pbit_t byte = in[i];
+		*cur++ = (byte >> 7) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 6) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 5) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 4) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 3) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 2) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 1) & 1;
+		if (cur >= limit)
+			break;
+		*cur++ = (byte >> 0) & 1;
+		if (cur >= limit)
+			break;
+	}
+	return cur - out;
+}
+
+/*! \brief convert unpacked bits to packed bits (extended options)
+ *  \param[out] out output buffer of packed bits
+ *  \param[in] out_ofs offset into output buffer
+ *  \param[in] in input buffer of unpacked bits
+ *  \param[in] in_ofs offset into input buffer
+ *  \param[in] num_bits number of bits
+ *  \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ *  \returns length in bytes (max written offset of output buffer + 1)
+ */
+int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
+                       const ubit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode)
+{
+	int i, op, bn;
+	for (i=0; i<num_bits; i++) {
+		op = out_ofs + i;
+		bn = lsb_mode ? (op&7) : (7-(op&7));
+		if (in[in_ofs+i])
+			out[op>>3] |= 1 << bn;
+		else
+			out[op>>3] &= ~(1 << bn);
+	}
+	return ((out_ofs + num_bits - 1) >> 3) + 1;
+}
+
+/*! \brief convert packed bits to unpacked bits (extended options)
+ *  \param[out] out output buffer of unpacked bits
+ *  \param[in] out_ofs offset into output buffer
+ *  \param[in] in input buffer of packed bits
+ *  \param[in] in_ofs offset into input buffer
+ *  \param[in] num_bits number of bits
+ *  \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ *  \returns length in bytes (max written offset of output buffer + 1)
+ */
+int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
+                       const pbit_t *in, unsigned int in_ofs,
+                       unsigned int num_bits, int lsb_mode)
+{
+	int i, ip, bn;
+	for (i=0; i<num_bits; i++) {
+		ip = in_ofs + i;
+		bn = lsb_mode ? (ip&7) : (7-(ip&7));
+		out[out_ofs+i] = !!(in[ip>>3] & (1<<bn));
+	}
+	return out_ofs + num_bits;
+}
+
+/* generalized bit reversal function, Chapter 7 "Hackers Delight" */
+uint32_t osmo_bit_reversal(uint32_t x, enum osmo_br_mode k)
+{
+	if (k &  1) x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>  1;
+	if (k &  2) x = (x & 0x33333333) <<  2 | (x & 0xCCCCCCCC) >>  2;
+	if (k &  4) x = (x & 0x0F0F0F0F) <<  4 | (x & 0xF0F0F0F0) >>  4;
+	if (k &  8) x = (x & 0x00FF00FF) <<  8 | (x & 0xFF00FF00) >>  8;
+	if (k & 16) x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16;
+
+	return x;
+}
+
+/* generalized bit reversal function, Chapter 7 "Hackers Delight" */
+uint32_t osmo_revbytebits_32(uint32_t x)
+{
+	x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>  1;
+	x = (x & 0x33333333) <<  2 | (x & 0xCCCCCCCC) >>  2;
+	x = (x & 0x0F0F0F0F) <<  4 | (x & 0xF0F0F0F0) >>  4;
+
+	return x;
+}
+
+uint32_t osmo_revbytebits_8(uint8_t x)
+{
+	x = (x & 0x55) <<  1 | (x & 0xAA) >>  1;
+	x = (x & 0x33) <<  2 | (x & 0xCC) >>  2;
+	x = (x & 0x0F) <<  4 | (x & 0xF0) >>  4;
+
+	return x;
+}
+
+void osmo_revbytebits_buf(uint8_t *buf, int len)
+{
+	unsigned int i;
+	unsigned int unaligned_cnt;
+	int len_remain = len;
+
+	unaligned_cnt = ((unsigned long)buf & 3);
+	for (i = 0; i < unaligned_cnt; i++) {
+		buf[i] = osmo_revbytebits_8(buf[i]);
+		len_remain--;
+		if (len_remain <= 0)
+			return;
+	}
+
+	for (i = unaligned_cnt; i < len; i += 4) {
+		uint32_t *cur = (uint32_t *) (buf + i);
+		*cur = osmo_revbytebits_32(*cur);
+		len_remain -= 4;
+	}
+
+	for (i = len - len_remain; i < len; i++) {
+		buf[i] = osmo_revbytebits_8(buf[i]);
+		len_remain--;
+	}
+}
+
+/*! @} */
diff --git a/src/bitvec.c b/src/bitvec.c
new file mode 100644
index 0000000..714c11b
--- /dev/null
+++ b/src/bitvec.c
@@ -0,0 +1,264 @@
+/* bit vector utility routines */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup bitvec
+ *  @{
+ */
+
+/*! \file bitvec.c
+ *  \brief Osmocom bit vector abstraction
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocom/core/bitvec.h>
+
+#define BITNUM_FROM_COMP(byte, bit)	((byte*8)+bit)
+
+static inline unsigned int bytenum_from_bitnum(unsigned int bitnum)
+{
+	unsigned int bytenum = bitnum / 8;
+
+	return bytenum;
+}
+
+/* convert ZERO/ONE/L/H to a bitmask at given pos in a byte */
+static uint8_t bitval2mask(enum bit_value bit, uint8_t bitnum)
+{
+	int bitval;
+
+	switch (bit) {
+	case ZERO:
+		bitval = (0 << bitnum);
+		break;
+	case ONE:
+		bitval = (1 << bitnum);
+		break;
+	case L:
+		bitval = ((0x2b ^ (0 << bitnum)) & (1 << bitnum));
+		break;
+	case H:
+		bitval = ((0x2b ^ (1 << bitnum)) & (1 << bitnum));
+		break;
+	default:
+		return 0;
+	}
+	return bitval;
+}
+
+/*! \brief check if the bit is 0 or 1 for a given position inside a bitvec
+ *  \param[in] bv the bit vector on which to check
+ *  \param[in] bitnr the bit number inside the bit vector to check
+ *  \returns 
+ */
+enum bit_value bitvec_get_bit_pos(const struct bitvec *bv, unsigned int bitnr)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	bitval = bitval2mask(ONE, bitnum);
+
+	if (bv->data[bytenum] & bitval)
+		return ONE;
+
+	return ZERO;
+}
+
+/*! \brief check if the bit is L or H for a given position inside a bitvec
+ *  \param[in] bv the bit vector on which to check
+ *  \param[in] bitnr the bit number inside the bit vector to check
+ */
+enum bit_value bitvec_get_bit_pos_high(const struct bitvec *bv,
+					unsigned int bitnr)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	bitval = bitval2mask(H, bitnum);
+
+	if ((bv->data[bytenum] & (1 << bitnum)) == bitval)
+		return H;
+
+	return L;
+}
+
+/*! \brief get the Nth set bit inside the bit vector
+ *  \param[in] bv the bit vector to use
+ *  \param[in] n the bit number to get
+ *  \returns the bit number (offset) of the Nth set bit in \a bv
+ */
+unsigned int bitvec_get_nth_set_bit(const struct bitvec *bv, unsigned int n)
+{
+	unsigned int i, k = 0;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i) == ONE) {
+			k++;
+			if (k == n)
+				return i;
+		}
+	}
+
+	return 0;
+}
+
+/*! \brief set a bit at given position in a bit vector
+ *  \param[in] bv bit vector on which to operate
+ *  \param[in] bitnum number of bit to be set
+ *  \param[in] bit value to which the bit is to be set
+ */
+int bitvec_set_bit_pos(struct bitvec *bv, unsigned int bitnr,
+			enum bit_value bit)
+{
+	unsigned int bytenum = bytenum_from_bitnum(bitnr);
+	unsigned int bitnum = 7 - (bitnr % 8);
+	uint8_t bitval;
+
+	if (bytenum >= bv->data_len)
+		return -EINVAL;
+
+	/* first clear the bit */
+	bitval = bitval2mask(ONE, bitnum);
+	bv->data[bytenum] &= ~bitval;
+
+	/* then set it to desired value */
+	bitval = bitval2mask(bit, bitnum);
+	bv->data[bytenum] |= bitval;
+
+	return 0;
+}
+
+/*! \brief set the next bit inside a bitvec
+ *  \param[in] bv bit vector to be used
+ *  \param[in] bit value of the bit to be set
+ */
+int bitvec_set_bit(struct bitvec *bv, enum bit_value bit)
+{
+	int rc;
+
+	rc = bitvec_set_bit_pos(bv, bv->cur_bit, bit);
+	if (!rc)
+		bv->cur_bit++;
+
+	return rc;
+}
+
+/*! \brief get the next bit (low/high) inside a bitvec */
+int bitvec_get_bit_high(struct bitvec *bv)
+{
+	int rc;
+
+	rc = bitvec_get_bit_pos_high(bv, bv->cur_bit);
+	if (rc >= 0)
+		bv->cur_bit++;
+
+	return rc;
+}
+
+/*! \brief set multiple bits (based on array of bitvals) at current pos
+ *  \param[in] bv bit vector
+ *  \param[in] bits array of \ref bit_value
+ *  \param[in] count number of bits to set
+ */
+int bitvec_set_bits(struct bitvec *bv, enum bit_value *bits, int count)
+{
+	int i, rc;
+
+	for (i = 0; i < count; i++) {
+		rc = bitvec_set_bit(bv, bits[i]);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*! \brief set multiple bits (based on numeric value) at current pos */
+int bitvec_set_uint(struct bitvec *bv, unsigned int ui, int num_bits)
+{
+	int i, rc;
+
+	for (i = 0; i < num_bits; i++) {
+		int bit = 0;
+		if (ui & (1 << (num_bits - i - 1)))
+			bit = 1;
+		rc = bitvec_set_bit(bv, bit);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+/*! \brief get multiple bits (based on numeric value) from current pos */
+int bitvec_get_uint(struct bitvec *bv, int num_bits)
+{
+	int i;
+	unsigned int ui = 0;
+
+	for (i = 0; i < num_bits; i++) {
+		int bit = bitvec_get_bit_pos(bv, bv->cur_bit);
+		if (bit < 0)
+			return bit;
+		if (bit)
+			ui |= (1 << (num_bits - i - 1));
+		bv->cur_bit++;
+	}
+
+	return ui;
+}
+
+/*! \brief pad all remaining bits up to num_bits */
+int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit)
+{
+	unsigned int i;
+
+	for (i = bv->cur_bit; i <= up_to_bit; i++)
+		bitvec_set_bit(bv, L);
+
+	return 0;
+}
+
+/*! \brief find first bit set in bit vector */
+int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n,
+			enum bit_value val)
+{
+	unsigned int i;
+
+	for (i = n; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i) == val)
+			return i;
+	}
+
+	return -1;
+}
+
+/*! @} */
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
new file mode 100644
index 0000000..d36e23f
--- /dev/null
+++ b/src/codec/Makefile.am
@@ -0,0 +1,11 @@
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
+LIBVERSION=0:0:0
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -fPIC -Wall
+
+lib_LTLIBRARIES = libosmocodec.la
+
+libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c
+libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION)
diff --git a/src/codec/gsm610.c b/src/codec/gsm610.c
new file mode 100644
index 0000000..09fbeb5
--- /dev/null
+++ b/src/codec/gsm610.c
@@ -0,0 +1,294 @@
+/* GSM 06.10 - GSM FR codec */
+
+/*
+ * (C) 2010 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+/* GSM FR - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 2.
+	 * It's also GSM 06.10 Table A.2.1a
+	 *
+	 * It converts between serial parameter output by the encoder and the
+	 * order needed before channel encoding.
+	 */
+uint16_t gsm610_bitorder[260] = {
+	0,	/* LARc0:5 */
+	47,	/* Xmaxc0:5 */
+	103,	/* Xmaxc1:5 */
+	159,	/* Xmaxc2:5 */
+	215,	/* Xmaxc3:5 */
+	1,	/* LARc0:4 */
+	6,	/* LARc1:5 */
+	12,	/* LARc2:4 */
+	2,	/* LARc0:3 */
+	7,	/* LARc1:4 */
+	13,	/* LARc2:3 */
+	17,	/* LARc3:4 */
+	36,	/* Nc0:6 */
+	92,	/* Nc1:6 */
+	148,	/* Nc2:6 */
+	204,	/* Nc3:6 */
+	48,	/* Xmaxc0:4 */
+	104,	/* Xmaxc1:4 */
+	160,	/* Xmaxc2:4 */
+	216,	/* Xmaxc3:4 */
+	8,	/* LARc1:3 */
+	22,	/* LARc4:3 */
+	26,	/* LARc5:3 */
+	37,	/* Nc0:5 */
+	93,	/* Nc1:5 */
+	149,	/* Nc2:5 */
+	205,	/* Nc3:5 */
+	38,	/* Nc0:4 */
+	94,	/* Nc1:4 */
+	150,	/* Nc2:4 */
+	206,	/* Nc3:4 */
+	39,	/* Nc0:3 */
+	95,	/* Nc1:3 */
+	151,	/* Nc2:3 */
+	207,	/* Nc3:3 */
+	40,	/* Nc0:2 */
+	96,	/* Nc1:2 */
+	152,	/* Nc2:2 */
+	208,	/* Nc3:2 */
+	49,	/* Xmaxc0:3 */
+	105,	/* Xmaxc1:3 */
+	161,	/* Xmaxc2:3 */
+	217,	/* Xmaxc3:3 */
+	3,	/* LARc0:2 */
+	18,	/* LARc3:3 */
+	30,	/* LARc6:2 */
+	41,	/* Nc0:1 */
+	97,	/* Nc1:1 */
+	153,	/* Nc2:1 */
+	209,	/* Nc3:1 */
+	23,	/* LARc4:2 */
+	27,	/* LARc5:2 */
+	43,	/* bc0:1 */
+	99,	/* bc1:1 */
+	155,	/* bc2:1 */
+	211,	/* bc3:1 */
+	42,	/* Nc0:0 */
+	98,	/* Nc1:0 */
+	154,	/* Nc2:0 */
+	210,	/* Nc3:0 */
+	45,	/* Mc0:1 */
+	101,	/* Mc1:1 */
+	157,	/* Mc2:1 */
+	213,	/* Mc3:1 */
+	4,	/* LARc0:1 */
+	9,	/* LARc1:2 */
+	14,	/* LARc2:2 */
+	33,	/* LARc7:2 */
+	19,	/* LARc3:2 */
+	24,	/* LARc4:1 */
+	31,	/* LARc6:1 */
+	44,	/* bc0:0 */
+	100,	/* bc1:0 */
+	156,	/* bc2:0 */
+	212,	/* bc3:0 */
+	50,	/* Xmaxc0:2 */
+	106,	/* Xmaxc1:2 */
+	162,	/* Xmaxc2:2 */
+	218,	/* Xmaxc3:2 */
+	53,	/* xmc0_0:2 */
+	56,	/* xmc0_1:2 */
+	59,	/* xmc0_2:2 */
+	62,	/* xmc0_3:2 */
+	65,	/* xmc0_4:2 */
+	68,	/* xmc0_5:2 */
+	71,	/* xmc0_6:2 */
+	74,	/* xmc0_7:2 */
+	77,	/* xmc0_8:2 */
+	80,	/* xmc0_9:2 */
+	83,	/* xmc0_10:2 */
+	86,	/* xmc0_11:2 */
+	89,	/* xmc0_12:2 */
+	109,	/* xmc1_0:2 */
+	112,	/* xmc1_1:2 */
+	115,	/* xmc1_2:2 */
+	118,	/* xmc1_3:2 */
+	121,	/* xmc1_4:2 */
+	124,	/* xmc1_5:2 */
+	127,	/* xmc1_6:2 */
+	130,	/* xmc1_7:2 */
+	133,	/* xmc1_8:2 */
+	136,	/* xmc1_9:2 */
+	139,	/* xmc1_10:2 */
+	142,	/* xmc1_11:2 */
+	145,	/* xmc1_12:2 */
+	165,	/* xmc2_0:2 */
+	168,	/* xmc2_1:2 */
+	171,	/* xmc2_2:2 */
+	174,	/* xmc2_3:2 */
+	177,	/* xmc2_4:2 */
+	180,	/* xmc2_5:2 */
+	183,	/* xmc2_6:2 */
+	186,	/* xmc2_7:2 */
+	189,	/* xmc2_8:2 */
+	192,	/* xmc2_9:2 */
+	195,	/* xmc2_10:2 */
+	198,	/* xmc2_11:2 */
+	201,	/* xmc2_12:2 */
+	221,	/* xmc3_0:2 */
+	224,	/* xmc3_1:2 */
+	227,	/* xmc3_2:2 */
+	230,	/* xmc3_3:2 */
+	233,	/* xmc3_4:2 */
+	236,	/* xmc3_5:2 */
+	239,	/* xmc3_6:2 */
+	242,	/* xmc3_7:2 */
+	245,	/* xmc3_8:2 */
+	248,	/* xmc3_9:2 */
+	251,	/* xmc3_10:2 */
+	254,	/* xmc3_11:2 */
+	257,	/* xmc3_12:2 */
+	46,	/* Mc0:0 */
+	102,	/* Mc1:0 */
+	158,	/* Mc2:0 */
+	214,	/* Mc3:0 */
+	51,	/* Xmaxc0:1 */
+	107,	/* Xmaxc1:1 */
+	163,	/* Xmaxc2:1 */
+	219,	/* Xmaxc3:1 */
+	54,	/* xmc0_0:1 */
+	57,	/* xmc0_1:1 */
+	60,	/* xmc0_2:1 */
+	63,	/* xmc0_3:1 */
+	66,	/* xmc0_4:1 */
+	69,	/* xmc0_5:1 */
+	72,	/* xmc0_6:1 */
+	75,	/* xmc0_7:1 */
+	78,	/* xmc0_8:1 */
+	81,	/* xmc0_9:1 */
+	84,	/* xmc0_10:1 */
+	87,	/* xmc0_11:1 */
+	90,	/* xmc0_12:1 */
+	110,	/* xmc1_0:1 */
+	113,	/* xmc1_1:1 */
+	116,	/* xmc1_2:1 */
+	119,	/* xmc1_3:1 */
+	122,	/* xmc1_4:1 */
+	125,	/* xmc1_5:1 */
+	128,	/* xmc1_6:1 */
+	131,	/* xmc1_7:1 */
+	134,	/* xmc1_8:1 */
+	137,	/* xmc1_9:1 */
+	140,	/* xmc1_10:1 */
+	143,	/* xmc1_11:1 */
+	146,	/* xmc1_12:1 */
+	166,	/* xmc2_0:1 */
+	169,	/* xmc2_1:1 */
+	172,	/* xmc2_2:1 */
+	175,	/* xmc2_3:1 */
+	178,	/* xmc2_4:1 */
+	181,	/* xmc2_5:1 */
+	184,	/* xmc2_6:1 */
+	187,	/* xmc2_7:1 */
+	190,	/* xmc2_8:1 */
+	193,	/* xmc2_9:1 */
+	196,	/* xmc2_10:1 */
+	199,	/* xmc2_11:1 */
+	202,	/* xmc2_12:1 */
+	222,	/* xmc3_0:1 */
+	225,	/* xmc3_1:1 */
+	228,	/* xmc3_2:1 */
+	231,	/* xmc3_3:1 */
+	234,	/* xmc3_4:1 */
+	237,	/* xmc3_5:1 */
+	240,	/* xmc3_6:1 */
+	243,	/* xmc3_7:1 */
+	246,	/* xmc3_8:1 */
+	249,	/* xmc3_9:1 */
+	252,	/* xmc3_10:1 */
+	255,	/* xmc3_11:1 */
+	258,	/* xmc3_12:1 */
+	5,	/* LARc0:0 */
+	10,	/* LARc1:1 */
+	15,	/* LARc2:1 */
+	28,	/* LARc5:1 */
+	32,	/* LARc6:0 */
+	34,	/* LARc7:1 */
+	35,	/* LARc7:0 */
+	16,	/* LARc2:0 */
+	20,	/* LARc3:1 */
+	21,	/* LARc3:0 */
+	25,	/* LARc4:0 */
+	52,	/* Xmaxc0:0 */
+	108,	/* Xmaxc1:0 */
+	164,	/* Xmaxc2:0 */
+	220,	/* Xmaxc3:0 */
+	55,	/* xmc0_0:0 */
+	58,	/* xmc0_1:0 */
+	61,	/* xmc0_2:0 */
+	64,	/* xmc0_3:0 */
+	67,	/* xmc0_4:0 */
+	70,	/* xmc0_5:0 */
+	73,	/* xmc0_6:0 */
+	76,	/* xmc0_7:0 */
+	79,	/* xmc0_8:0 */
+	82,	/* xmc0_9:0 */
+	85,	/* xmc0_10:0 */
+	88,	/* xmc0_11:0 */
+	91,	/* xmc0_12:0 */
+	111,	/* xmc1_0:0 */
+	114,	/* xmc1_1:0 */
+	117,	/* xmc1_2:0 */
+	120,	/* xmc1_3:0 */
+	123,	/* xmc1_4:0 */
+	126,	/* xmc1_5:0 */
+	129,	/* xmc1_6:0 */
+	132,	/* xmc1_7:0 */
+	135,	/* xmc1_8:0 */
+	138,	/* xmc1_9:0 */
+	141,	/* xmc1_10:0 */
+	144,	/* xmc1_11:0 */
+	147,	/* xmc1_12:0 */
+	167,	/* xmc2_0:0 */
+	170,	/* xmc2_1:0 */
+	173,	/* xmc2_2:0 */
+	176,	/* xmc2_3:0 */
+	179,	/* xmc2_4:0 */
+	182,	/* xmc2_5:0 */
+	185,	/* xmc2_6:0 */
+	188,	/* xmc2_7:0 */
+	191,	/* xmc2_8:0 */
+	194,	/* xmc2_9:0 */
+	197,	/* xmc2_10:0 */
+	200,	/* xmc2_11:0 */
+	203,	/* xmc2_12:0 */
+	223,	/* xmc3_0:0 */
+	226,	/* xmc3_1:0 */
+	229,	/* xmc3_2:0 */
+	232,	/* xmc3_3:0 */
+	235,	/* xmc3_4:0 */
+	238,	/* xmc3_5:0 */
+	241,	/* xmc3_6:0 */
+	244,	/* xmc3_7:0 */
+	247,	/* xmc3_8:0 */
+	250,	/* xmc3_9:0 */
+	253,	/* xmc3_10:0 */
+	256,	/* xmc3_11:0 */
+	259,	/* xmc3_12:0 */
+	11,	/* LARc1:0 */
+	29,	/* LARc5:0 */
+};
diff --git a/src/codec/gsm620.c b/src/codec/gsm620.c
new file mode 100644
index 0000000..09aca50
--- /dev/null
+++ b/src/codec/gsm620.c
@@ -0,0 +1,262 @@
+/* GSM 06.20 - GSM HR codec */
+
+/*
+ * (C) 2010 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+/* GSM HR unvoiced (mode=0) frames - subjective importance bit ordering */
+	/* This array encode mapping between GSM 05.03 Table 3a (bits
+	 * ordering before channel coding on TCH) and GSM 06.20 Table B.1
+	 * (bit ordering on A-bis */
+uint16_t gsm620_unvoiced_bitorder[112] = {
+	3,	/* R0:1 */
+	25,	/* LPC 3:7 */
+	52,	/* GSP 0-1:2 */
+	71,	/* GSP 0-2:2 */
+	90,	/* GSP 0-3:2 */
+	109,	/* GSP 0-4:2 */
+	15,	/* LPC 1:0 */
+	23,	/* LPC 2:1 */
+	22,	/* LPC 2:2 */
+	21,	/* LPC 2:3 */
+	20,	/* LPC 2:4 */
+	19,	/* LPC 2:5 */
+	31,	/* LPC 3:1 */
+	30,	/* LPC 3:2 */
+	29,	/* LPC 3:3 */
+	28,	/* LPC 3:4 */
+	27,	/* LPC 3:5 */
+	26,	/* LPC 3:6 */
+	61,	/* Code 1-2:0 */
+	68,	/* Code 2-2:0 */
+	67,	/* Code 2-2:1 */
+	66,	/* Code 2-2:2 */
+	65,	/* Code 2-2:3 */
+	64,	/* Code 2-2:4 */
+	63,	/* Code 2-2:5 */
+	62,	/* Code 2-2:6 */
+	80,	/* Code 1-3:0 */
+	79,	/* Code 1-3:1 */
+	78,	/* Code 1-3:2 */
+	77,	/* Code 1-3:3 */
+	76,	/* Code 1-3:4 */
+	75,	/* Code 1-3:5 */
+	74,	/* Code 1-3:6 */
+	84,	/* Code 2-3:3 */
+	83,	/* Code 2-3:4 */
+	82,	/* Code 2-3:5 */
+	81,	/* Code 2-3:6 */
+	32,	/* LPC 3:0 */
+	4,	/* R0:0 */
+	33,	/* INT-LPC:0 */
+	60,	/* Code 1-2:1 */
+	59,	/* Code 1-2:2 */
+	58,	/* Code 1-2:3 */
+	57,	/* Code 1-2:4 */
+	56,	/* Code 1-2:5 */
+	55,	/* Code 1-2:6 */
+	49,	/* Code 2-1:0 */
+	48,	/* Code 2-1:1 */
+	47,	/* Code 2-1:2 */
+	46,	/* Code 2-1:3 */
+	45,	/* Code 2-1:4 */
+	44,	/* Code 2-1:5 */
+	43,	/* Code 2-1:6 */
+	42,	/* Code 1-1:0 */
+	41,	/* Code 1-1:1 */
+	40,	/* Code 1-1:2 */
+	39,	/* Code 1-1:3 */
+	38,	/* Code 1-1:4 */
+	37,	/* Code 1-1:5 */
+	36,	/* Code 1-1:6 */
+	111,	/* GSP 0-4:0 */
+	92,	/* GSP 0-3:0 */
+	73,	/* GSP 0-2:0 */
+	54,	/* GSP 0-1:0 */
+	24,	/* LPC 2:0 */
+	110,	/* GSP 0-4:1 */
+	91,	/* GSP 0-3:1 */
+	72,	/* GSP 0-2:1 */
+	53,	/* GSP 0-1:1 */
+	14,	/* LPC 1:1 */
+	13,	/* LPC 1:2 */
+	12,	/* LPC 1:3 */
+	11,	/* LPC 1:4 */
+	10,	/* LPC 1:5 */
+	108,	/* GSP 0-4:3 */
+	89,	/* GSP 0-3:3 */
+	70,	/* GSP 0-2:3 */
+	51,	/* GSP 0-1:3 */
+	18,	/* LPC 2:6 */
+	17,	/* LPC 2:7 */
+	16,	/* LPC 2:8 */
+	107,	/* GSP 0-4:4 */
+	88,	/* GSP 0-3:4 */
+	69,	/* GSP 0-2:4 */
+	50,	/* GSP 0-1:4 */
+	9,	/* LPC 1:6 */
+	8,	/* LPC 1:7 */
+	7,	/* LPC 1:8 */
+	6,	/* LPC 1:9 */
+	2,	/* R0:2 */
+	5,	/* LPC 1:10 */
+	1,	/* R0:3 */
+	0,	/* R0:4 */
+	35,	/* Mode:0 */
+	34,	/* Mode:1 */
+	106,	/* Code 2-4:0 */
+	105,	/* Code 2-4:1 */
+	104,	/* Code 2-4:2 */
+	103,	/* Code 2-4:3 */
+	102,	/* Code 2-4:4 */
+	101,	/* Code 2-4:5 */
+	100,	/* Code 2-4:6 */
+	99,	/* Code 1-4:0 */
+	98,	/* Code 1-4:1 */
+	97,	/* Code 1-4:2 */
+	96,	/* Code 1-4:3 */
+	95,	/* Code 1-4:4 */
+	94,	/* Code 1-4:5 */
+	93,	/* Code 1-4:6 */
+	87,	/* Code 2-3:0 */
+	86,	/* Code 2-3:1 */
+	85,	/* Code 2-3:2 */
+};
+
+/* GSM HR voiced (mode=1,2,3) frames - subjective importance bit ordering */
+	/* This array encode mapping between GSM 05.03 Table 3b (bits
+	 * ordering before channel coding on TCH) and GSM 06.20 Table B.2
+	 * (bit ordering on A-bis */
+uint16_t gsm620_voiced_bitorder[112] = {
+	13,	/* LPC 1:2 */
+	14,	/* LPC 1:1 */
+	20,	/* LPC 2:4 */
+	19,	/* LPC 2:5 */
+	18,	/* LPC 2:6 */
+	53,	/* GSP 0-1:4 */
+	71,	/* GSP 0-2:4 */
+	89,	/* GSP 0-3:4 */
+	107,	/* GSP 0-4:4 */
+	54,	/* GSP 0-1:3 */
+	72,	/* GSP 0-2:3 */
+	90,	/* GSP 0-3:3 */
+	108,	/* GSP 0-4:3 */
+	55,	/* GSP 0-1:2 */
+	73,	/* GSP 0-2:2 */
+	91,	/* GSP 0-3:2 */
+	109,	/* GSP 0-4:2 */
+	52,	/* Code 1:0 */
+	51,	/* Code 1:1 */
+	50,	/* Code 1:2 */
+	49,	/* Code 1:3 */
+	48,	/* Code 1:4 */
+	47,	/* Code 1:5 */
+	46,	/* Code 1:6 */
+	45,	/* Code 1:7 */
+	44,	/* Code 1:8 */
+	65,	/* Code 2:5 */
+	64,	/* Code 2:6 */
+	63,	/* Code 2:7 */
+	62,	/* Code 2:8 */
+	70,	/* Code 2:0 */
+	69,	/* Code 2:1 */
+	68,	/* Code 2:2 */
+	80,	/* Code 3:8 */
+	66,	/* Code 2:4 */
+	67,	/* Code 2:3 */
+	56,	/* GSP 0-1:1 */
+	74,	/* GSP 0-2:1 */
+	92,	/* GSP 0-3:1 */
+	110,	/* GSP 0-4:1 */
+	57,	/* GSP 0-1:0 */
+	75,	/* GSP 0-2:0 */
+	93,	/* GSP 0-3:0 */
+	111,	/* GSP 0-4:0 */
+	33,	/* INT-LPC:0 */
+	24,	/* LPC 2:0 */
+	32,	/* LPC 3:0 */
+	97,	/* LAG 4:0 */
+	31,	/* LPC 3:1 */
+	23,	/* LPC 2:1 */
+	96,	/* LAG 4:1 */
+	79,	/* LAG 3:0 */
+	61,	/* LAG 2:0 */
+	43,	/* LAG 1:0 */
+	95,	/* LAG 4:2 */
+	78,	/* LAG 3:1 */
+	60,	/* LAG 2:1 */
+	42,	/* LAG 1:1 */
+	30,	/* LPC 3:2 */
+	29,	/* LPC 3:3 */
+	28,	/* LPC 3:4 */
+	22,	/* LPC 2:2 */
+	27,	/* LPC 3:5 */
+	26,	/* LPC 3:6 */
+	21,	/* LPC 2:3 */
+	4,	/* R0:0 */
+	25,	/* LPC 3:7 */
+	15,	/* LPC 1:0 */
+	94,	/* LAG 4:3 */
+	77,	/* LAG 3:2 */
+	59,	/* LAG 2:2 */
+	41,	/* LAG 1:2 */
+	3,	/* R0:1 */
+	76,	/* LAG 3:3 */
+	58,	/* LAG 2:3 */
+	40,	/* LAG 1:3 */
+	39,	/* LAG 1:4 */
+	17,	/* LPC 2:7 */
+	16,	/* LPC 2:8 */
+	12,	/* LPC 1:3 */
+	11,	/* LPC 1:4 */
+	10,	/* LPC 1:5 */
+	9,	/* LPC 1:6 */
+	2,	/* R0:2 */
+	38,	/* LAG 1:5 */
+	37,	/* LAG 1:6 */
+	36,	/* LAG 1:7 */
+	8,	/* LPC 1:7 */
+	7,	/* LPC 1:8 */
+	6,	/* LPC 1:9 */
+	5,	/* LPC 1:10 */
+	1,	/* R0:3 */
+	0,	/* R0:4 */
+	35,	/* Mode:0 */
+	34,	/* Mode:1 */
+	106,	/* Code 4:0 */
+	105,	/* Code 4:1 */
+	104,	/* Code 4:2 */
+	103,	/* Code 4:3 */
+	102,	/* Code 4:4 */
+	101,	/* Code 4:5 */
+	100,	/* Code 4:6 */
+	99,	/* Code 4:7 */
+	98,	/* Code 4:8 */
+	88,	/* Code 3:0 */
+	87,	/* Code 3:1 */
+	86,	/* Code 3:2 */
+	85,	/* Code 3:3 */
+	84,	/* Code 3:4 */
+	83,	/* Code 3:5 */
+	82,	/* Code 3:6 */
+	81,	/* Code 3:7 */
+};
diff --git a/src/codec/gsm660.c b/src/codec/gsm660.c
new file mode 100644
index 0000000..4fff5ff
--- /dev/null
+++ b/src/codec/gsm660.c
@@ -0,0 +1,256 @@
+/* GSM 06.60 - GSM EFR Codec */
+
+/*
+ * (C) 2010 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+/* GSM EFR - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 6.
+	 *
+	 * It converts between serial parameter output (as described in
+	 * GSM 06.60 Table 6 and GSM 05.03 Table 5) and the order needed
+	 * before channel encoding. CRC poly and bit repetition must be
+	 * applied prior to this table, as in GSM 05.03 3.1.1, to get 260
+	 * bits from a 244 bits raw EFR frame.
+	 */
+uint16_t gsm660_bitorder[260] = {
+	 38,  39,  40,  41,  42,  43,		/*   0 -> LTP-LAG 1: b8..b3 */
+	145, 146, 147, 148, 149, 150,		/*   6 -> LTP-LAG 3: b8..b3 */
+	 93,  94,				/*  12 -> LTP-LAG 2: b5..b4 */
+	200, 201,				/*  14 -> LTP-LAG 4: b5..b4 */
+	 47,					/*  16 -> LTP-GAIN 1: b3    */
+	 88,					/*  17 -> FCB-GAIN 1: b4    */
+	 99,					/*  18 -> LTP-GAIN 2: b3    */
+	140,					/*  19 -> FCB-GAIN 2: b4    */
+	 44,					/*  20 -> LTP-LAG 1: b2     */
+	151,					/*  21 -> LTP-LAG 3: b2     */
+	 95,					/*  22 -> LTP-LAG 2: b3     */
+	202,					/*  23 -> LTP-LAG 4: b3     */
+	  1,   2,				/*  24 -> LPC 1: b5..b4     */
+	  7,					/*  26 -> LPC 2: b7         */
+	  9,					/*  27 -> LPC 2: b5         */
+	 17,  18,				/*  28 -> LPC 3: b6..b5     */
+	 23,					/*  30 -> LPC 3: b0         */
+	 45,  46,				/*  31 -> LTP-LAG 1: b1..b0 */
+	152, 153,				/*  33 -> LTP-LAG 3: b1..b0 */
+	 96,					/*  35 -> LTP-LAG 2: b2     */
+	203,					/*  36 -> LTP-LAG 4: b2     */
+	  3,   4,				/*  37 -> LPC 1: b3..b2     */
+	 10,  11,				/*  39 -> LPC 2: b4..b3     */
+	 15,					/*  41 -> LPC 3: b8         */
+	  8,					/*  42 -> LPC 2: b6         */
+	  5,   6,				/*  43 -> LPC 1: b1..b0     */
+	 12,					/*  45 -> LPC 2: b2         */
+	 16,					/*  46 -> LPC 3: b7         */
+	 19,					/*  47 -> LPC 3: b4         */
+	 97,					/*  48 -> LTP-LAG 2: b1     */
+	204,					/*  49 -> LTP-LAG 4: b1     */
+	  0,					/*  50 -> LPC 1: b6         */
+	 13,  14,				/*  51 -> LPC 2: b1..b0     */
+	 20,					/*  53 -> LPC 3: b3         */
+	 24,  25,				/*  54 -> LPC 4: b7..b6     */
+	 27,					/*  56 -> LPC 4: b4         */
+	154,					/*  57 -> LTP-GAIN 3: b3    */
+	206,					/*  58 -> LTP-GAIN 4: b3    */
+	195,					/*  59 -> FCB-GAIN 3: b4    */
+	247,					/*  60 -> FCB-GAIN 4: b4    */
+	 89,					/*  61 -> FCB-GAIN 1: b3    */
+	141,					/*  62 -> FCB-GAIN 2: b3    */
+	196,					/*  63 -> FCB-GAIN 3: b3    */
+	248,					/*  64 -> FCB-GAIN 4: b3    */
+	252, 253, 254, 255, 256, 257, 258, 259,	/*  65 -> CRC-POLY: b7..b0  */
+	 48,					/*  73 -> LTP-GAIN 1: b2    */
+	100,					/*  74 -> LTP-GAIN 2: b2    */
+	155,					/*  75 -> LTP-GAIN 3: b2    */
+	207,					/*  76 -> LTP-GAIN 4: b2    */
+	 21,  22,				/*  77 -> LPC 3: b2..b1     */
+	 26,					/*  79 -> LPC 4: b5         */
+	 28,					/*  80 -> LPC 4: b3         */
+	 51,					/*  81 -> PULSE 1_1: b3     */
+	 55,					/*  82 -> PULSE 1_2: b3     */
+	 59,					/*  83 -> PULSE 1_3: b3     */
+	 63,					/*  84 -> PULSE 1_4: b3     */
+	 67,					/*  85 -> PULSE 1_5: b3     */
+	103,					/*  86 -> PULSE 2_1: b3     */
+	107,					/*  87 -> PULSE 2_2: b3     */
+	111,					/*  88 -> PULSE 2_3: b3     */
+	115,					/*  89 -> PULSE 2_4: b3     */
+	119,					/*  90 -> PULSE 2_5: b3     */
+	158,					/*  91 -> PULSE 3_1: b3     */
+	162,					/*  92 -> PULSE 3_2: b3     */
+	166,					/*  93 -> PULSE 3_3: b3     */
+	170,					/*  94 -> PULSE 3_4: b3     */
+	174,					/*  95 -> PULSE 3_5: b3     */
+	210,					/*  96 -> PULSE 4_1: b3     */
+	214,					/*  97 -> PULSE 4_2: b3     */
+	218,					/*  98 -> PULSE 4_3: b3     */
+	222,					/*  99 -> PULSE 4_4: b3     */
+	226,					/* 100 -> PULSE 4_5: b3     */
+	 90,					/* 101 -> FCB-GAIN 1: b2    */
+	142,					/* 102 -> FCB-GAIN 2: b2    */
+	197,					/* 103 -> FCB-GAIN 3: b2    */
+	249,					/* 104 -> FCB-GAIN 4: b2    */
+	 49,					/* 105 -> LTP-GAIN 1: b1    */
+	101,					/* 106 -> LTP-GAIN 2: b1    */
+	156,					/* 107 -> LTP-GAIN 3: b1    */
+	208,					/* 108 -> LTP-GAIN 4: b1    */
+	 29,  30,  31,				/* 109 -> LPC 4: b2..b0     */
+	 32,  33,  34,  35,			/* 112 -> LPC 5: b5..b2     */
+	 98,					/* 116 -> LTP-LAG 2: b0     */
+	205,					/* 117 -> LTP-LAG 4: b0     */
+	 52,					/* 118 -> PULSE 1_1: b2     */
+	 56,					/* 119 -> PULSE 1_2: b2     */
+	 60,					/* 120 -> PULSE 1_3: b2     */
+	 64,					/* 121 -> PULSE 1_4: b2     */
+	 68,					/* 122 -> PULSE 1_5: b2     */
+	104,					/* 123 -> PULSE 2_1: b2     */
+	108,					/* 124 -> PULSE 2_2: b2     */
+	112,					/* 125 -> PULSE 2_3: b2     */
+	116,					/* 126 -> PULSE 2_4: b2     */
+	120,					/* 127 -> PULSE 2_5: b2     */
+	159,					/* 128 -> PULSE 3_1: b2     */
+	163,					/* 129 -> PULSE 3_2: b2     */
+	167,					/* 130 -> PULSE 3_3: b2     */
+	171,					/* 131 -> PULSE 3_4: b2     */
+	175,					/* 132 -> PULSE 3_5: b2     */
+	211,					/* 133 -> PULSE 4_1: b2     */
+	215,					/* 134 -> PULSE 4_2: b2     */
+	219,					/* 135 -> PULSE 4_3: b2     */
+	223,					/* 136 -> PULSE 4_4: b2     */
+	227,					/* 137 -> PULSE 4_5: b2     */
+	 53,					/* 138 -> PULSE 1_1: b1     */
+	 57,					/* 139 -> PULSE 1_2: b1     */
+	 61,					/* 140 -> PULSE 1_3: b1     */
+	 65,					/* 141 -> PULSE 1_4: b1     */
+	105,					/* 142 -> PULSE 2_1: b1     */
+	109,					/* 143 -> PULSE 2_2: b1     */
+	113,					/* 144 -> PULSE 2_3: b1     */
+	117,					/* 145 -> PULSE 2_4: b1     */
+	160,					/* 146 -> PULSE 3_1: b1     */
+	164,					/* 147 -> PULSE 3_2: b1     */
+	168,					/* 148 -> PULSE 3_3: b1     */
+	172,					/* 149 -> PULSE 3_4: b1     */
+	212,					/* 150 -> PULSE 4_1: b1     */
+	220,					/* 151 -> PULSE 4_3: b1     */
+	224,					/* 152 -> PULSE 4_4: b1     */
+	 91,					/* 153 -> FCB-GAIN 1: b1    */
+	143,					/* 154 -> FCB-GAIN 2: b1    */
+	198,					/* 155 -> FCB-GAIN 3: b1    */
+	250,					/* 156 -> FCB-GAIN 4: b1    */
+	 50,					/* 157 -> LTP-GAIN 1: b0    */
+	102,					/* 158 -> LTP-GAIN 2: b0    */
+	157,					/* 159 -> LTP-GAIN 3: b0    */
+	209,					/* 160 -> LTP-GAIN 4: b0    */
+	 92,					/* 161 -> FCB-GAIN 1: b0    */
+	144,					/* 162 -> FCB-GAIN 2: b0    */
+	199,					/* 163 -> FCB-GAIN 3: b0    */
+	251,					/* 164 -> FCB-GAIN 4: b0    */
+	 54,					/* 165 -> PULSE 1_1: b0     */
+	 58,					/* 166 -> PULSE 1_2: b0     */
+	 62,					/* 167 -> PULSE 1_3: b0     */
+	 66,					/* 168 -> PULSE 1_4: b0     */
+	106,					/* 169 -> PULSE 2_1: b0     */
+	110,					/* 170 -> PULSE 2_2: b0     */
+	114,					/* 171 -> PULSE 2_3: b0     */
+	118,					/* 172 -> PULSE 2_4: b0     */
+	161,					/* 173 -> PULSE 3_1: b0     */
+	165,					/* 174 -> PULSE 3_2: b0     */
+	169,					/* 175 -> PULSE 3_3: b0     */
+	173,					/* 176 -> PULSE 3_4: b0     */
+	213,					/* 177 -> PULSE 4_1: b0     */
+	221,					/* 178 -> PULSE 4_3: b0     */
+	225,					/* 179 -> PULSE 4_4: b0     */
+	 36,  37,				/* 180 -> LPC 5: b1..b0     */
+	 69,					/* 182 -> PULSE 1_5: b1     */
+	 71,  72,				/* 183 -> PULSE 1_5: b1..b1 */
+	121,					/* 185 -> PULSE 2_5: b1     */
+	123, 124,				/* 186 -> PULSE 2_5: b1..b1 */
+	176,					/* 188 -> PULSE 3_5: b1     */
+	178, 179,				/* 189 -> PULSE 3_5: b1..b1 */
+	228,					/* 191 -> PULSE 4_5: b1     */
+	230, 231,				/* 192 -> PULSE 4_5: b1..b1 */
+	216, 217,				/* 194 -> PULSE 4_2: b1..b0 */
+	 70,					/* 196 -> PULSE 1_5: b0     */
+	122,					/* 197 -> PULSE 2_5: b0     */
+	177,					/* 198 -> PULSE 3_5: b0     */
+	229,					/* 199 -> PULSE 4_5: b0     */
+	 73,					/* 200 -> PULSE 1_6: b2     */
+	 76,					/* 201 -> PULSE 1_7: b2     */
+	 79,					/* 202 -> PULSE 1_8: b2     */
+	 82,					/* 203 -> PULSE 1_9: b2     */
+	 85,					/* 204 -> PULSE 1_10: b2    */
+	125,					/* 205 -> PULSE 2_6: b2     */
+	128,					/* 206 -> PULSE 2_7: b2     */
+	131,					/* 207 -> PULSE 2_8: b2     */
+	134,					/* 208 -> PULSE 2_9: b2     */
+	137,					/* 209 -> PULSE 2_10: b2    */
+	180,					/* 210 -> PULSE 3_6: b2     */
+	183,					/* 211 -> PULSE 3_7: b2     */
+	186,					/* 212 -> PULSE 3_8: b2     */
+	189,					/* 213 -> PULSE 3_9: b2     */
+	192,					/* 214 -> PULSE 3_10: b2    */
+	232,					/* 215 -> PULSE 4_6: b2     */
+	235,					/* 216 -> PULSE 4_7: b2     */
+	238,					/* 217 -> PULSE 4_8: b2     */
+	241,					/* 218 -> PULSE 4_9: b2     */
+	244,					/* 219 -> PULSE 4_10: b2    */
+	 74,					/* 220 -> PULSE 1_6: b1     */
+	 77,					/* 221 -> PULSE 1_7: b1     */
+	 80,					/* 222 -> PULSE 1_8: b1     */
+	 83,					/* 223 -> PULSE 1_9: b1     */
+	 86,					/* 224 -> PULSE 1_10: b1    */
+	126,					/* 225 -> PULSE 2_6: b1     */
+	129,					/* 226 -> PULSE 2_7: b1     */
+	132,					/* 227 -> PULSE 2_8: b1     */
+	135,					/* 228 -> PULSE 2_9: b1     */
+	138,					/* 229 -> PULSE 2_10: b1    */
+	181,					/* 230 -> PULSE 3_6: b1     */
+	184,					/* 231 -> PULSE 3_7: b1     */
+	187,					/* 232 -> PULSE 3_8: b1     */
+	190,					/* 233 -> PULSE 3_9: b1     */
+	193,					/* 234 -> PULSE 3_10: b1    */
+	233,					/* 235 -> PULSE 4_6: b1     */
+	236,					/* 236 -> PULSE 4_7: b1     */
+	239,					/* 237 -> PULSE 4_8: b1     */
+	242,					/* 238 -> PULSE 4_9: b1     */
+	245,					/* 239 -> PULSE 4_10: b1    */
+	 75,					/* 240 -> PULSE 1_6: b0     */
+	 78,					/* 241 -> PULSE 1_7: b0     */
+	 81,					/* 242 -> PULSE 1_8: b0     */
+	 84,					/* 243 -> PULSE 1_9: b0     */
+	 87,					/* 244 -> PULSE 1_10: b0    */
+	127,					/* 245 -> PULSE 2_6: b0     */
+	130,					/* 246 -> PULSE 2_7: b0     */
+	133,					/* 247 -> PULSE 2_8: b0     */
+	136,					/* 248 -> PULSE 2_9: b0     */
+	139,					/* 249 -> PULSE 2_10: b0    */
+	182,					/* 250 -> PULSE 3_6: b0     */
+	185,					/* 251 -> PULSE 3_7: b0     */
+	188,					/* 252 -> PULSE 3_8: b0     */
+	191,					/* 253 -> PULSE 3_9: b0     */
+	194,					/* 254 -> PULSE 3_10: b0    */
+	234,					/* 255 -> PULSE 4_6: b0     */
+	237,					/* 256 -> PULSE 4_7: b0     */
+	240,					/* 257 -> PULSE 4_8: b0     */
+	243,					/* 258 -> PULSE 4_9: b0     */
+	246,					/* 259 -> PULSE 4_10: b0    */
+};
diff --git a/src/codec/gsm690.c b/src/codec/gsm690.c
new file mode 100644
index 0000000..e5b9bd4
--- /dev/null
+++ b/src/codec/gsm690.c
@@ -0,0 +1,210 @@
+/* GSM 06.90 - GSM AMR Codec */
+
+/*
+ * (C) 2010 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+
+/*
+ * These table map between the raw encoder parameter output and
+ * the format used before channel coding. Both in GSM and in various
+ * file/network format (same tables used in several specs).
+ */
+
+/* AMR 12.2 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 7
+	 * It's also TS 26.101 Table B.8
+	 */
+uint16_t gsm690_12_2_bitorder[244] = {
+	  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+	 10,  11,  12,  13,  14,  23,  15,  16,  17,  18,
+	 19,  20,  21,  22,  24,  25,  26,  27,  28,  38,
+	141,  39, 142,  40, 143,  41, 144,  42, 145,  43,
+	146,  44, 147,  45, 148,  46, 149,  47,  97, 150,
+	200,  48,  98, 151, 201,  49,  99, 152, 202,  86,
+	136, 189, 239,  87, 137, 190, 240,  88, 138, 191,
+	241,  91, 194,  92, 195,  93, 196,  94, 197,  95,
+	198,  29,  30,  31,  32,  33,  34,  35,  50, 100,
+	153, 203,  89, 139, 192, 242,  51, 101, 154, 204,
+	 55, 105, 158, 208,  90, 140, 193, 243,  59, 109,
+	162, 212,  63, 113, 166, 216,  67, 117, 170, 220,
+	 36,  37,  54,  53,  52,  58,  57,  56,  62,  61,
+	 60,  66,  65,  64,  70,  69,  68, 104, 103, 102,
+	108, 107, 106, 112, 111, 110, 116, 115, 114, 120,
+	119, 118, 157, 156, 155, 161, 160, 159, 165, 164,
+	163, 169, 168, 167, 173, 172, 171, 207, 206, 205,
+	211, 210, 209, 215, 214, 213, 219, 218, 217, 223,
+	222, 221,  73,  72,  71,  76,  75,  74,  79,  78,
+	 77,  82,  81,  80,  85,  84,  83, 123, 122, 121,
+	126, 125, 124, 129, 128, 127, 132, 131, 130, 135,
+	134, 133, 176, 175, 174, 179, 178, 177, 182, 181,
+	180, 185, 184, 183, 188, 187, 186, 226, 225, 224,
+	229, 228, 227, 232, 231, 230, 235, 234, 233, 238,
+	237, 236,  96, 199,
+};
+
+/* AMR 10.2 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 8
+	 * It's also TS 26.101 Table B.7
+	 */
+uint16_t gsm690_10_2_bitorder[204] = {
+	  7,   6,   5,   4,   3,   2,   1,   0,  16,  15,
+	 14,  13,  12,  11,  10,   9,   8,  26,  27,  28,
+	 29,  30,  31, 115, 116, 117, 118, 119, 120,  72,
+	 73, 161, 162,  65,  68,  69, 108, 111, 112, 154,
+	157, 158, 197, 200, 201,  32,  33, 121, 122,  74,
+	 75, 163, 164,  66, 109, 155, 198,  19,  23,  21,
+	 22,  18,  17,  20,  24,  25,  37,  36,  35,  34,
+	 80,  79,  78,  77, 126, 125, 124, 123, 169, 168,
+	167, 166,  70,  67,  71, 113, 110, 114, 159, 156,
+	160, 202, 199, 203,  76, 165,  81,  82,  92,  91,
+	 93,  83,  95,  85,  84,  94, 101, 102,  96, 104,
+	 86, 103,  87,  97, 127, 128, 138, 137, 139, 129,
+	141, 131, 130, 140, 147, 148, 142, 150, 132, 149,
+	133, 143, 170, 171, 181, 180, 182, 172, 184, 174,
+	173, 183, 190, 191, 185, 193, 175, 192, 176, 186,
+	 38,  39,  49,  48,  50,  40,  52,  42,  41,  51,
+	 58,  59,  53,  61,  43,  60,  44,  54, 194, 179,
+	189, 196, 177, 195, 178, 187, 188, 151, 136, 146,
+	153, 134, 152, 135, 144, 145, 105,  90, 100, 107,
+	 88, 106,  89,  98,  99,  62,  47,  57,  64,  45,
+	 63,  46,  55,  56,
+};
+
+/* AMR 7.95 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 9
+	 * It's also TS 26.101 Table B.6
+	 */
+uint16_t gsm690_7_95_bitorder[159] = {
+	  8,   7,   6,   5,   4,   3,   2,  14,  16,   9,
+	 10,  12,  13,  15,  11,  17,  20,  22,  24,  23,
+	 19,  18,  21,  56,  88, 122, 154,  57,  89, 123,
+	155,  58,  90, 124, 156,  52,  84, 118, 150,  53,
+	 85, 119, 151,  27,  93,  28,  94,  29,  95,  30,
+	 96,  31,  97,  61, 127,  62, 128,  63, 129,  59,
+	 91, 125, 157,  32,  98,  64, 130,   1,   0,  25,
+	 26,  33,  99,  34, 100,  65, 131,  66, 132,  54,
+	 86, 120, 152,  60,  92, 126, 158,  55,  87, 121,
+	153, 117, 116, 115,  46,  78, 112, 144,  43,  75,
+	109, 141,  40,  72, 106, 138,  36,  68, 102, 134,
+	114, 149, 148, 147, 146,  83,  82,  81,  80,  51,
+	 50,  49,  48,  47,  45,  44,  42,  39,  35,  79,
+	 77,  76,  74,  71,  67, 113, 111, 110, 108, 105,
+	101, 145, 143, 142, 140, 137, 133,  41,  73, 107,
+	139,  37,  69, 103, 135,  38,  70, 104, 136,
+};
+
+/* AMR 7.4 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 10
+	 * It's also TS 26.101 Table B.5
+	 */
+uint16_t gsm690_7_4_bitorder[148] = {
+	  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+	 10,  11,  12,  13,  14,  15,  16,  26,  87,  27,
+	 88,  28,  89,  29,  90,  30,  91,  51,  80, 112,
+	141,  52,  81, 113, 142,  54,  83, 115, 144,  55,
+	 84, 116, 145,  58, 119,  59, 120,  21,  22,  23,
+	 17,  18,  19,  31,  60,  92, 121,  56,  85, 117,
+	146,  20,  24,  25,  50,  79, 111, 140,  57,  86,
+	118, 147,  49,  78, 110, 139,  48,  77,  53,  82,
+	114, 143, 109, 138,  47,  76, 108, 137,  32,  33,
+	 61,  62,  93,  94, 122, 123,  41,  42,  43,  44,
+	 45,  46,  70,  71,  72,  73,  74,  75, 102, 103,
+	104, 105, 106, 107, 131, 132, 133, 134, 135, 136,
+	 34,  63,  95, 124,  35,  64,  96, 125,  36,  65,
+	 97, 126,  37,  66,  98, 127,  38,  67,  99, 128,
+	 39,  68, 100, 129,  40,  69, 101, 130,
+};
+
+/* AMR 6.7 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 11
+	 * It's also TS 26.101 Table B.4
+	 */
+uint16_t gsm690_6_7_bitorder[134] = {
+	  0,   1,   4,   3,   5,   6,  13,   7,   2,   8,
+	  9,  11,  15,  12,  14,  10,  28,  82,  29,  83,
+	 27,  81,  26,  80,  30,  84,  16,  55, 109,  56,
+	110,  31,  85,  57, 111,  48,  73, 102, 127,  32,
+	 86,  51,  76, 105, 130,  52,  77, 106, 131,  58,
+	112,  33,  87,  19,  23,  53,  78, 107, 132,  21,
+	 22,  18,  17,  20,  24,  25,  50,  75, 104, 129,
+	 47,  72, 101, 126,  54,  79, 108, 133,  46,  71,
+	100, 125, 128, 103,  74,  49,  45,  70,  99, 124,
+	 42,  67,  96, 121,  39,  64,  93, 118,  38,  63,
+	 92, 117,  35,  60,  89, 114,  34,  59,  88, 113,
+	 44,  69,  98, 123,  43,  68,  97, 122,  41,  66,
+	 95, 120,  40,  65,  94, 119,  37,  62,  91, 116,
+	 36,  61,  90, 115,
+};
+
+/* AMR 5.9 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 12
+	 * It's also TS 26.101 Table B.3
+	 */
+uint16_t gsm690_5_9_bitorder[118] = {
+	  0,   1,   4,   5,   3,   6,   7,   2,  13,  15,
+	  8,   9,  11,  12,  14,  10,  16,  28,  74,  29,
+	 75,  27,  73,  26,  72,  30,  76,  51,  97,  50,
+	 71,  96, 117,  31,  77,  52,  98,  49,  70,  95,
+	116,  53,  99,  32,  78,  33,  79,  48,  69,  94,
+	115,  47,  68,  93, 114,  46,  67,  92, 113,  19,
+	 21,  23,  22,  18,  17,  20,  24, 111,  43,  89,
+	110,  64,  65,  44,  90,  25,  45,  66,  91, 112,
+	 54, 100,  40,  61,  86, 107,  39,  60,  85, 106,
+	 36,  57,  82, 103,  35,  56,  81, 102,  34,  55,
+	 80, 101,  42,  63,  88, 109,  41,  62,  87, 108,
+	 38,  59,  84, 105,  37,  58,  83, 104,
+};
+
+/* AMR 5.15 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 13
+	 * It's also TS 26.101 Table B.2
+	 */
+uint16_t gsm690_5_15_bitorder[103] = {
+	  7,   6,   5,   4,   3,   2,   1,   0,  15,  14,
+	 13,  12,  11,  10,   9,   8,  23,  24,  25,  26,
+	 27,  46,  65,  84,  45,  44,  43,  64,  63,  62,
+	 83,  82,  81, 102, 101, 100,  42,  61,  80,  99,
+	 28,  47,  66,  85,  18,  41,  60,  79,  98,  29,
+	 48,  67,  17,  20,  22,  40,  59,  78,  97,  21,
+	 30,  49,  68,  86,  19,  16,  87,  39,  38,  58,
+	 57,  77,  35,  54,  73,  92,  76,  96,  95,  36,
+	 55,  74,  93,  32,  51,  33,  52,  70,  71,  89,
+	 90,  31,  50,  69,  88,  37,  56,  75,  94,  34,
+	 53,  72,  91,
+};
+
+/* AMR 4.75 kbits - subjective importance bit ordering */
+	/* This array encodes GSM 05.03 Table 14
+	 * It's also TS 26.101 Table B.1
+	 */
+uint16_t gsm690_4_75_bitorder[95] = {
+	  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+	 10,  11,  12,  13,  14,  15,  23,  24,  25,  26,
+	 27,  28,  48,  49,  61,  62,  82,  83,  47,  46,
+	 45,  44,  81,  80,  79,  78,  17,  18,  20,  22,
+	 77,  76,  75,  74,  29,  30,  43,  42,  41,  40,
+	 38,  39,  16,  19,  21,  50,  51,  59,  60,  63,
+	 64,  72,  73,  84,  85,  93,  94,  32,  33,  35,
+	 36,  53,  54,  56,  57,  66,  67,  69,  70,  87,
+	 88,  90,  91,  34,  55,  68,  89,  37,  58,  71,
+	 92,  31,  52,  65,  86,
+};
diff --git a/src/conv.c b/src/conv.c
new file mode 100644
index 0000000..ebc3eda
--- /dev/null
+++ b/src/conv.c
@@ -0,0 +1,631 @@
+/*
+ * conv.c
+ *
+ * Generic convolutional encoding / decoding
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \addtogroup conv
+ *  @{
+ */
+
+/*! \file conv.c
+ *  \file Osmocom convolutional encoder and decoder
+ */
+#include "config.h"
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/conv.h>
+
+
+/* ------------------------------------------------------------------------ */
+/* Common                                                                   */
+/* ------------------------------------------------------------------------ */
+
+int
+osmo_conv_get_input_length(const struct osmo_conv_code *code, int len)
+{
+	return len <= 0 ? code->len : len;
+}
+
+int
+osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
+{
+	int pbits, in_len, out_len;
+
+	/* Input length */
+	in_len = osmo_conv_get_input_length(code, len);
+
+	/* Output length */
+	out_len = in_len * code->N;
+
+	if (code->term == CONV_TERM_FLUSH)
+		out_len += code->N * (code->K - 1);
+
+	/* Count punctured bits */
+	if (code->puncture) {
+		for (pbits=0; code->puncture[pbits] >= 0; pbits++);
+		out_len -= pbits;
+	}
+
+	return out_len;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* Encoding                                                                 */
+/* ------------------------------------------------------------------------ */
+
+/*! \brief Initialize a convolutional encoder
+ *  \param[in,out] encoder Encoder state to initialize
+ *  \param[in] code Description of convolutional code
+ */
+void
+osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
+                      const struct osmo_conv_code *code)
+{
+	memset(encoder, 0x00, sizeof(struct osmo_conv_encoder));
+	encoder->code = code;
+}
+
+void
+osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
+                            const ubit_t *input)
+{
+	int i;
+	uint8_t state = 0;
+
+	for (i=0; i<(encoder->code->K-1); i++)
+		state = (state << 1) | input[i];
+
+	encoder->state = state;
+}
+
+static inline int
+_conv_encode_do_output(struct osmo_conv_encoder *encoder,
+                       uint8_t out, ubit_t *output)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	int o_idx = 0;
+	int j;
+
+	if (code->puncture) {
+		for (j=0; j<code->N; j++)
+		{
+			int bit_no = code->N - j - 1;
+			int r_idx = encoder->i_idx * code->N + j;
+
+			if (code->puncture[encoder->p_idx] == r_idx)
+				encoder->p_idx++;
+			else
+				output[o_idx++] = (out >> bit_no) & 1;
+		}
+	} else {
+		for (j=0; j<code->N; j++)
+		{
+			int bit_no = code->N - j - 1;
+			output[o_idx++] = (out >> bit_no) & 1;
+		}
+	}
+
+	return o_idx;
+}
+
+int
+osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
+                     const ubit_t *input, ubit_t *output, int n)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	uint8_t state;
+	int i;
+	int o_idx;
+
+	o_idx = 0;
+	state = encoder->state;
+
+	for (i=0; i<n; i++) {
+		int bit = input[i];
+		uint8_t out;
+
+		out   = code->next_output[state][bit];
+		state = code->next_state[state][bit];
+
+		o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
+
+		encoder->i_idx++;
+	}
+
+	encoder->state = state;
+
+	return o_idx;
+}
+
+int
+osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
+                       ubit_t *output)
+{
+	const struct osmo_conv_code *code = encoder->code;
+	uint8_t state;
+	int n;
+	int i;
+	int o_idx;
+
+	n = code->K - 1;
+
+	o_idx = 0;
+	state = encoder->state;
+
+	for (i=0; i<n; i++) {
+		uint8_t out;
+
+		if (code->next_term_output) {
+			out   = code->next_term_output[state];
+			state = code->next_term_state[state];
+		} else {
+			out   = code->next_output[state][0];
+			state = code->next_state[state][0];
+		}
+
+		o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
+
+		encoder->i_idx++;
+	}
+
+	encoder->state = state;
+
+	return o_idx;
+}
+
+/*! \brief All-in-one convolutional encoding function
+ *  \param[in] code description of convolutional code to be used
+ *  \param[in] input array of unpacked bits (uncoded)
+ *  \param[out] output array of unpacked bits (encoded)
+ *  \return Number of produced output bits
+ *
+ * This is an all-in-one function, taking care of
+ * \ref osmo_conv_init, \ref osmo_conv_encode_load_state,
+ * \ref osmo_conv_encode_raw and \ref osmo_conv_encode_flush as needed.
+ */
+int
+osmo_conv_encode(const struct osmo_conv_code *code,
+                 const ubit_t *input, ubit_t *output)
+{
+	struct osmo_conv_encoder encoder;
+	int l;
+
+	osmo_conv_encode_init(&encoder, code);
+
+	if (code->term == CONV_TERM_TAIL_BITING) {
+		int eidx = code->len - code->K + 1;
+		osmo_conv_encode_load_state(&encoder, &input[eidx]);
+	}
+
+	l = osmo_conv_encode_raw(&encoder, input, output, code->len);
+
+	if (code->term == CONV_TERM_FLUSH)
+		l += osmo_conv_encode_flush(&encoder, &output[l]);
+
+	return l;
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* Decoding (viterbi)                                                       */
+/* ------------------------------------------------------------------------ */
+
+#define MAX_AE 0x00ffffff
+
+void
+osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
+                      const struct osmo_conv_code *code, int len, int start_state)
+{
+	int n_states;
+
+	/* Init */
+	if (len <= 0)
+		len =  code->len;
+
+	n_states = 1 << (code->K - 1);
+
+	memset(decoder, 0x00, sizeof(struct osmo_conv_decoder));
+
+	decoder->code = code;
+	decoder->n_states = n_states;
+	decoder->len = len;
+
+	/* Allocate arrays */
+	decoder->ae      = malloc(sizeof(unsigned int) * n_states);
+	decoder->ae_next = malloc(sizeof(unsigned int) * n_states);
+
+	decoder->state_history = malloc(sizeof(uint8_t) * n_states * (len + decoder->code->K - 1));
+
+	/* Classic reset */
+	osmo_conv_decode_reset(decoder, start_state);
+}
+
+void
+osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state)
+{
+	int i;
+
+	/* Reset indexes */
+	decoder->o_idx = 0;
+	decoder->p_idx = 0;
+
+	/* Initial error */
+	if (start_state < 0) {
+		/* All states possible */
+		memset(decoder->ae, 0x00, sizeof(unsigned int) * decoder->n_states);
+	} else {
+		/* Fixed start state */
+		for (i=0; i<decoder->n_states; i++) {
+			decoder->ae[i] = (i == start_state) ? 0 : MAX_AE;
+		}
+	}
+}
+
+void
+osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder)
+{
+	int i;
+	unsigned int min_ae = MAX_AE;
+
+	/* Reset indexes */
+	decoder->o_idx = 0;
+	decoder->p_idx = 0;
+
+	/* Initial error normalize (remove constant) */
+	for (i=0; i<decoder->n_states; i++) {
+		if (decoder->ae[i] < min_ae)
+			min_ae = decoder->ae[i];
+	}
+
+	for (i=0; i<decoder->n_states; i++)
+		decoder->ae[i] -= min_ae;
+}
+
+void
+osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder)
+{
+	free(decoder->ae);
+	free(decoder->ae_next);
+	free(decoder->state_history);
+
+	memset(decoder, 0x00, sizeof(struct osmo_conv_decoder));
+}
+
+int
+osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
+                      const sbit_t *input, int n)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int i, s, b, j;
+
+	int n_states;
+	unsigned int *ae;
+	unsigned int *ae_next;
+	uint8_t *state_history;
+	sbit_t *in_sym;
+
+	int i_idx, p_idx;
+
+	/* Prepare */
+	n_states = decoder->n_states;
+
+	ae      = decoder->ae;
+	ae_next = decoder->ae_next;
+	state_history = &decoder->state_history[n_states * decoder->o_idx];
+
+	in_sym  = alloca(sizeof(sbit_t) * code->N);
+
+	i_idx = 0;
+	p_idx = decoder->p_idx;
+
+	/* Scan the treillis */
+	for (i=0; i<n; i++)
+	{
+		/* Reset next accumulated error */
+		for (s=0; s<n_states; s++) {
+			ae_next[s] = MAX_AE;
+		}
+
+		/* Get input */
+		if (code->puncture) {
+			/* Hard way ... */
+			for (j=0; j<code->N; j++) {
+				int idx = ((decoder->o_idx + i) * code->N) + j;
+				if (idx == code->puncture[p_idx]) {
+					in_sym[j] = 0;	/* Undefined */
+					p_idx++;
+				} else {
+					in_sym[j] = input[i_idx];
+					i_idx++;
+				}
+			}
+		} else {
+			/* Easy, just copy N bits */
+			memcpy(in_sym, &input[i_idx], code->N);
+			i_idx += code->N;
+		}
+
+		/* Scan all state */
+		for (s=0; s<n_states; s++)
+		{
+			/* Scan possible input bits */
+			for (b=0; b<2; b++)
+			{
+				int nae, ov, e;
+				uint8_t m;
+
+				/* Next output and state */
+				uint8_t out   = code->next_output[s][b];
+				uint8_t state = code->next_state[s][b];
+
+				/* New error for this path */
+				nae = ae[s];			/* start from last error */
+				m = 1 << (code->N - 1);		/* mask for 'out' bit selection */
+
+				for (j=0; j<code->N; j++) {
+					int is = (int)in_sym[j];
+					if (is) {
+						ov = (out & m) ? -127 : 127; /* sbit_t value for it */
+						e = is - ov;                 /* raw error for this bit */
+						nae += (e * e) >> 9;         /* acc the squared/scaled value */
+					}
+					m >>= 1;                     /* next mask bit */
+				}
+
+				/* Is it survivor ? */
+				if (ae_next[state] > nae) {
+					ae_next[state] = nae;
+					state_history[(n_states * i) + state] = s;
+				}
+			}
+		}
+
+		/* Copy accumulated error */
+		memcpy(ae, ae_next, sizeof(unsigned int) * n_states);
+	}
+
+	/* Update decoder state */
+	decoder->p_idx = p_idx;
+	decoder->o_idx += n;
+
+	return i_idx;
+}
+
+int
+osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
+                       const sbit_t *input)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int i, s, j;
+
+	int n_states;
+	unsigned int *ae;
+	unsigned int *ae_next;
+	uint8_t *state_history;
+	sbit_t *in_sym;
+
+	int i_idx, p_idx;
+
+	/* Prepare */
+	n_states = decoder->n_states;
+
+	ae      = decoder->ae;
+	ae_next = decoder->ae_next;
+	state_history = &decoder->state_history[n_states * decoder->o_idx];
+
+	in_sym  = alloca(sizeof(sbit_t) * code->N);
+
+	i_idx = 0;
+	p_idx = decoder->p_idx;
+
+	/* Scan the treillis */
+	for (i=0; i<code->K-1; i++)
+	{
+		/* Reset next accumulated error */
+		for (s=0; s<n_states; s++) {
+			ae_next[s] = MAX_AE;
+		}
+
+		/* Get input */
+		if (code->puncture) {
+			/* Hard way ... */
+			for (j=0; j<code->N; j++) {
+				int idx = ((decoder->o_idx + i) * code->N) + j;
+				if (idx == code->puncture[p_idx]) {
+					in_sym[j] = 0;	/* Undefined */
+					p_idx++;
+				} else {
+					in_sym[j] = input[i_idx];
+					i_idx++;
+				}
+			}
+		} else {
+			/* Easy, just copy N bits */
+			memcpy(in_sym, &input[i_idx], code->N);
+			i_idx += code->N;
+		}
+
+		/* Scan all state */
+		for (s=0; s<n_states; s++)
+		{
+			int nae, ov, e;
+			uint8_t m;
+
+			/* Next output and state */
+			uint8_t out;
+			uint8_t state;
+
+			if (code->next_term_output) {
+				out   = code->next_term_output[s];
+				state = code->next_term_state[s];
+			} else {
+				out   = code->next_output[s][0];
+				state = code->next_state[s][0];
+			}
+
+			/* New error for this path */
+			nae = ae[s];			/* start from last error */
+			m = 1 << (code->N - 1);		/* mask for 'out' bit selection */
+
+			for (j=0; j<code->N; j++) {
+				int is = (int)in_sym[j];
+				if (is) {
+					ov = (out & m) ? -127 : 127; /* sbit_t value for it */
+					e = is - ov;                 /* raw error for this bit */
+					nae += (e * e) >> 9;         /* acc the squared/scaled value */
+				}
+				m >>= 1;                     /* next mask bit */
+			}
+
+			/* Is it survivor ? */
+			if (ae_next[state] > nae) {
+				ae_next[state] = nae;
+				state_history[(n_states * i) + state] = s;
+			}
+		}
+
+		/* Copy accumulated error */
+		memcpy(ae, ae_next, sizeof(unsigned int) * n_states);
+	}
+
+	/* Update decoder state */
+	decoder->p_idx = p_idx;
+	decoder->o_idx += code->K - 1;
+
+	return i_idx;
+}
+
+int
+osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+                            ubit_t *output, int has_flush, int end_state)
+{
+	const struct osmo_conv_code *code = decoder->code;
+
+	int min_ae;
+	uint8_t min_state, cur_state;
+	int i, s, n;
+
+	uint8_t *sh_ptr;
+
+	/* End state ? */
+	if (end_state < 0) {
+		/* Find state with least error */
+		min_ae = MAX_AE;
+		min_state = 0xff;
+
+		for (s=0; s<decoder->n_states; s++)
+		{
+			if (decoder->ae[s] < min_ae) {
+				min_ae = decoder->ae[s];
+				min_state = s;
+			}
+		}
+
+		if (min_state == 0xff)
+			return -1;
+	} else {
+		min_state = (uint8_t) end_state;
+		min_ae = decoder->ae[end_state];
+	}
+
+	/* Traceback */
+	cur_state = min_state;
+
+	n = decoder->o_idx;
+
+	sh_ptr = &decoder->state_history[decoder->n_states * (n-1)];
+
+		/* No output for the K-1 termination input bits */
+	if (has_flush) {
+		for (i=0; i<code->K-1; i++) {
+			cur_state = sh_ptr[cur_state];
+			sh_ptr -= decoder->n_states;
+		}
+		n -= code->K - 1;
+	}
+
+		/* Generate output backward */
+	for (i=n-1; i>=0; i--)
+	{
+		min_state = cur_state;
+		cur_state = sh_ptr[cur_state];
+
+		sh_ptr -= decoder->n_states;
+
+		if (code->next_state[cur_state][0] == min_state)
+			output[i] = 0;
+		else
+			output[i] = 1;
+	}
+
+	return min_ae;
+}
+
+/*! \brief All-in-one convolutional decoding function
+ *  \param[in] code description of convolutional code to be used
+ *  \param[in] input array of soft bits (coded)
+ *  \param[out] output array of unpacked bits (decoded)
+ *
+ * This is an all-in-one function, taking care of
+ * \ref osmo_conv_decode_init, \ref osmo_conv_decode_scan,
+ * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_output and
+ * \ref osmo_conv_decode_deinit.
+ */
+int
+osmo_conv_decode(const struct osmo_conv_code *code,
+                 const sbit_t *input, ubit_t *output)
+{
+	struct osmo_conv_decoder decoder;
+	int rv, l;
+
+	osmo_conv_decode_init(&decoder, code, 0, 0);
+
+	if (code->term == CONV_TERM_TAIL_BITING) {
+		osmo_conv_decode_scan(&decoder, input, code->len);
+		osmo_conv_decode_rewind(&decoder);
+	}
+
+	l = osmo_conv_decode_scan(&decoder, input, code->len);
+
+	if (code->term == CONV_TERM_FLUSH)
+		l = osmo_conv_decode_flush(&decoder, &input[l]);
+
+	rv = osmo_conv_decode_get_output(&decoder, output,
+		code->term == CONV_TERM_FLUSH,		/* has_flush */
+		code->term == CONV_TERM_FLUSH ? 0 : -1	/* end_state */
+	);
+
+	osmo_conv_decode_deinit(&decoder);
+
+	return rv;
+}
+
+/*! @} */
diff --git a/src/crc16.c b/src/crc16.c
new file mode 100644
index 0000000..2741cf5
--- /dev/null
+++ b/src/crc16.c
@@ -0,0 +1,62 @@
+/*
+ * This was copied from the linux kernel and adjusted for our types.
+ */
+/*
+ *      crc16.c
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2. See the file COPYING for more details.
+ */
+
+#include <osmocom/core/crc16.h>
+
+/** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */
+uint16_t const osmo_crc16_table[256] = {
+	0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
+	0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
+	0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
+	0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
+	0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
+	0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
+	0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
+	0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
+	0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
+	0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
+	0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
+	0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
+	0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
+	0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
+	0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
+	0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
+	0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
+	0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
+	0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
+	0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
+	0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
+	0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
+	0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
+	0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
+	0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
+	0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
+	0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
+	0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
+	0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
+	0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
+	0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
+	0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
+};
+
+/**
+ * crc16 - compute the CRC-16 for the data buffer
+ * @crc:	previous CRC value
+ * @buffer:	data pointer
+ * @len:	number of bytes in the buffer
+ *
+ * Returns the updated CRC value.
+ */
+uint16_t osmo_crc16(uint16_t crc, uint8_t const *buffer, size_t len)
+{
+	while (len--)
+		crc = osmo_crc16_byte(crc, *buffer++);
+	return crc;
+}
diff --git a/src/crcXXgen.c.tpl b/src/crcXXgen.c.tpl
new file mode 100644
index 0000000..80bf1e2
--- /dev/null
+++ b/src/crcXXgen.c.tpl
@@ -0,0 +1,120 @@
+/*
+ * crcXXgen.c
+ *
+ * Generic CRC routines (for max XX bits poly)
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \addtogroup crcgen
+ *  @{
+ */
+
+/*! \file crcXXgen.c
+ *  \file Osmocom generic CRC routines (for max XX bits poly)
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/crcXXgen.h>
+
+
+/*! \brief Compute the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \returns The CRC value
+ */
+uintXX_t
+osmo_crcXXgen_compute_bits(const struct osmo_crcXXgen_code *code,
+                           const ubit_t *in, int len)
+{
+	const uintXX_t poly = code->poly;
+	uintXX_t crc = code->init;
+	int i, n = code->bits-1;
+
+	for (i=0; i<len; i++) {
+		uintXX_t bit = in[i] & 1;
+		crc ^= (bit << n);
+		if (crc & (1 << n)) {
+			crc <<= 1;
+			crc ^= poly;
+		} else {
+			crc <<= 1;
+		}
+		crc &= (1ULL << code->bits) - 1;
+	}
+
+	crc ^= code->remainder;
+
+	return crc;
+}
+
+
+/*! \brief Checks the CRC value of a given array of hard-bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits with the alleged CRC
+ *  \returns 0 if CRC matches. 1 in case of error.
+ *
+ * The crc_bits array must have a length of code->len
+ */
+int
+osmo_crcXXgen_check_bits(const struct osmo_crcXXgen_code *code,
+                         const ubit_t *in, int len, const ubit_t *crc_bits)
+{
+	uintXX_t crc;
+	int i;
+
+	crc = osmo_crcXXgen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		if (crc_bits[i] ^ ((crc >> (code->bits-i-1)) & 1))
+			return 1;
+
+	return 0;
+}
+
+
+/*! \brief Computes and writes the CRC value of a given array of bits
+ *  \param[in] code The CRC code description to apply
+ *  \param[in] in Array of hard bits
+ *  \param[in] len Length of the array of hard bits
+ *  \param[in] crc_bits Array of hard bits to write the computed CRC to
+ *
+ * The crc_bits array must have a length of code->len
+ */
+void
+osmo_crcXXgen_set_bits(const struct osmo_crcXXgen_code *code,
+                       const ubit_t *in, int len, ubit_t *crc_bits)
+{
+	uintXX_t crc;
+	int i;
+
+	crc = osmo_crcXXgen_compute_bits(code, in, len);
+
+	for (i=0; i<code->bits; i++)
+		crc_bits[i] = ((crc >> (code->bits-i-1)) & 1);
+}
+
+/*! @} */
+
+/* vim: set syntax=c: */
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
new file mode 100644
index 0000000..7796caf
--- /dev/null
+++ b/src/gsm/Makefile.am
@@ -0,0 +1,27 @@
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
+LIBVERSION=2:0:1
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -fPIC -Wall ${GCC_FVISIBILITY_HIDDEN}
+
+# FIXME: this should eventually go into a milenage/Makefile.am
+noinst_HEADERS = milenage/aes.h milenage/aes_i.h milenage/aes_wrap.h \
+		 milenage/common.h milenage/crypto.h milenage/includes.h \
+		 milenage/milenage.h
+
+lib_LTLIBRARIES = libosmogsm.la
+
+libosmogsm_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c gsm_utils.c \
+                        rsl.c gsm48.c gsm48_ie.c gsm0808.c sysinfo.c \
+			gprs_cipher_core.c gsm0480.c abis_nm.c gsm0502.c \
+			gsm0411_utils.c gsm0411_smc.c gsm0411_smr.c \
+			lapd_core.c lapdm.c \
+			auth_core.c auth_comp128v1.c auth_milenage.c \
+			milenage/aes-encblock.c milenage/aes-internal.c \
+			milenage/aes-internal-enc.c milenage/milenage.c
+
+libosmogsm_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libosmogsm.map -version-info $(LIBVERSION)
+libosmogsm_la_LIBADD = $(top_builddir)/src/libosmocore.la
+
+EXTRA_DIST = libosmogsm.map
diff --git a/src/gsm/a5.c b/src/gsm/a5.c
new file mode 100644
index 0000000..356060a
--- /dev/null
+++ b/src/gsm/a5.c
@@ -0,0 +1,367 @@
+/*
+ * a5.c
+ *
+ * Full reimplementation of A5/1,2 (split and threadsafe)
+ *
+ * The logic behind the algorithm is taken from "A pedagogical implementation
+ * of the GSM A5/1 and A5/2 "voice privacy" encryption algorithms." by
+ * Marc Briceno, Ian Goldberg, and David Wagner.
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \addtogroup a5
+ *  @{
+ */
+
+/*! \file gsm/a5.c
+ *  \brief Osmocom GSM A5 ciphering algorithm implementation
+ */
+
+#include <string.h>
+
+#include <osmocom/gsm/a5.h>
+
+/*! \brief Main method to generate a A5/x cipher stream
+ *  \param[in] n Which A5/x method to use
+ *  \param[in] key 8 byte array for the key (as received from the SIM)
+ *  \param[in] fn Frame number
+ *  \param[out] dl Pointer to array of ubits to return Downlink cipher stream
+ *  \param[out] ul Pointer to array of ubits to return Uplink cipher stream
+ *
+ * Currently A5/[0-2] are supported.
+ * Either (or both) of dl/ul can be NULL if not needed.
+ */
+void
+osmo_a5(int n, const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul)
+{
+	switch (n)
+	{
+	case 0:
+		if (dl)
+			memset(dl, 0x00, 114);
+		if (ul)
+			memset(ul, 0x00, 114);
+		break;
+
+	case 1:
+		osmo_a5_1(key, fn, dl, ul);
+		break;
+
+	case 2:
+		osmo_a5_2(key, fn, dl, ul);
+		break;
+
+	default:
+		/* a5/[3..7] not supported here/yet */
+		break;
+	}
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* A5/1&2 common stuff                                                                     */
+/* ------------------------------------------------------------------------ */
+
+#define A5_R1_LEN	19
+#define A5_R2_LEN	22
+#define A5_R3_LEN	23
+#define A5_R4_LEN	17	/* A5/2 only */
+
+#define A5_R1_MASK	((1<<A5_R1_LEN)-1)
+#define A5_R2_MASK	((1<<A5_R2_LEN)-1)
+#define A5_R3_MASK	((1<<A5_R3_LEN)-1)
+#define A5_R4_MASK	((1<<A5_R4_LEN)-1)
+
+#define A5_R1_TAPS	0x072000 /* x^19 + x^18 + x^17 + x^14 + 1 */
+#define A5_R2_TAPS	0x300000 /* x^22 + x^21 + 1 */
+#define A5_R3_TAPS	0x700080 /* x^23 + x^22 + x^21 + x^8 + 1 */
+#define A5_R4_TAPS	0x010800 /* x^17 + x^12 + 1 */
+
+/*! \brief Computes parity of a 32-bit word
+ *  \param[in] x 32 bit word
+ *  \return Parity bit (xor of all bits) as 0 or 1
+ */
+static inline uint32_t
+_a5_12_parity(uint32_t x)
+{
+	x ^= x >> 16;
+	x ^= x >> 8;
+	x ^= x >> 4;
+	x &= 0xf;
+	return (0x6996 >> x) & 1;
+}
+
+/*! \brief Compute majority bit from 3 taps
+ *  \param[in] v1 LFSR state ANDed with tap-bit
+ *  \param[in] v2 LFSR state ANDed with tap-bit
+ *  \param[in] v3 LFSR state ANDed with tap-bit
+ *  \return The majority bit (0 or 1)
+ */
+static inline uint32_t
+_a5_12_majority(uint32_t v1, uint32_t v2, uint32_t v3)
+{
+	return (!!v1 + !!v2 + !!v3) >= 2;
+}
+
+/*! \brief Compute the next LFSR state
+ *  \param[in] r Current state
+ *  \param[in] mask LFSR mask
+ *  \param[in] taps LFSR taps
+ *  \return Next state
+ */
+static inline uint32_t
+_a5_12_clock(uint32_t r, uint32_t mask, uint32_t taps)
+{
+	return ((r << 1) & mask) | _a5_12_parity(r & taps);
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* A5/1                                                                     */
+/* ------------------------------------------------------------------------ */
+
+#define A51_R1_CLKBIT	0x000100
+#define A51_R2_CLKBIT	0x000400
+#define A51_R3_CLKBIT	0x000400
+
+/*! \brief GSM A5/1 Clocking function
+ *  \param[in] r Register state
+ *  \param[in] force Non-zero value disable conditional clocking
+ */
+static inline void
+_a5_1_clock(uint32_t r[], int force)
+{
+	int cb[3], maj;
+
+	cb[0] = !!(r[0] & A51_R1_CLKBIT);
+	cb[1] = !!(r[1] & A51_R2_CLKBIT);
+	cb[2] = !!(r[2] & A51_R3_CLKBIT);
+
+	maj = _a5_12_majority(cb[0], cb[1], cb[2]);
+
+	if (force || (maj == cb[0]))
+		r[0] = _a5_12_clock(r[0], A5_R1_MASK, A5_R1_TAPS);
+
+	if (force || (maj == cb[1]))
+		r[1] = _a5_12_clock(r[1], A5_R2_MASK, A5_R2_TAPS);
+
+	if (force || (maj == cb[2]))
+		r[2] = _a5_12_clock(r[2], A5_R3_MASK, A5_R3_TAPS);
+}
+
+/*! \brief GSM A5/1 Output function
+ *  \param[in] r Register state
+ *  \return The A5/1 output function bit
+ */
+static inline uint8_t
+_a5_1_get_output(uint32_t r[])
+{
+	return	(r[0] >> (A5_R1_LEN-1)) ^
+		(r[1] >> (A5_R2_LEN-1)) ^
+		(r[2] >> (A5_R3_LEN-1));
+}
+
+/*! \brief Generate a GSM A5/1 cipher stream
+ *  \param[in] key 8 byte array for the key (as received from the SIM)
+ *  \param[in] fn Frame number
+ *  \param[out] dl Pointer to array of ubits to return Downlink cipher stream
+ *  \param[out] ul Pointer to array of ubits to return Uplink cipher stream
+ *
+ * Either (or both) of dl/ul can be NULL if not needed.
+ */
+void
+osmo_a5_1(const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul)
+{
+	uint32_t r[3] = {0, 0, 0};
+	uint32_t fn_count;
+	uint32_t b;
+	int i;
+
+	/* Key load */
+	for (i=0; i<64; i++)
+	{
+		b = ( key[7 - (i>>3)] >> (i&7) ) & 1;
+
+		_a5_1_clock(r, 1);
+
+		r[0] ^= b;
+		r[1] ^= b;
+		r[2] ^= b;
+	}
+
+	/* Frame count load */
+	fn_count = osmo_a5_fn_count(fn);
+
+	for (i=0; i<22; i++)
+	{
+		b = (fn_count >> i) & 1;
+
+		_a5_1_clock(r, 1);
+
+		r[0] ^= b;
+		r[1] ^= b;
+		r[2] ^= b;
+	}
+
+	/* Mix */
+	for (i=0; i<100; i++)
+	{
+		_a5_1_clock(r, 0);
+	}
+
+	/* Output */
+	for (i=0; i<114; i++) {
+		_a5_1_clock(r, 0);
+		if (dl)
+			dl[i] = _a5_1_get_output(r);
+	}
+
+	for (i=0; i<114; i++) {
+		_a5_1_clock(r, 0);
+		if (ul)
+			ul[i] = _a5_1_get_output(r);
+	}
+}
+
+
+/* ------------------------------------------------------------------------ */
+/* A5/2                                                                     */
+/* ------------------------------------------------------------------------ */
+
+#define A52_R4_CLKBIT0	0x000400
+#define A52_R4_CLKBIT1	0x000008
+#define A52_R4_CLKBIT2	0x000080
+
+/*! \brief GSM A5/2 Clocking function
+ *  \param[in] r Register state
+ *  \param[in] force Non-zero value disable conditional clocking
+ */
+static inline void
+_a5_2_clock(uint32_t r[], int force)
+{
+	int cb[3], maj;
+
+	cb[0] = !!(r[3] & A52_R4_CLKBIT0);
+	cb[1] = !!(r[3] & A52_R4_CLKBIT1);
+	cb[2] = !!(r[3] & A52_R4_CLKBIT2);
+
+	maj = (cb[0] + cb[1] + cb[2]) >= 2;
+
+	if (force || (maj == cb[0]))
+		r[0] = _a5_12_clock(r[0], A5_R1_MASK, A5_R1_TAPS);
+
+	if (force || (maj == cb[1]))
+		r[1] = _a5_12_clock(r[1], A5_R2_MASK, A5_R2_TAPS);
+
+	if (force || (maj == cb[2]))
+		r[2] = _a5_12_clock(r[2], A5_R3_MASK, A5_R3_TAPS);
+
+	r[3] = _a5_12_clock(r[3], A5_R4_MASK, A5_R4_TAPS);
+}
+
+/*! \brief GSM A5/2 Output function
+ *  \param[in] r Register state
+ *  \return The A5/2 output function bit
+ */
+static inline uint8_t
+_a5_2_get_output(uint32_t r[])
+{
+	uint8_t b;
+
+	b = (r[0] >> (A5_R1_LEN-1)) ^
+	    (r[1] >> (A5_R2_LEN-1)) ^
+	    (r[2] >> (A5_R3_LEN-1)) ^
+	    _a5_12_majority( r[0] & 0x08000, ~r[0] & 0x04000,  r[0] & 0x1000) ^
+	    _a5_12_majority(~r[1] & 0x10000,  r[1] & 0x02000,  r[1] & 0x0200) ^
+	    _a5_12_majority( r[2] & 0x40000,  r[2] & 0x10000, ~r[2] & 0x2000);
+
+	return b;
+}
+
+/*! \brief Generate a GSM A5/1 cipher stream
+ *  \param[in] key 8 byte array for the key (as received from the SIM)
+ *  \param[in] fn Frame number
+ *  \param[out] dl Pointer to array of ubits to return Downlink cipher stream
+ *  \param[out] ul Pointer to array of ubits to return Uplink cipher stream
+ *
+ * Either (or both) of dl/ul can be NULL if not needed.
+ */
+void
+osmo_a5_2(const uint8_t *key, uint32_t fn, ubit_t *dl, ubit_t *ul)
+{
+	uint32_t r[4] = {0, 0, 0, 0};
+	uint32_t fn_count;
+	uint32_t b;
+	int i;
+
+	/* Key load */
+	for (i=0; i<64; i++)
+	{
+		b = ( key[7 - (i>>3)] >> (i&7) ) & 1;
+
+		_a5_2_clock(r, 1);
+
+		r[0] ^= b;
+		r[1] ^= b;
+		r[2] ^= b;
+		r[3] ^= b;
+	}
+
+	/* Frame count load */
+	fn_count = osmo_a5_fn_count(fn);
+
+	for (i=0; i<22; i++)
+	{
+		b = (fn_count >> i) & 1;
+
+		_a5_2_clock(r, 1);
+
+		r[0] ^= b;
+		r[1] ^= b;
+		r[2] ^= b;
+		r[3] ^= b;
+	}
+
+	r[0] |= 1 << 15;
+	r[1] |= 1 << 16;
+	r[2] |= 1 << 18;
+	r[3] |= 1 << 10;
+
+	/* Mix */
+	for (i=0; i<99; i++)
+	{
+		_a5_2_clock(r, 0);
+	}
+
+	/* Output */
+	for (i=0; i<114; i++) {
+		_a5_2_clock(r, 0);
+		if (dl)
+			dl[i] = _a5_2_get_output(r);
+	}
+
+	for (i=0; i<114; i++) {
+		_a5_2_clock(r, 0);
+		if (ul)
+			ul[i] = _a5_2_get_output(r);
+	}
+}
+
+/*! @} */
diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c
new file mode 100644
index 0000000..f6d4003
--- /dev/null
+++ b/src/gsm/abis_nm.c
@@ -0,0 +1,455 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*! \addtogroup oml
+ *  @{
+ */
+
+/*! \file abis_nm.c */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+
+/*! \brief unidirectional messages from BTS to BSC */
+const enum abis_nm_msgtype abis_nm_reports[4] = {
+	NM_MT_SW_ACTIVATED_REP,
+	NM_MT_TEST_REP,
+	NM_MT_STATECHG_EVENT_REP,
+	NM_MT_FAILURE_EVENT_REP,
+};
+
+/*! \brief messages without ACK/NACK */
+const enum abis_nm_msgtype abis_nm_no_ack_nack[3] = {
+	NM_MT_MEAS_RES_REQ,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+};
+
+/*! \brief messages related to software load */
+const enum abis_nm_msgtype abis_nm_sw_load_msgs[9] = {
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	//NM_MT_SW_ACT_REQ,
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,
+};
+
+/*! \brief All NACKs (negative acknowledgements */
+const enum abis_nm_msgtype abis_nm_nacks[33] = {
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST_NACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+	{ NM_MT_LOAD_INIT_NACK,		"SOFTWARE LOAD INIT" },
+	{ NM_MT_LOAD_END_NACK,		"SOFTWARE LOAD END" },
+	{ NM_MT_SW_ACT_REQ_NACK,	"SOFTWARE ACTIVATE REQUEST" },
+	{ NM_MT_ACTIVATE_SW_NACK,	"ACTIVATE SOFTWARE" },
+	{ NM_MT_ESTABLISH_TEI_NACK,	"ESTABLISH TEI" },
+	{ NM_MT_CONN_TERR_SIGN_NACK,	"CONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_DISC_TERR_SIGN_NACK,	"DISCONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_CONN_TERR_TRAF_NACK,	"CONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_DISC_TERR_TRAF_NACK,	"DISCONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_CONN_MDROP_LINK_NACK,	"CONNECT MULTI-DROP LINK" },
+	{ NM_MT_DISC_MDROP_LINK_NACK,	"DISCONNECT MULTI-DROP LINK" },
+	{ NM_MT_SET_BTS_ATTR_NACK,	"SET BTS ATTRIBUTE" },
+	{ NM_MT_SET_RADIO_ATTR_NACK,	"SET RADIO ATTRIBUTE" },
+	{ NM_MT_SET_CHAN_ATTR_NACK,	"SET CHANNEL ATTRIBUTE" },
+	{ NM_MT_PERF_TEST_NACK,		"PERFORM TEST" },
+	{ NM_MT_SEND_TEST_REP_NACK,	"SEND TEST REPORT" },
+	{ NM_MT_STOP_TEST_NACK,		"STOP TEST" },
+	{ NM_MT_STOP_EVENT_REP_NACK,	"STOP EVENT REPORT" },
+	{ NM_MT_REST_EVENT_REP_NACK,	"RESET EVENT REPORT" },
+	{ NM_MT_CHG_ADM_STATE_NACK,	"CHANGE ADMINISTRATIVE STATE" },
+	{ NM_MT_CHG_ADM_STATE_REQ_NACK,
+				"CHANGE ADMINISTRATIVE STATE REQUEST" },
+	{ NM_MT_REP_OUTST_ALARMS_NACK,	"REPORT OUTSTANDING ALARMS" },
+	{ NM_MT_CHANGEOVER_NACK,	"CHANGEOVER" },
+	{ NM_MT_OPSTART_NACK,		"OPSTART" },
+	{ NM_MT_REINIT_NACK,		"REINIT" },
+	{ NM_MT_SET_SITE_OUT_NACK,	"SET SITE OUTPUT" },
+	{ NM_MT_CHG_HW_CONF_NACK,	"CHANGE HARDWARE CONFIGURATION" },
+	{ NM_MT_GET_ATTR_NACK,		"GET ATTRIBUTE" },
+	{ NM_MT_SET_ALARM_THRES_NACK,	"SET ALARM THRESHOLD" },
+	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"BS11 BEGIN DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_END_DB_TX_NACK,	"BS11 END DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_CREATE_OBJ_NACK,	"BS11 CREATE OBJECT" },
+	{ NM_MT_BS11_DELETE_OBJ_NACK,	"BS11 DELETE OBJECT" },
+	{ 0,				NULL }
+};
+
+/*! \brief Get human-readable string for OML NACK message type */
+const char *abis_nm_nack_name(uint8_t nack)
+{
+	return get_value_string(nack_names, nack);
+}
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+	/* General Nack Causes */
+	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
+	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
+	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
+	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object class not supported" },
+	{ NM_NACK_BTSNR_UNKN,		"BTS no. unknown" },
+	{ NM_NACK_TRXNR_UNKN,		"Baseband Transceiver no. unknown" },
+	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
+	{ NM_NACK_ATTRID_INVAL,		"Invalid attribute identifier value" },
+	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute identifier not supported" },
+	{ NM_NACK_PARAM_RANGE,		"Parameter value outside permitted range" },
+	{ NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified implementation not supported" },
+	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
+	/* Specific Nack Causes */
+	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
+	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
+	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
+	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
+	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
+	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Physical configuration cannot be performed" },
+	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
+	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Physical configuration cannot be restored" },
+	{ NM_NACK_TEST_NOSUCH,		"No such test" },
+	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
+	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsistent with physical configuration" },
+	{ NM_NACK_FILE_INCOMPLETE,	"Complete file notreceived" },
+	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
+	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activate" },
+	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
+	{ NM_NACK_WAIT,			"Wait" },
+	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
+	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
+	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
+	{ 0,				NULL }
+};
+
+/*! \brief Get human-readable string for NACK cause */
+const char *abis_nm_nack_cause_name(uint8_t cause)
+{
+	return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+	{ NM_EVT_COMM_FAIL,		"communication failure" },
+	{ NM_EVT_QOS_FAIL,		"quality of service failure" },
+	{ NM_EVT_PROC_FAIL,		"processing failure" },
+	{ NM_EVT_EQUIP_FAIL,		"equipment failure" },
+	{ NM_EVT_ENV_FAIL,		"environment failure" },
+	{ 0,				NULL }
+};
+
+/*! \brief Get human-readable string for OML event type */
+const char *abis_nm_event_type_name(uint8_t cause)
+{
+	return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+	{ NM_SEVER_CEASED,		"failure ceased" },
+	{ NM_SEVER_CRITICAL,		"critical failure" },
+	{ NM_SEVER_MAJOR,		"major failure" },
+	{ NM_SEVER_MINOR,		"minor failure" },
+	{ NM_SEVER_WARNING,		"warning level failure" },
+	{ NM_SEVER_INDETERMINATE,	"indeterminate failure" },
+	{ 0,				NULL }
+};
+
+/*! \brief Get human-readable string for perceived OML severity */
+const char *abis_nm_severity_name(uint8_t cause)
+{
+	return get_value_string(severity_names, cause);
+}
+
+/*! \brief Attributes that the BSC can set, not only get, according to Section 9.4 */
+const enum abis_nm_attr abis_nm_att_settable[] = {
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_DEST,
+	NM_ATT_EVENT_TYPE,
+	NM_ATT_FILE_DATA,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MDROP_LINK,
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_SOURCE,
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_WINDOW_SIZE,
+	NM_ATT_SEVERITY,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+};
+
+/*! \brief GSM A-bis OML TLV parser definition */
+const struct tlv_definition abis_nm_att_tlvdef = {
+	.def = {
+		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V },
+		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV },
+		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_BSIC] =			{ TLV_TYPE_TV },
+		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV },
+		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV },
+		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V },
+		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV },
+		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_HSN] =			{ TLV_TYPE_TV },
+		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV },
+		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
+		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_MAIO] =			{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV },
+		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV },
+		[NM_ATT_NY1] =			{ TLV_TYPE_TV },
+		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV },
+		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV },
+		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV },
+		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV },
+		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV },
+		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
+		[NM_ATT_TEI] =			{ TLV_TYPE_TV },
+		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV },
+		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV },
+		[NM_ATT_TSC] =			{ TLV_TYPE_TV },
+		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV },
+		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V },
+		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV },
+		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V },
+	},
+};
+
+/*! \brief Human-readable strings for A-bis OML Object Class */
+const struct value_string abis_nm_obj_class_names[] = {
+	{ NM_OC_SITE_MANAGER,	"SITE-MANAGER" },
+	{ NM_OC_BTS,		"BTS" },
+	{ NM_OC_RADIO_CARRIER,	"RADIO-CARRIER" },
+	{ NM_OC_BASEB_TRANSC,	"BASEBAND-TRANSCEIVER" },
+	{ NM_OC_CHANNEL,	"CHANNEL" },
+	{ NM_OC_BS11_ADJC,	"ADJC" },
+	{ NM_OC_BS11_HANDOVER,	"HANDOVER" },
+	{ NM_OC_BS11_PWR_CTRL,	"POWER-CONTROL" },
+	{ NM_OC_BS11_BTSE,	"BTSE" },
+	{ NM_OC_BS11_RACK,	"RACK" },
+	{ NM_OC_BS11_TEST,	"TEST" },
+	{ NM_OC_BS11_ENVABTSE,	"ENVABTSE" },
+	{ NM_OC_BS11_BPORT,	"BPORT" },
+	{ NM_OC_GPRS_NSE,	"GPRS-NSE" },
+	{ NM_OC_GPRS_CELL,	"GPRS-CELL" },
+	{ NM_OC_GPRS_NSVC,	"GPRS-NSVC" },
+	{ NM_OC_BS11,		"SIEMENSHW" },
+	{ 0,			NULL }
+};
+
+/*! \brief Get human-readable string for OML Operational State */
+const char *abis_nm_opstate_name(uint8_t os)
+{
+	switch (os) {
+	case NM_OPSTATE_DISABLED:
+		return "Disabled";
+	case NM_OPSTATE_ENABLED:
+		return "Enabled";
+	case NM_OPSTATE_NULL:
+		return "NULL";
+	default:
+		return "RFU";
+	}
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+	{ 0, 	"In test" },
+	{ 1,	"Failed" },
+	{ 2,	"Power off" },
+	{ 3,	"Off line" },
+	/* Not used */
+	{ 5,	"Dependency" },
+	{ 6,	"Degraded" },
+	{ 7,	"Not installed" },
+	{ 0xff, "OK" },
+	{ 0,	NULL }
+};
+
+/*! \brief Get human-readable string for OML Availability State */
+const char *abis_nm_avail_name(uint8_t avail)
+{
+	return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+	/* FIXME: standard test names */
+	{ NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+	{ NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+	{ NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+	{ NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+	{ 0, NULL }
+};
+
+/*! \brief Get human-readable string for OML test */
+const char *abis_nm_test_name(uint8_t test)
+{
+	return get_value_string(test_names, test);
+}
+
+/*! \brief Human-readable names for OML administrative state */
+const struct value_string abis_nm_adm_state_names[] = {
+	{ NM_STATE_LOCKED,	"Locked" },
+	{ NM_STATE_UNLOCKED,	"Unlocked" },
+	{ NM_STATE_SHUTDOWN,	"Shutdown" },
+	{ NM_STATE_NULL,	"NULL" },
+	{ 0, NULL }
+};
+
+/*! \brief write a human-readable OML header to the debug log
+ *  \param[in] ss Logging sub-system
+ *  \param[in] foh A-bis OML FOM header
+ */
+void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh)
+{
+	DEBUGP(ss, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+		get_value_string(abis_nm_obj_class_names, foh->obj_class),
+		foh->obj_class, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+		foh->obj_inst.ts_nr);
+}
+
+static const enum abis_nm_chan_comb chcomb4pchan[] = {
+	[GSM_PCHAN_NONE]	= 0xff,
+	[GSM_PCHAN_CCCH]	= NM_CHANC_mainBCCH,
+	[GSM_PCHAN_CCCH_SDCCH4]	= NM_CHANC_BCCHComb,
+	[GSM_PCHAN_TCH_F]	= NM_CHANC_TCHFull,
+	[GSM_PCHAN_TCH_H]	= NM_CHANC_TCHHalf,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH,
+	[GSM_PCHAN_PDCH]	= NM_CHANC_IPAC_PDCH,
+	[GSM_PCHAN_TCH_F_PDCH]	= NM_CHANC_IPAC_TCHFull_PDCH,
+	[GSM_PCHAN_UNKNOWN]	= 0xff,
+	/* FIXME: bounds check */
+};
+
+/*! \brief Obtain OML Channel Combination for phnsical channel config */
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
+{
+	if (pchan < ARRAY_SIZE(chcomb4pchan))
+		return chcomb4pchan[pchan];
+
+	return -EINVAL;
+}
+
+/*! \brief Obtain physical channel config for OML Channel Combination */
+enum abis_nm_chan_comb abis_nm_pchan4chcomb(uint8_t chcomb)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(chcomb4pchan); i++) {
+		if (chcomb4pchan[i] == chcomb)
+			return i;
+	}
+	return GSM_PCHAN_NONE;
+}
+
+/*! @} */
diff --git a/src/gsm/auth_comp128v1.c b/src/gsm/auth_comp128v1.c
new file mode 100644
index 0000000..41aef71
--- /dev/null
+++ b/src/gsm/auth_comp128v1.c
@@ -0,0 +1,47 @@
+
+/* GSM/GPRS/3G authentication core infrastructure */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/comp128.h>
+
+static int c128v1_gen_vec(struct osmo_auth_vector *vec,
+			  struct osmo_sub_auth_data *aud,
+			  const uint8_t *_rand)
+{
+	comp128(aud->u.gsm.ki, _rand, vec->sres, vec->kc);
+	vec->auth_types = OSMO_AUTH_TYPE_GSM;
+
+	return 0;
+}
+
+static struct osmo_auth_impl c128v1_alg = {
+	.algo = OSMO_AUTH_ALG_COMP128v1,
+	.name = "COMP128v1 (libosmogsm built-in)",
+	.priority = 1000,
+	.gen_vec = &c128v1_gen_vec,
+};
+
+static __attribute__((constructor)) void on_dso_load_c128(void)
+{
+	osmo_auth_register(&c128v1_alg);
+}
diff --git a/src/gsm/auth_core.c b/src/gsm/auth_core.c
new file mode 100644
index 0000000..5e886ee
--- /dev/null
+++ b/src/gsm/auth_core.c
@@ -0,0 +1,121 @@
+/* GSM/GPRS/3G authentication core infrastructure */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/plugin.h>
+
+#include <osmocom/crypt/auth.h>
+
+static LLIST_HEAD(osmo_auths);
+
+static struct osmo_auth_impl *selected_auths[_OSMO_AUTH_ALG_NUM];
+
+/* register a cipher with the core */
+int osmo_auth_register(struct osmo_auth_impl *impl)
+{
+	if (impl->algo >= ARRAY_SIZE(selected_auths))
+		return -ERANGE;
+
+	llist_add_tail(&impl->list, &osmo_auths);
+
+	/* check if we want to select this implementation over others */
+	if (!selected_auths[impl->algo] ||
+	    (selected_auths[impl->algo]->priority > impl->priority))
+		selected_auths[impl->algo] = impl;
+
+	return 0;
+}
+
+/* load all available GPRS cipher plugins */
+int osmo_auth_load(const char *path)
+{
+	/* load all plugins available from path */
+	return osmo_plugin_load_all(path);
+}
+
+int osmo_auth_supported(enum osmo_auth_algo algo)
+{
+	if (algo >= ARRAY_SIZE(selected_auths))
+		return -ERANGE;
+
+	if (selected_auths[algo])
+		return 1;
+
+	return 0;
+}
+
+int osmo_auth_gen_vec(struct osmo_auth_vector *vec,
+		      struct osmo_sub_auth_data *aud,
+		      const uint8_t *_rand)
+{
+	struct osmo_auth_impl *impl = selected_auths[aud->algo];
+	int rc;
+
+	if (!impl)
+		return -ENOENT;
+
+	rc = impl->gen_vec(vec, aud, _rand);
+	if (rc < 0)
+		return rc;
+
+	memcpy(vec->rand, _rand, sizeof(vec->rand));
+
+	return 0;
+}
+
+int osmo_auth_gen_vec_auts(struct osmo_auth_vector *vec,
+			   struct osmo_sub_auth_data *aud,
+			   const uint8_t *rand_auts, const uint8_t *auts,
+			   const uint8_t *_rand)
+{
+	struct osmo_auth_impl *impl = selected_auths[aud->algo];
+
+	if (!impl || !impl->gen_vec_auts)
+		return -ENOENT;
+
+	return impl->gen_vec_auts(vec, aud, rand_auts, auts, _rand);
+}
+
+static const struct value_string auth_alg_vals[] = {
+	{ OSMO_AUTH_ALG_NONE, "None" },
+	{ OSMO_AUTH_ALG_COMP128v1, "COMP128v1" },
+	{ OSMO_AUTH_ALG_COMP128v2, "COMP128v2" },
+	{ OSMO_AUTH_ALG_COMP128v3, "COMP128v3" },
+	{ OSMO_AUTH_ALG_XOR, "XOR" },
+	{ OSMO_AUTH_ALG_MILENAGE, "MILENAGE" },
+	{ 0, NULL }
+};
+
+const char *osmo_auth_alg_name(enum osmo_auth_algo alg)
+{
+	return get_value_string(auth_alg_vals, alg);
+}
+
+enum osmo_auth_algo osmo_auth_alg_parse(const char *name)
+{
+	return get_string_value(auth_alg_vals, name);
+}
diff --git a/src/gsm/auth_milenage.c b/src/gsm/auth_milenage.c
new file mode 100644
index 0000000..5b2787d
--- /dev/null
+++ b/src/gsm/auth_milenage.c
@@ -0,0 +1,120 @@
+/* GSM/GPRS/3G authentication core infrastructure */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/crypt/auth.h>
+#include "milenage/common.h"
+#include "milenage/milenage.h"
+
+static void sqn_u64_to_48bit(uint8_t *sqn, const uint64_t sqn64)
+{
+	sqn[5] = (sqn64 >>  0) & 0xff;
+	sqn[4] = (sqn64 >>  8) & 0xff;
+	sqn[3] = (sqn64 >> 16) & 0xff;
+	sqn[2] = (sqn64 >> 24) & 0xff;
+	sqn[1] = (sqn64 >> 32) & 0xff;
+	sqn[0] = (sqn64 >> 40) & 0xff;
+}
+
+static uint64_t sqn_48bit_to_u64(const uint8_t *sqn)
+{
+	uint64_t sqn64;
+
+	sqn64 = sqn[0];
+	sqn64 <<= 8;
+	sqn64 |= sqn[1];
+	sqn64 <<= 8;
+	sqn64 |= sqn[2];
+	sqn64 <<= 8;
+	sqn64 |= sqn[3];
+	sqn64 <<= 8;
+	sqn64 |= sqn[4];
+	sqn64 <<= 8;
+	sqn64 |= sqn[5];
+
+	return sqn64;
+}
+
+
+static int milenage_gen_vec(struct osmo_auth_vector *vec,
+			    struct osmo_sub_auth_data *aud,
+			    const uint8_t *_rand)
+{
+	size_t res_len = sizeof(vec->res);
+	uint8_t sqn[6];
+	int rc;
+
+	sqn_u64_to_48bit(sqn, aud->u.umts.sqn);
+	milenage_generate(aud->u.umts.opc, aud->u.umts.amf, aud->u.umts.k,
+			  sqn, _rand,
+			  vec->autn, vec->ik, vec->ck, vec->res, &res_len);
+	vec->res_len = res_len;
+	rc = gsm_milenage(aud->u.umts.opc, aud->u.umts.k, _rand, vec->sres, vec->kc);
+	if (rc < 0)
+		return rc;
+
+	vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM;
+	aud->u.umts.sqn++;
+
+	return 0;
+}
+
+static int milenage_gen_vec_auts(struct osmo_auth_vector *vec,
+				 struct osmo_sub_auth_data *aud,
+				 const uint8_t *auts, const uint8_t *rand_auts,
+				 const uint8_t *_rand)
+{
+	uint8_t sqn_out[6];
+	uint8_t gen_opc[16];
+	uint8_t *opc;
+	int rc;
+
+	/* Check if we only know OP and compute OPC if required */
+	if (aud->type == OSMO_AUTH_TYPE_UMTS && aud->u.umts.opc_is_op) {
+		rc = milenage_opc_gen(gen_opc, aud->u.umts.k,
+				      aud->u.umts.opc);
+		if (rc < 0)
+			return rc;
+		opc = gen_opc;
+	} else
+		opc = aud->u.umts.opc;
+
+	rc = milenage_auts(opc, aud->u.umts.k, rand_auts, auts, sqn_out);
+	if (rc < 0)
+		return rc;
+
+	aud->u.umts.sqn = sqn_48bit_to_u64(sqn_out) + 1;
+
+	return milenage_gen_vec(vec, aud, _rand);
+}
+
+static struct osmo_auth_impl milenage_alg = {
+	.algo = OSMO_AUTH_ALG_MILENAGE,
+	.name = "MILENAGE (libosmogsm built-in)",
+	.priority = 1000,
+	.gen_vec = &milenage_gen_vec,
+	.gen_vec_auts = &milenage_gen_vec_auts,
+};
+
+static __attribute__((constructor)) void on_dso_load_milenage(void)
+{
+	osmo_auth_register(&milenage_alg);
+}
diff --git a/src/gsm/comp128.c b/src/gsm/comp128.c
new file mode 100644
index 0000000..b7a2382
--- /dev/null
+++ b/src/gsm/comp128.c
@@ -0,0 +1,230 @@
+/*
+ * COMP128 implementation
+ *
+ *
+ * This code is inspired by original code from :
+ *  Marc Briceno <marc@scard.org>, Ian Goldberg <iang@cs.berkeley.edu>,
+ *  and David Wagner <daw@cs.berkeley.edu>
+ *
+ * But it has been fully rewritten from various PDFs found online describing
+ * the algorithm because the licence of the code referenced above was unclear.
+ * A comment snippet from the original code is included below, it describes
+ * where the doc came from and how the algorithm was reverse engineered.
+ *
+ *
+ * (C) 2009 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*
+ * --- SNIP ---
+ *
+ * This code derived from a leaked document from the GSM standards.
+ * Some missing pieces were filled in by reverse-engineering a working SIM.
+ * We have verified that this is the correct COMP128 algorithm.
+ *
+ * The first page of the document identifies it as
+ * 	_Technical Information: GSM System Security Study_.
+ * 	10-1617-01, 10th June 1988.
+ * The bottom of the title page is marked
+ * 	Racal Research Ltd.
+ * 	Worton Drive, Worton Grange Industrial Estate,
+ * 	Reading, Berks. RG2 0SB, England.
+ * 	Telephone: Reading (0734) 868601   Telex: 847152
+ * The relevant bits are in Part I, Section 20 (pages 66--67).  Enjoy!
+ *
+ * Note: There are three typos in the spec (discovered by
+ * reverse-engineering).
+ * First, "z = (2 * x[n] + x[n]) mod 2^(9-j)" should clearly read
+ * "z = (2 * x[m] + x[n]) mod 2^(9-j)".
+ * Second, the "k" loop in the "Form bits from bytes" section is severely
+ * botched: the k index should run only from 0 to 3, and clearly the range
+ * on "the (8-k)th bit of byte j" is also off (should be 0..7, not 1..8,
+ * to be consistent with the subsequent section).
+ * Third, SRES is taken from the first 8 nibbles of x[], not the last 8 as
+ * claimed in the document.  (And the document doesn't specify how Kc is
+ * derived, but that was also easily discovered with reverse engineering.)
+ * All of these typos have been corrected in the following code.
+ *
+ * --- /SNIP ---
+ */
+
+#include <string.h>
+#include <stdint.h>
+
+/* The compression tables (just copied ...) */
+static const uint8_t table_0[512] = {
+ 102, 177, 186, 162,   2, 156, 112,  75,  55,  25,   8,  12, 251, 193, 246, 188,
+ 109, 213, 151,  53,  42,  79, 191, 115, 233, 242, 164, 223, 209, 148, 108, 161,
+ 252,  37, 244,  47,  64, 211,   6, 237, 185, 160, 139, 113,  76, 138,  59,  70,
+  67,  26,  13, 157,  63, 179, 221,  30, 214,  36, 166,  69, 152, 124, 207, 116,
+ 247, 194,  41,  84,  71,   1,  49,  14,  95,  35, 169,  21,  96,  78, 215, 225,
+ 182, 243,  28,  92, 201, 118,   4,  74, 248, 128,  17,  11, 146, 132, 245,  48,
+ 149,  90, 120,  39,  87, 230, 106, 232, 175,  19, 126, 190, 202, 141, 137, 176,
+ 250,  27, 101,  40, 219, 227,  58,  20,  51, 178,  98, 216, 140,  22,  32, 121,
+  61, 103, 203,  72,  29, 110,  85, 212, 180, 204, 150, 183,  15,  66, 172, 196,
+  56, 197, 158,   0, 100,  45, 153,   7, 144, 222, 163, 167,  60, 135, 210, 231,
+ 174, 165,  38, 249, 224,  34, 220, 229, 217, 208, 241,  68, 206, 189, 125, 255,
+ 239,  54, 168,  89, 123, 122,  73, 145, 117, 234, 143,  99, 129, 200, 192,  82,
+ 104, 170, 136, 235,  93,  81, 205, 173, 236,  94, 105,  52,  46, 228, 198,   5,
+  57, 254,  97, 155, 142, 133, 199, 171, 187,  50,  65, 181, 127, 107, 147, 226,
+ 184, 218, 131,  33,  77,  86,  31,  44,  88,  62, 238,  18,  24,  43, 154,  23,
+  80, 159, 134, 111,   9, 114,   3,  91,  16, 130,  83,  10, 195, 240, 253, 119,
+ 177, 102, 162, 186, 156,   2,  75, 112,  25,  55,  12,   8, 193, 251, 188, 246,
+ 213, 109,  53, 151,  79,  42, 115, 191, 242, 233, 223, 164, 148, 209, 161, 108,
+  37, 252,  47, 244, 211,  64, 237,   6, 160, 185, 113, 139, 138,  76,  70,  59,
+  26,  67, 157,  13, 179,  63,  30, 221,  36, 214,  69, 166, 124, 152, 116, 207,
+ 194, 247,  84,  41,   1,  71,  14,  49,  35,  95,  21, 169,  78,  96, 225, 215,
+ 243, 182,  92,  28, 118, 201,  74,   4, 128, 248,  11,  17, 132, 146,  48, 245,
+  90, 149,  39, 120, 230,  87, 232, 106,  19, 175, 190, 126, 141, 202, 176, 137,
+  27, 250,  40, 101, 227, 219,  20,  58, 178,  51, 216,  98,  22, 140, 121,  32,
+ 103,  61,  72, 203, 110,  29, 212,  85, 204, 180, 183, 150,  66,  15, 196, 172,
+ 197,  56,   0, 158,  45, 100,   7, 153, 222, 144, 167, 163, 135,  60, 231, 210,
+ 165, 174, 249,  38,  34, 224, 229, 220, 208, 217,  68, 241, 189, 206, 255, 125,
+  54, 239,  89, 168, 122, 123, 145,  73, 234, 117,  99, 143, 200, 129,  82, 192,
+ 170, 104, 235, 136,  81,  93, 173, 205,  94, 236,  52, 105, 228,  46,   5, 198,
+ 254,  57, 155,  97, 133, 142, 171, 199,  50, 187, 181,  65, 107, 127, 226, 147,
+ 218, 184,  33, 131,  86,  77,  44,  31,  62,  88,  18, 238,  43,  24,  23, 154,
+ 159,  80, 111, 134, 114,   9,  91,   3, 130,  16,  10,  83, 240, 195, 119, 253,
+}, table_1[256] = {
+  19,  11,  80, 114,  43,   1,  69,  94,  39,  18, 127, 117,  97,   3,  85,  43,
+  27, 124,  70,  83,  47,  71,  63,  10,  47,  89,  79,   4,  14,  59,  11,   5,
+  35, 107, 103,  68,  21,  86,  36,  91,  85, 126,  32,  50, 109,  94, 120,   6,
+  53,  79,  28,  45,  99,  95,  41,  34,  88,  68,  93,  55, 110, 125, 105,  20,
+  90,  80,  76,  96,  23,  60,  89,  64, 121,  56,  14,  74, 101,   8,  19,  78,
+  76,  66, 104,  46, 111,  50,  32,   3,  39,   0,  58,  25,  92,  22,  18,  51,
+  57,  65, 119, 116,  22, 109,   7,  86,  59,  93,  62, 110,  78,  99,  77,  67,
+  12, 113,  87,  98, 102,   5,  88,  33,  38,  56,  23,   8,  75,  45,  13,  75,
+  95,  63,  28,  49, 123, 120,  20, 112,  44,  30,  15,  98, 106,   2, 103,  29,
+  82, 107,  42, 124,  24,  30,  41,  16, 108, 100, 117,  40,  73,  40,   7, 114,
+  82, 115,  36, 112,  12, 102, 100,  84,  92,  48,  72,  97,   9,  54,  55,  74,
+ 113, 123,  17,  26,  53,  58,   4,   9,  69, 122,  21, 118,  42,  60,  27,  73,
+ 118, 125,  34,  15,  65, 115,  84,  64,  62,  81,  70,   1,  24, 111, 121,  83,
+ 104,  81,  49, 127,  48, 105,  31,  10,   6,  91,  87,  37,  16,  54, 116, 126,
+  31,  38,  13,   0,  72, 106,  77,  61,  26,  67,  46,  29,  96,  37,  61,  52,
+ 101,  17,  44, 108,  71,  52,  66,  57,  33,  51,  25,  90,   2, 119, 122,  35,
+}, table_2[128] = {
+ 52,  50,  44,   6,  21,  49,  41,  59,  39,  51,  25,  32,  51,  47,  52,  43,
+ 37,   4,  40,  34,  61,  12,  28,   4,  58,  23,   8,  15,  12,  22,   9,  18,
+ 55,  10,  33,  35,  50,   1,  43,   3,  57,  13,  62,  14,   7,  42,  44,  59,
+ 62,  57,  27,   6,   8,  31,  26,  54,  41,  22,  45,  20,  39,   3,  16,  56,
+ 48,   2,  21,  28,  36,  42,  60,  33,  34,  18,   0,  11,  24,  10,  17,  61,
+ 29,  14,  45,  26,  55,  46,  11,  17,  54,  46,   9,  24,  30,  60,  32,   0,
+ 20,  38,   2,  30,  58,  35,   1,  16,  56,  40,  23,  48,  13,  19,  19,  27,
+ 31,  53,  47,  38,  63,  15,  49,   5,  37,  53,  25,  36,  63,  29,   5,   7,
+}, table_3[64] = {
+  1,   5,  29,   6,  25,   1,  18,  23,  17,  19,   0,   9,  24,  25,   6,  31,
+ 28,  20,  24,  30,   4,  27,   3,  13,  15,  16,  14,  18,   4,   3,   8,   9,
+ 20,   0,  12,  26,  21,   8,  28,   2,  29,   2,  15,   7,  11,  22,  14,  10,
+ 17,  21,  12,  30,  26,  27,  16,  31,  11,   7,  13,  23,  10,   5,  22,  19,
+}, table_4[32] = {
+ 15,  12,  10,   4,   1,  14,  11,   7,   5,   0,  14,   7,   1,   2,  13,   8,
+ 10,   3,   4,   9,   6,   0,   3,   2,   5,   6,   8,   9,  11,  13,  15,  12,
+};
+
+static const uint8_t *_comp128_table[5] = { table_0, table_1, table_2, table_3, table_4 };
+
+
+static inline void
+_comp128_compression_round(uint8_t *x, int n, const uint8_t *tbl)
+{
+	int i, j, m, a, b, y, z;
+	m = 4 - n;
+	for (i=0; i<(1<<n); i++)
+		for (j=0; j<(1<<m); j++) {
+			a = j + i * (2<<m);
+			b = a + (1<<m);
+			y = (x[a] + (x[b]<<1)) & ((32<<m)-1);
+			z = ((x[a]<<1) + x[b]) & ((32<<m)-1);
+			x[a] = tbl[y];
+			x[b] = tbl[z];
+		}
+}
+
+static inline void
+_comp128_compression(uint8_t *x)
+{
+	int n;
+	for (n=0; n<5; n++)
+		_comp128_compression_round(x, n, _comp128_table[n]);
+}
+
+static inline void
+_comp128_bitsfrombytes(uint8_t *x, uint8_t *bits)
+{
+	int i;
+	memset(bits, 0x00, 128);
+	for (i=0; i<128; i++)
+		if (x[i>>2] & (1<<(3-(i&3))))
+			bits[i] = 1;
+}
+
+static inline void
+_comp128_permutation(uint8_t *x, uint8_t *bits)
+{
+	int i;
+	memset(&x[16], 0x00, 16);
+	for (i=0; i<128; i++)
+		x[(i>>3)+16] |= bits[(i*17) & 127] << (7-(i&7));
+}
+
+void
+comp128(const uint8_t *ki, const uint8_t *rand, uint8_t *sres, uint8_t *kc)
+{
+	int i;
+	uint8_t x[32], bits[128];
+
+	/* x[16-31] = RAND */
+	memcpy(&x[16], rand, 16);
+
+	/* Round 1-7 */
+	for (i=0; i<7; i++) {
+		/* x[0-15] = Ki */
+		memcpy(x, ki, 16);
+
+		/* Compression */
+		_comp128_compression(x);
+
+		/* FormBitFromBytes */
+		_comp128_bitsfrombytes(x, bits);
+
+		/* Permutation */
+		_comp128_permutation(x, bits);
+	}
+
+	/* Round 8 (final) */
+		/* x[0-15] = Ki */
+	memcpy(x, ki, 16);
+
+		/* Compression */
+	_comp128_compression(x);
+
+	/* Output stage */
+	for (i=0; i<8; i+=2)
+		sres[i>>1] = x[i]<<4 | x[i+1];
+
+	for (i=0; i<12; i+=2)
+		kc[i>>1] = (x[i + 18] << 6) |
+		           (x[i + 19] << 2) |
+		           (x[i + 20] >> 2);
+
+	kc[6] = (x[30]<<6) | (x[31]<<2);
+	kc[7] = 0;
+}
+
diff --git a/src/gsm/gprs_cipher_core.c b/src/gsm/gprs_cipher_core.c
new file mode 100644
index 0000000..b9a22a1
--- /dev/null
+++ b/src/gsm/gprs_cipher_core.c
@@ -0,0 +1,99 @@
+/* GPRS LLC cipher core infrastructure */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/plugin.h>
+
+#include <osmocom/crypt/gprs_cipher.h>
+
+static LLIST_HEAD(gprs_ciphers);
+
+static struct gprs_cipher_impl *selected_ciphers[_GPRS_ALGO_NUM];
+
+/* register a cipher with the core */
+int gprs_cipher_register(struct gprs_cipher_impl *ciph)
+{
+	if (ciph->algo >= ARRAY_SIZE(selected_ciphers))
+		return -ERANGE;
+
+	llist_add_tail(&ciph->list, &gprs_ciphers);
+
+	/* check if we want to select this implementation over others */
+	if (!selected_ciphers[ciph->algo] ||
+	    (selected_ciphers[ciph->algo]->priority > ciph->priority))
+		selected_ciphers[ciph->algo] = ciph;
+
+	return 0;
+}
+
+/* load all available GPRS cipher plugins */
+int gprs_cipher_load(const char *path)
+{
+	/* load all plugins available from path */
+	return osmo_plugin_load_all(path);
+}
+
+/* function to be called by core code */
+int gprs_cipher_run(uint8_t *out, uint16_t len, enum gprs_ciph_algo algo,
+		    uint64_t kc, uint32_t iv, enum gprs_cipher_direction dir)
+{
+	if (algo >= ARRAY_SIZE(selected_ciphers))
+		return -ERANGE;
+
+	if (!selected_ciphers[algo])
+		return -EINVAL;
+
+	if (len > GSM0464_CIPH_MAX_BLOCK)
+		return -ERANGE;
+
+	/* run the actual cipher from the plugin */
+	return selected_ciphers[algo]->run(out, len, kc, iv, dir);
+}
+
+int gprs_cipher_supported(enum gprs_ciph_algo algo)
+{
+	if (algo >= ARRAY_SIZE(selected_ciphers))
+		return -ERANGE;
+
+	if (selected_ciphers[algo])
+		return 1;
+
+	return 0;
+}
+
+/* GSM TS 04.64 / Section A.2.1 : Generation of 'input' */
+uint32_t gprs_cipher_gen_input_ui(uint32_t iov_ui, uint8_t sapi, uint32_t lfn, uint32_t oc)
+{
+	uint32_t sx = ((1<<27) * sapi) + (1<<31);
+
+	return (iov_ui ^ sx) + lfn + oc;
+}
+
+/* GSM TS 04.64 / Section A.2.1 : Generation of 'input' */
+uint32_t gprs_cipher_gen_input_i(uint32_t iov_i, uint32_t lfn, uint32_t oc)
+{
+	return iov_i + lfn + oc;
+}
diff --git a/src/gsm/gsm0411_smc.c b/src/gsm/gsm0411_smc.c
new file mode 100644
index 0000000..54e6129
--- /dev/null
+++ b/src/gsm/gsm0411_smc.c
@@ -0,0 +1,539 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Notes on msg:
+ *
+ * Messages from lower layer are freed by lower layer.
+ *
+ * Messages to upper layer are freed after upper layer call returns, so upper
+ * layer cannot use data after returning. Upper layer must not free the msg.
+ *
+ * This implies: Lower layer messages can be forwarded to upper layer.
+ *
+ * Upper layer messages are freed by lower layer, so they must not be freed
+ * after calling lower layer.
+ *
+ *
+ * Notes on release:
+ *
+ * Whenever the process returns to IDLE, the MM connection is released using
+ * MMSMS-REL-REQ. It is allowed to destroy this process while processing
+ * this message.
+ *
+ * There is expeption, if MMSMS-REL-IND is received from lower layer, the
+ * process returns to IDLE without sending MMSMS-REL-REQ.
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+
+#include <osmocom/gsm/gsm0411_utils.h>
+#include <osmocom/gsm/gsm0411_smc.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+static void cp_timer_expired(void *data);
+
+#define MAX_SMS_RETRY 2
+
+/* init a new instance */
+void gsm411_smc_init(struct gsm411_smc_inst *inst, int network,
+	int (*mn_recv) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg),
+	int (*mm_send) (struct gsm411_smc_inst *inst, int msg_type,
+			struct msgb *msg, int cp_msg_type))
+{
+	memset(inst, 0, sizeof(*inst));
+	inst->network = network;
+	inst->cp_max_retr = MAX_SMS_RETRY;
+	inst->cp_tc1 = GSM411_TMR_TC1A_SEC / (inst->cp_max_retr + 1);
+	inst->cp_state = GSM411_CPS_IDLE;
+	inst->mn_recv = mn_recv;
+	inst->mm_send = mm_send;
+
+	LOGP(DLSMS, LOGL_INFO, "New SMC instance created\n");
+}
+
+/* clear instance */
+void gsm411_smc_clear(struct gsm411_smc_inst *inst)
+{
+	LOGP(DLSMS, LOGL_INFO, "Clear SMC instance\n");
+
+	osmo_timer_del(&inst->cp_timer);
+
+	/* free stored msg */
+	if (inst->cp_msg) {
+		LOGP(DLSMS, LOGL_INFO, "Dropping pending message\n");
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+}
+
+const char *smc_state_names[] = {
+	"IDLE",
+	"MM_CONN_PENDING",
+	"WAIT_CP_ACK",
+	"MM_ESTABLISHED",
+};
+
+const struct value_string gsm411_cp_cause_strs[] = {
+	{ GSM411_CP_CAUSE_NET_FAIL,	"Network Failure" },
+	{ GSM411_CP_CAUSE_CONGESTION,	"Congestion" },
+	{ GSM411_CP_CAUSE_INV_TRANS_ID,	"Invalid Transaction ID" },
+	{ GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_CP_CAUSE_INV_MAND_INF,	"Invalid Mandatory Information" },
+	{ GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" },
+	{ GSM411_CP_CAUSE_MSG_INCOMP_STATE,
+				"Message incompatible with protocol state" },
+	{ GSM411_CP_CAUSE_IE_NOTEXIST,	"IE does not exist" },
+	{ GSM411_CP_CAUSE_PROTOCOL_ERR,	"Protocol Error" },
+	{ 0, 0 }
+};
+
+static void new_cp_state(struct gsm411_smc_inst *inst,
+	enum gsm411_cp_state state)
+{
+	LOGP(DLSMS, LOGL_INFO, "New CP state %s -> %s\n",
+		smc_state_names[inst->cp_state], smc_state_names[state]);
+	inst->cp_state = state;
+}
+
+static int gsm411_tx_cp_error(struct gsm411_smc_inst *inst, uint8_t cause)
+{
+	struct msgb *nmsg = gsm411_msgb_alloc();
+	uint8_t *causep;
+
+	LOGP(DLSMS, LOGL_NOTICE, "TX CP-ERROR, cause %d (%s)\n", cause,
+		get_value_string(gsm411_cp_cause_strs, cause));
+
+	causep = msgb_put(nmsg, 1);
+	*causep = cause;
+
+	return inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg,
+		GSM411_MT_CP_ERROR);
+}
+
+/* etablish SMC connection */
+static int gsm411_mnsms_est_req(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	struct msgb *nmsg;
+
+	if (inst->cp_msg) {
+		LOGP(DLSMS, LOGL_FATAL, "EST REQ, but we already have an "
+			"cp_msg. This should never happen, please fix!\n");
+		msgb_free(inst->cp_msg);
+	}
+
+	inst->cp_msg = msg;
+	new_cp_state(inst, GSM411_CPS_MM_CONN_PENDING);
+	/* clear stored release flag */
+	inst->cp_rel = 0;
+	/* send MMSMS_EST_REQ */
+	nmsg = gsm411_msgb_alloc();
+	return inst->mm_send(inst, GSM411_MMSMS_EST_REQ, nmsg, 0);
+}
+
+static int gsm411_mmsms_send_msg(struct gsm411_smc_inst *inst)
+{
+	struct msgb *nmsg;
+
+	LOGP(DLSMS, LOGL_INFO, "Send CP data\n");
+	/* reset retry counter */
+	if (inst->cp_state != GSM411_CPS_WAIT_CP_ACK)
+		inst->cp_retx = 0;
+	/* 5.2.3.1.2: enter MO-wait for CP-ACK */
+	/* 5.2.3.2.3: enter MT-wait for CP-ACK */
+	new_cp_state(inst, GSM411_CPS_WAIT_CP_ACK);
+	inst->cp_timer.data = inst;
+	inst->cp_timer.cb = cp_timer_expired;
+	/* 5.3.2.1: Set Timer TC1A */
+	osmo_timer_schedule(&inst->cp_timer, inst->cp_tc1, 0);
+	/* clone cp_msg */
+	nmsg = gsm411_msgb_alloc();
+	memcpy(msgb_put(nmsg, inst->cp_msg->len), inst->cp_msg->data,
+		inst->cp_msg->len);
+	/* send MMSMS_DATA_REQ with CP-DATA */
+	return inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg,
+				GSM411_MT_CP_DATA);
+}
+
+static int gsm411_mmsms_est_cnf(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	if (!inst->cp_msg) {
+		LOGP(DLSMS, LOGL_FATAL, "EST CNF, but we have no cp_msg. This "
+			"should never happen, please fix!\n");
+		return -EINVAL;
+	}
+
+	return gsm411_mmsms_send_msg(inst);
+}
+
+/* SMC TC1* is expired */
+static void cp_timer_expired(void *data)
+{
+	struct gsm411_smc_inst *inst = data;
+	struct msgb *nmsg;
+
+	if (inst->cp_retx == inst->cp_max_retr) {
+
+		LOGP(DLSMS, LOGL_INFO, "TC1* timeout, no more retries.\n");
+		/* 5.3.2.1: enter idle state */
+		new_cp_state(inst, GSM411_CPS_IDLE);
+		/* indicate error */
+		nmsg = gsm411_msgb_alloc();
+		inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
+		msgb_free(nmsg);
+		/* free pending stored msg */
+		if (inst->cp_msg) {
+			msgb_free(inst->cp_msg);
+			inst->cp_msg = NULL;
+		}
+		/* release MM connection */
+		nmsg = gsm411_msgb_alloc();
+		inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
+		return;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "TC1* timeout, retrying...\n");
+	inst->cp_retx++;
+	gsm411_mmsms_est_cnf(inst, NULL);
+}
+
+static int gsm411_mmsms_cp_ack(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	/* free stored msg */
+	if (inst->cp_msg) {
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Received CP-ACK\n");
+	/* 5.3.2.1 enter MM Connection established */
+	new_cp_state(inst, GSM411_CPS_MM_ESTABLISHED);
+	/* 5.3.2.1: Reset Timer TC1* */
+	osmo_timer_del(&inst->cp_timer);
+
+	/* pending release? */
+	if (inst->cp_rel) {
+		struct msgb *nmsg;
+
+		LOGP(DLSMS, LOGL_INFO, "We have pending release.\n");
+		new_cp_state(inst, GSM411_CPS_IDLE);
+		/* release MM connection */
+		nmsg = gsm411_msgb_alloc();
+		return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
+	}
+
+	return 0;
+}
+
+static int gsm411_mmsms_cp_data(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	struct msgb *nmsg;
+	int mt = GSM411_MNSMS_DATA_IND;
+
+	LOGP(DLSMS, LOGL_INFO, "Received CP-DATA\n");
+	/* 5.3.1 enter MM Connection established (if idle) */
+	if (inst->cp_state == GSM411_CPS_IDLE) {
+		new_cp_state(inst, GSM411_CPS_MM_ESTABLISHED);
+		mt = GSM411_MNSMS_EST_IND;
+		/* clear stored release flag */
+		inst->cp_rel = 0;
+	}
+	/* send MMSMS_DATA_REQ (CP ACK) */
+	nmsg = gsm411_msgb_alloc();
+	inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg, GSM411_MT_CP_ACK);
+	/* indicate data */
+	inst->mn_recv(inst, mt, msg);
+
+	return 0;
+}
+
+/* send CP DATA */
+static int gsm411_mnsms_data_req(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	if (inst->cp_msg) {
+		LOGP(DLSMS, LOGL_FATAL, "DATA REQ, but we already have an "
+			"cp_msg. This should never happen, please fix!\n");
+		msgb_free(inst->cp_msg);
+	}
+
+	/* store and send */
+	inst->cp_msg = msg;
+	return gsm411_mmsms_send_msg(inst);
+}
+
+/* release SMC connection */
+static int gsm411_mnsms_rel_req(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	struct msgb *nmsg;
+
+	msgb_free(msg);
+
+	/* discard silently */
+	if (inst->cp_state == GSM411_CPS_IDLE)
+		return 0;
+
+	/* store release, until established or released */
+	if (inst->cp_state != GSM411_CPS_MM_ESTABLISHED) {
+		LOGP(DLSMS, LOGL_NOTICE, "Cannot release yet.\n");
+		inst->cp_rel = 1;
+		return 0;
+	}
+
+	/* free stored msg */
+	if (inst->cp_msg) {
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+
+	new_cp_state(inst, GSM411_CPS_IDLE);
+	/* release MM connection */
+	nmsg = gsm411_msgb_alloc();
+	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
+}
+
+static int gsm411_mmsms_cp_error(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	struct msgb *nmsg;
+
+	/* free stored msg */
+	if (inst->cp_msg) {
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Received CP-ERROR\n");
+	/* 5.3.4 enter idle */
+	new_cp_state(inst, GSM411_CPS_IDLE);
+	/* indicate error */
+	inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, msg);
+	/* release MM connection */
+	nmsg = gsm411_msgb_alloc();
+	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
+}
+
+static int gsm411_mmsms_rel_ind(struct gsm411_smc_inst *inst, struct msgb *msg)
+{
+	struct msgb *nmsg;
+
+	/* free stored msg */
+	if (inst->cp_msg) {
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "MM layer is released\n");
+	/* 5.3.4 enter idle */
+	new_cp_state(inst, GSM411_CPS_IDLE);
+	/* indicate error */
+	nmsg = gsm411_msgb_alloc();
+	inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
+	msgb_free(nmsg);
+
+	return 0;
+}
+
+/* abort SMC connection */
+static int gsm411_mnsms_abort_req(struct gsm411_smc_inst *inst,
+	struct msgb *msg)
+{
+	struct msgb *nmsg;
+
+	/* free stored msg */
+	if (inst->cp_msg) {
+		msgb_free(inst->cp_msg);
+		inst->cp_msg = NULL;
+	}
+
+	/* 5.3.4 go idle */
+	new_cp_state(inst, GSM411_CPS_IDLE);
+	/* send MMSMS_DATA_REQ with CP-ERROR */
+	inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, msg, GSM411_MT_CP_ERROR);
+	/* release MM connection */
+	nmsg = gsm411_msgb_alloc();
+	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
+}
+
+/* statefull handling for MNSMS SAP messages */
+static struct smcdownstate {
+	uint32_t	states;
+	int		type;
+	const char 	*name;
+	int		(*rout) (struct gsm411_smc_inst *inst,
+					struct msgb *msg);
+} smcdownstatelist[] = {
+	/* establish request */
+	{SBIT(GSM411_CPS_IDLE),
+	GSM411_MNSMS_EST_REQ,
+	 "MNSMS-EST-REQ", gsm411_mnsms_est_req},
+
+	/* release request */
+	{ALL_STATES,
+	GSM411_MNSMS_REL_REQ,
+	 "MNSMS-REL-REQ", gsm411_mnsms_rel_req},
+
+	/* data request */
+	{SBIT(GSM411_CPS_MM_ESTABLISHED),
+	GSM411_MNSMS_DATA_REQ,
+	 "MNSMS-DATA-REQ", gsm411_mnsms_data_req},
+
+	/* abort request */
+	{ALL_STATES - SBIT(GSM411_CPS_IDLE),
+	GSM411_MNSMS_ABORT_REQ,
+	 "MNSMS-ABORT-REQ", gsm411_mnsms_abort_req},
+};
+
+#define SMCDOWNSLLEN \
+	(sizeof(smcdownstatelist) / sizeof(struct smcdownstate))
+
+/* message from upper layer */
+int gsm411_smc_send(struct gsm411_smc_inst *inst, int msg_type,
+	struct msgb *msg)
+{
+	int i, rc;
+
+	/* find function for current state and message */
+	for (i = 0; i < SMCDOWNSLLEN; i++) {
+		if ((msg_type == smcdownstatelist[i].type)
+		  && (SBIT(inst->cp_state) & smcdownstatelist[i].states))
+				break;
+	}
+	if (i == SMCDOWNSLLEN) {
+		LOGP(DLSMS, LOGL_NOTICE, "Message %u unhandled at this state "
+			"%s.\n", msg_type, smc_state_names[inst->cp_state]);
+		msgb_free(msg);
+		return 0;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n",
+		smcdownstatelist[i].name, smc_state_names[inst->cp_state]);
+
+	rc = smcdownstatelist[i].rout(inst, msg);
+
+	return rc;
+}
+
+/* statefull handling for MMSMS SAP messages */
+static struct smcdatastate {
+	uint32_t	states;
+	int		type, cp_type;
+	const char 	*name;
+	int		(*rout) (struct gsm411_smc_inst *inst,
+					struct msgb *msg);
+} smcdatastatelist[] = {
+	/* establish confirm */
+	{SBIT(GSM411_CPS_MM_CONN_PENDING),
+	 GSM411_MMSMS_EST_CNF, 0,
+	 "MMSMS-EST-CNF", gsm411_mmsms_est_cnf},
+
+	/* establish indication (CP DATA) */
+	{SBIT(GSM411_CPS_IDLE),
+	 GSM411_MMSMS_EST_IND, GSM411_MT_CP_DATA,
+	 "MMSMS-EST-IND (CP DATA)", gsm411_mmsms_cp_data},
+
+	/* data indication (CP DATA) */
+	{SBIT(GSM411_CPS_MM_ESTABLISHED),
+	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_DATA,
+	 "MMSMS-DATA-IND (CP DATA)", gsm411_mmsms_cp_data},
+
+	/* data indication (CP ACK) */
+	{SBIT(GSM411_CPS_WAIT_CP_ACK),
+	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_ACK,
+	 "MMSMS-DATA-IND (CP ACK)", gsm411_mmsms_cp_ack},
+
+	/* data indication (CP ERROR) */
+	{ALL_STATES,
+	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_ERROR,
+	 "MMSMS-DATA-IND (CP_ERROR)", gsm411_mmsms_cp_error},
+
+	/* release indication */
+	{ALL_STATES - SBIT(GSM411_CPS_IDLE),
+	 GSM411_MMSMS_REL_IND, 0,
+	 "MMSMS-REL-IND", gsm411_mmsms_rel_ind},
+
+};
+
+#define SMCDATASLLEN \
+	(sizeof(smcdatastatelist) / sizeof(struct smcdatastate))
+
+/* message from lower layer
+ * WARNING: We must not free msg, since it will be performed by the
+ * lower layer. */
+int gsm411_smc_recv(struct gsm411_smc_inst *inst, int msg_type,
+	struct msgb *msg, int cp_msg_type)
+{
+	int i, rc;
+
+	/* find function for current state and message */
+	for (i = 0; i < SMCDATASLLEN; i++) {
+		/* state must machtch, MM message must match
+		 * CP msg must match only in case of MMSMS_DATA_IND
+		 */
+		if ((msg_type == smcdatastatelist[i].type)
+		  && (SBIT(inst->cp_state) & smcdatastatelist[i].states)
+		  && (msg_type != GSM411_MMSMS_DATA_IND
+		   || cp_msg_type == smcdatastatelist[i].cp_type))
+				break;
+	}
+	if (i == SMCDATASLLEN) {
+		LOGP(DLSMS, LOGL_NOTICE, "Message 0x%x/%u unhandled at this "
+			"state %s.\n", msg_type, cp_msg_type,
+			smc_state_names[inst->cp_state]);
+		if (msg_type == GSM411_MMSMS_EST_IND
+		 || msg_type == GSM411_MMSMS_DATA_IND) {
+			struct msgb *nmsg;
+
+			LOGP(DLSMS, LOGL_NOTICE, "RX Unimplemented CP "
+				"msg_type: 0x%02x\n", msg_type);
+			/* 5.3.4 enter idle */
+			new_cp_state(inst, GSM411_CPS_IDLE);
+			/* indicate error */
+			gsm411_tx_cp_error(inst,
+				GSM411_CP_CAUSE_MSGTYPE_NOTEXIST);
+			/* send error indication to upper layer */
+			nmsg = gsm411_msgb_alloc();
+			inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
+			msgb_free(nmsg);
+			/* release MM connection */
+			nmsg = gsm411_msgb_alloc();
+			return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg,
+						0);
+		}
+		return 0;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n",
+		smcdatastatelist[i].name, smc_state_names[inst->cp_state]);
+
+	rc = smcdatastatelist[i].rout(inst, msg);
+
+	return rc;
+}
diff --git a/src/gsm/gsm0411_smr.c b/src/gsm/gsm0411_smr.c
new file mode 100644
index 0000000..d5ca923
--- /dev/null
+++ b/src/gsm/gsm0411_smr.c
@@ -0,0 +1,451 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* Notes on msg:
+ *
+ * Messages from lower layer are freed by lower layer.
+ *
+ * Messages to upper layer are freed after upper layer call returns, so upper
+ * layer cannot use data after returning. Upper layer must not free the msg.
+ *
+ * This implies: Lower layer messages can be forwarded to upper layer.
+ *
+ * Upper layer messages are freed by lower layer, so they must not be freed
+ * after calling lower layer.
+ *
+ *
+ * Notes on release:
+ *
+ * Sending Abort/Release (MNSMS-ABORT-REQ or MNSMS-REL-REQ) may cause the
+ * lower layer to become IDLE. Then it is allowed to destroy this instance,
+ * so sending this this MUST be the last thing that is done.
+ *
+ */
+
+
+#include <string.h>
+#include <errno.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gsm/gsm0411_utils.h>
+#include <osmocom/gsm/gsm0411_smc.h>
+#include <osmocom/gsm/gsm0411_smr.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+static void rp_timer_expired(void *data);
+
+/* init a new instance */
+void gsm411_smr_init(struct gsm411_smr_inst *inst, int network,
+	int (*rl_recv) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg),
+	int (*mn_send) (struct gsm411_smr_inst *inst, int msg_type,
+			struct msgb *msg))
+{
+	memset(inst, 0, sizeof(*inst));
+	inst->network = network;
+	inst->rp_state = GSM411_RPS_IDLE;
+	inst->rl_recv = rl_recv;
+	inst->mn_send = mn_send;
+	inst->rp_timer.data = inst;
+	inst->rp_timer.cb = rp_timer_expired;
+
+	LOGP(DLSMS, LOGL_INFO, "New SMR instance created\n");
+}
+
+/* clear instance */
+void gsm411_smr_clear(struct gsm411_smr_inst *inst)
+{
+	LOGP(DLSMS, LOGL_INFO, "Clear SMR instance\n");
+
+	osmo_timer_del(&inst->rp_timer);
+}
+
+const char *smr_state_names[] = {
+	"IDLE",
+	"WAIT_FOR_RP_ACK",
+	"illegal state 2"
+	"WAIT_TO_TX_RP_ACK",
+	"WAIT_FOR_RETRANS_T",
+};
+
+const struct value_string gsm411_rp_cause_strs[] = {
+	{ GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" },
+	{ GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" },
+	{ GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" },
+	{ GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" },
+	{ GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" },
+	{ GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" },
+	{ GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" },
+	{ GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" },
+	{ GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" },
+	{ GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" },
+	{ GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" },
+	{ GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" },
+	{ GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" },
+	/* valid only for MT */
+	{ GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" },
+	/* valid for both directions */
+	{ GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" },
+	{ GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" },
+	{ GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" },
+	{ GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" },
+	{ GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" },
+	{ GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" },
+	{ 0, NULL }
+};
+
+static void new_rp_state(struct gsm411_smr_inst *inst,
+	enum gsm411_rp_state state)
+{
+	LOGP(DLSMS, LOGL_INFO, "New RP state %s -> %s\n",
+		smr_state_names[inst->rp_state], smr_state_names[state]);
+	inst->rp_state = state;
+
+	/* stop timer when going idle */
+	if (state == GSM411_RPS_IDLE)
+		osmo_timer_del(&inst->rp_timer);
+}
+
+/* Prefix msg with a RP-DATA header and send as CP-DATA */
+static int gsm411_rp_sendmsg(struct gsm411_smr_inst *inst, struct msgb *msg,
+			     uint8_t rp_msg_type, uint8_t rp_msg_ref,
+			     int mnsms_msg_type)
+{
+	struct gsm411_rp_hdr *rp;
+	uint8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return inst->mn_send(inst, mnsms_msg_type, msg);
+}
+
+static int gsm411_send_rp_error(struct gsm411_smr_inst *inst,
+				uint8_t msg_ref, uint8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, cause);
+
+	LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+		get_value_string(gsm411_rp_cause_strs, cause));
+
+	return gsm411_rp_sendmsg(inst, msg,
+		(inst->network) ? GSM411_MT_RP_ERROR_MT : GSM411_MT_RP_ERROR_MO,
+		msg_ref, GSM411_MNSMS_DATA_REQ);
+}
+
+static int gsm411_send_release(struct gsm411_smr_inst *inst)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	LOGP(DLSMS, LOGL_NOTICE, "TX: MNSMS-REL-REQ\n");
+
+	return inst->mn_send(inst, GSM411_MNSMS_REL_REQ, msg);
+}
+
+static int gsm411_send_abort(struct gsm411_smr_inst *inst)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, 111); //FIXME: better idea ? */
+	LOGP(DLSMS, LOGL_NOTICE, "TX: MNSMS-ABORT-REQ\n");
+
+	return inst->mn_send(inst, GSM411_MNSMS_ABORT_REQ, msg);
+}
+
+static int gsm411_send_report(struct gsm411_smr_inst *inst)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	LOGP(DLSMS, LOGL_NOTICE, "send empty SM_RL_REPORT_IND\n");
+
+	return inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg);
+}
+
+static int gsm411_rl_data_req(struct gsm411_smr_inst *inst, struct msgb *msg)
+{
+	LOGP(DLSMS, LOGL_DEBUG,  "TX SMS RP-DATA\n");
+	/* start TR1N and enter 'wait for RP-ACK state' */
+	osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR1M);
+	new_rp_state(inst, GSM411_RPS_WAIT_FOR_RP_ACK);
+
+	return inst->mn_send(inst, GSM411_MNSMS_EST_REQ, msg);
+}
+
+static int gsm411_rl_report_req(struct gsm411_smr_inst *inst, struct msgb *msg)
+{
+	LOGP(DLSMS, LOGL_DEBUG,  "TX SMS REPORT\n");
+	new_rp_state(inst, GSM411_RPS_IDLE);
+
+	inst->mn_send(inst, GSM411_MNSMS_DATA_REQ, msg);
+	gsm411_send_release(inst);
+	return 0;
+}
+
+static int gsm411_mnsms_est_ind(struct gsm411_smr_inst *inst, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr*)msg->l3h;
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	uint8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc;
+
+	/* check direction */
+	if (inst->network == (msg_type & 1)) {
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		gsm411_send_rp_error(inst, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		gsm411_send_release(inst);
+		return -EINVAL;
+	}
+
+	switch (msg_type) {
+	case GSM411_MT_RP_DATA_MT:
+	case GSM411_MT_RP_DATA_MO:
+		LOGP(DLSMS, LOGL_DEBUG,  "RX SMS RP-DATA\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR2M);
+		new_rp_state(inst, GSM411_RPS_WAIT_TO_TX_RP_ACK);
+		rc = inst->rl_recv(inst, GSM411_SM_RL_DATA_IND, msg);
+		break;
+	case GSM411_MT_RP_SMMA_MO:
+		LOGP(DLSMS, LOGL_DEBUG,  "RX SMS RP-SMMA\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		osmo_timer_schedule(&inst->rp_timer, GSM411_TMR_TR2M);
+		new_rp_state(inst, GSM411_RPS_WAIT_TO_TX_RP_ACK);
+		rc = inst->rl_recv(inst, GSM411_SM_RL_DATA_IND, msg);
+		break;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		gsm411_send_rp_error(inst, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSGTYPE_NOTEXIST);
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+static int gsm411_mnsms_data_ind_tx(struct gsm411_smr_inst *inst,
+	struct msgb *msg)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr*)msg->l3h;
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	uint8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc;
+
+	/* check direction */
+	if (inst->network == (msg_type & 1)) {
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		gsm411_send_rp_error(inst, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		gsm411_send_release(inst);
+		return -EINVAL;
+	}
+
+	switch (msg_type) {
+	case GSM411_MT_RP_ACK_MO:
+	case GSM411_MT_RP_ACK_MT:
+		LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-ACK\n");
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg);
+		gsm411_send_release(inst);
+		return 0;
+	case GSM411_MT_RP_ERROR_MO:
+	case GSM411_MT_RP_ERROR_MT:
+		LOGP(DLSMS, LOGL_DEBUG, "RX SMS RP-ERROR\n");
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg);
+		gsm411_send_release(inst);
+		return 0;
+	default:
+		LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		gsm411_send_rp_error(inst, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSGTYPE_NOTEXIST);
+		new_rp_state(inst, GSM411_RPS_IDLE);
+		gsm411_send_release(inst);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int gsm411_mnsms_error_ind_tx(struct gsm411_smr_inst *inst,
+	struct msgb *msg)
+{
+	LOGP(DLSMS, LOGL_DEBUG, "RX SMS MNSMS-ERROR-IND\n");
+	new_rp_state(inst, GSM411_RPS_IDLE);
+	inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg);
+	gsm411_send_release(inst);
+	return 0;
+}
+
+static int gsm411_mnsms_error_ind_rx(struct gsm411_smr_inst *inst,
+	struct msgb *msg)
+{
+	LOGP(DLSMS, LOGL_DEBUG, "RX SMS MNSMS-ERROR-IND\n");
+	new_rp_state(inst, GSM411_RPS_IDLE);
+	return inst->rl_recv(inst, GSM411_SM_RL_REPORT_IND, msg);
+}
+
+/* SMR TR1* is expired */
+static void rp_timer_expired(void *data)
+{
+	struct gsm411_smr_inst *inst = data;
+
+	if (inst->rp_state == GSM411_RPS_WAIT_TO_TX_RP_ACK)
+		LOGP(DLSMS, LOGL_DEBUG, "TR2N\n");
+	else
+		LOGP(DLSMS, LOGL_DEBUG, "TR1N\n");
+	gsm411_send_report(inst);
+	gsm411_send_abort(inst);
+}
+
+/* statefull handling for SM-RL SAP messages */
+static struct smrdownstate {
+	uint32_t	states;
+	int		type;
+	const char 	*name;
+	int		(*rout) (struct gsm411_smr_inst *inst,
+					struct msgb *msg);
+} smrdownstatelist[] = {
+	/* data request */
+	{SBIT(GSM411_RPS_IDLE),
+	GSM411_SM_RL_DATA_REQ,
+	 "SM-RL-DATA_REQ", gsm411_rl_data_req},
+
+	/* report request */
+	{SBIT(GSM411_RPS_WAIT_TO_TX_RP_ACK),
+	GSM411_SM_RL_REPORT_REQ,
+	 "SM-RL-REPORT_REQ", gsm411_rl_report_req},
+};
+
+#define SMRDOWNSLLEN \
+	(sizeof(smrdownstatelist) / sizeof(struct smrdownstate))
+
+/* message from upper layer */
+int gsm411_smr_send(struct gsm411_smr_inst *inst, int msg_type,
+	struct msgb *msg)
+{
+	int i, rc;
+
+	/* find function for current state and message */
+	for (i = 0; i < SMRDOWNSLLEN; i++) {
+		if ((msg_type == smrdownstatelist[i].type)
+		  && (SBIT(inst->rp_state) & smrdownstatelist[i].states))
+				break;
+	}
+	if (i == SMRDOWNSLLEN) {
+		LOGP(DLSMS, LOGL_NOTICE, "Message %u unhandled at this state "
+			"%s.\n", msg_type, smr_state_names[inst->rp_state]);
+		msgb_free(msg);
+		return 0;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n",
+		smrdownstatelist[i].name, smr_state_names[inst->rp_state]);
+
+	rc = smrdownstatelist[i].rout(inst, msg);
+
+	return rc;
+}
+
+/* statefull handling for MMSMS SAP messages */
+static struct smrdatastate {
+	uint32_t	states;
+	int		type;
+	const char 	*name;
+	int		(*rout) (struct gsm411_smr_inst *inst,
+					struct msgb *msg);
+} smrdatastatelist[] = {
+	/* establish indication */
+	{SBIT(GSM411_RPS_IDLE),
+	 GSM411_MNSMS_EST_IND,
+	 "MNSMS-EST-IND", gsm411_mnsms_est_ind},
+
+	/* data indication */
+	{SBIT(GSM411_RPS_WAIT_FOR_RP_ACK),
+	 GSM411_MNSMS_DATA_IND,
+	 "MNSMS-DATA-IND", gsm411_mnsms_data_ind_tx},
+
+	/* error indication */
+	{SBIT(GSM411_RPS_WAIT_FOR_RP_ACK),
+	 GSM411_MNSMS_ERROR_IND,
+	 "MNSMS-ERROR-IND", gsm411_mnsms_error_ind_tx},
+
+	/* error indication */
+	{SBIT(GSM411_RPS_WAIT_TO_TX_RP_ACK),
+	 GSM411_MNSMS_ERROR_IND,
+	 "MNSMS-ERROR-IND", gsm411_mnsms_error_ind_rx},
+
+};
+
+#define SMRDATASLLEN \
+	(sizeof(smrdatastatelist) / sizeof(struct smrdatastate))
+
+/* message from lower layer
+ * WARNING: We must not free msg, since it will be performed by the
+ * lower layer. */
+int gsm411_smr_recv(struct gsm411_smr_inst *inst, int msg_type,
+	struct msgb *msg)
+{
+	int i, rc;
+
+	/* find function for current state and message */
+	for (i = 0; i < SMRDATASLLEN; i++) {
+		/* state must machtch, MM message must match
+		 * CP msg must match only in case of MMSMS_DATA_IND
+		 */
+		if ((msg_type == smrdatastatelist[i].type)
+		  && (SBIT(inst->rp_state) & smrdatastatelist[i].states))
+			break;
+	}
+	if (i == SMRDATASLLEN) {
+		LOGP(DLSMS, LOGL_NOTICE, "Message %u unhandled at this state "
+			"%s.\n", msg_type, smr_state_names[inst->rp_state]);
+		return 0;
+	}
+
+	LOGP(DLSMS, LOGL_INFO, "Message %s received in state %s\n",
+		smrdatastatelist[i].name, smr_state_names[inst->rp_state]);
+
+	rc = smrdatastatelist[i].rout(inst, msg);
+
+	return rc;
+}
diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c
new file mode 100644
index 0000000..5076ec8
--- /dev/null
+++ b/src/gsm/gsm0411_utils.c
@@ -0,0 +1,306 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <time.h>
+#include <string.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#define GSM411_ALLOC_SIZE	1024
+#define GSM411_ALLOC_HEADROOM	128
+
+struct msgb *gsm411_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM,
+				   "GSM 04.11");
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+uint8_t gsm411_bcdify(uint8_t value)
+{
+	uint8_t ret;
+
+	ret = value / 10;
+	ret |= (value % 10) << 4;
+
+	return ret;
+}
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+uint8_t gsm411_unbcdify(uint8_t value)
+{
+	uint8_t ret;
+
+	if ((value & 0x0F) > 9 || (value >> 4) > 9)
+		LOGP(DLSMS, LOGL_ERROR,
+		     "gsm411_unbcdify got too big nibble: 0x%02X\n", value);
+
+	ret = (value&0x0F)*10;
+	ret += value>>4;
+
+	return ret;
+}
+
+/* Generate 03.40 TP-SCTS */
+void gsm340_gen_scts(uint8_t *scts, time_t time)
+{
+	struct tm *tm = gmtime(&time);
+
+	*scts++ = gsm411_bcdify(tm->tm_year % 100);
+	*scts++ = gsm411_bcdify(tm->tm_mon + 1);
+	*scts++ = gsm411_bcdify(tm->tm_mday);
+	*scts++ = gsm411_bcdify(tm->tm_hour);
+	*scts++ = gsm411_bcdify(tm->tm_min);
+	*scts++ = gsm411_bcdify(tm->tm_sec);
+	*scts++ = gsm411_bcdify(0); /* GMT */
+}
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+time_t gsm340_scts(uint8_t *scts)
+{
+	struct tm tm;
+	uint8_t yr = gsm411_unbcdify(*scts++);
+	int ofs;
+
+	memset(&tm, 0x00, sizeof(struct tm));
+
+	if (yr <= 80)
+		tm.tm_year = 100 + yr;
+	else
+		tm.tm_year = yr;
+	tm.tm_mon  = gsm411_unbcdify(*scts++) - 1;
+	tm.tm_mday = gsm411_unbcdify(*scts++);
+	tm.tm_hour = gsm411_unbcdify(*scts++);
+	tm.tm_min  = gsm411_unbcdify(*scts++);
+	tm.tm_sec  = gsm411_unbcdify(*scts++);
+
+	/* according to gsm 03.40 time zone is
+	   "expressed in quarters of an hour" */
+	ofs = gsm411_unbcdify(*scts++) * 15*60;
+
+	return mktime(&tm) - ofs;
+}
+
+/* Return the default validity period in minutes */
+static unsigned long gsm340_vp_default(void)
+{
+	unsigned long minutes;
+	/* Default validity: two days */
+	minutes = 24 * 60 * 2;
+	return minutes;
+}
+
+/* Decode validity period format 'relative' */
+static unsigned long gsm340_vp_relative(uint8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.1 */
+	uint8_t vp;
+	unsigned long minutes;
+
+	vp = *(sms_vp);
+	if (vp <= 143)
+		minutes = vp + 1 * 5;
+	else if (vp <= 167)
+		minutes = 12*60 + (vp-143) * 30;
+	else if (vp <= 196)
+		minutes = vp-166 * 60 * 24;
+	else
+		minutes = vp-192 * 60 * 24 * 7;
+	return minutes;
+}
+
+/* Decode validity period format 'absolute' */
+static unsigned long gsm340_vp_absolute(uint8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.2 */
+	time_t expires, now;
+	unsigned long minutes;
+
+	expires = gsm340_scts(sms_vp);
+	now = time(NULL);
+	if (expires <= now)
+		minutes = 0;
+	else
+		minutes = (expires-now)/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in integer representation' */
+static unsigned long gsm340_vp_relative_integer(uint8_t *sms_vp)
+{
+	uint8_t vp;
+	unsigned long minutes;
+	vp = *(sms_vp);
+	if (vp == 0) {
+		LOGP(DLSMS, LOGL_ERROR,
+		     "reserved relative_integer validity period\n");
+		return gsm340_vp_default();
+	}
+	minutes = vp/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in semi-octet representation' */
+static unsigned long gsm340_vp_relative_semioctet(uint8_t *sms_vp)
+{
+	unsigned long minutes;
+	minutes = gsm411_unbcdify(*sms_vp++)*60;  /* hours */
+	minutes += gsm411_unbcdify(*sms_vp++);    /* minutes */
+	minutes += gsm411_unbcdify(*sms_vp++)/60; /* seconds */
+	return minutes;
+}
+
+/* decode validity period. return minutes */
+unsigned long gsm340_validity_period(uint8_t sms_vpf, uint8_t *sms_vp)
+{
+	uint8_t fi; /* functionality indicator */
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		return gsm340_vp_relative(sms_vp);
+	case GSM340_TP_VPF_ABSOLUTE:
+		return gsm340_vp_absolute(sms_vp);
+	case GSM340_TP_VPF_ENHANCED:
+		/* Chapter 9.2.3.12.3 */
+		fi = *sms_vp++;
+		/* ignore additional fi */
+		if (fi & (1<<7)) sms_vp++;
+		/* read validity period format */
+		switch (fi & 0x7) {
+		case 0x0:
+			return gsm340_vp_default(); /* no vpf specified */
+		case 0x1:
+			return gsm340_vp_relative(sms_vp);
+		case 0x2:
+			return gsm340_vp_relative_integer(sms_vp);
+		case 0x3:
+			return gsm340_vp_relative_semioctet(sms_vp);
+		default:
+			/* The GSM spec says that the SC should reject any
+			   unsupported and/or undefined values. FIXME */
+			LOGP(DLSMS, LOGL_ERROR,
+			     "Reserved enhanced validity period format\n");
+			return gsm340_vp_default();
+		}
+	case GSM340_TP_VPF_NONE:
+	default:
+		return gsm340_vp_default();
+	}
+}
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs)
+{
+	uint8_t cgbits = dcs >> 4;
+	enum sms_alphabet alpha = DCS_NONE;
+
+	if ((cgbits & 0xc) == 0) {
+		if (cgbits & 2) {
+			LOGP(DLSMS, LOGL_NOTICE,
+			     "Compressed SMS not supported yet\n");
+			return 0xffffffff;
+		}
+
+		switch ((dcs >> 2)&0x03) {
+		case 0:
+			alpha = DCS_7BIT_DEFAULT;
+			break;
+		case 1:
+			alpha = DCS_8BIT_DATA;
+			break;
+		case 2:
+			alpha = DCS_UCS2;
+			break;
+		}
+	} else if (cgbits == 0xc || cgbits == 0xd)
+		alpha = DCS_7BIT_DEFAULT;
+	else if (cgbits == 0xe)
+		alpha = DCS_UCS2;
+	else if (cgbits == 0xf) {
+		if (dcs & 4)
+			alpha = DCS_8BIT_DATA;
+		else
+			alpha = DCS_7BIT_DEFAULT;
+	}
+
+	return alpha;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type,
+	uint8_t plan, const char *number)
+{
+	int len_in_bytes;
+
+	/* prevent buffer overflows */
+	if (strlen(number) > 20)
+		number = "";
+
+//	oa[1] = 0xb9; /* networks-specific number, private numbering plan */
+	oa[1] = 0x80 | (type << 4) | plan;
+
+	len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, number);
+
+	/* GSM 03.40 tells us the length is in 'useful semi-octets' */
+	oa[0] = strlen(number) & 0xff;
+
+	return len_in_bytes;
+}
+
+/* Prefix msg with a RP header */
+int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type,
+	uint8_t rp_msg_ref)
+{
+	struct gsm411_rp_hdr *rp;
+	uint8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return 0;
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans,
+			     uint8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = proto | (trans << 4);
+	gh->msg_type = msg_type;
+
+	return 0;
+}
diff --git a/src/gsm/gsm0480.c b/src/gsm/gsm0480.c
new file mode 100644
index 0000000..b9b3ed9
--- /dev/null
+++ b/src/gsm/gsm0480.c
@@ -0,0 +1,461 @@
+/* Format functions for GSM 04.80 */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_04_80.h>
+
+#include <string.h>
+
+static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag)
+{
+	uint8_t *data = msgb_push(msgb, 2);
+
+	data[0] = tag;
+	data[1] = msgb->len - 2;
+	return data;
+}
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag,
+					    uint8_t value)
+{
+	uint8_t *data = msgb_push(msgb, 3);
+
+	data[0] = tag;
+	data[1] = 1;
+	data[2] = value;
+	return data;
+}
+
+/* wrap an invoke around it... the other way around
+ *
+ * 1.) Invoke Component tag
+ * 2.) Invoke ID Tag
+ * 3.) Operation
+ * 4.) Data
+ */
+int gsm0480_wrap_invoke(struct msgb *msg, int op, int link_id)
+{
+	/* 3. operation */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, op);
+
+	/* 2. invoke id tag */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, link_id);
+
+	/* 1. component tag */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_INVOKE);
+
+	return 0;
+}
+
+/* wrap the GSM 04.08 Facility IE around it */
+int gsm0480_wrap_facility(struct msgb *msg)
+{
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	return 0;
+}
+
+struct msgb *gsm0480_create_unstructuredSS_Notify(int alertPattern, const char *text)
+{
+	struct msgb *msg;
+	uint8_t *seq_len_ptr, *ussd_len_ptr, *data;
+	int len;
+
+	msg = msgb_alloc_headroom(1024, 128, "GSM 04.80");
+	if (!msg)
+		return NULL;
+
+	/* SEQUENCE { */
+	msgb_put_u8(msg, GSM_0480_SEQUENCE_TAG);
+	seq_len_ptr = msgb_put(msg, 1);
+
+	/* DCS { */
+	msgb_put_u8(msg, ASN1_OCTET_STRING_TAG);
+	msgb_put_u8(msg, 1);
+	msgb_put_u8(msg, 0x0F);
+	/* } DCS */
+
+	/* USSD-String { */
+	msgb_put_u8(msg, ASN1_OCTET_STRING_TAG);
+	ussd_len_ptr = msgb_put(msg, 1);
+	data = msgb_put(msg, 0);
+	len = gsm_7bit_encode(data, text);
+	msgb_put(msg, len);
+	ussd_len_ptr[0] = len;
+	/* USSD-String } */
+
+	/* alertingPattern { */
+	msgb_put_u8(msg, ASN1_OCTET_STRING_TAG);
+	msgb_put_u8(msg, 1);
+	msgb_put_u8(msg, alertPattern);
+	/* } alertingPattern */
+
+	seq_len_ptr[0] = 3 + 2 + ussd_len_ptr[0] + 3;
+	/* } SEQUENCE */
+
+	return msg;
+}
+
+struct msgb *gsm0480_create_notifySS(const char *text)
+{
+	struct msgb *msg;
+	uint8_t *data, *tmp_len;
+	uint8_t *seq_len_ptr, *cal_len_ptr, *opt_len_ptr, *nam_len_ptr;
+	int len;
+
+	len = strlen(text);
+	if (len < 1 || len > 160)
+		return NULL;
+
+	msg = msgb_alloc_headroom(1024, 128, "GSM 04.80");
+	if (!msg)
+		return NULL;
+
+	msgb_put_u8(msg, GSM_0480_SEQUENCE_TAG);
+	seq_len_ptr = msgb_put(msg, 1);
+
+	/* ss_code for CNAP { */
+	msgb_put_u8(msg, 0x81);
+	msgb_put_u8(msg, 1);
+	msgb_put_u8(msg, 0x19);
+	/* } ss_code */
+
+
+	/* nameIndicator { */
+	msgb_put_u8(msg, 0xB4);
+	nam_len_ptr = msgb_put(msg, 1);
+
+	/* callingName { */
+	msgb_put_u8(msg, 0xA0);
+	opt_len_ptr = msgb_put(msg, 1);
+	msgb_put_u8(msg, 0xA0);
+	cal_len_ptr = msgb_put(msg, 1);
+
+	/* namePresentationAllowed { */
+	/* add the DCS value */
+	msgb_put_u8(msg, 0x80);
+	msgb_put_u8(msg, 1);
+	msgb_put_u8(msg, 0x0F);
+
+	/* add the lengthInCharacters */
+	msgb_put_u8(msg, 0x81);
+	msgb_put_u8(msg, 1);
+	msgb_put_u8(msg, strlen(text));
+
+	/* add the actual string */
+	msgb_put_u8(msg, 0x82);
+	tmp_len = msgb_put(msg, 1);
+	data = msgb_put(msg, 0);
+	len = gsm_7bit_encode(data, text);
+	tmp_len[0] = len;
+	msgb_put(msg, len);
+
+	/* }; namePresentationAllowed */
+
+	cal_len_ptr[0] = 3 + 3 + 2 + len;
+	opt_len_ptr[0] = cal_len_ptr[0] + 2;
+	/* }; callingName */
+
+	nam_len_ptr[0] = opt_len_ptr[0] + 2;
+	/* ); nameIndicator */
+
+	/* write the lengths... */
+	seq_len_ptr[0] = 3 + nam_len_ptr[0] + 2;
+
+	return msg;
+}
+
+/* Forward declarations */
+static int parse_ussd(const struct gsm48_hdr *hdr,
+		      uint16_t len, struct ussd_request *req);
+static int parse_ussd_info_elements(const uint8_t *ussd_ie, uint16_t len,
+					struct ussd_request *req);
+static int parse_facility_ie(const uint8_t *facility_ie, uint16_t length,
+					struct ussd_request *req);
+static int parse_ss_invoke(const uint8_t *invoke_data, uint16_t length,
+					struct ussd_request *req);
+static int parse_process_uss_req(const uint8_t *uss_req_data, uint16_t length,
+					struct ussd_request *req);
+
+/* Decode a mobile-originated USSD-request message */
+int gsm0480_decode_ussd_request(const struct gsm48_hdr *hdr, uint16_t len,
+				struct ussd_request *req)
+{
+	int rc = 0;
+
+	if (len < sizeof(*hdr) + 2) {
+		LOGP(0, LOGL_DEBUG, "USSD Request is too short.\n");
+		return 0;
+	}
+
+	if ((hdr->proto_discr & 0x0f) == GSM48_PDISC_NC_SS) {
+		req->transaction_id = hdr->proto_discr & 0x70;
+		rc = parse_ussd(hdr, len, req);
+	}
+
+	if (!rc)
+		LOGP(0, LOGL_DEBUG, "Error occurred while parsing received USSD!\n");
+
+	return rc;
+}
+
+static int parse_ussd(const struct gsm48_hdr *hdr, uint16_t len, struct ussd_request *req)
+{
+	int rc = 1;
+	uint8_t msg_type = hdr->msg_type & 0xBF;  /* message-type - section 3.4 */
+
+	switch (msg_type) {
+	case GSM0480_MTYPE_RELEASE_COMPLETE:
+		LOGP(0, LOGL_DEBUG, "USS Release Complete\n");
+		/* could also parse out the optional Cause/Facility data */
+		req->text[0] = 0xFF;
+		break;
+	case GSM0480_MTYPE_REGISTER:
+	case GSM0480_MTYPE_FACILITY:
+		rc &= parse_ussd_info_elements(&hdr->data[0], len - sizeof(*hdr), req);
+		break;
+	default:
+		LOGP(0, LOGL_DEBUG, "Unknown GSM 04.80 message-type field 0x%02x\n",
+			hdr->msg_type);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static int parse_ussd_info_elements(const uint8_t *ussd_ie, uint16_t len,
+				    struct ussd_request *req)
+{
+	int rc = -1;
+	/* Information Element Identifier - table 3.2 & GSM 04.08 section 10.5 */
+	uint8_t iei;
+	uint8_t iei_length;
+
+	iei = ussd_ie[0];
+	iei_length = ussd_ie[1];
+
+	/* If the data does not fit, report an error */
+	if (len - 2 < iei_length)
+		return 0;
+
+	switch (iei) {
+	case GSM48_IE_CAUSE:
+		break;
+	case GSM0480_IE_FACILITY:
+		rc = parse_facility_ie(ussd_ie+2, iei_length, req);
+		break;
+	case GSM0480_IE_SS_VERSION:
+		break;
+	default:
+		LOGP(0, LOGL_DEBUG, "Unhandled GSM 04.08 or 04.80 IEI 0x%02x\n",
+			iei);
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static int parse_facility_ie(const uint8_t *facility_ie, uint16_t length,
+						struct ussd_request *req)
+{
+	int rc = 1;
+	uint8_t offset = 0;
+
+	while (offset + 2 <= length) {
+		/* Component Type tag - table 3.7 */
+		uint8_t component_type = facility_ie[offset];
+		uint8_t component_length = facility_ie[offset+1];
+
+		/* size check */
+		if (offset + 2 + component_length > length) {
+			LOGP(0, LOGL_ERROR, "Component does not fit.\n");
+			return 0;
+		}
+
+		switch (component_type) {
+		case GSM0480_CTYPE_INVOKE:
+			rc &= parse_ss_invoke(facility_ie+2,
+						component_length,
+						req);
+			break;
+		case GSM0480_CTYPE_RETURN_RESULT:
+			break;
+		case GSM0480_CTYPE_RETURN_ERROR:
+			break;
+		case GSM0480_CTYPE_REJECT:
+			break;
+		default:
+			LOGP(0, LOGL_DEBUG, "Unknown GSM 04.80 Facility "
+				"Component Type 0x%02x\n", component_type);
+			rc = 0;
+			break;
+		}
+		offset += (component_length+2);
+	};
+
+	return rc;
+}
+
+/* Parse an Invoke component - see table 3.3 */
+static int parse_ss_invoke(const uint8_t *invoke_data, uint16_t length,
+						struct ussd_request *req)
+{
+	int rc = 1;
+	uint8_t offset;
+
+	if (length < 3)
+		return 0;
+
+	/* mandatory part */
+	if (invoke_data[0] != GSM0480_COMPIDTAG_INVOKE_ID) {
+		LOGP(0, LOGL_DEBUG, "Unexpected GSM 04.80 Component-ID tag "
+			"0x%02x (expecting Invoke ID tag)\n", invoke_data[0]);
+	}
+
+	offset = invoke_data[1] + 2;
+	req->invoke_id = invoke_data[2];
+
+	/* look ahead once */
+	if (offset + 1 > length)
+		return 0;
+
+	/* optional part */
+	if (invoke_data[offset] == GSM0480_COMPIDTAG_LINKED_ID)
+		offset += invoke_data[offset+1] + 2;  /* skip over it */
+
+	/* mandatory part */
+	if (invoke_data[offset] == GSM0480_OPERATION_CODE) {
+		if (offset + 2 > length)
+			return 0;
+		uint8_t operation_code = invoke_data[offset+2];
+		switch (operation_code) {
+		case GSM0480_OP_CODE_PROCESS_USS_REQ:
+			rc = parse_process_uss_req(invoke_data + offset + 3,
+						   length - offset - 3,
+						   req);
+			break;
+		default:
+			LOGP(0, LOGL_DEBUG, "GSM 04.80 operation code 0x%02x "
+				"is not yet handled\n", operation_code);
+			rc = 0;
+			break;
+		}
+	} else {
+		LOGP(0, LOGL_DEBUG, "Unexpected GSM 04.80 Component-ID tag 0x%02x "
+			"(expecting Operation Code tag)\n",
+			invoke_data[0]);
+		rc = 0;
+	}
+
+	return rc;
+}
+
+/* Parse the parameters of a Process UnstructuredSS Request */
+static int parse_process_uss_req(const uint8_t *uss_req_data, uint16_t length,
+					struct ussd_request *req)
+{
+	int rc = 0;
+	int num_chars;
+	uint8_t dcs;
+
+
+	/* we need at least that much */
+	if (length < 8)
+		return 0;
+
+
+	if (uss_req_data[0] == GSM_0480_SEQUENCE_TAG) {
+		if (uss_req_data[2] == ASN1_OCTET_STRING_TAG) {
+			dcs = uss_req_data[4];
+			if ((dcs == 0x0F) &&
+			    (uss_req_data[5] == ASN1_OCTET_STRING_TAG)) {
+				num_chars = (uss_req_data[6] * 8) / 7;
+				/* Prevent a mobile-originated buffer-overrun! */
+				if (num_chars > MAX_LEN_USSD_STRING)
+					num_chars = MAX_LEN_USSD_STRING;
+				gsm_7bit_decode(req->text,
+						&(uss_req_data[7]), num_chars);
+				rc = 1;
+			}
+		}
+	}
+	return rc;
+}
+
+struct msgb *gsm0480_create_ussd_resp(uint8_t invoke_id, uint8_t trans_id, const char *text)
+{
+	struct msgb *msg;
+	struct gsm48_hdr *gh;
+	uint8_t *ptr8;
+	int response_len;
+
+	msg = msgb_alloc_headroom(1024, 128, "GSM 04.80");
+	if (!msg)
+		return NULL;
+
+	/* First put the payload text into the message */
+	ptr8 = msgb_put(msg, 0);
+	response_len = gsm_7bit_encode(ptr8, text);
+	msgb_put(msg, response_len);
+
+	/* Then wrap it as an Octet String */
+	msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+	/* Pre-pend the DCS octet string */
+	msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+	/* Then wrap these as a Sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the operation code */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+			GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+	/* Wrap the operation code and IA5 string as a sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, invoke_id);
+
+	/* Wrap this up as a Return Result component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS | trans_id
+					| (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return msg;
+}
diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c
new file mode 100644
index 0000000..df1d8e9
--- /dev/null
+++ b/src/gsm/gsm0502.c
@@ -0,0 +1,43 @@
+/* Paging helper code */
+
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/rsl.h>
+
+unsigned int
+gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi)
+{
+	int ccch_conf;
+	int bs_cc_chans;
+	int blocks;
+	unsigned int group;
+
+	ccch_conf = chan_desc->ccch_conf;
+	bs_cc_chans = rsl_ccch_conf_to_bs_cc_chans(ccch_conf);
+	/* code word + 2, as 2 channels equals 0x0 */
+	blocks = gsm48_number_of_paging_subchannels(chan_desc);
+	group = gsm0502_get_paging_group(imsi, bs_cc_chans, blocks);
+
+	return group;
+}
diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c
new file mode 100644
index 0000000..3009827
--- /dev/null
+++ b/src/gsm/gsm0808.c
@@ -0,0 +1,402 @@
+/* (C) 2009,2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009,2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <arpa/inet.h>
+
+#define BSSMAP_MSG_SIZE 512
+#define BSSMAP_MSG_HEADROOM 128
+
+struct msgb *gsm0808_create_layer3(struct msgb *msg_l3, uint16_t nc, uint16_t cc, int lac, uint16_t _ci)
+{
+	struct msgb* msg;
+	struct {
+		uint8_t ident;
+		struct gsm48_loc_area_id lai;
+		uint16_t ci;
+	} __attribute__ ((packed)) lai_ci;
+
+	msg  = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+				   "bssmap cmpl l3");
+	if (!msg)
+		return NULL;
+
+	/* create layer 3 header */
+	msgb_v_put(msg, BSS_MAP_MSG_COMPLETE_LAYER_3);
+
+	/* create the cell header */
+	lai_ci.ident = CELL_IDENT_WHOLE_GLOBAL;
+	gsm48_generate_lai(&lai_ci.lai, cc, nc, lac);
+	lai_ci.ci = htons(_ci);
+	msgb_tlv_put(msg, GSM0808_IE_CELL_IDENTIFIER, sizeof(lai_ci),
+		     (uint8_t *) &lai_ci);
+
+	/* copy the layer3 data */
+	msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION,
+		     msgb_l3len(msg_l3), msg_l3->l3h);
+
+	/* push the bssmap header */
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_reset(void)
+{
+	uint8_t cause = GSM0808_CAUSE_EQUIPMENT_FAILURE;
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: reset");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_RESET);
+	msgb_tlv_put(msg, GSM0808_IE_CAUSE, 1, &cause);
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_clear_complete(void)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: clear complete");
+	uint8_t val = BSS_MAP_MSG_CLEAR_COMPLETE;
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msg->data;
+	msgb_tlv_put(msg, BSSAP_MSG_BSS_MANAGEMENT, 1, &val);
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_clear_command(uint8_t reason)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: clear command");
+	if (!msg)
+		return NULL;
+
+	msg->l3h = msgb_tv_put(msg, BSSAP_MSG_BSS_MANAGEMENT, 4);
+	msgb_v_put(msg, BSS_MAP_MSG_CLEAR_CMD);
+	msgb_tlv_put(msg, GSM0808_IE_CAUSE, 1, &reason);
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "cipher-complete");
+	if (!msg)
+		return NULL;
+
+        /* send response with BSS override for A5/1... cheating */
+	msgb_v_put(msg, BSS_MAP_MSG_CIPHER_MODE_COMPLETE);
+
+	/* include layer3 in case we have at least two octets */
+	if (layer3 && msgb_l3len(layer3) > 2) {
+		msg->l4h = msgb_tlv_put(msg, GSM0808_IE_LAYER_3_MESSAGE_CONTENTS,
+					msgb_l3len(layer3), layer3->l3h);
+	}
+
+	/* and the optional BSS message */
+	msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, alg_id);
+
+	/* pre-pend the header */
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_cipher_reject(uint8_t cause)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: clear complete");
+	if (!msg)
+		return NULL;
+
+	msgb_tv_put(msg, BSS_MAP_MSG_CIPHER_MODE_REJECT, cause);
+
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_classmark_update(const uint8_t *cm2, uint8_t cm2_len,
+					     const uint8_t *cm3, uint8_t cm3_len)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "classmark-update");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_CLASSMARK_UPDATE);
+	msgb_tlv_put(msg, GSM0808_IE_CLASSMARK_INFORMATION_T2, cm2_len, cm2);
+	if (cm3)
+		msgb_tlv_put(msg, GSM0808_IE_CLASSMARK_INFORMATION_T3,
+			     cm3_len, cm3);
+
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: sapi 'n' reject");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_SAPI_N_REJECT);
+	msgb_v_put(msg, link_id);
+	msgb_v_put(msg, GSM0808_CAUSE_BSS_NOT_EQUIPPED);
+
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_assignment_completed(uint8_t rr_cause,
+						 uint8_t chosen_channel, uint8_t encr_alg_id,
+						 uint8_t speech_mode)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+						"bssmap: ass compl");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_ASSIGMENT_COMPLETE);
+
+	/* write 3.2.2.22 */
+	msgb_tv_put(msg, GSM0808_IE_RR_CAUSE, rr_cause);
+
+	/* write cirtcuit identity  code 3.2.2.2 */
+	/* write cell identifier 3.2.2.17 */
+	/* write chosen channel 3.2.2.33 when BTS picked it */
+	msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, chosen_channel);
+
+	/* write chosen encryption algorithm 3.2.2.44 */
+	msgb_tv_put(msg, GSM0808_IE_CHOSEN_ENCR_ALG, encr_alg_id);
+
+	/* write circuit pool 3.2.2.45 */
+	/* write speech version chosen: 3.2.2.51 when BTS picked it */
+	if (speech_mode != 0)
+		msgb_tv_put(msg, GSM0808_IE_SPEECH_VERSION, speech_mode);
+
+	/* write LSA identifier 3.2.2.15 */
+
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_assignment_failure(uint8_t cause, uint8_t *rr_cause)
+{
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "bssmap: ass fail");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_ASSIGMENT_FAILURE);
+	msgb_tlv_put(msg, GSM0808_IE_CAUSE, 1, &cause);
+
+	/* RR cause 3.2.2.22 */
+	if (rr_cause)
+		msgb_tv_put(msg, GSM0808_IE_RR_CAUSE, *rr_cause);
+
+	/* Circuit pool 3.22.45 */
+	/* Circuit pool list 3.2.2.46 */
+
+	/* update the size */
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+struct msgb *gsm0808_create_clear_rqst(uint8_t cause)
+{
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+				  "bssmap: clear rqst");
+	if (!msg)
+		return NULL;
+
+	msgb_v_put(msg, BSS_MAP_MSG_CLEAR_RQST);
+	msgb_tlv_put(msg, GSM0808_IE_CAUSE, 1, &cause);
+	msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+	return msg;
+}
+
+void gsm0808_prepend_dtap_header(struct msgb *msg, uint8_t link_id)
+{
+	uint8_t *hh = msgb_push(msg, 3);
+	hh[0] = BSSAP_MSG_DTAP;
+	hh[1] = link_id;
+	hh[2] = msg->len - 3;
+}
+
+struct msgb *gsm0808_create_dtap(struct msgb *msg_l3, uint8_t link_id)
+{
+	struct dtap_header *header;
+	uint8_t *data;
+	struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
+					       "dtap");
+	if (!msg)
+		return NULL;
+
+	/* DTAP header */
+	msg->l3h = msgb_put(msg, sizeof(*header));
+	header = (struct dtap_header *) &msg->l3h[0];
+	header->type = BSSAP_MSG_DTAP;
+	header->link_id = link_id;
+	header->length = msgb_l3len(msg_l3);
+
+	/* Payload */
+	data = msgb_put(msg, header->length);
+	memcpy(data, msg_l3->l3h, header->length);
+
+	return msg;
+}
+
+static const struct tlv_definition bss_att_tlvdef = {
+	.def = {
+		[GSM0808_IE_IMSI]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_TMSI]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_CELL_IDENTIFIER_LIST]   = { TLV_TYPE_TLV },
+		[GSM0808_IE_CHANNEL_NEEDED]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_EMLPP_PRIORITY]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_CHANNEL_TYPE]	    = { TLV_TYPE_TLV },
+		[GSM0808_IE_PRIORITY]		    = { TLV_TYPE_TLV },
+		[GSM0808_IE_CIRCUIT_IDENTITY_CODE]  = { TLV_TYPE_FIXED, 2 },
+		[GSM0808_IE_DOWNLINK_DTX_FLAG]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_INTERFERENCE_BAND_TO_USE] = { TLV_TYPE_TV },
+		[GSM0808_IE_CLASSMARK_INFORMATION_T2] = { TLV_TYPE_TLV },
+		[GSM0808_IE_GROUP_CALL_REFERENCE]   = { TLV_TYPE_TLV },
+		[GSM0808_IE_TALKER_FLAG]	    = { TLV_TYPE_T },
+		[GSM0808_IE_CONFIG_EVO_INDI]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_LSA_ACCESS_CTRL_SUPPR]  = { TLV_TYPE_TV },
+		[GSM0808_IE_SERVICE_HANDOVER]	    = { TLV_TYPE_TLV },
+		[GSM0808_IE_ENCRYPTION_INFORMATION] = { TLV_TYPE_TLV },
+		[GSM0808_IE_CIPHER_RESPONSE_MODE]   = { TLV_TYPE_TV },
+		[GSM0808_IE_CELL_IDENTIFIER]	    = { TLV_TYPE_TLV },
+		[GSM0808_IE_CHOSEN_CHANNEL]	    = { TLV_TYPE_TV },
+		[GSM0808_IE_LAYER_3_INFORMATION]    = { TLV_TYPE_TLV },
+		[GSM0808_IE_SPEECH_VERSION]         = { TLV_TYPE_TV },
+		[GSM0808_IE_CHOSEN_ENCR_ALG]        = { TLV_TYPE_TV },
+	},
+};
+
+const struct tlv_definition *gsm0808_att_tlvdef(void)
+{
+	return &bss_att_tlvdef;
+}
+
+static const struct value_string gsm0808_msgt_names[] = {
+	{ BSS_MAP_MSG_ASSIGMENT_RQST,		"ASSIGNMENT REQ" },
+	{ BSS_MAP_MSG_ASSIGMENT_COMPLETE,	"ASSIGNMENT COMPL" },
+	{ BSS_MAP_MSG_ASSIGMENT_FAILURE,	"ASSIGNMENT FAIL" },
+
+	{ BSS_MAP_MSG_HANDOVER_RQST,		"HANDOVER REQ" },
+	{ BSS_MAP_MSG_HANDOVER_REQUIRED,	"HANDOVER REQUIRED" },
+	{ BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE,"HANDOVER REQ ACK" },
+	{ BSS_MAP_MSG_HANDOVER_CMD,		"HANDOVER CMD" },
+	{ BSS_MAP_MSG_HANDOVER_COMPLETE,	"HANDOVER COMPLETE" },
+	{ BSS_MAP_MSG_HANDOVER_SUCCEEDED,	"HANDOVER SUCCESS" },
+	{ BSS_MAP_MSG_HANDOVER_FAILURE,		"HANDOVER FAILURE" },
+	{ BSS_MAP_MSG_HANDOVER_PERFORMED,	"HANDOVER PERFORMED" },
+	{ BSS_MAP_MSG_HANDOVER_CANDIDATE_ENQUIRE, "HANDOVER CAND ENQ" },
+	{ BSS_MAP_MSG_HANDOVER_CANDIDATE_RESPONSE, "HANDOVER CAND RESP" },
+	{ BSS_MAP_MSG_HANDOVER_REQUIRED_REJECT,	"HANDOVER REQ REJ" },
+	{ BSS_MAP_MSG_HANDOVER_DETECT,		"HANDOVER DETECT" },
+
+	{ BSS_MAP_MSG_CLEAR_CMD,		"CLEAR COMMAND" },
+	{ BSS_MAP_MSG_CLEAR_COMPLETE,		"CLEAR COMPLETE" },
+	{ BSS_MAP_MSG_CLEAR_RQST,		"CLEAR REQUEST" },
+	{ BSS_MAP_MSG_SAPI_N_REJECT,		"SAPI N REJECT" },
+	{ BSS_MAP_MSG_CONFUSION,		"CONFUSION" },
+
+	{ BSS_MAP_MSG_SUSPEND,			"SUSPEND" },
+	{ BSS_MAP_MSG_RESUME,			"RESUME" },
+	{ BSS_MAP_MSG_CONNECTION_ORIENTED_INFORMATION, "CONN ORIENT INFO" },
+	{ BSS_MAP_MSG_PERFORM_LOCATION_RQST,	"PERFORM LOC REQ" },
+	{ BSS_MAP_MSG_LSA_INFORMATION,		"LSA INFORMATION" },
+	{ BSS_MAP_MSG_PERFORM_LOCATION_RESPONSE, "PERFORM LOC RESP" },
+	{ BSS_MAP_MSG_PERFORM_LOCATION_ABORT,	"PERFORM LOC ABORT" },
+	{ BSS_MAP_MSG_COMMON_ID,		"COMMON ID" },
+
+	{ BSS_MAP_MSG_RESET,			"RESET" },
+	{ BSS_MAP_MSG_RESET_ACKNOWLEDGE,	"RESET ACK" },
+	{ BSS_MAP_MSG_OVERLOAD,			"OVERLOAD" },
+	{ BSS_MAP_MSG_RESET_CIRCUIT,		"RESET CIRCUIT" },
+	{ BSS_MAP_MSG_RESET_CIRCUIT_ACKNOWLEDGE, "RESET CIRCUIT ACK" },
+	{ BSS_MAP_MSG_MSC_INVOKE_TRACE,		"MSC INVOKE TRACE" },
+	{ BSS_MAP_MSG_BSS_INVOKE_TRACE,		"BSS INVOKE TRACE" },
+	{ BSS_MAP_MSG_CONNECTIONLESS_INFORMATION, "CONNLESS INFO" },
+
+	{ BSS_MAP_MSG_BLOCK,			"BLOCK" },
+	{ BSS_MAP_MSG_BLOCKING_ACKNOWLEDGE,	"BLOCK ACK" },
+	{ BSS_MAP_MSG_UNBLOCK,			"UNBLOCK" },
+	{ BSS_MAP_MSG_UNBLOCKING_ACKNOWLEDGE,	"UNBLOCK ACK" },
+	{ BSS_MAP_MSG_CIRCUIT_GROUP_BLOCK,	"CIRC GROUP BLOCK" },
+	{ BSS_MAP_MSG_CIRCUIT_GROUP_BLOCKING_ACKNOWLEDGE, "CIRC GORUP BLOCK ACK" },
+	{ BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCK,	"CIRC GROUP UNBLOCK" },
+	{ BSS_MAP_MSG_CIRCUIT_GROUP_UNBLOCKING_ACKNOWLEDGE, "CIRC GROUP UNBLOCK ACK" },
+	{ BSS_MAP_MSG_UNEQUIPPED_CIRCUIT,	"UNEQUIPPED CIRCUIT" },
+	{ BSS_MAP_MSG_CHANGE_CIRCUIT,		"CHANGE CIRCUIT" },
+	{ BSS_MAP_MSG_CHANGE_CIRCUIT_ACKNOWLEDGE, "CHANGE CIRCUIT ACK" },
+
+	{ BSS_MAP_MSG_RESOURCE_RQST,		"RESOURCE REQ" },
+	{ BSS_MAP_MSG_RESOURCE_INDICATION,	"RESOURCE IND" },
+	{ BSS_MAP_MSG_PAGING,			"PAGING" },
+	{ BSS_MAP_MSG_CIPHER_MODE_CMD,		"CIPHER MODE CMD" },
+	{ BSS_MAP_MSG_CLASSMARK_UPDATE,		"CLASSMARK UPDATE" },
+	{ BSS_MAP_MSG_CIPHER_MODE_COMPLETE,	"CIPHER MODE COMPLETE" },
+	{ BSS_MAP_MSG_QUEUING_INDICATION,	"QUEUING INDICATION" },
+	{ BSS_MAP_MSG_COMPLETE_LAYER_3,		"COMPLETE LAYER 3" },
+	{ BSS_MAP_MSG_CLASSMARK_RQST,		"CLASSMARK REQ" },
+	{ BSS_MAP_MSG_CIPHER_MODE_REJECT,	"CIPHER MODE REJECT" },
+	{ BSS_MAP_MSG_LOAD_INDICATION,		"LOAD IND" },
+
+	/* FIXME: VGCS/VBS */
+
+	{ 0, NULL }
+};
+
+const char *gsm0808_bssmap_name(uint8_t msg_type)
+{
+	return get_value_string(gsm0808_msgt_names, msg_type);
+}
+
+static const struct value_string gsm0808_bssap_names[] = {
+	{ BSSAP_MSG_BSS_MANAGEMENT, 		"MANAGEMENT" },
+	{ BSSAP_MSG_DTAP,			"DTAP" },
+};
+
+const char *gsm0808_bssap_name(uint8_t msg_type)
+{
+	return get_value_string(gsm0808_bssap_names, msg_type);
+}
diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c
new file mode 100644
index 0000000..379ed65
--- /dev/null
+++ b/src/gsm/gsm48.c
@@ -0,0 +1,428 @@
+/* GSM Mobile Radio Interface Layer 3 messages
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+const struct tlv_definition gsm48_att_tlvdef = {
+	.def = {
+		[GSM48_IE_MOBILE_ID]	= { TLV_TYPE_TLV },
+		[GSM48_IE_NAME_LONG]	= { TLV_TYPE_TLV },
+		[GSM48_IE_NAME_SHORT]	= { TLV_TYPE_TLV },
+		[GSM48_IE_UTC]		= { TLV_TYPE_TV },
+		[GSM48_IE_NET_TIME_TZ]	= { TLV_TYPE_FIXED, 7 },
+		[GSM48_IE_LSA_IDENT]	= { TLV_TYPE_TLV },
+
+		[GSM48_IE_BEARER_CAP]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CAUSE]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CC_CAP]	= { TLV_TYPE_TLV },
+		[GSM48_IE_ALERT]	= { TLV_TYPE_TLV },
+		[GSM48_IE_FACILITY]	= { TLV_TYPE_TLV },
+		[GSM48_IE_PROGR_IND]	= { TLV_TYPE_TLV },
+		[GSM48_IE_AUX_STATUS]	= { TLV_TYPE_TLV },
+		[GSM48_IE_NOTIFY]	= { TLV_TYPE_TV },
+		[GSM48_IE_KPD_FACILITY]	= { TLV_TYPE_TV },
+		[GSM48_IE_SIGNAL]	= { TLV_TYPE_TV },
+		[GSM48_IE_CONN_BCD]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CONN_SUB]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CALLING_BCD]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CALLING_SUB]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CALLED_BCD]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CALLED_SUB]	= { TLV_TYPE_TLV },
+		[GSM48_IE_REDIR_BCD]	= { TLV_TYPE_TLV },
+		[GSM48_IE_REDIR_SUB]	= { TLV_TYPE_TLV },
+		[GSM48_IE_LOWL_COMPAT]	= { TLV_TYPE_TLV },
+		[GSM48_IE_HIGHL_COMPAT]	= { TLV_TYPE_TLV },
+		[GSM48_IE_USER_USER]	= { TLV_TYPE_TLV },
+		[GSM48_IE_SS_VERS]	= { TLV_TYPE_TLV },
+		[GSM48_IE_MORE_DATA]	= { TLV_TYPE_T },
+		[GSM48_IE_CLIR_SUPP]	= { TLV_TYPE_T },
+		[GSM48_IE_CLIR_INVOC]	= { TLV_TYPE_T },
+		[GSM48_IE_REV_C_SETUP]	= { TLV_TYPE_T },
+		[GSM48_IE_REPEAT_CIR]   = { TLV_TYPE_T },
+		[GSM48_IE_REPEAT_SEQ]   = { TLV_TYPE_T },
+		/* FIXME: more elements */
+	},
+};
+
+/* RR elements */
+const struct tlv_definition gsm48_rr_att_tlvdef = {
+	.def = {
+		/* NOTE: Don't add IE 17 = MOBILE_ID here, it already used. */
+		[GSM48_IE_VGCS_TARGET]		= { TLV_TYPE_TLV },
+		[GSM48_IE_FRQSHORT_AFTER]	= { TLV_TYPE_FIXED, 9 },
+		[GSM48_IE_MUL_RATE_CFG]		= { TLV_TYPE_TLV },
+		[GSM48_IE_FREQ_L_AFTER]		= { TLV_TYPE_TLV },
+		[GSM48_IE_MSLOT_DESC]		= { TLV_TYPE_TLV },
+		[GSM48_IE_CHANMODE_2]		= { TLV_TYPE_TV },
+		[GSM48_IE_FRQSHORT_BEFORE]	= { TLV_TYPE_FIXED, 9 },
+		[GSM48_IE_CHANMODE_3]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHANMODE_4]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHANMODE_5]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHANMODE_6]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHANMODE_7]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHANMODE_8]		= { TLV_TYPE_TV },
+		[GSM48_IE_FREQ_L_BEFORE]	= { TLV_TYPE_TLV },
+		[GSM48_IE_CH_DESC_1_BEFORE]	= { TLV_TYPE_FIXED, 3 },
+		[GSM48_IE_CH_DESC_2_BEFORE]	= { TLV_TYPE_FIXED, 3 },
+		[GSM48_IE_F_CH_SEQ_BEFORE]	= { TLV_TYPE_FIXED, 9 },
+		[GSM48_IE_CLASSMARK3]		= { TLV_TYPE_TLV },
+		[GSM48_IE_MA_BEFORE]		= { TLV_TYPE_TLV },
+		[GSM48_IE_RR_PACKET_UL]		= { TLV_TYPE_TLV },
+		[GSM48_IE_RR_PACKET_DL]		= { TLV_TYPE_TLV },
+		[GSM48_IE_CELL_CH_DESC]		= { TLV_TYPE_FIXED, 16 },
+		[GSM48_IE_CHANMODE_1]		= { TLV_TYPE_TV },
+		[GSM48_IE_CHDES_2_AFTER]	= { TLV_TYPE_FIXED, 3 },
+		[GSM48_IE_MODE_SEC_CH]		= { TLV_TYPE_TV },
+		[GSM48_IE_F_CH_SEQ_AFTER]		= { TLV_TYPE_FIXED, 9 },
+		[GSM48_IE_MA_AFTER]		= { TLV_TYPE_TLV },
+		[GSM48_IE_BA_RANGE]		= { TLV_TYPE_TLV },
+		[GSM48_IE_GROUP_CHDES]		= { TLV_TYPE_TLV },
+		[GSM48_IE_BA_LIST_PREF]		= { TLV_TYPE_TLV },
+		[GSM48_IE_MOB_OVSERV_DIF]	= { TLV_TYPE_TLV },
+		[GSM48_IE_REALTIME_DIFF]	= { TLV_TYPE_TLV },
+		[GSM48_IE_START_TIME]		= { TLV_TYPE_FIXED, 2 },
+		[GSM48_IE_TIMING_ADVANCE]	= { TLV_TYPE_TV },
+		[GSM48_IE_GROUP_CIP_SEQ]	= { TLV_TYPE_SINGLE_TV },
+		[GSM48_IE_CIP_MODE_SET]		= { TLV_TYPE_SINGLE_TV },
+		[GSM48_IE_GPRS_RESUMPT]		= { TLV_TYPE_SINGLE_TV },
+		[GSM48_IE_SYNC_IND]		= { TLV_TYPE_SINGLE_TV },
+	},
+};
+
+/* MM elements */
+const struct tlv_definition gsm48_mm_att_tlvdef = {
+	.def = {
+		[GSM48_IE_MOBILE_ID]		= { TLV_TYPE_TLV },
+		[GSM48_IE_NAME_LONG]		= { TLV_TYPE_TLV },
+		[GSM48_IE_NAME_SHORT]		= { TLV_TYPE_TLV },
+		[GSM48_IE_UTC]			= { TLV_TYPE_TV },
+		[GSM48_IE_NET_TIME_TZ]		= { TLV_TYPE_FIXED, 7 },
+		[GSM48_IE_LSA_IDENT]		= { TLV_TYPE_TLV },
+
+		[GSM48_IE_LOCATION_AREA]	= { TLV_TYPE_FIXED, 5 },
+		[GSM48_IE_PRIORITY_LEV]		= { TLV_TYPE_SINGLE_TV },
+		[GSM48_IE_FOLLOW_ON_PROC]	= { TLV_TYPE_T },
+		[GSM48_IE_CTS_PERMISSION]	= { TLV_TYPE_T },
+	},
+};
+
+static const struct value_string rr_cause_names[] = {
+	{ GSM48_RR_CAUSE_NORMAL,		"Normal event" },
+	{ GSM48_RR_CAUSE_ABNORMAL_UNSPEC,	"Abnormal release, unspecified" },
+	{ GSM48_RR_CAUSE_ABNORMAL_UNACCT,	"Abnormal release, channel unacceptable" },
+	{ GSM48_RR_CAUSE_ABNORMAL_TIMER,	"Abnormal release, timer expired" },
+	{ GSM48_RR_CAUSE_ABNORMAL_NOACT,	"Abnormal release, no activity on radio path" },
+	{ GSM48_RR_CAUSE_PREMPTIVE_REL,		"Preemptive release" },
+	{ GSM48_RR_CAUSE_HNDOVER_IMP,		"Handover impossible, timing advance out of range" },
+	{ GSM48_RR_CAUSE_CHAN_MODE_UNACCT,	"Channel mode unacceptable" },
+	{ GSM48_RR_CAUSE_FREQ_NOT_IMPL,		"Frequency not implemented" },
+	{ GSM48_RR_CAUSE_CALL_CLEARED,		"Call already cleared" },
+	{ GSM48_RR_CAUSE_SEMANT_INCORR,		"Semantically incorrect message" },
+	{ GSM48_RR_CAUSE_INVALID_MAND_INF,	"Invalid mandatory information" },
+	{ GSM48_RR_CAUSE_MSG_TYPE_N,		"Message type non-existant or not implemented" },
+	{ GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT,	"Message type not compatible with protocol state" },
+	{ GSM48_RR_CAUSE_COND_IE_ERROR,		"Conditional IE error" },
+	{ GSM48_RR_CAUSE_NO_CELL_ALLOC_A,	"No cell allocation available" },
+	{ GSM48_RR_CAUSE_PROT_ERROR_UNSPC,	"Protocol error unspecified" },
+	{ 0,					NULL },
+};
+
+/* FIXME: convert to value_string */
+static const char *cc_state_names[32] = {
+	"NULL",
+	"INITIATED",
+	"MM_CONNECTION_PEND",
+	"MO_CALL_PROC",
+	"CALL_DELIVERED",
+	"illegal state 5",
+	"CALL_PRESENT",
+	"CALL_RECEIVED",
+	"CONNECT_REQUEST",
+	"MO_TERM_CALL_CONF",
+	"ACTIVE",
+	"DISCONNECT_REQ",
+	"DISCONNECT_IND",
+	"illegal state 13",
+	"illegal state 14",
+	"illegal state 15",
+	"illegal state 16",
+	"illegal state 17",
+	"illegal state 18",
+	"RELEASE_REQ",
+	"illegal state 20",
+	"illegal state 21",
+	"illegal state 22",
+	"illegal state 23",
+	"illegal state 24",
+	"illegal state 25",
+	"MO_ORIG_MODIFY",
+	"MO_TERM_MODIFY",
+	"CONNECT_IND",
+	"illegal state 29",
+	"illegal state 30",
+	"illegal state 31",
+};
+
+const char *gsm48_cc_state_name(uint8_t state)
+{
+	if (state < ARRAY_SIZE(cc_state_names))
+		return cc_state_names[state];
+
+	return "invalid";
+}
+
+static const struct value_string cc_msg_names[] = {
+	{ GSM48_MT_CC_ALERTING,		"ALERTING" },
+	{ GSM48_MT_CC_CALL_PROC,	"CALL_PROC" },
+	{ GSM48_MT_CC_PROGRESS,		"PROGRESS" },
+	{ GSM48_MT_CC_ESTAB,		"ESTAB" },
+	{ GSM48_MT_CC_SETUP,		"SETUP" },
+	{ GSM48_MT_CC_ESTAB_CONF,	"ESTAB_CONF" },
+	{ GSM48_MT_CC_CONNECT,		"CONNECT" },
+	{ GSM48_MT_CC_CALL_CONF,	"CALL_CONF" },
+	{ GSM48_MT_CC_START_CC,		"START_CC" },
+	{ GSM48_MT_CC_RECALL,		"RECALL" },
+	{ GSM48_MT_CC_EMERG_SETUP,	"EMERG_SETUP" },
+	{ GSM48_MT_CC_CONNECT_ACK,	"CONNECT_ACK" },
+	{ GSM48_MT_CC_USER_INFO,	"USER_INFO" },
+	{ GSM48_MT_CC_MODIFY_REJECT,	"MODIFY_REJECT" },
+	{ GSM48_MT_CC_MODIFY,		"MODIFY" },
+	{ GSM48_MT_CC_HOLD,		"HOLD" },
+	{ GSM48_MT_CC_HOLD_ACK,		"HOLD_ACK" },
+	{ GSM48_MT_CC_HOLD_REJ,		"HOLD_REJ" },
+	{ GSM48_MT_CC_RETR,		"RETR" },
+	{ GSM48_MT_CC_RETR_ACK,		"RETR_ACK" },
+	{ GSM48_MT_CC_RETR_REJ,		"RETR_REJ" },
+	{ GSM48_MT_CC_MODIFY_COMPL,	"MODIFY_COMPL" },
+	{ GSM48_MT_CC_DISCONNECT,	"DISCONNECT" },
+	{ GSM48_MT_CC_RELEASE_COMPL,	"RELEASE_COMPL" },
+	{ GSM48_MT_CC_RELEASE,		"RELEASE" },
+	{ GSM48_MT_CC_STOP_DTMF,	"STOP_DTMF" },
+	{ GSM48_MT_CC_STOP_DTMF_ACK,	"STOP_DTMF_ACK" },
+	{ GSM48_MT_CC_STATUS_ENQ,	"STATUS_ENQ" },
+	{ GSM48_MT_CC_START_DTMF,	"START_DTMF" },
+	{ GSM48_MT_CC_START_DTMF_ACK,	"START_DTMF_ACK" },
+	{ GSM48_MT_CC_START_DTMF_REJ,	"START_DTMF_REJ" },
+	{ GSM48_MT_CC_CONG_CTRL,	"CONG_CTRL" },
+	{ GSM48_MT_CC_FACILITY,		"FACILITY" },
+	{ GSM48_MT_CC_STATUS,		"STATUS" },
+	{ GSM48_MT_CC_NOTIFY,		"NOTFIY" },
+	{ 0,				NULL }
+};
+
+const char *gsm48_cc_msg_name(uint8_t msgtype)
+{
+	return get_value_string(cc_msg_names, msgtype);
+}
+
+const char *rr_cause_name(uint8_t cause)
+{
+	return get_value_string(rr_cause_names, cause);
+}
+
+static void to_bcd(uint8_t *bcd, uint16_t val)
+{
+	bcd[2] = val % 10;
+	val = val / 10;
+	bcd[1] = val % 10;
+	val = val / 10;
+	bcd[0] = val % 10;
+	val = val / 10;
+}
+
+void gsm48_generate_lai(struct gsm48_loc_area_id *lai48, uint16_t mcc,
+			uint16_t mnc, uint16_t lac)
+{
+	uint8_t bcd[3];
+
+	to_bcd(bcd, mcc);
+	lai48->digits[0] = bcd[0] | (bcd[1] << 4);
+	lai48->digits[1] = bcd[2];
+
+	to_bcd(bcd, mnc);
+	/* FIXME: do we need three-digit MNC? See Table 10.5.3 */
+	if (mnc > 99) {
+		lai48->digits[1] |= bcd[2] << 4;
+		lai48->digits[2] = bcd[0] | (bcd[1] << 4);
+	} else {
+		lai48->digits[1] |= 0xf << 4;
+		lai48->digits[2] = bcd[1] | (bcd[2] << 4);
+	}
+
+	lai48->lac = htons(lac);
+}
+
+int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi)
+{
+	uint32_t *tptr = (uint32_t *) &buf[3];
+
+	buf[0] = GSM48_IE_MOBILE_ID;
+	buf[1] = GSM48_TMSI_LEN;
+	buf[2] = 0xf0 | GSM_MI_TYPE_TMSI;
+	*tptr = htonl(tmsi);
+
+	return 7;
+}
+
+int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi)
+{
+	unsigned int length = strlen(imsi), i, off = 0;
+	uint8_t odd = (length & 0x1) == 1;
+
+	buf[0] = GSM48_IE_MOBILE_ID;
+	buf[2] = osmo_char2bcd(imsi[0]) << 4 | GSM_MI_TYPE_IMSI | (odd << 3);
+
+	/* if the length is even we will fill half of the last octet */
+	if (odd)
+		buf[1] = (length + 1) >> 1;
+	else
+		buf[1] = (length + 2) >> 1;
+
+	for (i = 1; i < buf[1]; ++i) {
+		uint8_t lower, upper;
+
+		lower = osmo_char2bcd(imsi[++off]);
+		if (!odd && off + 1 == length)
+			upper = 0x0f;
+		else
+			upper = osmo_char2bcd(imsi[++off]) & 0x0f;
+
+		buf[2 + i] = (upper << 4) | lower;
+	}
+
+	return 2 + buf[1];
+}
+
+/* Convert Mobile Identity (10.5.1.4) to string */
+int gsm48_mi_to_string(char *string, const int str_len, const uint8_t *mi,
+		       const int mi_len)
+{
+	int i;
+	uint8_t mi_type;
+	char *str_cur = string;
+	uint32_t tmsi;
+
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_NONE:
+		break;
+	case GSM_MI_TYPE_TMSI:
+		/* Table 10.5.4.3, reverse generate_mid_from_tmsi */
+		if (mi_len == GSM48_TMSI_LEN && mi[0] == (0xf0 | GSM_MI_TYPE_TMSI)) {
+			memcpy(&tmsi, &mi[1], 4);
+			tmsi = ntohl(tmsi);
+			return snprintf(string, str_len, "%u", tmsi);
+		}
+		break;
+	case GSM_MI_TYPE_IMSI:
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		*str_cur++ = osmo_bcd2char(mi[0] >> 4);
+
+                for (i = 1; i < mi_len; i++) {
+			if (str_cur + 2 >= string + str_len)
+				return str_cur - string;
+			*str_cur++ = osmo_bcd2char(mi[i] & 0xf);
+			/* skip last nibble in last input byte when GSM_EVEN */
+			if( (i != mi_len-1) || (mi[0] & GSM_MI_ODD))
+				*str_cur++ = osmo_bcd2char(mi[i] >> 4);
+		}
+		break;
+	default:
+		break;
+	}
+	*str_cur++ = '\0';
+
+	return str_cur - string;
+}
+
+void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf)
+{
+	raid->mcc = (buf[0] & 0xf) * 100;
+	raid->mcc += (buf[0] >> 4) * 10;
+	raid->mcc += (buf[1] & 0xf) * 1;
+
+	/* I wonder who came up with the stupidity of encoding the MNC
+	 * differently depending on how many digits its decimal number has! */
+	if ((buf[1] >> 4) == 0xf) {
+		raid->mnc = (buf[2] & 0xf) * 10;
+		raid->mnc += (buf[2] >> 4) * 1;
+	} else {
+		raid->mnc = (buf[2] & 0xf) * 100;
+		raid->mnc += (buf[2] >> 4) * 10;
+		raid->mnc += (buf[1] >> 4) * 1;
+	}
+
+	raid->lac = ntohs(*(uint16_t *)(buf + 3));
+	raid->rac = buf[5];
+}
+
+int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
+{
+	uint16_t mcc = raid->mcc;
+	uint16_t mnc = raid->mnc;
+
+	buf[0] = ((mcc / 100) % 10) | (((mcc / 10) % 10) << 4);
+	buf[1] = (mcc % 10);
+
+	/* I wonder who came up with the stupidity of encoding the MNC
+	 * differently depending on how many digits its decimal number has! */
+	if (mnc < 100) {
+		buf[1] |= 0xf0;
+		buf[2] = ((mnc / 10) % 10) | ((mnc % 10) << 4);
+	} else {
+		buf[1] |= (mnc % 10) << 4;
+		buf[2] = ((mnc / 100) % 10) | (((mnc / 10) % 10) << 4);
+	}
+
+	*(uint16_t *)(buf+3) = htons(raid->lac);
+
+	buf[5] = raid->rac;
+
+	return 6;
+}
+
+/* From Table 10.5.33 of GSM 04.08 */
+int gsm48_number_of_paging_subchannels(struct gsm48_control_channel_descr *chan_desc)
+{
+	unsigned int n_pag_blocks = gsm0502_get_n_pag_blocks(chan_desc);
+
+	if (chan_desc->ccch_conf == RSL_BCCH_CCCH_CONF_1_C)
+		return OSMO_MAX(1, n_pag_blocks) * (chan_desc->bs_pa_mfrms + 2);
+	else
+		return n_pag_blocks * (chan_desc->bs_pa_mfrms + 2);
+}
diff --git a/src/gsm/gsm48_ie.c b/src/gsm/gsm48_ie.c
new file mode 100644
index 0000000..c10d0ed
--- /dev/null
+++ b/src/gsm/gsm48_ie.c
@@ -0,0 +1,1107 @@
+/* GSM Mobile Radio Interface Layer 3 messages
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Andreas Eversberg
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48_ie.h>
+
+static const char bcd_num_digits[] = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', '*', '#', 'a', 'b', 'c', '\0'
+};
+
+/* decode a 'called/calling/connect party BCD number' as in 10.5.4.7 */
+int gsm48_decode_bcd_number(char *output, int output_len,
+			    const uint8_t *bcd_lv, int h_len)
+{
+	uint8_t in_len = bcd_lv[0];
+	int i;
+
+	for (i = 1 + h_len; i <= in_len; i++) {
+		/* lower nibble */
+		output_len--;
+		if (output_len <= 1)
+			break;
+		*output++ = bcd_num_digits[bcd_lv[i] & 0xf];
+
+		/* higher nibble */
+		output_len--;
+		if (output_len <= 1)
+			break;
+		*output++ = bcd_num_digits[bcd_lv[i] >> 4];
+	}
+	if (output_len >= 1)
+		*output++ = '\0';
+
+	return 0;
+}
+
+/* convert a single ASCII character to call-control BCD */
+static int asc_to_bcd(const char asc)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bcd_num_digits); i++) {
+		if (bcd_num_digits[i] == asc)
+			return i;
+	}
+	return -EINVAL;
+}
+
+/* convert a ASCII phone number to 'called/calling/connect party BCD number' */
+int gsm48_encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len,
+		      int h_len, const char *input)
+{
+	int in_len = strlen(input);
+	int i;
+	uint8_t *bcd_cur = bcd_lv + 1 + h_len;
+
+	/* two digits per byte, plus type byte */
+	bcd_lv[0] = in_len/2 + h_len;
+	if (in_len % 2)
+		bcd_lv[0]++;
+
+	if (bcd_lv[0] > max_len)
+		return -EIO;
+
+	for (i = 0; i < in_len; i++) {
+		int rc = asc_to_bcd(input[i]);
+		if (rc < 0)
+			return rc;
+		if (i % 2 == 0)
+			*bcd_cur = rc;
+		else
+			*bcd_cur++ |= (rc << 4);
+	}
+	/* append padding nibble in case of odd length */
+	if (i % 2)
+		*bcd_cur++ |= 0xf0;
+
+	/* return how many bytes we used */
+	return (bcd_cur - bcd_lv);
+}
+
+/* decode 'bearer capability' */
+int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
+			     const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+	int i, s;
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	bcap->speech_ver[0] = -1; /* end of list, of maximum 7 values */
+
+	/* octet 3 */
+	bcap->transfer = lv[1] & 0x07;
+	bcap->mode = (lv[1] & 0x08) >> 3;
+	bcap->coding = (lv[1] & 0x10) >> 4;
+	bcap->radio = (lv[1] & 0x60) >> 5;
+
+	if (bcap->transfer == GSM_MNCC_BCAP_SPEECH) {
+		i = 1;
+		s = 0;
+		while(!(lv[i] & 0x80)) {
+			i++; /* octet 3a etc */
+			if (in_len < i)
+				return 0;
+			bcap->speech_ver[s++] = lv[i] & 0x0f;
+			bcap->speech_ver[s] = -1; /* end of list */
+			if (i == 2) /* octet 3a */
+				bcap->speech_ctm = (lv[i] & 0x20) >> 5;
+			if (s == 7) /* maximum speech versions + end of list */
+				return 0;
+		}
+	} else {
+		i = 1;
+		while (!(lv[i] & 0x80)) {
+			i++; /* octet 3a etc */
+			if (in_len < i)
+				return 0;
+			/* ignore them */
+		}
+		/* FIXME: implement OCTET 4+ parsing */
+	}
+
+	return 0;
+}
+
+/* encode 'bearer capability' */
+int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
+			     const struct gsm_mncc_bearer_cap *bcap)
+{
+	uint8_t lv[32 + 1];
+	int i = 1, s;
+
+	lv[1] = bcap->transfer;
+	lv[1] |= bcap->mode << 3;
+	lv[1] |= bcap->coding << 4;
+	lv[1] |= bcap->radio << 5;
+
+	if (bcap->transfer == GSM_MNCC_BCAP_SPEECH) {
+		for (s = 0; bcap->speech_ver[s] >= 0; s++) {
+			i++; /* octet 3a etc */
+			lv[i] = bcap->speech_ver[s];
+			if (i == 2) /* octet 3a */
+				lv[i] |= bcap->speech_ctm << 5;
+		}
+		lv[i] |= 0x80; /* last IE of octet 3 etc */
+	} else {
+		/* FIXME: implement OCTET 4+ encoding */
+	}
+
+	lv[0] = i;
+	if (lv_only)
+		msgb_lv_put(msg, lv[0], lv+1);
+	else
+		msgb_tlv_put(msg, GSM48_IE_BEARER_CAP, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'call control cap' */
+int gsm48_decode_cccap(struct gsm_mncc_cccap *ccap, const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	/* octet 3 */
+	ccap->dtmf = lv[1] & 0x01;
+	ccap->pcp = (lv[1] & 0x02) >> 1;
+
+	return 0;
+}
+
+/* encode 'call control cap' */
+int gsm48_encode_cccap(struct msgb *msg,
+			const struct gsm_mncc_cccap *ccap)
+{
+	uint8_t lv[2];
+
+	lv[0] = 1;
+	lv[1] = 0;
+	if (ccap->dtmf)
+		lv [1] |= 0x01;
+	if (ccap->pcp)
+		lv [1] |= 0x02;
+
+	msgb_tlv_put(msg, GSM48_IE_CC_CAP, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'called party BCD number' */
+int gsm48_decode_called(struct gsm_mncc_number *called,
+			 const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	/* octet 3 */
+	called->plan = lv[1] & 0x0f;
+	called->type = (lv[1] & 0x70) >> 4;
+
+	/* octet 4..N */
+	gsm48_decode_bcd_number(called->number, sizeof(called->number), lv, 1);
+
+	return 0;
+}
+
+/* encode 'called party BCD number' */
+int gsm48_encode_called(struct msgb *msg,
+			 const struct gsm_mncc_number *called)
+{
+	uint8_t lv[18];
+	int ret;
+
+	/* octet 3 */
+	lv[1] = 0x80; /* no extension */
+	lv[1] |= called->plan;
+	lv[1] |= called->type << 4;
+
+	/* octet 4..N, octet 2 */
+	ret = gsm48_encode_bcd_number(lv, sizeof(lv), 1, called->number);
+	if (ret < 0)
+		return ret;
+
+	msgb_tlv_put(msg, GSM48_IE_CALLED_BCD, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode callerid of various IEs */
+int gsm48_decode_callerid(struct gsm_mncc_number *callerid,
+			 const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+	int i = 1;
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	/* octet 3 */
+	callerid->plan = lv[1] & 0x0f;
+	callerid->type = (lv[1] & 0x70) >> 4;
+
+	/* octet 3a */
+	if (!(lv[1] & 0x80)) {
+		callerid->screen = lv[2] & 0x03;
+		callerid->present = (lv[2] & 0x60) >> 5;
+		i = 2;
+	}
+
+	/* octet 4..N */
+	gsm48_decode_bcd_number(callerid->number, sizeof(callerid->number), lv, i);
+
+	return 0;
+}
+
+/* encode callerid of various IEs */
+int gsm48_encode_callerid(struct msgb *msg, int ie, int max_len,
+			   const struct gsm_mncc_number *callerid)
+{
+	uint8_t lv[max_len - 1];
+	int h_len = 1;
+	int ret;
+
+	/* octet 3 */
+	lv[1] = callerid->plan;
+	lv[1] |= callerid->type << 4;
+
+	if (callerid->present || callerid->screen) {
+		/* octet 3a */
+		lv[2] = callerid->screen;
+		lv[2] |= callerid->present << 5;
+		lv[2] |= 0x80;
+		h_len++;
+	} else
+		lv[1] |= 0x80;
+
+	/* octet 4..N, octet 2 */
+	ret = gsm48_encode_bcd_number(lv, sizeof(lv), h_len, callerid->number);
+	if (ret < 0)
+		return ret;
+
+	msgb_tlv_put(msg, ie, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'cause' */
+int gsm48_decode_cause(struct gsm_mncc_cause *cause,
+			const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+	int i;
+
+	if (in_len < 2)
+		return -EINVAL;
+
+	cause->diag_len = 0;
+
+	/* octet 3 */
+	cause->location = lv[1] & 0x0f;
+	cause->coding = (lv[1] & 0x60) >> 5;
+
+	i = 1;
+	if (!(lv[i] & 0x80)) {
+		i++; /* octet 3a */
+		if (in_len < i+1)
+			return 0;
+		cause->rec = 1;
+		cause->rec_val = lv[i] & 0x7f;
+	}
+	i++;
+
+	/* octet 4 */
+	cause->value = lv[i] & 0x7f;
+	i++;
+
+	if (in_len < i) /* no diag */
+		return 0;
+
+	if (in_len - (i-1) > 32) /* maximum 32 octets */
+		return 0;
+
+	/* octet 5-N */
+	memcpy(cause->diag, lv + i, in_len - (i-1));
+	cause->diag_len = in_len - (i-1);
+
+	return 0;
+}
+
+/* encode 'cause' */
+int gsm48_encode_cause(struct msgb *msg, int lv_only,
+			const struct gsm_mncc_cause *cause)
+{
+	uint8_t lv[32+4];
+	int i;
+
+	if (cause->diag_len > 32)
+		return -EINVAL;
+
+	/* octet 3 */
+	lv[1] = cause->location;
+	lv[1] |= cause->coding << 5;
+
+	i = 1;
+	if (cause->rec) {
+		i++; /* octet 3a */
+		lv[i] = cause->rec_val;
+	}
+	lv[i] |= 0x80; /* end of octet 3 */
+
+	/* octet 4 */
+	i++;
+	lv[i] = 0x80 | cause->value;
+
+	/* octet 5-N */
+	if (cause->diag_len) {
+		memcpy(lv + i, cause->diag, cause->diag_len);
+		i += cause->diag_len;
+	}
+
+	lv[0] = i;
+	if (lv_only)
+		msgb_lv_put(msg, lv[0], lv+1);
+	else
+		msgb_tlv_put(msg, GSM48_IE_CAUSE, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'calling number' */
+int gsm48_decode_calling(struct gsm_mncc_number *calling,
+			 const uint8_t *lv)
+{
+	return gsm48_decode_callerid(calling, lv);
+}
+
+/* encode 'calling number' */
+int gsm48_encode_calling(struct msgb *msg, 
+			  const struct gsm_mncc_number *calling)
+{
+	return gsm48_encode_callerid(msg, GSM48_IE_CALLING_BCD, 14, calling);
+}
+
+/* decode 'connected number' */
+int gsm48_decode_connected(struct gsm_mncc_number *connected,
+			 const uint8_t *lv)
+{
+	return gsm48_decode_callerid(connected, lv);
+}
+
+/* encode 'connected number' */
+int gsm48_encode_connected(struct msgb *msg,
+			    const struct gsm_mncc_number *connected)
+{
+	return gsm48_encode_callerid(msg, GSM48_IE_CONN_BCD, 14, connected);
+}
+
+/* decode 'redirecting number' */
+int gsm48_decode_redirecting(struct gsm_mncc_number *redirecting,
+			 const uint8_t *lv)
+{
+	return gsm48_decode_callerid(redirecting, lv);
+}
+
+/* encode 'redirecting number' */
+int gsm48_encode_redirecting(struct msgb *msg,
+			      const struct gsm_mncc_number *redirecting)
+{
+	return gsm48_encode_callerid(msg, GSM48_IE_REDIR_BCD, 19, redirecting);
+}
+
+/* decode 'facility' */
+int gsm48_decode_facility(struct gsm_mncc_facility *facility,
+			   const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	if (in_len > sizeof(facility->info))
+		return -EINVAL;
+
+	memcpy(facility->info, lv+1, in_len);
+	facility->len = in_len;
+
+	return 0;
+}
+
+/* encode 'facility' */
+int gsm48_encode_facility(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_facility *facility)
+{
+	uint8_t lv[GSM_MAX_FACILITY + 1];
+
+	if (facility->len < 1 || facility->len > GSM_MAX_FACILITY)
+		return -EINVAL;
+
+	memcpy(lv+1, facility->info, facility->len);
+	lv[0] = facility->len;
+	if (lv_only)
+		msgb_lv_put(msg, lv[0], lv+1);
+	else
+		msgb_tlv_put(msg, GSM48_IE_FACILITY, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'notify' */
+int gsm48_decode_notify(int *notify, const uint8_t *v)
+{
+	*notify = v[0] & 0x7f;
+
+	return 0;
+}
+
+/* encode 'notify' */
+int gsm48_encode_notify(struct msgb *msg, int notify)
+{
+	msgb_v_put(msg, notify | 0x80);
+
+	return 0;
+}
+
+/* decode 'signal' */
+int gsm48_decode_signal(int *signal, const uint8_t *v)
+{
+	*signal = v[0];
+
+	return 0;
+}
+
+/* encode 'signal' */
+int gsm48_encode_signal(struct msgb *msg, int signal)
+{
+	msgb_tv_put(msg, GSM48_IE_SIGNAL, signal);
+
+	return 0;
+}
+
+/* decode 'keypad' */
+int gsm48_decode_keypad(int *keypad, const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	*keypad = lv[1] & 0x7f;
+
+	return 0;
+}
+
+/* encode 'keypad' */
+int gsm48_encode_keypad(struct msgb *msg, int keypad)
+{
+	msgb_tv_put(msg, GSM48_IE_KPD_FACILITY, keypad);
+
+	return 0;
+}
+
+/* decode 'progress' */
+int gsm48_decode_progress(struct gsm_mncc_progress *progress,
+			   const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 2)
+		return -EINVAL;
+
+	progress->coding = (lv[1] & 0x60) >> 5;
+	progress->location = lv[1] & 0x0f;
+	progress->descr = lv[2] & 0x7f;
+
+	return 0;
+}
+
+/* encode 'progress' */
+int gsm48_encode_progress(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_progress *p)
+{
+	uint8_t lv[3];
+
+	lv[0] = 2;
+	lv[1] = 0x80 | ((p->coding & 0x3) << 5) | (p->location & 0xf);
+	lv[2] = 0x80 | (p->descr & 0x7f);
+	if (lv_only)
+		msgb_lv_put(msg, lv[0], lv+1);
+	else
+		msgb_tlv_put(msg, GSM48_IE_PROGR_IND, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'user-user' */
+int gsm48_decode_useruser(struct gsm_mncc_useruser *uu,
+			   const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+	char *info = uu->info;
+	int info_len = sizeof(uu->info);
+	int i;
+
+	if (in_len < 1)
+		return -EINVAL;
+
+	uu->proto = lv[1];
+
+	for (i = 2; i <= in_len; i++) {
+		info_len--;
+		if (info_len <= 1)
+			break;
+		*info++ = lv[i];
+	}
+	if (info_len >= 1)
+		*info++ = '\0';
+
+	return 0;
+}
+
+/* encode 'useruser' */
+int gsm48_encode_useruser(struct msgb *msg, int lv_only,
+			   const struct gsm_mncc_useruser *uu)
+{
+	uint8_t lv[GSM_MAX_USERUSER + 2];
+
+	if (strlen(uu->info) > GSM_MAX_USERUSER)
+		return -EINVAL;
+
+	lv[0] = 1 + strlen(uu->info);
+	lv[1] = uu->proto;
+	memcpy(lv + 2, uu->info, strlen(uu->info));
+	if (lv_only)
+		msgb_lv_put(msg, lv[0], lv+1);
+	else
+		msgb_tlv_put(msg, GSM48_IE_USER_USER, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'ss version' */
+int gsm48_decode_ssversion(struct gsm_mncc_ssversion *ssv,
+			    const uint8_t *lv)
+{
+	uint8_t in_len = lv[0];
+
+	if (in_len < 1 || in_len < sizeof(ssv->info))
+		return -EINVAL;
+
+	memcpy(ssv->info, lv + 1, in_len);
+	ssv->len = in_len;
+
+	return 0;
+}
+
+/* encode 'ss version' */
+int gsm48_encode_ssversion(struct msgb *msg,
+			   const struct gsm_mncc_ssversion *ssv)
+{
+	uint8_t lv[GSM_MAX_SSVERSION + 1];
+
+	if (ssv->len > GSM_MAX_SSVERSION)
+		return -EINVAL;
+
+	lv[0] = ssv->len;
+	memcpy(lv + 1, ssv->info, ssv->len);
+	msgb_tlv_put(msg, GSM48_IE_SS_VERS, lv[0], lv+1);
+
+	return 0;
+}
+
+/* decode 'more data' does not require a function, because it has no value */
+
+/* encode 'more data' */
+int gsm48_encode_more(struct msgb *msg)
+{
+	uint8_t *ie;
+
+	ie = msgb_put(msg, 1);
+	ie[0] = GSM48_IE_MORE_DATA;
+
+	return 0;
+}
+
+static int32_t smod(int32_t n, int32_t m)
+{
+	int32_t res;
+
+	res = n % m;
+
+	if (res <= 0)
+		res += m;
+
+	return res;
+}
+
+/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */
+int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd,
+			   uint8_t len, uint8_t mask, uint8_t frqt)
+{
+	int i;
+
+	/* NOTES:
+	 *
+	 * The Range format uses "SMOD" computation.
+	 * e.g. "n SMOD m" equals "((n - 1) % m) + 1"
+	 * A cascade of multiple SMOD computations is simpified:
+	 * "(n SMOD m) SMOD o" equals "(((n - 1) % m) % o) + 1"
+	 *
+	 * The Range format uses 16 octets of data in SYSTEM INFORMATION.
+	 * When used in dedicated messages, the length can be less.
+	 * In this case the ranges are decoded for all frequencies that
+	 * fit in the block of given length.
+	 */
+
+	/* tabula rasa */
+	for (i = 0; i < 1024; i++)
+		f[i].mask &= ~frqt;
+
+	/* 00..XXX. */
+	if ((cd[0] & 0xc0 & mask) == 0x00) {
+		/* Bit map 0 format */
+		if (len < 16)
+			return -EINVAL;
+		for (i = 1; i <= 124; i++)
+			if ((cd[15 - ((i-1) >> 3)] & (1 << ((i-1) & 7))))
+				f[i].mask |= frqt;
+
+		return 0;
+	}
+
+	/* 10..0XX. */
+	if ((cd[0] & 0xc8 & mask) == 0x80) {
+		/* Range 1024 format */
+		uint16_t w[17]; /* 1..16 */
+		struct gsm48_range_1024 *r = (struct gsm48_range_1024 *)cd;
+
+		if (len < 2)
+			return -EINVAL;
+		memset(w, 0, sizeof(w));
+		if (r->f0)
+			f[0].mask |= frqt;
+		w[1] = (r->w1_hi << 8) | r->w1_lo;
+		if (len >= 4)
+			w[2] = (r->w2_hi << 1) | r->w2_lo;
+		if (len >= 5)
+			w[3] = (r->w3_hi << 2) | r->w3_lo;
+		if (len >= 6)
+			w[4] = (r->w4_hi << 2) | r->w4_lo;
+		if (len >= 7)
+			w[5] = (r->w5_hi << 2) | r->w5_lo;
+		if (len >= 8)
+			w[6] = (r->w6_hi << 2) | r->w6_lo;
+		if (len >= 9)
+			w[7] = (r->w7_hi << 2) | r->w7_lo;
+		if (len >= 10)
+			w[8] = (r->w8_hi << 1) | r->w8_lo;
+		if (len >= 10)
+			w[9] = r->w9;
+		if (len >= 11)
+			w[10] = r->w10;
+		if (len >= 12)
+			w[11] = (r->w11_hi << 6) | r->w11_lo;
+		if (len >= 13)
+			w[12] = (r->w12_hi << 5) | r->w12_lo;
+		if (len >= 14)
+			w[13] = (r->w13_hi << 4) | r->w13_lo;
+		if (len >= 15)
+			w[14] = (r->w14_hi << 3) | r->w14_lo;
+		if (len >= 16)
+			w[15] = (r->w15_hi << 2) | r->w15_lo;
+		if (len >= 16)
+			w[16] = r->w16;
+		if (w[1])
+			f[w[1]].mask |= frqt;
+		if (w[2])
+			f[smod(w[1] - 512 + w[2], 1023)].mask |= frqt;
+		if (w[3])
+			f[smod(w[1]       + w[3], 1023)].mask |= frqt;
+		if (w[4])
+			f[smod(w[1] - 512 + smod(w[2] - 256 + w[4], 511), 1023)].mask |= frqt;
+		if (w[5])
+			f[smod(w[1]       + smod(w[3] - 256 + w[5], 511), 1023)].mask |= frqt;
+		if (w[6])
+			f[smod(w[1] - 512 + smod(w[2]       + w[6], 511), 1023)].mask |= frqt;
+		if (w[7])
+			f[smod(w[1]       + smod(w[3]       + w[7], 511), 1023)].mask |= frqt;
+		if (w[8])
+			f[smod(w[1] - 512 + smod(w[2] - 256 + smod(w[4] - 128 + w[8] , 255), 511), 1023)].mask |= frqt;
+		if (w[9])
+			f[smod(w[1]       + smod(w[3] - 256 + smod(w[5] - 128 + w[9] , 255), 511), 1023)].mask |= frqt;
+		if (w[10])
+			f[smod(w[1] - 512 + smod(w[2]       + smod(w[6] - 128 + w[10], 255), 511), 1023)].mask |= frqt;
+		if (w[11])
+			f[smod(w[1]       + smod(w[3]       + smod(w[7] - 128 + w[11], 255), 511), 1023)].mask |= frqt;
+		if (w[12])
+			f[smod(w[1] - 512 + smod(w[2] - 256 + smod(w[4]       + w[12], 255), 511), 1023)].mask |= frqt;
+		if (w[13])
+			f[smod(w[1]       + smod(w[3] - 256 + smod(w[5]       + w[13], 255), 511), 1023)].mask |= frqt;
+		if (w[14])
+			f[smod(w[1] - 512 + smod(w[2]       + smod(w[6]       + w[14], 255), 511), 1023)].mask |= frqt;
+		if (w[15])
+			f[smod(w[1]       + smod(w[3]       + smod(w[7]       + w[15], 255), 511), 1023)].mask |= frqt;
+		if (w[16])
+			f[smod(w[1] - 512 + smod(w[2] - 256 + smod(w[4] - 128 + smod(w[8] - 64 + w[16], 127), 255), 511), 1023)].mask |= frqt;
+
+		return 0;
+	}
+	/* 10..100. */
+	if ((cd[0] & 0xce & mask) == 0x88) {
+		/* Range 512 format */
+		uint16_t w[18]; /* 1..17 */
+		struct gsm48_range_512 *r = (struct gsm48_range_512 *)cd;
+
+		if (len < 4)
+			return -EINVAL;
+		memset(w, 0, sizeof(w));
+		w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
+		w[1] = (r->w1_hi << 2) | r->w1_lo;
+		if (len >= 5)
+			w[2] = (r->w2_hi << 2) | r->w2_lo;
+		if (len >= 6)
+			w[3] = (r->w3_hi << 2) | r->w3_lo;
+		if (len >= 7)
+			w[4] = (r->w4_hi << 1) | r->w4_lo;
+		if (len >= 7)
+			w[5] = r->w5;
+		if (len >= 8)
+			w[6] = r->w6;
+		if (len >= 9)
+			w[7] = (r->w7_hi << 6) | r->w7_lo;
+		if (len >= 10)
+			w[8] = (r->w8_hi << 4) | r->w8_lo;
+		if (len >= 11)
+			w[9] = (r->w9_hi << 2) | r->w9_lo;
+		if (len >= 11)
+			w[10] = r->w10;
+		if (len >= 12)
+			w[11] = r->w11;
+		if (len >= 13)
+			w[12] = (r->w12_hi << 4) | r->w12_lo;
+		if (len >= 14)
+			w[13] = (r->w13_hi << 2) | r->w13_lo;
+		if (len >= 14)
+			w[14] = r->w14;
+		if (len >= 15)
+			w[15] = r->w15;
+		if (len >= 16)
+			w[16] = (r->w16_hi << 3) | r->w16_lo;
+		if (len >= 16)
+			w[17] = r->w17;
+		f[w[0]].mask |= frqt;
+		if (w[1])
+			f[(w[0] + w[1]) % 1024].mask |= frqt;
+		if (w[2])
+			f[(w[0] + smod(w[1] - 256 + w[2], 511)) % 1024].mask |= frqt;
+		if (w[3])
+			f[(w[0] + smod(w[1]       + w[3], 511)) % 1024].mask |= frqt;
+		if (w[4])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2] - 128 + w[4], 255), 511)) % 1024].mask |= frqt;
+		if (w[5])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 128 + w[5], 255), 511)) % 1024].mask |= frqt;
+		if (w[6])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2]       + w[6], 255), 511)) % 1024].mask |= frqt;
+		if (w[7])
+			f[(w[0] + smod(w[1]       + smod(w[3]       + w[7], 255), 511)) % 1024].mask |= frqt;
+		if (w[8])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2] - 128 + smod(w[4] - 64 + w[8] , 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[9])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 128 + smod(w[5] - 64 + w[9] , 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[10])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2]       + smod(w[6] - 64 + w[10], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[11])
+			f[(w[0] + smod(w[1]       + smod(w[3]       + smod(w[7] - 64 + w[11], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[12])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2] - 128 + smod(w[4]      + w[12], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[13])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 128 + smod(w[5]      + w[13], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[14])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2]       + smod(w[6]      + w[14], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[15])
+			f[(w[0] + smod(w[1]       + smod(w[3]       + smod(w[7]      + w[15], 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[16])
+			f[(w[0] + smod(w[1] - 256 + smod(w[2] - 128 + smod(w[4] - 64 + smod(w[8] - 32 + w[16], 63), 127), 255), 511)) % 1024].mask |= frqt;
+		if (w[17])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 128 + smod(w[5] - 64 + smod(w[9] - 32 + w[17], 63), 127), 255), 511)) % 1024].mask |= frqt;
+
+		return 0;
+	}
+	/* 10..101. */
+	if ((cd[0] & 0xce & mask) == 0x8a) {
+		/* Range 256 format */
+		uint16_t w[22]; /* 1..21 */
+		struct gsm48_range_256 *r = (struct gsm48_range_256 *)cd;
+
+		if (len < 4)
+			return -EINVAL;
+		memset(w, 0, sizeof(w));
+		w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
+		w[1] = (r->w1_hi << 1) | r->w1_lo;
+		if (len >= 4)
+			w[2] = r->w2;
+		if (len >= 5)
+			w[3] = r->w3;
+		if (len >= 6)
+			w[4] = (r->w4_hi << 5) | r->w4_lo;
+		if (len >= 7)
+			w[5] = (r->w5_hi << 3) | r->w5_lo;
+		if (len >= 8)
+			w[6] = (r->w6_hi << 1) | r->w6_lo;
+		if (len >= 8)
+			w[7] = r->w7;
+		if (len >= 9)
+			w[8] = (r->w8_hi << 4) | r->w8_lo;
+		if (len >= 10)
+			w[9] = (r->w9_hi << 1) | r->w9_lo;
+		if (len >= 10)
+			w[10] = r->w10;
+		if (len >= 11)
+			w[11] = (r->w11_hi << 3) | r->w11_lo;
+		if (len >= 11)
+			w[12] = r->w12;
+		if (len >= 12)
+			w[13] = r->w13;
+		if (len >= 13)
+			w[14] = r->w15;
+		if (len >= 13)
+			w[15] = (r->w14_hi << 2) | r->w14_lo;
+		if (len >= 14)
+			w[16] = (r->w16_hi << 3) | r->w16_lo;
+		if (len >= 14)
+			w[17] = r->w17;
+		if (len >= 15)
+			w[18] = r->w19;
+		if (len >= 15)
+			w[19] = (r->w18_hi << 3) | r->w18_lo;
+		if (len >= 16)
+			w[20] = (r->w20_hi << 3) | r->w20_lo;
+		if (len >= 16)
+			w[21] = r->w21;
+		f[w[0]].mask |= frqt;
+		if (w[1])
+			f[(w[0] + w[1]) % 1024].mask |= frqt;
+		if (w[2])
+			f[(w[0] + smod(w[1] - 128 + w[2], 255)) % 1024].mask |= frqt;
+		if (w[3])
+			f[(w[0] + smod(w[1]       + w[3], 255)) % 1024].mask |= frqt;
+		if (w[4])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2] - 64 + w[4], 127), 255)) % 1024].mask |= frqt;
+		if (w[5])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 64 + w[5], 127), 255)) % 1024].mask |= frqt;
+		if (w[6])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2]      + w[6], 127), 255)) % 1024].mask |= frqt;
+		if (w[7])
+			f[(w[0] + smod(w[1]       + smod(w[3]      + w[7], 127), 255)) % 1024].mask |= frqt;
+		if (w[8])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2] - 64 + smod(w[4] - 32 + w[8] , 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[9])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 64 + smod(w[5] - 32 + w[9] , 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[10])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2]      + smod(w[6] - 32 + w[10], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[11])
+			f[(w[0] + smod(w[1]       + smod(w[3]      + smod(w[7] - 32 + w[11], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[12])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2] - 64 + smod(w[4]      + w[12], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[13])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 64 + smod(w[5]      + w[13], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[14])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2]      + smod(w[6]      + w[14], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[15])
+			f[(w[0] + smod(w[1]       + smod(w[3]      + smod(w[7]      + w[15], 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[16])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2] - 64 + smod(w[4] - 32 + smod(w[8]  - 16 + w[16], 31), 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[17])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 64 + smod(w[5] - 32 + smod(w[9]  - 16 + w[17], 31), 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[18])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2]      + smod(w[6] - 32 + smod(w[10] - 16 + w[18], 31), 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[19])
+			f[(w[0] + smod(w[1]       + smod(w[3]      + smod(w[7] - 32 + smod(w[11] - 16 + w[19], 31), 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[20])
+			f[(w[0] + smod(w[1] - 128 + smod(w[2] - 64 + smod(w[4]      + smod(w[12] - 16 + w[20], 31), 63), 127), 255)) % 1024].mask |= frqt;
+		if (w[21])
+			f[(w[0] + smod(w[1]       + smod(w[3] - 64 + smod(w[5]      + smod(w[13] - 16 + w[21], 31), 63), 127), 255)) % 1024].mask |= frqt;
+
+		return 0;
+	}
+	/* 10..110. */
+	if ((cd[0] & 0xce & mask) == 0x8c) {
+		/* Range 128 format */
+		uint16_t w[29]; /* 1..28 */
+		struct gsm48_range_128 *r = (struct gsm48_range_128 *)cd;
+
+		if (len < 3)
+			return -EINVAL;
+		memset(w, 0, sizeof(w));
+		w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
+		w[1] = r->w1;
+		if (len >= 4)
+			w[2] = r->w2;
+		if (len >= 5)
+			w[3] = (r->w3_hi << 4) | r->w3_lo;
+		if (len >= 6)
+			w[4] = (r->w4_hi << 1) | r->w4_lo;
+		if (len >= 6)
+			w[5] = r->w5;
+		if (len >= 7)
+			w[6] = (r->w6_hi << 3) | r->w6_lo;
+		if (len >= 7)
+			w[7] = r->w7;
+		if (len >= 8)
+			w[8] = r->w8;
+		if (len >= 8)
+			w[9] = r->w9;
+		if (len >= 9)
+			w[10] = r->w10;
+		if (len >= 9)
+			w[11] = r->w11;
+		if (len >= 10)
+			w[12] = r->w12;
+		if (len >= 10)
+			w[13] = r->w13;
+		if (len >= 11)
+			w[14] = r->w14;
+		if (len >= 11)
+			w[15] = r->w15;
+		if (len >= 12)
+			w[16] = r->w16;
+		if (len >= 12)
+			w[17] = r->w17;
+		if (len >= 13)
+			w[18] = (r->w18_hi << 1) | r->w18_lo;
+		if (len >= 13)
+			w[19] = r->w19;
+		if (len >= 13)
+			w[20] = r->w20;
+		if (len >= 14)
+			w[21] = (r->w21_hi << 2) | r->w21_lo;
+		if (len >= 14)
+			w[22] = r->w22;
+		if (len >= 14)
+			w[23] = r->w23;
+		if (len >= 15)
+			w[24] = r->w24;
+		if (len >= 15)
+			w[25] = r->w25;
+		if (len >= 16)
+			w[26] = (r->w26_hi << 1) | r->w26_lo;
+		if (len >= 16)
+			w[27] = r->w27;
+		if (len >= 16)
+			w[28] = r->w28;
+		f[w[0]].mask |= frqt;
+		if (w[1])
+			f[(w[0] + w[1]) % 1024].mask |= frqt;
+		if (w[2])
+			f[(w[0] + smod(w[1] - 64 + w[2], 127)) % 1024].mask |= frqt;
+		if (w[3])
+			f[(w[0] + smod(w[1]      + w[3], 127)) % 1024].mask |= frqt;
+		if (w[4])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + w[4], 63), 127)) % 1024].mask |= frqt;
+		if (w[5])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + w[5], 63), 127)) % 1024].mask |= frqt;
+		if (w[6])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + w[6], 63), 127)) % 1024].mask |= frqt;
+		if (w[7])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + w[7], 63), 127)) % 1024].mask |= frqt;
+		if (w[8])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4] - 16 + w[8] , 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[9])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + smod(w[5] - 16 + w[9] , 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[10])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + smod(w[6] - 16 + w[10], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[11])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + smod(w[7] - 16 + w[11], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[12])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4]      + w[12], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[13])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + smod(w[5]      + w[13], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[14])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + smod(w[6]      + w[14], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[15])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + smod(w[7]      + w[15], 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[16])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4] - 16 + smod(w[8]  - 8 + w[16], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[17])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + smod(w[5] - 16 + smod(w[9]  - 8 + w[17], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[18])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + smod(w[6] - 16 + smod(w[10] - 8 + w[18], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[19])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + smod(w[7] - 16 + smod(w[11] - 8 + w[19], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[20])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4]      + smod(w[12] - 8 + w[20], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[21])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + smod(w[5]      + smod(w[13] - 8 + w[21], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[22])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + smod(w[6]      + smod(w[14] - 8 + w[22], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[23])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + smod(w[7]      + smod(w[15] - 8 + w[23], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[24])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4] - 16 + smod(w[8]      + w[24], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[25])
+			f[(w[0] + smod(w[1]      + smod(w[3] - 32 + smod(w[5] - 16 + smod(w[9]      + w[25], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[26])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2]      + smod(w[6] - 16 + smod(w[10]     + w[26], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[27])
+			f[(w[0] + smod(w[1]      + smod(w[3]      + smod(w[7] - 16 + smod(w[11]     + w[27], 15), 31), 63), 127)) % 1024].mask |= frqt;
+		if (w[28])
+			f[(w[0] + smod(w[1] - 64 + smod(w[2] - 32 + smod(w[4]      + smod(w[12]     + w[28], 15), 31), 63), 127)) % 1024].mask |= frqt;
+
+		return 0;
+	}
+	/* 10..111. */
+	if ((cd[0] & 0xce & mask) == 0x8e) {
+		/* Variable bitmap format (can be any length >= 3) */
+		uint16_t orig = 0;
+		struct gsm48_var_bit *r = (struct gsm48_var_bit *)cd;
+
+		if (len < 3)
+			return -EINVAL;
+		orig = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
+		f[orig].mask |= frqt;
+		for (i = 1; 2 + (i >> 3) < len; i++)
+			if ((cd[2 + (i >> 3)] & (0x80 >> (i & 7))))
+				f[(orig + i) % 1024].mask |= frqt;
+
+		return 0;
+	}
+
+	return 0;
+}
diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c
new file mode 100644
index 0000000..8d072a1
--- /dev/null
+++ b/src/gsm/gsm_utils.c
@@ -0,0 +1,606 @@
+/*
+ * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Nico Golde <nico@ngolde.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \mainpage libosmogsm Documentation
+ *
+ * \section sec_intro Introduction
+ * This library is a collection of common code used in various
+ * GSM related sub-projects inside the Osmocom family of projects.  It
+ * includes A5/1 and A5/2 ciphers, COMP128v1, a LAPDm implementation,
+ * a GSM TLV parser, SMS utility routines as well as 
+ * protocol definitions for a series of protocols:
+ * 	* Um L2 (04.06)
+ * 	* Um L3 (04.08)
+ * 	* A-bis RSL (08.58)
+ * 	* A-bis OML (08.59, 12.21)
+ * 	* A (08.08)
+ * \n\n
+ * Please note that C language projects inside Osmocom are typically
+ * single-threaded event-loop state machine designs.  As such,
+ * routines in libosmogsm are not thread-safe.  If you must use them in
+ * a multi-threaded context, you have to add your own locking.
+ *
+ * \section sec_copyright Copyright and License
+ * Copyright © 2008-2011 - Harald Welte, Holger Freyther and contributors\n
+ * All rights reserved. \n\n
+ * The source code of libosmogsm is licensed under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License, or (at your option) any later
+ * version.\n
+ * See <http://www.gnu.org/licenses/> or COPYING included in the source
+ * code package istelf.\n
+ * The information detailed here is provided AS IS with NO WARRANTY OF
+ * ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ * \n\n
+ *
+ * \section sec_contact Contact and Support
+ * Community-based support is available at the OpenBSC mailing list
+ * <http://lists.osmocom.org/mailman/listinfo/openbsc>\n
+ * Commercial support options available upon request from
+ * <http://sysmocom.de/>
+ */
+
+//#include <openbsc/gsm_data.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "../../config.h"
+
+/* ETSI GSM 03.38 6.2.1 and 6.2.1.1 default alphabet
+ * Greek symbols at hex positions 0x10 and 0x12-0x1a
+ * left out as they can't be handled with a char and
+ * since most phones don't display or write these
+ * characters this would only needlessly make the code
+ * more complex
+*/
+static unsigned char gsm_7bit_alphabet[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0a, 0xff, 0xff, 0x0d, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0x20, 0x21, 0x22, 0x23, 0x02, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+	0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+	0x3c, 0x3d, 0x3e, 0x3f, 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+	0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+	0x5a, 0x3c, 0x2f, 0x3e, 0x14, 0x11, 0xff, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+	0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+	0x78, 0x79, 0x7a, 0x28, 0x40, 0x29, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0x0c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5e, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x40, 0xff, 0x01, 0xff,
+	0x03, 0xff, 0x7b, 0x7d, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5c, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5b, 0x7e, 0x5d, 0xff, 0x7c, 0xff, 0xff, 0xff,
+	0xff, 0x5b, 0x0e, 0x1c, 0x09, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5d,
+	0xff, 0xff, 0xff, 0xff, 0x5c, 0xff, 0x0b, 0xff, 0xff, 0xff, 0x5e, 0xff, 0xff, 0x1e, 0x7f,
+	0xff, 0xff, 0xff, 0x7b, 0x0f, 0x1d, 0xff, 0x04, 0x05, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff,
+	0xff, 0x7d, 0x08, 0xff, 0xff, 0xff, 0x7c, 0xff, 0x0c, 0x06, 0xff, 0xff, 0x7e, 0xff, 0xff
+};
+
+/* GSM 03.38 6.2.1 Character lookup for decoding */
+static int gsm_septet_lookup(uint8_t ch)
+{
+	int i = 0;
+	for (; i < sizeof(gsm_7bit_alphabet); i++) {
+		if (gsm_7bit_alphabet[i] == ch)
+			return i;
+	}
+	return -1;
+}
+
+/* Compute the number of octets from the number of septets, for instance: 47 septets needs 41,125 = 42 octets */
+uint8_t gsm_get_octet_len(const uint8_t sept_len){
+	int octet_len = (sept_len * 7) / 8;
+	if ((sept_len * 7) % 8 != 0)
+		octet_len++;
+
+	return octet_len;
+}
+
+/* GSM 03.38 6.2.1 Character unpacking */
+int gsm_7bit_decode_hdr(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
+{
+	int i = 0;
+	int shift = 0;
+
+	uint8_t *rtext = calloc(septet_l, sizeof(uint8_t));
+	uint8_t tmp;
+
+	/* skip the user data header */
+	if (ud_hdr_ind) {
+		/* get user data header length + 1 (for the 'user data header length'-field) */
+		shift = ((user_data[0] + 1) * 8) / 7;
+		if ((((user_data[0] + 1) * 8) % 7) != 0)
+			shift++;
+		septet_l = septet_l - shift;
+	}
+
+	for (i = 0; i < septet_l; i++) {
+		rtext[i] =
+			((user_data[((i + shift) * 7 + 7) >> 3] <<
+			  (7 - (((i + shift) * 7 + 7) & 7))) |
+			 (user_data[((i + shift) * 7) >> 3] >>
+			  (((i + shift) * 7) & 7))) & 0x7f;
+	}
+
+	for (i = 0; i < septet_l; i++) {
+		/* this is an extension character */
+		if(rtext[i] == 0x1b && i + 1 < septet_l){
+			tmp = rtext[i+1];
+			*(text++) = gsm_7bit_alphabet[0x7f + tmp];
+			i++;
+			continue;
+		}
+
+		*(text++) = gsm_septet_lookup(rtext[i]);
+	}
+
+	if (ud_hdr_ind)
+		i += shift;
+	*text = '\0';
+	free(rtext);
+
+	return i;
+}
+
+int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t septet_l)
+{
+	return gsm_7bit_decode_hdr(text, user_data, septet_l, 0);
+}
+
+/* GSM 03.38 6.2.1 Prepare character packing */
+int gsm_septet_encode(uint8_t *result, const char *data)
+{
+	int i, y = 0;
+	uint8_t ch;
+	for (i = 0; i < strlen(data); i++) {
+		ch = data[i];
+		switch(ch){
+		/* fall-through for extension characters */
+		case 0x0c:
+		case 0x5e:
+		case 0x7b:
+		case 0x7d:
+		case 0x5c:
+		case 0x5b:
+		case 0x7e:
+		case 0x5d:
+		case 0x7c:
+			result[y++] = 0x1b;
+		default:
+			result[y] = gsm_7bit_alphabet[ch];
+			break;
+		}
+		y++;
+	}
+
+	return y;
+}
+
+/* 7bit to octet packing */
+int gsm_septets2octets(uint8_t *result, uint8_t *rdata, uint8_t septet_len, uint8_t padding){
+	int i = 0, z = 0;
+	uint8_t cb, nb;
+	int shift = 0;
+	uint8_t *data = calloc(septet_len + 1, sizeof(uint8_t));
+
+	if (padding) {
+		shift = 7 - padding;
+		/* the first zero is needed for padding */
+		memcpy(data + 1, rdata, septet_len);
+		septet_len++;
+	} else
+		memcpy(data, rdata, septet_len);
+
+	for (i = 0; i < septet_len; i++) {
+		if (shift == 7) {
+			/*
+			 * special end case with the. This is necessary if the
+			 * last septet fits into the previous octet. E.g. 48
+			 * non-extension characters:
+			 *   ....ag ( a = 1100001, g = 1100111)
+			 * result[40] = 100001 XX, result[41] = 1100111 1 */
+			if (i + 1 < septet_len) {
+				shift = 0;
+				continue;
+			} else if (i + 1 == septet_len)
+				break;
+		}
+
+		cb = (data[i] & 0x7f) >> shift;
+		if (i + 1 < septet_len) {
+			nb = (data[i + 1] & 0x7f) << (7 - shift);
+			cb = cb | nb;
+		}
+
+		result[z++] = cb;
+		shift++;
+	}
+
+	free(data);
+
+	return z;
+}
+
+/* GSM 03.38 6.2.1 Character packing */
+int gsm_7bit_encode(uint8_t *result, const char *data)
+{
+	int y = 0, z = 0;
+	/* prepare for the worst case, every character expanding to two bytes */
+	uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t));
+	y = gsm_septet_encode(rdata, data);
+	z = gsm_septets2octets(result, rdata, y, 0);
+
+	free(rdata);
+
+	/*
+	 * We don't care about the number of octets (z), because they are not
+	 * unique. E.g.:
+	 *  1.) 46 non-extension characters + 1 extension character
+	 *         => (46 * 7 bit + (1 * (2 * 7 bit))) / 8 bit =  42 octets
+	 *  2.) 47 non-extension characters
+	 *         => (47 * 7 bit) / 8 bit = 41,125 = 42 octets
+	 *  3.) 48 non-extension characters
+	 *         => (48 * 7 bit) / 8 bit = 42 octects
+	 */
+	return y;
+}
+
+/* convert power class to dBm according to GSM TS 05.05 */
+unsigned int ms_class_gmsk_dbm(enum gsm_band band, int class)
+{
+	switch (band) {
+	case GSM_BAND_450:
+	case GSM_BAND_480:
+	case GSM_BAND_750:
+	case GSM_BAND_900:
+	case GSM_BAND_810:
+	case GSM_BAND_850:
+		if (class == 1)
+			return 43; /* 20W */
+		if (class == 2)
+			return 39; /* 8W */
+		if (class == 3)
+			return 37; /* 5W */
+		if (class == 4)
+			return 33; /* 2W */
+		if (class == 5)
+			return 29; /* 0.8W */
+		break;
+	case GSM_BAND_1800:
+		if (class == 1)
+			return 30; /* 1W */
+		if (class == 2)
+			return 24; /* 0.25W */
+		if (class == 3)
+			return 36; /* 4W */
+		break;
+	case GSM_BAND_1900:
+		if (class == 1)
+			return 30; /* 1W */
+		if (class == 2)
+			return 24; /* 0.25W */
+		if (class == 3)
+			return 33; /* 2W */
+		break;
+	}
+	return -EINVAL;
+}
+
+/* determine power control level for given dBm value, as indicated
+ * by the tables in chapter 4.1.1 of GSM TS 05.05 */
+int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm)
+{
+	switch (band) {
+	case GSM_BAND_450:
+	case GSM_BAND_480:
+	case GSM_BAND_750:
+	case GSM_BAND_900:
+	case GSM_BAND_810:
+	case GSM_BAND_850:
+		if (dbm >= 39)
+			return 0;
+		else if (dbm < 5)
+			return 19;
+		else {
+			/* we are guaranteed to have (5 <= dbm < 39) */
+			return 2 + ((39 - dbm) / 2);
+		}
+		break;
+	case GSM_BAND_1800:
+		if (dbm >= 36)
+			return 29;
+		else if (dbm >= 34)	
+			return 30;
+		else if (dbm >= 32)
+			return 31;
+		else if (dbm == 31)
+			return 0;
+		else {
+			/* we are guaranteed to have (0 <= dbm < 31) */
+			return (30 - dbm) / 2;
+		}
+		break;
+	case GSM_BAND_1900:
+		if (dbm >= 33)
+			return 30;
+		else if (dbm >= 32)
+			return 31;
+		else if (dbm == 31)
+			return 0;
+		else {
+			/* we are guaranteed to have (0 <= dbm < 31) */
+			return (30 - dbm) / 2;
+		}
+		break;
+	}
+	return -EINVAL;
+}
+
+int ms_pwr_dbm(enum gsm_band band, uint8_t lvl)
+{
+	lvl &= 0x1f;
+
+	switch (band) {
+	case GSM_BAND_450:
+	case GSM_BAND_480:
+	case GSM_BAND_750:
+	case GSM_BAND_900:
+	case GSM_BAND_810:
+	case GSM_BAND_850:
+		if (lvl < 2)
+			return 39;
+		else if (lvl < 20)
+			return 39 - ((lvl - 2) * 2) ;
+		else
+			return 5;
+		break;
+	case GSM_BAND_1800:
+		if (lvl < 16)
+			return 30 - (lvl * 2);
+		else if (lvl < 29)
+			return 0;
+		else
+			return 36 - ((lvl - 29) * 2);
+		break;
+	case GSM_BAND_1900:
+		if (lvl < 16)
+			return 30 - (lvl * 2);
+		else if (lvl < 30)
+			return -EINVAL;
+		else
+			return 33 - (lvl - 30);
+		break;
+	}
+	return -EINVAL;
+}
+
+/* According to TS 08.05 Chapter 8.1.4 */
+int rxlev2dbm(uint8_t rxlev)
+{
+	if (rxlev > 63)
+		rxlev = 63;
+
+	return -110 + rxlev;
+}
+
+/* According to TS 08.05 Chapter 8.1.4 */
+uint8_t dbm2rxlev(int dbm)
+{
+	int rxlev = dbm + 110;
+
+	if (rxlev > 63)
+		rxlev = 63;
+	else if (rxlev < 0)
+		rxlev = 0;
+
+	return rxlev;
+}
+
+const char *gsm_band_name(enum gsm_band band)
+{
+	switch (band) {
+	case GSM_BAND_450:
+		return "GSM450";
+	case GSM_BAND_480:
+		return "GSM480";
+	case GSM_BAND_750:
+		return "GSM750";
+	case GSM_BAND_810:
+		return "GSM810";
+	case GSM_BAND_850:
+		return "GSM850";
+	case GSM_BAND_900:
+		return "GSM900";
+	case GSM_BAND_1800:
+		return "DCS1800";
+	case GSM_BAND_1900:
+		return "PCS1900";
+	}
+	return "invalid";
+}
+
+enum gsm_band gsm_band_parse(const char* mhz)
+{
+	while (*mhz && !isdigit(*mhz))
+		mhz++;
+
+	if (*mhz == '\0')
+		return -EINVAL;
+
+	switch (strtol(mhz, NULL, 10)) {
+	case 450:
+		return GSM_BAND_450;
+	case 480:
+		return GSM_BAND_480;
+	case 750:
+		return GSM_BAND_750;
+	case 810:
+		return GSM_BAND_810;
+	case 850:
+		return GSM_BAND_850;
+	case 900:
+		return GSM_BAND_900;
+	case 1800:
+		return GSM_BAND_1800;
+	case 1900:
+		return GSM_BAND_1900;
+	default:
+		return -EINVAL;
+	}
+}
+
+enum gsm_band gsm_arfcn2band(uint16_t arfcn)
+{
+	int is_pcs = arfcn & ARFCN_PCS;
+
+	arfcn &= ~ARFCN_FLAG_MASK;
+
+	if (is_pcs)
+		return GSM_BAND_1900;
+	else if (arfcn <= 124)
+		return GSM_BAND_900;
+	else if (arfcn >= 955 && arfcn <= 1023)
+		return GSM_BAND_900;
+	else if (arfcn >= 128 && arfcn <= 251)
+		return GSM_BAND_850;
+	else if (arfcn >= 512 && arfcn <= 885)
+		return GSM_BAND_1800;
+	else if (arfcn >= 259 && arfcn <= 293)
+		return GSM_BAND_450;
+	else if (arfcn >= 306 && arfcn <= 340)
+		return GSM_BAND_480;
+	else if (arfcn >= 350 && arfcn <= 425)
+		return GSM_BAND_810;
+	else if (arfcn >= 438 && arfcn <= 511)
+		return GSM_BAND_750;
+	else
+		return GSM_BAND_1800;
+}
+
+/* Convert an ARFCN to the frequency in MHz * 10 */
+uint16_t gsm_arfcn2freq10(uint16_t arfcn, int uplink)
+{
+	uint16_t freq10_ul;
+	uint16_t freq10_dl;
+	int is_pcs = arfcn & ARFCN_PCS;
+
+	arfcn &= ~ARFCN_FLAG_MASK;
+
+	if (is_pcs) {
+		/* DCS 1900 */
+		arfcn &= ~ARFCN_PCS;
+		freq10_ul = 18502 + 2 * (arfcn-512);
+		freq10_dl = freq10_ul + 800;
+	} else if (arfcn <= 124) {
+		/* Primary GSM + ARFCN 0 of E-GSM */
+		freq10_ul = 8900 + 2 * arfcn;
+		freq10_dl = freq10_ul + 450;
+	} else if (arfcn >= 955 && arfcn <= 1023) {
+		/* E-GSM and R-GSM */
+		freq10_ul = 8900 + 2 * (arfcn - 1024);
+		freq10_dl = freq10_ul + 450;
+	} else if (arfcn >= 128 && arfcn <= 251) {
+		/* GSM 850 */
+		freq10_ul = 8242 + 2 * (arfcn - 128);
+		freq10_dl = freq10_ul + 450;
+	} else if (arfcn >= 512 && arfcn <= 885) {
+		/* DCS 1800 */
+		freq10_ul = 17102 + 2 * (arfcn - 512);
+		freq10_dl = freq10_ul + 950;
+	} else if (arfcn >= 259 && arfcn <= 293) {
+		/* GSM 450 */
+		freq10_ul = 4506 + 2 * (arfcn - 259);
+		freq10_dl = freq10_ul + 100;
+	} else if (arfcn >= 306 && arfcn <= 340) {
+		/* GSM 480 */
+		freq10_ul = 4790 + 2 * (arfcn - 306);
+		freq10_dl = freq10_ul + 100;
+	} else if (arfcn >= 350 && arfcn <= 425) {
+		/* GSM 810 */
+		freq10_ul = 8060 + 2 * (arfcn - 350);
+		freq10_dl = freq10_ul + 450;
+	} else if (arfcn >= 438 && arfcn <= 511) {
+		/* GSM 750 */
+		freq10_ul = 7472 + 2 * (arfcn - 438);
+		freq10_dl = freq10_ul + 300;
+	} else
+		return 0xffff;
+
+	if (uplink)
+		return freq10_ul;
+	else
+		return freq10_dl;
+}
+
+void gsm_fn2gsmtime(struct gsm_time *time, uint32_t fn)
+{
+	time->fn = fn;
+	time->t1 = time->fn / (26*51);
+	time->t2 = time->fn % 26;
+	time->t3 = time->fn % 51;
+	time->tc = (time->fn / 51) % 8;
+}
+
+uint32_t gsm_gsmtime2fn(struct gsm_time *time)
+{
+	/* TS 05.02 Chapter 4.3.3 TDMA frame number */
+	return (51 * ((time->t3 - time->t2 + 26) % 26) + time->t3 + (26 * 51 * time->t1));
+}
+
+/* TS 03.03 Chapter 2.6 */
+int gprs_tlli_type(uint32_t tlli)
+{
+	if ((tlli & 0xc0000000) == 0xc0000000)
+		return TLLI_LOCAL;
+	else if ((tlli & 0xc0000000) == 0x80000000)
+		return TLLI_FOREIGN;
+	else if ((tlli & 0xf8000000) == 0x78000000)
+		return TLLI_RANDOM;
+	else if ((tlli & 0xf8000000) == 0x70000000)
+		return TLLI_AUXILIARY;
+
+	return TLLI_RESERVED;
+}
+
+uint32_t gprs_tmsi2tlli(uint32_t p_tmsi, enum gprs_tlli_type type)
+{
+	uint32_t tlli;
+	switch (type) {
+	case TLLI_LOCAL:
+		tlli = p_tmsi | 0xc0000000;
+		break;
+	case TLLI_FOREIGN:
+		tlli = (p_tmsi & 0x3fffffff) | 0x80000000;
+		break;
+	default:
+		tlli = 0;
+		break;
+	}
+	return tlli;
+}
diff --git a/src/gsm/lapd_core.c b/src/gsm/lapd_core.c
new file mode 100644
index 0000000..96099ed
--- /dev/null
+++ b/src/gsm/lapd_core.c
@@ -0,0 +1,2169 @@
+/* LAPD core implementation */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup lapd
+ *  @{
+ */
+
+/*! \file lapd.c */
+
+/*!
+ * Notes on Buffering: rcv_buffer, tx_queue, tx_hist, send_buffer, send_queue
+ *
+ * RX data is stored in the rcv_buffer (pointer). If the message is complete, it
+ * is removed from rcv_buffer pointer and forwarded to L3. If the RX data is
+ * received while there is an incomplete rcv_buffer, it is appended to it.
+ *
+ * TX data is stored in the send_queue first. When transmitting a frame,
+ * the first message in the send_queue is moved to the send_buffer. There it
+ * resides until all fragments are acknowledged. Fragments to be sent by I
+ * frames are stored in the tx_hist buffer for resend, if required. Also the
+ * current fragment is copied into the tx_queue. There it resides until it is
+ * forwarded to layer 1.
+ *
+ * In case we have SAPI 0, we only have a window size of 1, so the unack-
+ * nowledged message resides always in the send_buffer. In case of a suspend,
+ * it can be written back to the first position of the send_queue.
+ *
+ * The layer 1 normally sends a PH-READY-TO-SEND. But because we use
+ * asynchronous transfer between layer 1 and layer 2 (serial link), we must
+ * send a frame before layer 1 reaches the right timeslot to send it. So we
+ * move the tx_queue to layer 1 when there is not already a pending frame, and
+ * wait until acknowledge after the frame has been sent. If we receive an
+ * acknowledge, we can send the next frame from the buffer, if any.
+ *
+ * The moving of tx_queue to layer 1 may also trigger T200, if desired. Also it
+ * will trigger next I frame, if possible.
+ *
+ * T203 is optional. It will be stated when entering MF EST state. It will also
+ * be started when I or S frame is received in that state . It will be
+ * restarted in the lapd_acknowledge() function, in case outstanding frames
+ * will not trigger T200. It will be stoped, when T200 is started in MF EST
+ * state. It will also be stoped when leaving MF EST state.
+ *
+ */
+
+/* Enable this to test content resolution on network side:
+ * - The first SABM is received, UA is dropped.
+ * - The phone repeats SABM, but it's content is wrong, so it is ignored
+ * - The phone repeats SABM again, content is right, so UA is sent.
+ */
+//#define TEST_CONTENT_RESOLUTION_NETWORK
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/lapd_core.h>
+
+/* TS 04.06 Table 4 / Section 3.8.1 */
+#define LAPD_U_SABM	0x7
+#define LAPD_U_SABME	0xf
+#define LAPD_U_DM	0x3
+#define LAPD_U_UI	0x0
+#define LAPD_U_DISC	0x8
+#define LAPD_U_UA	0xC
+#define LAPD_U_FRMR	0x11
+
+#define LAPD_S_RR	0x0
+#define LAPD_S_RNR	0x1
+#define LAPD_S_REJ	0x2
+
+#define CR_USER2NET_CMD		0
+#define CR_USER2NET_RESP	1
+#define CR_NET2USER_CMD		1
+#define CR_NET2USER_RESP	0
+
+#define LAPD_HEADROOM	56
+
+#define SBIT(a) (1 << a)
+#define ALL_STATES 0xffffffff
+
+static void lapd_t200_cb(void *data);
+static void lapd_t203_cb(void *data);
+static int lapd_send_i(struct lapd_msg_ctx *lctx, int line);
+static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
+
+/* UTILITY FUNCTIONS */
+
+struct msgb *lapd_msgb_alloc(int length, const char *name)
+{
+	/* adding space for padding, FIXME: add as an option */
+	if (length < 21)
+		length = 21;
+	return msgb_alloc_headroom(length + LAPD_HEADROOM, LAPD_HEADROOM, name);
+}
+
+static inline uint8_t do_mod(uint8_t x, uint8_t m)
+{
+	return x & (m - 1);
+}
+
+static inline uint8_t inc_mod(uint8_t x, uint8_t m)
+{
+	return (x + 1) & (m - 1);
+}
+
+static inline uint8_t add_mod(uint8_t x, uint8_t y, uint8_t m)
+{
+	return (x + y) & (m - 1);
+}
+
+static inline uint8_t sub_mod(uint8_t x, uint8_t y, uint8_t m)
+{
+	return (x - y) & (m - 1); /* handle negative results correctly */
+}
+
+static void lapd_dl_flush_send(struct lapd_datalink *dl)
+{
+	struct msgb *msg;
+
+	/* Flush send-queue */
+	while ((msg = msgb_dequeue(&dl->send_queue)))
+		msgb_free(msg);
+
+	/* Clear send-buffer */
+	if (dl->send_buffer) {
+		msgb_free(dl->send_buffer);
+		dl->send_buffer = NULL;
+	}
+}
+
+static void lapd_dl_flush_hist(struct lapd_datalink *dl)
+{
+	unsigned int i;
+
+	for (i = 0; i < dl->range_hist; i++) {
+		if (dl->tx_hist[i].msg) {
+			msgb_free(dl->tx_hist[i].msg);
+			dl->tx_hist[i].msg = NULL;
+		}
+	}
+}
+
+static void lapd_dl_flush_tx(struct lapd_datalink *dl)
+{
+	struct msgb *msg;
+
+	while ((msg = msgb_dequeue(&dl->tx_queue)))
+		msgb_free(msg);
+	lapd_dl_flush_hist(dl);
+}
+
+/* Figure B.2/Q.921 */
+const char *lapd_state_names[] = {
+	"LAPD_STATE_NULL",
+	"LAPD_STATE_TEI_UNASS",
+	"LAPD_STATE_ASS_TEI_WAIT",
+	"LAPD_STATE_EST_TEI_WAIT",
+	"LAPD_STATE_IDLE",
+	"LAPD_STATE_SABM_SENT",
+	"LAPD_STATE_DISC_SENT",
+	"LAPD_STATE_MF_EST",
+	"LAPD_STATE_TIMER_RECOV",
+
+};
+
+static void lapd_start_t200(struct lapd_datalink *dl)
+{
+	if (osmo_timer_pending(&dl->t200))
+		return;
+	LOGP(DLLAPD, LOGL_INFO, "start T200\n");
+	osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec);
+}
+
+static void lapd_start_t203(struct lapd_datalink *dl)
+{
+	if (osmo_timer_pending(&dl->t203))
+		return;
+	LOGP(DLLAPD, LOGL_INFO, "start T203\n");
+	osmo_timer_schedule(&dl->t203, dl->t203_sec, dl->t203_usec);
+}
+
+static void lapd_stop_t200(struct lapd_datalink *dl)
+{
+	if (!osmo_timer_pending(&dl->t200))
+		return;
+	LOGP(DLLAPD, LOGL_INFO, "stop T200\n");
+	osmo_timer_del(&dl->t200);
+}
+
+static void lapd_stop_t203(struct lapd_datalink *dl)
+{
+	if (!osmo_timer_pending(&dl->t203))
+		return;
+	LOGP(DLLAPD, LOGL_INFO, "stop T203\n");
+	osmo_timer_del(&dl->t203);
+}
+
+static void lapd_dl_newstate(struct lapd_datalink *dl, uint32_t state)
+{
+	LOGP(DLLAPD, LOGL_INFO, "new state %s -> %s\n",
+		lapd_state_names[dl->state], lapd_state_names[state]);
+
+	if (state != LAPD_STATE_MF_EST && dl->state == LAPD_STATE_MF_EST) {
+		/* stop T203 on leaving MF EST state, if running */
+		lapd_stop_t203(dl);
+		/* remove content res. (network side) on leaving MF EST state */
+		if (dl->cont_res) {
+			msgb_free(dl->cont_res);
+			dl->cont_res = NULL;
+		}
+	}
+
+	/* start T203 on entering MF EST state, if enabled */
+	if ((dl->t203_sec || dl->t203_usec)
+	 && state == LAPD_STATE_MF_EST && dl->state != LAPD_STATE_MF_EST)
+		lapd_start_t203(dl);
+
+	dl->state = state;
+}
+
+static void *tall_lapd_ctx = NULL;
+
+/* init datalink instance and allocate history */
+void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range,
+	int maxf)
+{
+	int m;
+
+	memset(dl, 0, sizeof(*dl));
+	INIT_LLIST_HEAD(&dl->send_queue);
+	INIT_LLIST_HEAD(&dl->tx_queue);
+	dl->reestablish = 1;
+	dl->n200_est_rel = 3;
+	dl->n200 = 3;
+	dl->t200_sec = 1;
+	dl->t200_usec = 0;
+	dl->t200.data = dl;
+	dl->t200.cb = &lapd_t200_cb;
+	dl->t203_sec = 10;
+	dl->t203_usec = 0;
+	dl->t203.data = dl;
+	dl->t203.cb = &lapd_t203_cb;
+	dl->maxf = maxf;
+	if (k > v_range - 1)
+		k = v_range - 1;
+	dl->k = k;
+	dl->v_range = v_range;
+
+	/* Calculate modulo for history array:
+	 * - The history range must be at least k+1.
+	 * - The history range must be 2^x, where x is as low as possible.
+	 */
+	k++;
+	for (m = 0x80; m; m >>= 1) {
+		if ((m & k)) {
+			if (k > m)
+				m <<= 1;
+			dl->range_hist = m;
+			break;
+		}
+	}
+
+	LOGP(DLLAPD, LOGL_INFO, "Init DL layer: sequence range = %d, k = %d, "
+		"history range = %d\n", dl->v_range, dl->k, dl->range_hist);
+
+	lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+
+	if (!tall_lapd_ctx)
+		tall_lapd_ctx = talloc_named_const(NULL, 1, "lapd context");
+	dl->tx_hist = (struct lapd_history *) talloc_zero_array(tall_lapd_ctx,
+					struct log_info, dl->range_hist);
+}
+
+/* reset to IDLE state */
+void lapd_dl_reset(struct lapd_datalink *dl)
+{
+	if (dl->state == LAPD_STATE_IDLE)
+		return;
+	LOGP(DLLAPD, LOGL_INFO, "Resetting LAPDm instance\n");
+	/* enter idle state (and remove eventual cont_res) */
+	lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+	/* flush buffer */
+	lapd_dl_flush_tx(dl);
+	lapd_dl_flush_send(dl);
+	/* Discard partly received L3 message */
+	if (dl->rcv_buffer) {
+		msgb_free(dl->rcv_buffer);
+		dl->rcv_buffer = NULL;
+	}
+	/* stop Timers */
+	lapd_stop_t200(dl);
+	lapd_stop_t203(dl);
+}
+
+/* reset and de-allocate history buffer */
+void lapd_dl_exit(struct lapd_datalink *dl)
+{
+	/* free all ressources except history buffer */
+	lapd_dl_reset(dl);
+	/* free history buffer list */
+	talloc_free(dl->tx_hist);
+}
+
+/*! \brief Set the \ref lapdm_mode of a LAPDm entity */
+int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode)
+{
+	switch (mode) {
+	case LAPD_MODE_USER:
+		dl->cr.loc2rem.cmd = CR_USER2NET_CMD;
+		dl->cr.loc2rem.resp = CR_USER2NET_RESP;
+		dl->cr.rem2loc.cmd = CR_NET2USER_CMD;
+		dl->cr.rem2loc.resp = CR_NET2USER_RESP;
+		break;
+	case LAPD_MODE_NETWORK:
+		dl->cr.loc2rem.cmd = CR_NET2USER_CMD;
+		dl->cr.loc2rem.resp = CR_NET2USER_RESP;
+		dl->cr.rem2loc.cmd = CR_USER2NET_CMD;
+		dl->cr.rem2loc.resp = CR_USER2NET_RESP;
+		break;
+	default:
+		return -EINVAL;
+	}
+	dl->mode = mode;
+
+	return 0;
+}
+
+/* send DL message with optional msgb */
+static int send_dl_l3(uint8_t prim, uint8_t op, struct lapd_msg_ctx *lctx,
+	struct msgb *msg)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct osmo_dlsap_prim dp;
+
+	osmo_prim_init(&dp.oph, 0, prim, op, msg);
+	return dl->send_dlsap(&dp, lctx);
+}
+
+/* send simple DL message */
+static inline int send_dl_simple(uint8_t prim, uint8_t op,
+	struct lapd_msg_ctx *lctx)
+{
+	struct msgb *msg = lapd_msgb_alloc(0, "DUMMY");
+
+	return send_dl_l3(prim, op, lctx, msg);
+}
+
+/* send MDL-ERROR INDICATION */
+static int mdl_error(uint8_t cause, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct osmo_dlsap_prim dp;
+
+	LOGP(DLLAPD, LOGL_NOTICE, "sending MDL-ERROR-IND cause %d\n",
+		cause);
+	osmo_prim_init(&dp.oph, 0, PRIM_MDL_ERROR, PRIM_OP_INDICATION, NULL);
+	dp.u.error_ind.cause = cause;
+	return dl->send_dlsap(&dp, lctx);
+}
+
+/* send UA response */
+static int lapd_send_ua(struct lapd_msg_ctx *lctx, uint8_t len, uint8_t *data)
+{
+	struct msgb *msg = lapd_msgb_alloc(len, "LAPD UA");
+	struct lapd_msg_ctx nctx;
+	struct lapd_datalink *dl = lctx->dl;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	msg->l3h = msgb_put(msg, len);
+	if (len)
+		memcpy(msg->l3h, data, len);
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.resp;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = LAPD_U_UA;
+	/* keep nctx.p_f */
+	nctx.length = len;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* send DM response */
+static int lapd_send_dm(struct lapd_msg_ctx *lctx)
+{
+	struct msgb *msg = lapd_msgb_alloc(0, "LAPD DM");
+	struct lapd_msg_ctx nctx;
+	struct lapd_datalink *dl = lctx->dl;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.resp;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = LAPD_U_DM;
+	/* keep nctx.p_f */
+	nctx.length = 0;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* send RR response / command */
+static int lapd_send_rr(struct lapd_msg_ctx *lctx, uint8_t f_bit, uint8_t cmd)
+{
+	struct msgb *msg = lapd_msgb_alloc(0, "LAPD RR");
+	struct lapd_msg_ctx nctx;
+	struct lapd_datalink *dl = lctx->dl;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = (cmd) ? dl->cr.loc2rem.cmd : dl->cr.loc2rem.resp;
+	nctx.format = LAPD_FORM_S;
+	nctx.s_u = LAPD_S_RR;
+	nctx.p_f = f_bit;
+	nctx.n_recv = dl->v_recv;
+	nctx.length = 0;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* send RNR response / command */
+static int lapd_send_rnr(struct lapd_msg_ctx *lctx, uint8_t f_bit, uint8_t cmd)
+{
+	struct msgb *msg = lapd_msgb_alloc(0, "LAPD RNR");
+	struct lapd_msg_ctx nctx;
+	struct lapd_datalink *dl = lctx->dl;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = (cmd) ? dl->cr.loc2rem.cmd : dl->cr.loc2rem.resp;
+	nctx.format = LAPD_FORM_S;
+	nctx.s_u = LAPD_S_RNR;
+	nctx.p_f = f_bit;
+	nctx.n_recv = dl->v_recv;
+	nctx.length = 0;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* send REJ response */
+static int lapd_send_rej(struct lapd_msg_ctx *lctx, uint8_t f_bit)
+{
+	struct msgb *msg = lapd_msgb_alloc(0, "LAPD REJ");
+	struct lapd_msg_ctx nctx;
+	struct lapd_datalink *dl = lctx->dl;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.resp;
+	nctx.format = LAPD_FORM_S;
+	nctx.s_u = LAPD_S_REJ;
+	nctx.p_f = f_bit;
+	nctx.n_recv = dl->v_recv;
+	nctx.length = 0;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* resend SABM or DISC message */
+static int lapd_send_resend(struct lapd_datalink *dl)
+{
+	struct msgb *msg;
+	uint8_t h = do_mod(dl->v_send, dl->range_hist);
+	int length = dl->tx_hist[h].msg->len;
+	struct lapd_msg_ctx nctx;
+
+	/* assemble message */
+	memcpy(&nctx, &dl->lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.cmd;
+	nctx.format = LAPD_FORM_U;
+	if (dl->state == LAPD_STATE_SABM_SENT)
+		nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM;
+	else
+		nctx.s_u = LAPD_U_DISC;
+	nctx.p_f = 1;
+	nctx.length = length;
+	nctx.more = 0;
+
+	/* Resend SABM/DISC from tx_hist */
+	msg = lapd_msgb_alloc(length, "LAPD resend");
+	msg->l3h = msgb_put(msg, length);
+	if (length)
+		memcpy(msg->l3h, dl->tx_hist[h].msg->data, length);
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* reestablish link */
+static int lapd_reestablish(struct lapd_datalink *dl)
+{
+	struct osmo_dlsap_prim dp;
+	struct msgb *msg;
+
+	msg = lapd_msgb_alloc(0, "DUMMY");
+	osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg);
+	
+	return lapd_est_req(&dp, &dl->lctx);
+}
+
+/* Timer callback on T200 expiry */
+static void lapd_t200_cb(void *data)
+{
+	struct lapd_datalink *dl = data;
+
+	LOGP(DLLAPD, LOGL_INFO, "Timeout T200 (%p) state=%d\n", dl,
+		(int) dl->state);
+
+	switch (dl->state) {
+	case LAPD_STATE_SABM_SENT:
+		/* 5.4.1.3 */
+		if (dl->retrans_ctr + 1 >= dl->n200_est_rel + 1) {
+			/* send RELEASE INDICATION to L3 */
+			send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION,
+				&dl->lctx);
+			/* send MDL ERROR INIDCATION to L3 */
+			mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx);
+			/* flush tx and send buffers */
+			lapd_dl_flush_tx(dl);
+			lapd_dl_flush_send(dl);
+			/* go back to idle state */
+			lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+			/* NOTE: we must not change any other states or buffers
+			 * and queues, since we may reconnect after handover
+			 * failure. the buffered messages is replaced there */
+			break;
+		}
+		/* retransmit SABM command */
+		lapd_send_resend(dl);
+		/* increment re-transmission counter */
+		dl->retrans_ctr++;
+		/* restart T200 (PH-READY-TO-SEND) */
+		lapd_start_t200(dl);
+		break;
+	case LAPD_STATE_DISC_SENT:
+		/* 5.4.4.3 */
+		if (dl->retrans_ctr + 1 >= dl->n200_est_rel + 1) {
+			/* send RELEASE INDICATION to L3 */
+			send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
+			/* send MDL ERROR INIDCATION to L3 */
+			mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx);
+			/* flush tx and send buffers */
+			lapd_dl_flush_tx(dl);
+			lapd_dl_flush_send(dl);
+			/* go back to idle state */
+			lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+			/* NOTE: we must not change any other states or buffers
+			 * and queues, since we may reconnect after handover
+			 * failure. the buffered messages is replaced there */
+			break;
+		}
+		/* retransmit DISC command */
+		lapd_send_resend(dl);
+		/* increment re-transmission counter */
+		dl->retrans_ctr++;
+		/* restart T200 (PH-READY-TO-SEND) */
+		lapd_start_t200(dl);
+		break;
+	case LAPD_STATE_MF_EST:
+		/* 5.5.7 */
+		dl->retrans_ctr = 0;
+		lapd_dl_newstate(dl, LAPD_STATE_TIMER_RECOV);
+		/* fall through */
+	case LAPD_STATE_TIMER_RECOV:
+		dl->retrans_ctr++;
+		if (dl->retrans_ctr < dl->n200) {
+			uint8_t vs = sub_mod(dl->v_send, 1, dl->v_range);
+			uint8_t h = do_mod(vs, dl->range_hist);
+			/* retransmit I frame (V_s-1) with P=1, if any */
+			if (dl->tx_hist[h].msg) {
+				struct msgb *msg;
+				int length = dl->tx_hist[h].msg->len;
+				struct lapd_msg_ctx nctx;
+
+				LOGP(DLLAPD, LOGL_INFO, "retransmit last frame"
+					" V(S)=%d\n", vs);
+				/* Create I frame (segment) from tx_hist */
+				memcpy(&nctx, &dl->lctx, sizeof(nctx));
+				/* keep nctx.ldp */
+				/* keep nctx.sapi */
+				/* keep nctx.tei */
+				nctx.cr = dl->cr.loc2rem.cmd;
+				nctx.format = LAPD_FORM_I;
+				nctx.p_f = 1;
+				nctx.n_send = vs;
+				nctx.n_recv = dl->v_recv;
+				nctx.length = length;
+				nctx.more = dl->tx_hist[h].more;
+				msg = lapd_msgb_alloc(length, "LAPD I resend");
+				msg->l3h = msgb_put(msg, length);
+				memcpy(msg->l3h, dl->tx_hist[h].msg->data,
+					length);
+				dl->send_ph_data_req(&nctx, msg);
+			} else {
+			/* OR send appropriate supervision frame with P=1 */
+				if (!dl->own_busy && !dl->seq_err_cond) {
+					lapd_send_rr(&dl->lctx, 1, 1);
+					/* NOTE: In case of sequence error
+					 * condition, the REJ frame has been
+					 * transmitted when entering the
+					 * condition, so it has not be done
+					 * here
+				 	 */
+				} else if (dl->own_busy) {
+					lapd_send_rnr(&dl->lctx, 1, 1);
+				} else {
+					LOGP(DLLAPD, LOGL_INFO, "unhandled, "
+						"pls. fix\n");
+				}
+			}
+			/* restart T200 (PH-READY-TO-SEND) */
+			lapd_start_t200(dl);
+		} else {
+			/* send MDL ERROR INIDCATION to L3 */
+			mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx);
+			/* reestablish */
+			if (!dl->reestablish)
+				break;
+			LOGP(DLLAPD, LOGL_NOTICE, "N200 reached, performing "
+				"reestablishment.\n");
+			lapd_reestablish(dl);
+		}
+		break;
+	default:
+		LOGP(DLLAPD, LOGL_INFO, "T200 expired in unexpected "
+			"dl->state %d\n", (int) dl->state);
+	}
+}
+
+/* Timer callback on T203 expiry */
+static void lapd_t203_cb(void *data)
+{
+	struct lapd_datalink *dl = data;
+
+	LOGP(DLLAPD, LOGL_INFO, "Timeout T203 (%p) state=%d\n", dl,
+		(int) dl->state);
+
+	if (dl->state != LAPD_STATE_MF_EST) {
+		LOGP(DLLAPD, LOGL_ERROR, "T203 fired outside MF EST state, "
+			"please fix!\n");
+		return;
+	}
+
+	/* set retransmission counter to 0 */
+	dl->retrans_ctr = 0;
+	/* enter timer recovery state */
+	lapd_dl_newstate(dl, LAPD_STATE_TIMER_RECOV);
+	/* transmit a supervisory command with P bit set to 1 as follows: */
+	if (!dl->own_busy) {
+		LOGP(DLLAPD, LOGL_INFO, "transmit an RR poll command\n");
+		/* Send RR with P=1 */
+		lapd_send_rr(&dl->lctx, 1, 1);
+	} else {
+		LOGP(DLLAPD, LOGL_INFO, "transmit an RNR poll command\n");
+		/* Send RNR with P=1 */
+		lapd_send_rnr(&dl->lctx, 1, 1);
+	}
+	/* start T200 */
+	lapd_start_t200(dl);
+}
+
+/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value */
+static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	uint8_t nr = lctx->n_recv;
+	int s = 0, rej = 0, t200_reset = 0;
+	int i, h;
+
+	/* supervisory frame ? */
+	if (lctx->format == LAPD_FORM_S)
+		s = 1;
+	/* REJ frame ? */
+	if (s && lctx->s_u == LAPD_S_REJ)
+	 	rej = 1;
+
+	/* Flush all transmit buffers of acknowledged frames */
+	for (i = dl->v_ack; i != nr; i = inc_mod(i, dl->v_range)) {
+		h = do_mod(i, dl->range_hist);
+		if (dl->tx_hist[h].msg) {
+			msgb_free(dl->tx_hist[h].msg);
+			dl->tx_hist[h].msg = NULL;
+			LOGP(DLLAPD, LOGL_INFO, "ack frame %d\n", i);
+		}
+	}
+
+	if (dl->state != LAPD_STATE_TIMER_RECOV) {
+		/* When not in the timer recovery condition, the data
+		 * link layer entity shall reset the timer T200 on
+		 * receipt of a valid I frame with N(R) higher than V(A),
+		 * or an REJ with an N(R) equal to V(A). */
+		if ((!rej && nr != dl->v_ack)
+		 || (rej && nr == dl->v_ack)) {
+		 	t200_reset = 1;
+			lapd_stop_t200(dl);
+			/* 5.5.3.1 Note 1 + 2 imply timer recovery cond. */
+		}
+		/* 5.7.4: N(R) sequence error
+		 * N(R) is called valid, if and only if
+		 * (N(R)-V(A)) mod 8 <= (V(S)-V(A)) mod 8.
+		 */
+		if (sub_mod(nr, dl->v_ack, dl->v_range)
+				> sub_mod(dl->v_send, dl->v_ack, dl->v_range)) {
+			LOGP(DLLAPD, LOGL_NOTICE, "N(R) sequence error\n");
+			mdl_error(MDL_CAUSE_SEQ_ERR, lctx);
+		}
+	}
+
+	/* V(A) shall be set to the value of N(R) */
+	dl->v_ack = nr;
+
+	/* If T200 has been stopped by the receipt of an I, RR or RNR frame,
+	 * and if there are outstanding I frames, restart T200 */
+	if (t200_reset && !rej) {
+		if (dl->tx_hist[sub_mod(dl->v_send, 1, dl->range_hist)].msg) {
+			LOGP(DLLAPD, LOGL_INFO, "start T200, due to unacked I "
+				"frame(s)\n");
+			lapd_start_t200(dl);
+		}
+	}
+
+	/* This also does a restart, when I or S frame is received */
+
+	/* Stop T203, if running */
+	lapd_stop_t203(dl);
+	/* Start T203, if T200 is not running in MF EST state, if enabled */
+	if (!osmo_timer_pending(&dl->t200)
+	 && (dl->t203_sec || dl->t203_usec)
+	 && (dl->state == LAPD_STATE_MF_EST)) {
+		lapd_start_t203(dl);
+	}
+}
+
+/* L1 -> L2 */
+
+/* Receive a LAPD U (Unnumbered) message from L1 */
+static int lapd_rx_u(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	int length = lctx->length;
+	int rc = 0;
+	uint8_t prim, op;
+
+	switch (lctx->s_u) {
+	case LAPD_U_SABM:
+	case LAPD_U_SABME:
+		prim = PRIM_DL_EST;
+		op = PRIM_OP_INDICATION;
+
+		LOGP(DLLAPD, LOGL_INFO, "SABM(E) received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* 5.7.1 */
+		dl->seq_err_cond = 0;
+		/* G.2.2 Wrong value of the C/R bit */
+		if (lctx->cr == dl->cr.rem2loc.resp) {
+			LOGP(DLLAPD, LOGL_NOTICE, "SABM response error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+			return -EINVAL;
+		}
+
+		/* G.4.5 If SABM is received with L>N201 or with M bit
+		 * set, AN MDL-ERROR-INDICATION is sent to MM.
+		 */
+		if (lctx->more || length > lctx->n201) {
+			LOGP(DLLAPD, LOGL_NOTICE, "SABM too large error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+			return -EIO;
+		}
+
+		switch (dl->state) {
+		case LAPD_STATE_IDLE:
+			break;
+		case LAPD_STATE_MF_EST:
+			LOGP(DLLAPD, LOGL_INFO, "SABM command, multiple "
+				"frame established state\n");
+			/* If link is lost on the remote side, we start over
+			 * and send DL-ESTABLISH indication again. */
+			if (dl->v_send != dl->v_recv) {
+				LOGP(DLLAPD, LOGL_INFO, "Remote reestablish\n");
+				mdl_error(MDL_CAUSE_SABM_MF, lctx);
+				break;
+			}
+			/* Ignore SABM if content differs from first SABM. */
+			if (dl->mode == LAPD_MODE_NETWORK && length
+			 && dl->cont_res) {
+#ifdef TEST_CONTENT_RESOLUTION_NETWORK
+				dl->cont_res->data[0] ^= 0x01;
+#endif
+				if (memcmp(dl->cont_res, msg->data, length)) {
+					LOGP(DLLAPD, LOGL_INFO, "Another SABM "
+						"with diffrent content - "
+						"ignoring!\n");
+					msgb_free(msg);
+					return 0;
+				}
+			}
+			/* send UA again */
+			lapd_send_ua(lctx, length, msg->l3h);
+			msgb_free(msg);
+			return 0;
+		case LAPD_STATE_DISC_SENT:
+			/* 5.4.6.2 send DM with F=P */
+			lapd_send_dm(lctx);
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			msgb_free(msg);
+			return send_dl_simple(prim, op, lctx);
+		default:
+			/* collision: Send UA, but still wait for rx UA, then
+			 * change to MF_EST state.
+			 */
+			/* check for contention resoultion */
+			if (dl->tx_hist[0].msg && dl->tx_hist[0].msg->len) {
+				LOGP(DLLAPD, LOGL_NOTICE, "SABM not allowed "
+					"during contention resolution\n");
+				mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx);
+			}
+			lapd_send_ua(lctx, length, msg->l3h);
+			msgb_free(msg);
+			return 0;
+		}
+		/* save message context for further use */
+		memcpy(&dl->lctx, lctx, sizeof(dl->lctx));
+#ifndef TEST_CONTENT_RESOLUTION_NETWORK
+		/* send UA response */
+		lapd_send_ua(lctx, length, msg->l3h);
+#endif
+		/* set Vs, Vr and Va to 0 */
+		dl->v_send = dl->v_recv = dl->v_ack = 0;
+		/* clear tx_hist */
+		lapd_dl_flush_hist(dl);
+		/* enter multiple-frame-established state */
+		lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+		/* store content resolution data on network side
+		 * Note: cont_res will be removed when changing state again,
+		 * so it must be allocated AFTER lapd_dl_newstate(). */
+		if (dl->mode == LAPD_MODE_NETWORK && length) {
+			dl->cont_res = lapd_msgb_alloc(length, "CONT RES");
+			memcpy(msgb_put(dl->cont_res, length), msg->l3h,
+				length);
+			LOGP(DLLAPD, LOGL_NOTICE, "Store content res.\n");
+		}
+		/* send notification to L3 */
+		if (length == 0) {
+			/* 5.4.1.2 Normal establishment procedures */
+			rc = send_dl_simple(prim, op, lctx);
+			msgb_free(msg);
+		} else {
+			/* 5.4.1.4 Contention resolution establishment */
+			rc = send_dl_l3(prim, op, lctx, msg);
+		}
+		break;
+	case LAPD_U_DM:
+		LOGP(DLLAPD, LOGL_INFO, "DM received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* G.2.2 Wrong value of the C/R bit */
+		if (lctx->cr == dl->cr.rem2loc.cmd) {
+			LOGP(DLLAPD, LOGL_NOTICE, "DM command error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+			return -EINVAL;
+		}
+		if (!lctx->p_f) {
+			/* 5.4.1.2 DM responses with the F bit set to "0"
+			 * shall be ignored.
+			 */
+			msgb_free(msg);
+			return 0;
+		}
+		switch (dl->state) {
+		case LAPD_STATE_SABM_SENT:
+			break;
+		case LAPD_STATE_MF_EST:
+			if (lctx->p_f) {
+				LOGP(DLLAPD, LOGL_INFO, "unsolicited DM "
+					"response\n");
+				mdl_error(MDL_CAUSE_UNSOL_DM_RESP, lctx);
+			} else {
+				LOGP(DLLAPD, LOGL_INFO, "unsolicited DM "
+					"response, multiple frame established "
+					"state\n");
+				mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
+				/* reestablish */
+				if (!dl->reestablish) {
+					msgb_free(msg);
+					return 0;
+				}
+				LOGP(DLLAPD, LOGL_NOTICE, "Performing "
+					"reestablishment.\n");
+				lapd_reestablish(dl);
+			}
+			msgb_free(msg);
+			return 0;
+		case LAPD_STATE_TIMER_RECOV:
+			/* FP = 0 (DM is normal in case PF = 1) */
+			if (!lctx->p_f) {
+				LOGP(DLLAPD, LOGL_INFO, "unsolicited DM "
+					"response, multiple frame established "
+					"state\n");
+				mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
+				msgb_free(msg);
+				/* reestablish */
+				if (!dl->reestablish)
+					return 0;
+				LOGP(DLLAPD, LOGL_NOTICE, "Performing "
+					"reestablishment.\n");
+				return lapd_reestablish(dl);
+			}
+			break;
+		case LAPD_STATE_DISC_SENT:
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* go to idle state */
+			lapd_dl_flush_tx(dl);
+			lapd_dl_flush_send(dl);
+			lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+			rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
+			msgb_free(msg);
+			return 0;
+		case LAPD_STATE_IDLE:
+			/* 5.4.5 all other frame types shall be discarded */
+		default:
+			LOGP(DLLAPD, LOGL_INFO, "unsolicited DM response! "
+				"(discarding)\n");
+			msgb_free(msg);
+			return 0;
+		}
+		/* stop timer T200 */
+		lapd_stop_t200(dl);
+		/* go to idle state */
+		lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+		rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx);
+		msgb_free(msg);
+		break;
+	case LAPD_U_UI:
+		LOGP(DLLAPD, LOGL_INFO, "UI received\n");
+		/* G.2.2 Wrong value of the C/R bit */
+		if (lctx->cr == dl->cr.rem2loc.resp) {
+			LOGP(DLLAPD, LOGL_NOTICE, "UI indicates response "
+				"error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+			return -EINVAL;
+		}
+
+		/* G.4.5 If UI is received with L>N201 or with M bit
+		 * set, AN MDL-ERROR-INDICATION is sent to MM.
+		 */
+		if (length > lctx->n201 || lctx->more) {
+			LOGP(DLLAPD, LOGL_NOTICE, "UI too large error "
+				"(%d > N201(%d) or M=%d)\n", length,
+				lctx->n201, lctx->more);
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+			return -EIO;
+		}
+
+		/* do some length checks */
+		if (length == 0) {
+			/* 5.3.3 UI frames received with the length indicator
+			 * set to "0" shall be ignored
+			 */
+			LOGP(DLLAPD, LOGL_INFO, "length=0 (discarding)\n");
+			msgb_free(msg);
+			return 0;
+		}
+		rc = send_dl_l3(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION, lctx,
+				msg);
+		break;
+	case LAPD_U_DISC:
+		prim = PRIM_DL_REL;
+		op = PRIM_OP_INDICATION;
+
+		LOGP(DLLAPD, LOGL_INFO, "DISC received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* flush tx and send buffers */
+		lapd_dl_flush_tx(dl);
+		lapd_dl_flush_send(dl);
+		/* 5.7.1 */
+		dl->seq_err_cond = 0;
+		/* G.2.2 Wrong value of the C/R bit */
+		if (lctx->cr == dl->cr.rem2loc.resp) {
+			LOGP(DLLAPD, LOGL_NOTICE, "DISC response error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+			return -EINVAL;
+		}
+		if (length > 0 || lctx->more) {
+			/* G.4.4 If a DISC or DM frame is received with L>0 or
+			 * with the M bit set to "1", an MDL-ERROR-INDICATION
+			 * primitive with cause "U frame with incorrect
+			 * parameters" is sent to the mobile management entity.
+			 */
+			LOGP(DLLAPD, LOGL_NOTICE,
+				"U frame iwth incorrect parameters ");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+			return -EIO;
+		}
+		switch (dl->state) {
+		case LAPD_STATE_IDLE:
+			LOGP(DLLAPD, LOGL_INFO, "DISC in idle state\n");
+			/* send DM with F=P */
+			msgb_free(msg);
+			return lapd_send_dm(lctx);
+		case LAPD_STATE_SABM_SENT:
+			LOGP(DLLAPD, LOGL_INFO, "DISC in SABM state\n");
+			/* 5.4.6.2 send DM with F=P */
+			lapd_send_dm(lctx);
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* go to idle state */
+			lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+			msgb_free(msg);
+			return send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION,
+				lctx);
+		case LAPD_STATE_MF_EST:
+		case LAPD_STATE_TIMER_RECOV:
+			LOGP(DLLAPD, LOGL_INFO, "DISC in est state\n");
+			break;
+		case LAPD_STATE_DISC_SENT:
+			LOGP(DLLAPD, LOGL_INFO, "DISC in disc state\n");
+			prim = PRIM_DL_REL;
+			op = PRIM_OP_CONFIRM;
+			break;
+		default:
+			lapd_send_ua(lctx, length, msg->l3h);
+			msgb_free(msg);
+			return 0;
+		}
+		/* send UA response */
+		lapd_send_ua(lctx, length, msg->l3h);
+		/* stop Timer T200 */
+		lapd_stop_t200(dl);
+		/* enter idle state, keep tx-buffer with UA response */
+		lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+		/* send notification to L3 */
+		rc = send_dl_simple(prim, op, lctx);
+		msgb_free(msg);
+		break;
+	case LAPD_U_UA:
+		LOGP(DLLAPD, LOGL_INFO, "UA received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* G.2.2 Wrong value of the C/R bit */
+		if (lctx->cr == dl->cr.rem2loc.cmd) {
+			LOGP(DLLAPD, LOGL_NOTICE, "UA indicates command "
+				"error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+			return -EINVAL;
+		}
+
+		/* G.4.5 If UA is received with L>N201 or with M bit
+		 * set, AN MDL-ERROR-INDICATION is sent to MM.
+		 */
+		if (lctx->more || length > lctx->n201) {
+			LOGP(DLLAPD, LOGL_NOTICE, "UA too large error\n");
+			msgb_free(msg);
+			mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+			return -EIO;
+		}
+
+		if (!lctx->p_f) {
+			/* 5.4.1.2 A UA response with the F bit set to "0"
+			 * shall be ignored.
+			 */
+			LOGP(DLLAPD, LOGL_INFO, "F=0 (discarding)\n");
+			msgb_free(msg);
+			return 0;
+		}
+		switch (dl->state) {
+		case LAPD_STATE_SABM_SENT:
+			break;
+		case LAPD_STATE_MF_EST:
+		case LAPD_STATE_TIMER_RECOV:
+			LOGP(DLLAPD, LOGL_INFO, "unsolicited UA response! "
+				"(discarding)\n");
+			mdl_error(MDL_CAUSE_UNSOL_UA_RESP, lctx);
+			msgb_free(msg);
+			return 0;
+		case LAPD_STATE_DISC_SENT:
+			LOGP(DLLAPD, LOGL_INFO, "UA in disconnect state\n");
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* go to idle state */
+			lapd_dl_flush_tx(dl);
+			lapd_dl_flush_send(dl);
+			lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+			rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
+			msgb_free(msg);
+			return 0;
+		case LAPD_STATE_IDLE:
+			/* 5.4.5 all other frame types shall be discarded */
+		default:
+			LOGP(DLLAPD, LOGL_INFO, "unsolicited UA response! "
+				"(discarding)\n");
+			msgb_free(msg);
+			return 0;
+		}
+		LOGP(DLLAPD, LOGL_INFO, "UA in SABM state\n");
+		/* stop Timer T200 */
+		lapd_stop_t200(dl);
+		/* compare UA with SABME if contention resolution is applied */
+		if (dl->tx_hist[0].msg->len) {
+			if (length != (dl->tx_hist[0].msg->len)
+			 || !!memcmp(dl->tx_hist[0].msg->data, msg->l3h,
+			 					length)) {
+				LOGP(DLLAPD, LOGL_INFO, "**** UA response "
+					"mismatches ****\n");
+				rc = send_dl_simple(PRIM_DL_REL,
+					PRIM_OP_INDICATION, lctx);
+				msgb_free(msg);
+				/* go to idle state */
+				lapd_dl_flush_tx(dl);
+				lapd_dl_flush_send(dl);
+				lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+				return 0;
+			}
+		}
+		/* set Vs, Vr and Va to 0 */
+		dl->v_send = dl->v_recv = dl->v_ack = 0;
+		/* clear tx_hist */
+		lapd_dl_flush_hist(dl);
+		/* enter multiple-frame-established state */
+		lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+		/* send outstanding frames, if any (resume / reconnect) */
+		lapd_send_i(lctx, __LINE__);
+		/* send notification to L3 */
+		rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx);
+		msgb_free(msg);
+		break;
+	case LAPD_U_FRMR:
+		LOGP(DLLAPD, LOGL_NOTICE, "Frame reject received\n");
+		/* send MDL ERROR INIDCATION to L3 */
+		mdl_error(MDL_CAUSE_FRMR, lctx);
+		msgb_free(msg);
+		/* reestablish */
+		if (!dl->reestablish)
+			break;
+		LOGP(DLLAPD, LOGL_NOTICE, "Performing reestablishment.\n");
+		rc = lapd_reestablish(dl);
+		break;
+	default:
+		/* G.3.1 */
+		LOGP(DLLAPD, LOGL_NOTICE, "Unnumbered frame not allowed.\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+		return -EINVAL;
+	}
+	return rc;
+}
+
+/* Receive a LAPD S (Supervisory) message from L1 */
+static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	int length = lctx->length;
+
+	if (length > 0 || lctx->more) {
+		/* G.4.3 If a supervisory frame is received with L>0 or
+		 * with the M bit set to "1", an MDL-ERROR-INDICATION
+		 * primitive with cause "S frame with incorrect
+		 * parameters" is sent to the mobile management entity. */
+		LOGP(DLLAPD, LOGL_NOTICE,
+				"S frame with incorrect parameters\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_SFRM_INC_PARAM, lctx);
+		return -EIO;
+	}
+
+	if (lctx->cr == dl->cr.rem2loc.resp
+	 && lctx->p_f
+	 && dl->state != LAPD_STATE_TIMER_RECOV) {
+		/* 5.4.2.2: Inidcate error on supervisory reponse F=1 */
+		LOGP(DLLAPD, LOGL_NOTICE, "S frame response with F=1 error\n");
+		mdl_error(MDL_CAUSE_UNSOL_SPRV_RESP, lctx);
+	}
+
+	switch (dl->state) {
+	case LAPD_STATE_IDLE:
+		/* if P=1, respond DM with F=1 (5.2.2) */
+		/* 5.4.5 all other frame types shall be discarded */
+		if (lctx->p_f)
+			lapd_send_dm(lctx); /* F=P */
+		/* fall though */
+	case LAPD_STATE_SABM_SENT:
+	case LAPD_STATE_DISC_SENT:
+		LOGP(DLLAPD, LOGL_NOTICE, "S frame ignored in this state\n");
+		msgb_free(msg);
+		return 0;
+	}
+	switch (lctx->s_u) {
+	case LAPD_S_RR:
+		LOGP(DLLAPD, LOGL_INFO, "RR received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */
+		lapd_acknowledge(lctx);
+
+		/* 5.5.3.2 */
+		if (lctx->cr == dl->cr.rem2loc.cmd
+		 && lctx->p_f) {
+		 	if (!dl->own_busy && !dl->seq_err_cond) {
+				LOGP(DLLAPD, LOGL_INFO, "RR frame command "
+					"with polling bit set and we are not "
+					"busy, so we reply with RR frame "
+					"response\n");
+				lapd_send_rr(lctx, 1, 0);
+				/* NOTE: In case of sequence error condition,
+				 * the REJ frame has been transmitted when
+				 * entering the condition, so it has not be
+				 * done here
+				 */
+			} else if (dl->own_busy) {
+				LOGP(DLLAPD, LOGL_INFO, "RR frame command "
+					"with polling bit set and we are busy, "
+					"so we reply with RR frame response\n");
+				lapd_send_rnr(lctx, 1, 0);
+			}
+		} else if (lctx->cr == dl->cr.rem2loc.resp
+			&& lctx->p_f
+			&& dl->state == LAPD_STATE_TIMER_RECOV) {
+			LOGP(DLLAPD, LOGL_INFO, "RR response with F==1, "
+				"and we are in timer recovery state, so "
+				"we leave that state\n");
+			/* V(S) to the N(R) in the RR frame */
+			dl->v_send = lctx->n_recv;
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* 5.5.7 Clear timer recovery condition */
+			lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+		}
+		/* Send message, if possible due to acknowledged data */
+		lapd_send_i(lctx, __LINE__);
+
+		break;
+	case LAPD_S_RNR:
+		LOGP(DLLAPD, LOGL_INFO, "RNR received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */
+		lapd_acknowledge(lctx);
+
+		/* 5.5.5 */
+		/* Set peer receiver busy condition */
+		dl->peer_busy = 1;
+
+		if (lctx->p_f) {
+			if (lctx->cr == dl->cr.rem2loc.cmd) {
+				if (!dl->own_busy) {
+					LOGP(DLLAPD, LOGL_INFO, "RNR poll "
+						"command and we are not busy, "
+						"so we reply with RR final "
+						"response\n");
+					/* Send RR with F=1 */
+					lapd_send_rr(lctx, 1, 0);
+				} else {
+					LOGP(DLLAPD, LOGL_INFO, "RNR poll "
+						"command and we are busy, so "
+						"we reply with RNR final "
+						"response\n");
+					/* Send RNR with F=1 */
+					lapd_send_rnr(lctx, 1, 0);
+				}
+			} else if (dl->state == LAPD_STATE_TIMER_RECOV) {
+				LOGP(DLLAPD, LOGL_INFO, "RNR poll response "
+					"and we in timer recovery state, so "
+					"we leave that state\n");
+				/* 5.5.7 Clear timer recovery condition */
+				lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+				/* V(S) to the N(R) in the RNR frame */
+				dl->v_send = lctx->n_recv;
+			}
+		} else
+			LOGP(DLLAPD, LOGL_INFO, "RNR not polling/final state "
+				"received\n");
+
+		/* Send message, if possible due to acknowledged data */
+		lapd_send_i(lctx, __LINE__);
+
+		break;
+	case LAPD_S_REJ:
+		LOGP(DLLAPD, LOGL_INFO, "REJ received in state %s\n",
+			lapd_state_names[dl->state]);
+		/* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */
+		lapd_acknowledge(lctx);
+
+		/* 5.5.4.1 */
+		if (dl->state != LAPD_STATE_TIMER_RECOV) {
+			/* Clear an existing peer receiver busy condition */
+			dl->peer_busy = 0;
+			/* V(S) and V(A) to the N(R) in the REJ frame */
+			dl->v_send = dl->v_ack = lctx->n_recv;
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* 5.5.3.2 */
+			if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) {
+				if (!dl->own_busy && !dl->seq_err_cond) {
+					LOGP(DLLAPD, LOGL_INFO, "REJ poll "
+						"command not in timer recovery "
+						"state and not in own busy "
+						"condition received, so we "
+						"respond with RR final "
+						"response\n");
+					lapd_send_rr(lctx, 1, 0);
+					/* NOTE: In case of sequence error
+					 * condition, the REJ frame has been
+					 * transmitted when entering the
+					 * condition, so it has not be done
+					 * here
+				 	 */
+				} else if (dl->own_busy) {
+					LOGP(DLLAPD, LOGL_INFO, "REJ poll "
+						"command not in timer recovery "
+						"state and in own busy "
+						"condition received, so we "
+						"respond with RNR final "
+						"response\n");
+					lapd_send_rnr(lctx, 1, 0);
+				}
+			} else
+				LOGP(DLLAPD, LOGL_INFO, "REJ response or not "
+					"polling command not in timer recovery "
+					"state received\n");
+			/* send MDL ERROR INIDCATION to L3 */
+			if (lctx->cr == dl->cr.rem2loc.resp && lctx->p_f) {
+				mdl_error(MDL_CAUSE_UNSOL_SPRV_RESP, lctx);
+			}
+
+		} else if (lctx->cr == dl->cr.rem2loc.resp && lctx->p_f) {
+			LOGP(DLLAPD, LOGL_INFO, "REJ poll response in timer "
+				"recovery state received\n");
+			/* Clear an existing peer receiver busy condition */
+			dl->peer_busy = 0;
+			/* V(S) and V(A) to the N(R) in the REJ frame */
+			dl->v_send = dl->v_ack = lctx->n_recv;
+			/* stop Timer T200 */
+			lapd_stop_t200(dl);
+			/* 5.5.7 Clear timer recovery condition */
+			lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+		} else {
+			/* Clear an existing peer receiver busy condition */
+			dl->peer_busy = 0;
+			/* V(S) and V(A) to the N(R) in the REJ frame */
+			dl->v_send = dl->v_ack = lctx->n_recv;
+			/* 5.5.3.2 */
+			if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) {
+				if (!dl->own_busy && !dl->seq_err_cond) {
+					LOGP(DLLAPD, LOGL_INFO, "REJ poll "
+						"command in timer recovery "
+						"state and not in own busy "
+						"condition received, so we "
+						"respond with RR final "
+						"response\n");
+					lapd_send_rr(lctx, 1, 0);
+					/* NOTE: In case of sequence error
+					 * condition, the REJ frame has been
+					 * transmitted when entering the
+					 * condition, so it has not be done
+					 * here
+				 	 */
+				} else if (dl->own_busy) {
+					LOGP(DLLAPD, LOGL_INFO, "REJ poll "
+						"command in timer recovery "
+						"state and in own busy "
+						"condition received, so we "
+						"respond with RNR final "
+						"response\n");
+					lapd_send_rnr(lctx, 1, 0);
+				}
+			} else
+				LOGP(DLLAPD, LOGL_INFO, "REJ response or not "
+					"polling command in timer recovery "
+					"state received\n");
+		}
+
+		/* FIXME: 5.5.4.2 2) */
+
+		/* Send message, if possible due to acknowledged data */
+		lapd_send_i(lctx, __LINE__);
+
+		break;
+	default:
+		/* G.3.1 */
+		LOGP(DLLAPD, LOGL_NOTICE, "Supervisory frame not allowed.\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+		return -EINVAL;
+	}
+	msgb_free(msg);
+	return 0;
+}
+
+/* Receive a LAPD I (Information) message from L1 */
+static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	//uint8_t nr = lctx->n_recv;
+	uint8_t ns = lctx->n_send;
+	int length = lctx->length;
+	int rc;
+
+	LOGP(DLLAPD, LOGL_INFO, "I received in state %s\n",
+		lapd_state_names[dl->state]);
+		
+	/* G.2.2 Wrong value of the C/R bit */
+	if (lctx->cr == dl->cr.rem2loc.resp) {
+		LOGP(DLLAPD, LOGL_NOTICE, "I frame response not allowed\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+		return -EINVAL;
+	}
+
+	if (length == 0 || length > lctx->n201) {
+		/* G.4.2 If the length indicator of an I frame is set
+		 * to a numerical value L>N201 or L=0, an MDL-ERROR-INDICATION
+		 * primitive with cause "I frame with incorrect length"
+		 * is sent to the mobile management entity. */
+		LOGP(DLLAPD, LOGL_NOTICE, "I frame length not allowed\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_IFRM_INC_LEN, lctx);
+		return -EIO;
+	}
+
+	/* G.4.2 If the numerical value of L is L<N201 and the M
+	 * bit is set to "1", then an MDL-ERROR-INDICATION primitive with
+	 * cause "I frame with incorrect use of M bit" is sent to the
+	 * mobile management entity. */
+	if (lctx->more && length < lctx->n201) {
+		LOGP(DLLAPD, LOGL_NOTICE, "I frame with M bit too short\n");
+		msgb_free(msg);
+		mdl_error(MDL_CAUSE_IFRM_INC_MBITS, lctx);
+		return -EIO;
+	}
+
+	switch (dl->state) {
+	case LAPD_STATE_IDLE:
+		/* if P=1, respond DM with F=1 (5.2.2) */
+		/* 5.4.5 all other frame types shall be discarded */
+		if (lctx->p_f)
+			lapd_send_dm(lctx); /* F=P */
+		/* fall though */
+	case LAPD_STATE_SABM_SENT:
+	case LAPD_STATE_DISC_SENT:
+		LOGP(DLLAPD, LOGL_NOTICE, "I frame ignored in this state\n");
+		msgb_free(msg);
+		return 0;
+	}
+
+	/* 5.7.1: N(s) sequence error */
+	if (ns != dl->v_recv) {
+		LOGP(DLLAPD, LOGL_NOTICE, "N(S) sequence error: N(S)=%u, "
+		     "V(R)=%u\n", ns, dl->v_recv);
+		/* discard data */
+		msgb_free(msg);
+		if (dl->seq_err_cond != 1) {
+			/* FIXME: help me understand what exactly todo here
+			*/
+			dl->seq_err_cond = 1;
+			lapd_send_rej(lctx, lctx->p_f);
+		} else {
+			/* If there are two subsequent sequence errors received,
+			 * ignore it. (Ignore every second subsequent error.)
+			 * This happens if our reply with the REJ is too slow,
+			 * so the remote gets a T200 timeout and sends another
+			 * frame with a sequence error.
+			 * Test showed that replying with two subsequent REJ
+			 * messages could the remote L2 process to abort.
+			 * Replying too slow shouldn't happen, but may happen
+			 * over serial link between BB and LAPD.
+			 */
+			dl->seq_err_cond = 2;
+		}
+		/* Even if N(s) sequence error, acknowledge to N(R)-1 */
+		/* 5.5.3.1: Acknowlege all transmitted frames up the N(R)-1 */
+		lapd_acknowledge(lctx); /* V(A) is also set here */
+
+		/* Send message, if possible due to acknowledged data */
+		lapd_send_i(lctx, __LINE__);
+
+		return 0;
+	}
+	dl->seq_err_cond = 0;
+
+	/* Increment receiver state */
+	dl->v_recv = inc_mod(dl->v_recv, dl->v_range);
+	LOGP(DLLAPD, LOGL_INFO, "incrementing V(R) to %u\n", dl->v_recv);
+
+	/* 5.5.3.1: Acknowlege all transmitted frames up the the N(R)-1 */
+	lapd_acknowledge(lctx); /* V(A) is also set here */
+
+	/* Only if we are not in own receiver busy condition */
+	if (!dl->own_busy) {
+		/* if the frame carries a complete segment */
+		if (!lctx->more && !dl->rcv_buffer) {
+			LOGP(DLLAPD, LOGL_INFO, "message in single I frame\n");
+			/* send a DATA INDICATION to L3 */
+			msg->len = length;
+			msg->tail = msg->data + length;
+			rc = send_dl_l3(PRIM_DL_DATA, PRIM_OP_INDICATION, lctx,
+				msg);
+		} else {
+			/* create rcv_buffer */
+			if (!dl->rcv_buffer) {
+				LOGP(DLLAPD, LOGL_INFO, "message in multiple "
+					"I frames (first message)\n");
+				dl->rcv_buffer = lapd_msgb_alloc(dl->maxf,
+					"LAPD RX");
+				dl->rcv_buffer->l3h = dl->rcv_buffer->data;
+			}
+			/* concat. rcv_buffer */
+			if (msgb_l3len(dl->rcv_buffer) + length > dl->maxf) {
+				LOGP(DLLAPD, LOGL_NOTICE, "Received frame "
+					"overflow!\n");
+			} else {
+				memcpy(msgb_put(dl->rcv_buffer, length),
+					msg->l3h, length);
+			}
+			/* if the last segment was received */
+			if (!lctx->more) {
+				LOGP(DLLAPD, LOGL_INFO, "message in multiple "
+					"I frames (last message)\n");
+				rc = send_dl_l3(PRIM_DL_DATA,
+					PRIM_OP_INDICATION, lctx,
+					dl->rcv_buffer);
+				dl->rcv_buffer = NULL;
+			} else
+				LOGP(DLLAPD, LOGL_INFO, "message in multiple "
+					"I frames (next message)\n");
+			msgb_free(msg);
+
+		}
+	} else
+		LOGP(DLLAPD, LOGL_INFO, "I frame ignored during own receiver "
+			"busy condition\n");
+
+	/* Check for P bit */
+	if (lctx->p_f) {
+		/* 5.5.2.1 */
+		/* check if we are not in own receiver busy */
+		if (!dl->own_busy) {
+			LOGP(DLLAPD, LOGL_INFO, "we are not busy, send RR\n");
+			/* Send RR with F=1 */
+			rc = lapd_send_rr(lctx, 1, 0);
+		} else {
+			LOGP(DLLAPD, LOGL_INFO, "we are busy, send RNR\n");
+			/* Send RNR with F=1 */
+			rc = lapd_send_rnr(lctx, 1, 0);
+		}
+	} else {
+		/* 5.5.2.2 */
+		/* check if we are not in own receiver busy */
+		if (!dl->own_busy) {
+			/* NOTE: V(R) is already set above */
+			rc = lapd_send_i(lctx, __LINE__);
+			if (rc) {
+				LOGP(DLLAPD, LOGL_INFO, "we are not busy and "
+					"have no pending data, send RR\n");
+				/* Send RR with F=0 */
+				return lapd_send_rr(lctx, 0, 0);
+			}
+			/* all I or one RR is sent, we are done */
+			return 0;
+		} else {
+			LOGP(DLLAPD, LOGL_INFO, "we are busy, send RNR\n");
+			/* Send RNR with F=0 */
+			rc = lapd_send_rnr(lctx, 0, 0);
+		}
+	}
+
+	/* Send message, if possible due to acknowledged data */
+	lapd_send_i(lctx, __LINE__);
+
+	return rc;
+}
+
+/* Receive a LAPD message from L1 */
+int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+	int rc;
+
+	switch (lctx->format) {
+	case LAPD_FORM_U:
+		rc = lapd_rx_u(msg, lctx);
+		break;
+	case LAPD_FORM_S:
+		rc = lapd_rx_s(msg, lctx);
+		break;
+	case LAPD_FORM_I:
+		rc = lapd_rx_i(msg, lctx);
+		break;
+	default:
+		LOGP(DLLAPD, LOGL_NOTICE, "unknown LAPD format\n");
+		msgb_free(msg);
+		rc = -EINVAL;
+	}
+	return rc;
+}
+
+/* L3 -> L2 */
+
+/* send unit data */
+static int lapd_udata_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+	struct lapd_msg_ctx nctx;
+
+	memcpy(&nctx, lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.cmd;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = LAPD_U_UI;
+	/* keep nctx.p_f */
+	nctx.length = msg->len;
+	nctx.more = 0;
+
+	return dl->send_ph_data_req(&nctx, msg);
+}
+
+/* request link establishment */
+static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+	struct lapd_msg_ctx nctx;
+
+	if (msg->len)
+		LOGP(DLLAPD, LOGL_INFO, "perform establishment with content "
+			"(SABM)\n");
+	else
+		LOGP(DLLAPD, LOGL_INFO, "perform normal establishm. (SABM)\n");
+
+	/* Flush send-queue */
+	/* Clear send-buffer */
+	lapd_dl_flush_send(dl);
+	/* be sure that history is empty */
+	lapd_dl_flush_hist(dl);
+
+	/* save message context for further use */
+	memcpy(&dl->lctx, lctx, sizeof(dl->lctx));
+
+	/* Discard partly received L3 message */
+	if (dl->rcv_buffer) {
+		msgb_free(dl->rcv_buffer);
+		dl->rcv_buffer = NULL;
+	}
+
+	/* assemble message */
+	memcpy(&nctx, &dl->lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.cmd;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM;
+	nctx.p_f = 1;
+	nctx.length = msg->len;
+	nctx.more = 0;
+
+	/* Transmit-buffer carries exactly one segment */
+	dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
+	msgb_put(dl->tx_hist[0].msg, msg->len);
+	if (msg->len)
+		memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
+	dl->tx_hist[0].more = 0;
+	/* set Vs to 0, because it is used as index when resending SABM */
+	dl->v_send = 0;
+	
+	/* Set states */
+	dl->own_busy = dl->peer_busy = 0;
+	dl->retrans_ctr = 0;
+	lapd_dl_newstate(dl, LAPD_STATE_SABM_SENT);
+
+	/* Tramsmit and start T200 */
+	dl->send_ph_data_req(&nctx, msg);
+	lapd_start_t200(dl);
+
+	return 0;
+}
+
+/* send data */
+static int lapd_data_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+
+	if (msgb_l3len(msg) == 0) {
+		LOGP(DLLAPD, LOGL_ERROR,
+			"writing an empty message is not possible.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	LOGP(DLLAPD, LOGL_INFO,
+	     "writing message to send-queue: l3len: %d\n", msgb_l3len(msg));
+
+	/* Write data into the send queue */
+	msgb_enqueue(&dl->send_queue, msg);
+
+	/* Send message, if possible */
+	lapd_send_i(&dl->lctx, __LINE__);
+
+	return 0;
+}
+
+/* Send next I frame from queued/buffered data */
+static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	uint8_t k = dl->k;
+	uint8_t h;
+	struct msgb *msg;
+	int length, left;
+	int rc = - 1; /* we sent nothing */
+	struct lapd_msg_ctx nctx;
+
+
+	LOGP(DLLAPD, LOGL_INFO, "%s() called from line %d\n", __func__, line);
+
+	next_frame:
+
+	if (dl->peer_busy) {
+		LOGP(DLLAPD, LOGL_INFO, "peer busy, not sending\n");
+		return rc;
+	}
+
+	if (dl->state == LAPD_STATE_TIMER_RECOV) {
+		LOGP(DLLAPD, LOGL_INFO, "timer recovery, not sending\n");
+		return rc;
+	}
+
+	/* If the send state variable V(S) is equal to V(A) plus k
+	 * (where k is the maximum number of outstanding I frames - see
+	 * subclause 5.8.4), the data link layer entity shall not transmit any
+	 * new I frames, but shall retransmit an I frame as a result
+	 * of the error recovery procedures as described in subclauses 5.5.4 and
+	 * 5.5.7. */
+	if (dl->v_send == add_mod(dl->v_ack, k, dl->v_range)) {
+		LOGP(DLLAPD, LOGL_INFO, "k frames outstanding, not sending "
+			"more (k=%u V(S)=%u V(A)=%u)\n", k, dl->v_send,
+			dl->v_ack);
+		return rc;
+	}
+
+	h = do_mod(dl->v_send, dl->range_hist);
+
+	/* if we have no tx_hist yet, we create it */
+	if (!dl->tx_hist[h].msg) {
+		/* Get next message into send-buffer, if any */
+		if (!dl->send_buffer) {
+			next_message:
+			dl->send_out = 0;
+			dl->send_buffer = msgb_dequeue(&dl->send_queue);
+			/* No more data to be sent */
+			if (!dl->send_buffer)
+				return rc;
+			LOGP(DLLAPD, LOGL_INFO, "get message from "
+				"send-queue\n");
+		}
+
+		/* How much is left in the send-buffer? */
+		left = msgb_l3len(dl->send_buffer) - dl->send_out;
+		/* Segment, if data exceeds N201 */
+		length = left;
+		if (length > lctx->n201)
+			length = lctx->n201;
+		LOGP(DLLAPD, LOGL_INFO, "msg-len %d sent %d left %d N201 %d "
+			"length %d first byte %02x\n",
+			msgb_l3len(dl->send_buffer), dl->send_out, left,
+			lctx->n201, length, dl->send_buffer->l3h[0]);
+		/* If message in send-buffer is completely sent */
+		if (left == 0) {
+			msgb_free(dl->send_buffer);
+			dl->send_buffer = NULL;
+			goto next_message;
+		}
+
+		LOGP(DLLAPD, LOGL_INFO, "send I frame %sV(S)=%d\n",
+			(left > length) ? "segment " : "", dl->v_send);
+
+		/* Create I frame (segment) and transmit-buffer content */
+		msg = lapd_msgb_alloc(length, "LAPD I");
+		msg->l3h = msgb_put(msg, length);
+		/* assemble message */
+		memcpy(&nctx, &dl->lctx, sizeof(nctx));
+		/* keep nctx.ldp */
+		/* keep nctx.sapi */
+		/* keep nctx.tei */
+		nctx.cr = dl->cr.loc2rem.cmd;
+		nctx.format = LAPD_FORM_I;
+		nctx.p_f = 0;
+		nctx.n_send = dl->v_send;
+		nctx.n_recv = dl->v_recv;
+		nctx.length = length;
+		if (left > length)
+			nctx.more = 1;
+		else
+			nctx.more = 0;
+		if (length)
+			memcpy(msg->l3h, dl->send_buffer->l3h + dl->send_out,
+				length);
+		/* store in tx_hist */
+		dl->tx_hist[h].msg = lapd_msgb_alloc(msg->len, "HIST");
+		msgb_put(dl->tx_hist[h].msg, msg->len);
+		if (length)
+			memcpy(dl->tx_hist[h].msg->data, msg->l3h, msg->len);
+		dl->tx_hist[h].more = nctx.more;
+		/* Add length to track how much is already in the tx buffer */
+		dl->send_out += length;
+	} else {
+		LOGP(DLLAPD, LOGL_INFO, "resend I frame from tx buffer "
+			"V(S)=%d\n", dl->v_send);
+
+		/* Create I frame (segment) from tx_hist */
+		length = dl->tx_hist[h].msg->len;
+		msg = lapd_msgb_alloc(length, "LAPD I resend");
+		msg->l3h = msgb_put(msg, length);
+		/* assemble message */
+		memcpy(&nctx, &dl->lctx, sizeof(nctx));
+		/* keep nctx.ldp */
+		/* keep nctx.sapi */
+		/* keep nctx.tei */
+		nctx.cr = dl->cr.loc2rem.cmd;
+		nctx.format = LAPD_FORM_I;
+		nctx.p_f = 0;
+		nctx.n_send = dl->v_send;
+		nctx.n_recv = dl->v_recv;
+		nctx.length = length;
+		nctx.more = dl->tx_hist[h].more;
+		if (length)
+			memcpy(msg->l3h, dl->tx_hist[h].msg->data, length);
+	}
+
+	/* The value of the send state variable V(S) shall be incremented by 1
+	 * at the end of the transmission of the I frame */
+	dl->v_send = inc_mod(dl->v_send, dl->v_range);
+
+	/* If timer T200 is not running at the time right before transmitting a
+	 * frame, when the PH-READY-TO-SEND primitive is received from the
+	 * physical layer., it shall be set. */
+	if (!osmo_timer_pending(&dl->t200)) {
+		/* stop Timer T203, if running */
+		lapd_stop_t203(dl);
+		/* start Timer T200 */
+		lapd_start_t200(dl);
+	}
+
+	dl->send_ph_data_req(&nctx, msg);
+
+	rc = 0; /* we sent something */
+	goto next_frame;
+}
+
+/* request link suspension */
+static int lapd_susp_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+
+	LOGP(DLLAPD, LOGL_INFO, "perform suspension\n");
+
+	/* put back the send-buffer to the send-queue (first position) */
+	if (dl->send_buffer) {
+		LOGP(DLLAPD, LOGL_INFO, "put frame in sendbuffer back to "
+			"queue\n");
+		llist_add(&dl->send_buffer->list, &dl->send_queue);
+		dl->send_buffer = NULL;
+	} else
+		LOGP(DLLAPD, LOGL_INFO, "no frame in sendbuffer\n");
+
+	/* Clear transmit buffer, but keep send buffer */
+	lapd_dl_flush_tx(dl);
+	/* Stop timers (there is no state change, so we must stop all timers */
+	lapd_stop_t200(dl);
+	lapd_stop_t203(dl);
+
+	msgb_free(msg);
+
+	return send_dl_simple(PRIM_DL_SUSP, PRIM_OP_CONFIRM, &dl->lctx);
+}
+
+/* requesst resume or reconnect of link */
+static int lapd_res_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+	struct lapd_msg_ctx nctx;
+
+	LOGP(DLLAPD, LOGL_INFO, "perform re-establishment (SABM) length=%d\n",
+		msg->len);
+	
+	/* be sure that history is empty */
+	lapd_dl_flush_hist(dl);
+
+	/* save message context for further use */
+	memcpy(&dl->lctx, lctx, sizeof(dl->lctx));
+
+	/* Replace message in the send-buffer (reconnect) */
+	if (dl->send_buffer)
+		msgb_free(dl->send_buffer);
+	dl->send_out = 0;
+	if (msg && msg->len)
+		/* Write data into the send buffer, to be sent first */
+		dl->send_buffer = msg;
+	else
+		dl->send_buffer = NULL;
+
+	/* Discard partly received L3 message */
+	if (dl->rcv_buffer) {
+		msgb_free(dl->rcv_buffer);
+		dl->rcv_buffer = NULL;
+	}
+
+	/* Create new msgb (old one is now free) */
+	msg = lapd_msgb_alloc(0, "LAPD SABM");
+	msg->l3h = msg->data;
+	/* assemble message */
+	memcpy(&nctx, &dl->lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.cmd;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM;
+	nctx.p_f = 1;
+	nctx.length = 0;
+	nctx.more = 0;
+
+	dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
+	msgb_put(dl->tx_hist[0].msg, msg->len);
+	if (msg->len)
+		memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
+	dl->tx_hist[0].more = 0;
+	/* set Vs to 0, because it is used as index when resending SABM */
+	dl->v_send = 0;
+
+	/* Set states */
+	dl->own_busy = dl->peer_busy = 0;
+	dl->retrans_ctr = 0;
+	lapd_dl_newstate(dl, LAPD_STATE_SABM_SENT);
+
+	/* Tramsmit and start T200 */
+	dl->send_ph_data_req(&nctx, msg);
+	lapd_start_t200(dl);
+
+	return 0;
+}
+
+/* requesst release of link */
+static int lapd_rel_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+	struct lapd_msg_ctx nctx;
+
+	/* local release */
+	if (dp->u.rel_req.mode) {
+		LOGP(DLLAPD, LOGL_INFO, "perform local release\n");
+		msgb_free(msg);
+		/* stop Timer T200 */
+		lapd_stop_t200(dl);
+		/* enter idle state, T203 is stopped here, if running */
+		lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+		/* flush buffers */
+		lapd_dl_flush_tx(dl);
+		lapd_dl_flush_send(dl);
+		/* send notification to L3 */
+		return send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
+	}
+
+	/* in case we are already disconnecting */
+	if (dl->state == LAPD_STATE_DISC_SENT)
+		return -EBUSY;
+
+	/* flush tx_hist */
+	lapd_dl_flush_hist(dl);
+
+	LOGP(DLLAPD, LOGL_INFO, "perform normal release (DISC)\n");
+
+	/* Push LAPD header on msgb */
+	/* assemble message */
+	memcpy(&nctx, &dl->lctx, sizeof(nctx));
+	/* keep nctx.ldp */
+	/* keep nctx.sapi */
+	/* keep nctx.tei */
+	nctx.cr = dl->cr.loc2rem.cmd;
+	nctx.format = LAPD_FORM_U;
+	nctx.s_u = LAPD_U_DISC;
+	nctx.p_f = 1;
+	nctx.length = 0;
+	nctx.more = 0;
+
+	dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
+	msgb_put(dl->tx_hist[0].msg, msg->len);
+	if (msg->len)
+		memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
+	dl->tx_hist[0].more = 0;
+	/* set Vs to 0, because it is used as index when resending DISC */
+	dl->v_send = 0;
+	
+	/* Set states */
+	dl->own_busy = dl->peer_busy = 0;
+	dl->retrans_ctr = 0;
+	lapd_dl_newstate(dl, LAPD_STATE_DISC_SENT);
+
+	/* Tramsmit and start T200 */
+	dl->send_ph_data_req(&nctx, msg);
+	lapd_start_t200(dl);
+
+	return 0;
+}
+
+/* request release of link in idle state */
+static int lapd_rel_req_idle(struct osmo_dlsap_prim *dp,
+	struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct msgb *msg = dp->oph.msg;
+
+	msgb_free(msg);
+
+	/* send notification to L3 */
+	return send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
+}
+
+/* statefull handling for DL SAP messages from L3 */
+static struct l2downstate {
+	uint32_t	states;
+	int		prim, op;
+	const char 	*name;
+	int		(*rout) (struct osmo_dlsap_prim *dp,
+					struct lapd_msg_ctx *lctx);
+} l2downstatelist[] = {
+	/* create and send UI command */
+	{ALL_STATES,
+	 PRIM_DL_UNIT_DATA, PRIM_OP_REQUEST, 
+	 "DL-UNIT-DATA-REQUEST", lapd_udata_req},
+
+	/* create and send SABM command */
+	{SBIT(LAPD_STATE_IDLE),
+	 PRIM_DL_EST, PRIM_OP_REQUEST,
+	 "DL-ESTABLISH-REQUEST", lapd_est_req},
+
+	/* create and send I command */
+	{SBIT(LAPD_STATE_MF_EST) |
+	 SBIT(LAPD_STATE_TIMER_RECOV),
+	 PRIM_DL_DATA, PRIM_OP_REQUEST,
+	 "DL-DATA-REQUEST", lapd_data_req},
+
+	/* suspend datalink */
+	{SBIT(LAPD_STATE_MF_EST) |
+	 SBIT(LAPD_STATE_TIMER_RECOV),
+	 PRIM_DL_SUSP, PRIM_OP_REQUEST,
+	 "DL-SUSPEND-REQUEST", lapd_susp_req},
+
+	/* create and send SABM command (resume) */
+	{SBIT(LAPD_STATE_MF_EST) |
+	 SBIT(LAPD_STATE_TIMER_RECOV),
+	 PRIM_DL_RES, PRIM_OP_REQUEST,
+	 "DL-RESUME-REQUEST", lapd_res_req},
+
+	/* create and send SABM command (reconnect) */
+	{SBIT(LAPD_STATE_IDLE) |
+	 SBIT(LAPD_STATE_MF_EST) |
+	 SBIT(LAPD_STATE_TIMER_RECOV),
+	 PRIM_DL_RECON, PRIM_OP_REQUEST,
+	 "DL-RECONNECT-REQUEST", lapd_res_req},
+
+	/* create and send DISC command */
+	{SBIT(LAPD_STATE_SABM_SENT) |
+	 SBIT(LAPD_STATE_MF_EST) |
+	 SBIT(LAPD_STATE_TIMER_RECOV) |
+	 SBIT(LAPD_STATE_DISC_SENT),
+	 PRIM_DL_REL, PRIM_OP_REQUEST,
+	 "DL-RELEASE-REQUEST", lapd_rel_req},
+
+	/* release in idle state */
+	{SBIT(LAPD_STATE_IDLE),
+	 PRIM_DL_REL, PRIM_OP_REQUEST,
+	 "DL-RELEASE-REQUEST", lapd_rel_req_idle},
+};
+
+#define L2DOWNSLLEN \
+	(sizeof(l2downstatelist) / sizeof(struct l2downstate))
+
+int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	int i, supported = 0;
+	struct msgb *msg = dp->oph.msg;
+	int rc;
+
+	/* find function for current state and message */
+	for (i = 0; i < L2DOWNSLLEN; i++) {
+		if (dp->oph.primitive == l2downstatelist[i].prim
+		 && dp->oph.operation == l2downstatelist[i].op) {
+			supported = 1;
+		 	if ((SBIT(dl->state) & l2downstatelist[i].states))
+				break;
+		}
+	}
+	if (!supported) {
+		LOGP(DLLAPD, LOGL_NOTICE, "Message %u/%u unsupported.\n",
+			dp->oph.primitive, dp->oph.operation);
+		msgb_free(msg);
+		return 0;
+	}
+	if (i == L2DOWNSLLEN) {
+		LOGP(DLLAPD, LOGL_NOTICE, "Message %u/%u unhandled at this "
+			"state %s.\n", dp->oph.primitive, dp->oph.operation,
+			lapd_state_names[dl->state]);
+		msgb_free(msg);
+		return 0;
+	}
+
+	LOGP(DLLAPD, LOGL_INFO, "Message %s received in state %s\n",
+		l2downstatelist[i].name, lapd_state_names[dl->state]);
+
+	rc = l2downstatelist[i].rout(dp, lctx);
+
+	return rc;
+}
+
diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c
new file mode 100644
index 0000000..1c08113
--- /dev/null
+++ b/src/gsm/lapdm.c
@@ -0,0 +1,1249 @@
+/* GSM LAPDm (TS 04.06) implementation */
+
+/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2011 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup lapdm
+ *  @{
+ */
+
+/*! \file lapdm.c */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+/* TS 04.06 Figure 4 / Section 3.2 */
+#define LAPDm_LPD_NORMAL  0
+#define LAPDm_LPD_SMSCB	  1
+#define LAPDm_SAPI_NORMAL 0
+#define LAPDm_SAPI_SMS	  3
+#define LAPDm_ADDR(lpd, sapi, cr) ((((lpd) & 0x3) << 5) | (((sapi) & 0x7) << 2) | (((cr) & 0x1) << 1) | 0x1)
+
+#define LAPDm_ADDR_LPD(addr) (((addr) >> 5) & 0x3)
+#define LAPDm_ADDR_SAPI(addr) (((addr) >> 2) & 0x7)
+#define LAPDm_ADDR_CR(addr) (((addr) >> 1) & 0x1)
+#define LAPDm_ADDR_EA(addr) ((addr) & 0x1)
+
+/* TS 04.06 Table 3 / Section 3.4.3 */
+#define LAPDm_CTRL_I(nr, ns, p)	((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((ns) & 0x7) << 1))
+#define LAPDm_CTRL_S(nr, s, p)	((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((s) & 0x3) << 2) | 0x1)
+#define LAPDm_CTRL_U(u, p)	((((u) & 0x1c) << (5-2)) | (((p) & 0x1) << 4) | (((u) & 0x3) << 2) | 0x3)
+
+#define LAPDm_CTRL_is_I(ctrl)	(((ctrl) & 0x1) == 0)
+#define LAPDm_CTRL_is_S(ctrl)	(((ctrl) & 0x3) == 1)
+#define LAPDm_CTRL_is_U(ctrl)	(((ctrl) & 0x3) == 3)
+
+#define LAPDm_CTRL_U_BITS(ctrl)	((((ctrl) & 0xC) >> 2) | ((ctrl) & 0xE0) >> 3)
+#define LAPDm_CTRL_PF_BIT(ctrl)	(((ctrl) >> 4) & 0x1)
+
+#define LAPDm_CTRL_S_BITS(ctrl)	(((ctrl) & 0xC) >> 2)
+
+#define LAPDm_CTRL_I_Ns(ctrl)	(((ctrl) & 0xE) >> 1)
+#define LAPDm_CTRL_Nr(ctrl)	(((ctrl) & 0xE0) >> 5)
+
+#define LAPDm_LEN(len)	((len << 2) | 0x1)
+#define LAPDm_MORE	0x2
+#define LAPDm_EL	0x1
+
+#define LAPDm_U_UI	0x0
+
+/* TS 04.06 Section 5.8.3 */
+#define N201_AB_SACCH		18
+#define N201_AB_SDCCH		20
+#define N201_AB_FACCH		20
+#define N201_Bbis		23
+#define N201_Bter_SACCH		21
+#define N201_Bter_SDCCH		23
+#define N201_Bter_FACCH		23
+#define N201_B4			19
+
+/* 5.8.2.1 N200 during establish and release */
+#define N200_EST_REL		5
+/* 5.8.2.1 N200 during timer recovery state */
+#define N200_TR_SACCH		5
+#define N200_TR_SDCCH		23
+#define N200_TR_FACCH_FR	34
+#define N200_TR_EFACCH_FR	48
+#define N200_TR_FACCH_HR	29
+/* FIXME: set N200 depending on chan_nr */
+#define N200 N200_TR_SDCCH
+
+enum lapdm_format {
+	LAPDm_FMT_A,
+	LAPDm_FMT_B,
+	LAPDm_FMT_Bbis,
+	LAPDm_FMT_Bter,
+	LAPDm_FMT_B4,
+};
+
+static int lapdm_send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg);
+static int send_rslms_dlsap(struct osmo_dlsap_prim *dp,
+	struct lapd_msg_ctx *lctx);
+
+static void lapdm_dl_init(struct lapdm_datalink *dl,
+			  struct lapdm_entity *entity, int t200)
+{
+	memset(dl, 0, sizeof(*dl));
+	dl->entity = entity;
+	lapd_dl_init(&dl->dl, 1, 8, 200);
+	dl->dl.reestablish = 0; /* GSM uses no reestablish */
+	dl->dl.send_ph_data_req = lapdm_send_ph_data_req;
+	dl->dl.send_dlsap = send_rslms_dlsap;
+	dl->dl.n200_est_rel = N200_EST_REL;
+	dl->dl.n200 = N200;
+	dl->dl.t203_sec = 0; dl->dl.t203_usec = 0;
+	dl->dl.t200_sec = t200; dl->dl.t200_usec = 0;
+}
+
+/*! \brief initialize a LAPDm entity and all datalinks inside
+ *  \param[in] le LAPDm entity
+ *  \param[in] mode \ref lapdm_mode (BTS/MS)
+ */
+void lapdm_entity_init(struct lapdm_entity *le, enum lapdm_mode mode, int t200)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++)
+		lapdm_dl_init(&le->datalink[i], le, t200);
+
+	lapdm_entity_set_mode(le, mode);
+}
+
+/*! \brief initialize a LAPDm channel and all its channels
+ *  \param[in] lc \ref lapdm_channel to be initialized
+ *  \param[in] mode \ref lapdm_mode (BTS/MS)
+ *
+ * This really is a convenience wrapper around calling \ref
+ * lapdm_entity_init twice.
+ */
+void lapdm_channel_init(struct lapdm_channel *lc, enum lapdm_mode mode)
+{
+	lapdm_entity_init(&lc->lapdm_acch, mode, 2);
+	/* FIXME: this depends on chan type */
+	lapdm_entity_init(&lc->lapdm_dcch, mode, 1);
+}
+
+/*! \brief flush and release all resoures in LAPDm entity */
+void lapdm_entity_exit(struct lapdm_entity *le)
+{
+	unsigned int i;
+	struct lapdm_datalink *dl;
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
+		dl = &le->datalink[i];
+		lapd_dl_exit(&dl->dl);
+	}
+}
+
+/* \brief lfush and release all resources in LAPDm channel
+ *
+ * A convenience wrapper calling \ref lapdm_entity_exit on both
+ * entities inside the \ref lapdm_channel
+ */
+void lapdm_channel_exit(struct lapdm_channel *lc)
+{
+	lapdm_entity_exit(&lc->lapdm_acch);
+	lapdm_entity_exit(&lc->lapdm_dcch);
+}
+
+static struct lapdm_datalink *datalink_for_sapi(struct lapdm_entity *le, uint8_t sapi)
+{
+	switch (sapi) {
+	case LAPDm_SAPI_NORMAL:
+		return &le->datalink[0];
+	case LAPDm_SAPI_SMS:
+		return &le->datalink[1];
+	default:
+		return NULL;
+	}
+}
+
+/* remove the L2 header from a MSGB */
+static inline unsigned char *msgb_pull_l2h(struct msgb *msg)
+{
+	unsigned char *ret = msgb_pull(msg, msg->l3h - msg->l2h);
+	msg->l2h = NULL;
+	return ret;
+}
+
+/* Append padding (if required) */
+static void lapdm_pad_msgb(struct msgb *msg, uint8_t n201)
+{
+	int pad_len = n201 - msgb_l2len(msg);
+	uint8_t *data;
+
+	if (pad_len < 0) {
+		LOGP(DLLAPD, LOGL_ERROR,
+		     "cannot pad message that is already too big!\n");
+		return;
+	}
+
+	data = msgb_put(msg, pad_len);
+	memset(data, 0x2B, pad_len);
+}
+
+/* input function that L2 calls when sending messages up to L3 */
+static int rslms_sendmsg(struct msgb *msg, struct lapdm_entity *le)
+{
+	if (!le->l3_cb) {
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	/* call the layer2 message handler that is registered */
+	return le->l3_cb(msg, le, le->l3_ctx);
+}
+
+/* write a frame into the tx queue */
+static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg,
+				uint8_t chan_nr, uint8_t link_id, uint8_t pad)
+{
+	struct lapdm_entity *le = dl->entity;
+	struct osmo_phsap_prim pp;
+
+	/* if there is a pending message, queue it */
+	if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) {
+		*msgb_push(msg, 1) = pad;
+		*msgb_push(msg, 1) = link_id;
+		*msgb_push(msg, 1) = chan_nr;
+		msgb_enqueue(&dl->dl.tx_queue, msg);
+		return -EBUSY;
+	}
+
+	osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA,
+			PRIM_OP_REQUEST, msg);
+	pp.u.data.chan_nr = chan_nr;
+	pp.u.data.link_id = link_id;
+
+	/* send the frame now */
+	le->tx_pending = 0; /* disabled flow control */
+	lapdm_pad_msgb(msg, pad);
+
+	return le->l1_prim_cb(&pp.oph, le->l1_ctx);
+}
+
+static struct msgb *tx_dequeue_msgb(struct lapdm_entity *le)
+{
+	struct lapdm_datalink *dl;
+	int last = le->last_tx_dequeue;
+	int i = last, n = ARRAY_SIZE(le->datalink);
+	struct msgb *msg = NULL;
+
+	/* round-robin dequeue */
+	do {
+		/* next */
+		i = (i + 1) % n;
+		dl = &le->datalink[i];
+		if ((msg = msgb_dequeue(&dl->dl.tx_queue)))
+			break;
+	} while (i != last);
+
+	if (msg) {
+		/* Set last dequeue position */
+		le->last_tx_dequeue = i;
+	}
+
+	return msg;
+}
+
+/*! \brief dequeue a msg that's pending transmission via L1 and wrap it into
+ * a osmo_phsap_prim */
+int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp)
+{
+	struct msgb *msg;
+	uint8_t pad;
+
+	msg = tx_dequeue_msgb(le);
+	if (!msg)
+		return -ENODEV;
+
+	/* if we have a message, send PH-DATA.req */
+	osmo_prim_init(&pp->oph, SAP_GSM_PH, PRIM_PH_DATA,
+			PRIM_OP_REQUEST, msg);
+
+	/* Pull chan_nr and link_id */
+	pp->u.data.chan_nr = *msg->data;
+	msgb_pull(msg, 1);
+	pp->u.data.link_id = *msg->data;
+	msgb_pull(msg, 1);
+	pad = *msg->data;
+	msgb_pull(msg, 1);
+
+	/* Pad the frame, we can transmit now */
+	lapdm_pad_msgb(msg, pad);
+
+	return 0;
+}
+
+/* get next frame from the tx queue. because the ms has multiple datalinks,
+ * each datalink's queue is read round-robin.
+ */
+static int l2_ph_data_conf(struct msgb *msg, struct lapdm_entity *le)
+{
+	struct osmo_phsap_prim pp;
+
+	/* we may send again */
+	le->tx_pending = 0;
+
+	/* free confirm message */
+	if (msg)
+		msgb_free(msg);
+
+	if (lapdm_phsap_dequeue_prim(le, &pp) < 0) {
+		/* no message in all queues */
+
+		/* If user didn't request PH-EMPTY_FRAME.req, abort */
+		if (!(le->flags & LAPDM_ENT_F_EMPTY_FRAME))
+			return 0;
+
+		/* otherwise, send PH-EMPTY_FRAME.req */
+		osmo_prim_init(&pp.oph, SAP_GSM_PH,
+				PRIM_PH_EMPTY_FRAME,
+				PRIM_OP_REQUEST, NULL);
+	} else {
+		le->tx_pending = 1;
+	}
+
+	return le->l1_prim_cb(&pp.oph, le->l1_ctx);
+}
+
+/* Create RSLms various RSLms messages */
+static int send_rslms_rll_l3(uint8_t msg_type, struct lapdm_msg_ctx *mctx,
+			     struct msgb *msg)
+{
+	/* Add the RSL + RLL header */
+	rsl_rll_push_l3(msg, msg_type, mctx->chan_nr, mctx->link_id, 1);
+
+	/* send off the RSLms message to L3 */
+	return rslms_sendmsg(msg, mctx->dl->entity);
+}
+
+/* Take a B4 format message from L1 and create RSLms UNIT DATA IND */
+static int send_rslms_rll_l3_ui(struct lapdm_msg_ctx *mctx, struct msgb *msg)
+{
+	uint8_t l3_len = msg->tail - (uint8_t *)msgb_l3(msg);
+	struct abis_rsl_rll_hdr *rllh;
+
+	/* Add the RSL + RLL header */
+	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+	msgb_push(msg, 2 + 2);
+	rsl_rll_push_hdr(msg, RSL_MT_UNIT_DATA_IND, mctx->chan_nr,
+		mctx->link_id, 1);
+	rllh = (struct abis_rsl_rll_hdr *)msgb_l2(msg);
+
+	rllh->data[0] = RSL_IE_TIMING_ADVANCE;
+	rllh->data[1] = mctx->ta_ind;
+
+	rllh->data[2] = RSL_IE_MS_POWER;
+	rllh->data[3] = mctx->tx_power_ind;
+	
+	return rslms_sendmsg(msg, mctx->dl->entity);
+}
+
+static int send_rll_simple(uint8_t msg_type, struct lapdm_msg_ctx *mctx)
+{
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(msg_type, mctx->chan_nr, mctx->link_id, 1);
+
+	/* send off the RSLms message to L3 */
+	return rslms_sendmsg(msg, mctx->dl->entity);
+}
+
+static int rsl_rll_error(uint8_t cause, struct lapdm_msg_ctx *mctx)
+{
+	struct msgb *msg;
+
+	LOGP(DLLAPD, LOGL_NOTICE, "sending MDL-ERROR-IND %d\n", cause);
+	msg = rsl_rll_simple(RSL_MT_ERROR_IND, mctx->chan_nr, mctx->link_id, 1);
+	msgb_tlv_put(msg, RSL_IE_RLM_CAUSE, 1, &cause);
+	return rslms_sendmsg(msg, mctx->dl->entity);
+}
+
+/* DLSAP L2 -> L3 (RSLms) */
+static int send_rslms_dlsap(struct osmo_dlsap_prim *dp,
+	struct lapd_msg_ctx *lctx)
+{
+	struct lapd_datalink *dl = lctx->dl;
+	struct lapdm_datalink *mdl =
+		container_of(dl, struct lapdm_datalink, dl);
+	struct lapdm_msg_ctx *mctx = &mdl->mctx;
+	uint8_t rll_msg = 0;
+
+	switch (OSMO_PRIM_HDR(&dp->oph)) {
+	case OSMO_PRIM(PRIM_DL_EST, PRIM_OP_INDICATION):
+		rll_msg = RSL_MT_EST_IND;
+		break;
+	case OSMO_PRIM(PRIM_DL_EST, PRIM_OP_CONFIRM):
+		rll_msg = RSL_MT_EST_CONF;
+		break;
+	case OSMO_PRIM(PRIM_DL_DATA, PRIM_OP_INDICATION):
+		rll_msg = RSL_MT_DATA_IND;
+		break;
+	case OSMO_PRIM(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION):
+		return send_rslms_rll_l3_ui(mctx, dp->oph.msg);
+	case OSMO_PRIM(PRIM_DL_REL, PRIM_OP_INDICATION):
+		rll_msg = RSL_MT_REL_IND;
+		break;
+	case OSMO_PRIM(PRIM_DL_REL, PRIM_OP_CONFIRM):
+		rll_msg = RSL_MT_REL_CONF;
+		break;
+	case OSMO_PRIM(PRIM_DL_SUSP, PRIM_OP_CONFIRM):
+		rll_msg = RSL_MT_SUSP_CONF;
+		break;
+	case OSMO_PRIM(PRIM_MDL_ERROR, PRIM_OP_INDICATION):
+		rsl_rll_error(dp->u.error_ind.cause, mctx);
+		if (dp->oph.msg)
+			msgb_free(dp->oph.msg);
+		return 0;
+	}
+
+	if (!rll_msg) {
+		LOGP(DLLAPD, LOGL_ERROR, "Unsupported op %d, prim %d. Please "
+			"fix!\n", dp->oph.primitive, dp->oph.operation);
+		return -EINVAL;
+	}
+
+	if (!dp->oph.msg)
+		return send_rll_simple(rll_msg, mctx);
+
+	return send_rslms_rll_l3(rll_msg, mctx, dp->oph.msg);
+}
+
+/* send a data frame to layer 1 */
+static int lapdm_send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg)
+{
+	uint8_t l3_len = msg->tail - msg->data;
+	struct lapd_datalink *dl = lctx->dl;
+	struct lapdm_datalink *mdl =
+		container_of(dl, struct lapdm_datalink, dl);
+	struct lapdm_msg_ctx *mctx = &mdl->mctx;
+	int format = lctx->format;
+
+	/* prepend l2 header */
+	msg->l2h = msgb_push(msg, 3);
+	msg->l2h[0] = LAPDm_ADDR(lctx->lpd, lctx->sapi, lctx->cr);
+					/* EA is set here too */
+	switch (format) {
+	case LAPD_FORM_I:
+		msg->l2h[1] = LAPDm_CTRL_I(lctx->n_recv, lctx->n_send,
+						lctx->p_f);
+		break;
+	case LAPD_FORM_S:
+		msg->l2h[1] = LAPDm_CTRL_S(lctx->n_recv, lctx->s_u, lctx->p_f);
+		break;
+	case LAPD_FORM_U:
+		msg->l2h[1] = LAPDm_CTRL_U(lctx->s_u, lctx->p_f);
+		break;
+	default:
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msg->l2h[2] = LAPDm_LEN(l3_len); /* EL is set here too */
+	if (lctx->more)
+		msg->l2h[2] |= LAPDm_MORE;
+
+	/* add ACCH header with last indicated tx-power and TA */
+	if ((mctx->link_id & 0x40)) {
+		struct lapdm_entity *le = mdl->entity;
+
+		msg->l2h = msgb_push(msg, 2);
+		msg->l2h[0] = le->tx_power;
+		msg->l2h[1] = le->ta;
+	}
+
+	return tx_ph_data_enqueue(mctx->dl, msg, mctx->chan_nr, mctx->link_id,
+			23);
+}
+
+/* input into layer2 (from layer 1) */
+static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
+	uint8_t chan_nr, uint8_t link_id)
+{
+	uint8_t cbits = chan_nr >> 3;
+	uint8_t sapi; /* we cannot take SAPI from link_id, as L1 has no clue */
+	struct lapdm_msg_ctx mctx;
+	struct lapd_msg_ctx lctx;
+	int rc = 0;
+	int n201;
+
+	/* when we reach here, we have a msgb with l2h pointing to the raw
+	 * 23byte mac block. The l1h has already been purged. */
+
+	memset(&mctx, 0, sizeof(mctx));
+	mctx.chan_nr = chan_nr;
+	mctx.link_id = link_id;
+
+	/* check for L1 chan_nr/link_id and determine LAPDm hdr format */
+	if (cbits == 0x10 || cbits == 0x12) {
+		/* Format Bbis is used on BCCH and CCCH(PCH, NCH and AGCH) */
+		mctx.lapdm_fmt = LAPDm_FMT_Bbis;
+		n201 = N201_Bbis;
+		sapi = 0;
+	} else {
+		if (mctx.link_id & 0x40) {
+			/* It was received from network on SACCH */
+
+			/* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */
+			if (le->mode == LAPDM_MODE_MS
+			 && LAPDm_CTRL_is_U(msg->l2h[3])
+			 && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) {
+				mctx.lapdm_fmt = LAPDm_FMT_B4;
+				n201 = N201_B4;
+				LOGP(DLLAPD, LOGL_INFO, "fmt=B4\n");
+			} else {
+				mctx.lapdm_fmt = LAPDm_FMT_B;
+				n201 = N201_AB_SACCH;
+				LOGP(DLLAPD, LOGL_INFO, "fmt=B\n");
+			}
+			/* SACCH frames have a two-byte L1 header that
+			 * OsmocomBB L1 doesn't strip */
+			mctx.tx_power_ind = msg->l2h[0] & 0x1f;
+			mctx.ta_ind = msg->l2h[1];
+			msgb_pull(msg, 2);
+			msg->l2h += 2;
+			sapi = (msg->l2h[0] >> 2) & 7;
+		} else {
+			mctx.lapdm_fmt = LAPDm_FMT_B;
+			LOGP(DLLAPD, LOGL_INFO, "fmt=B\n");
+			n201 = N201_AB_SDCCH;
+			sapi = (msg->l2h[0] >> 2) & 7;
+		}
+	}
+
+	mctx.dl = datalink_for_sapi(le, sapi);
+	/* G.2.1 No action on frames containing an unallocated SAPI. */
+	if (!mctx.dl) {
+		LOGP(DLLAPD, LOGL_NOTICE, "Received frame for unsupported "
+			"SAPI %d!\n", sapi);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	switch (mctx.lapdm_fmt) {
+	case LAPDm_FMT_A:
+	case LAPDm_FMT_B:
+	case LAPDm_FMT_B4:
+		lctx.dl = &mctx.dl->dl;
+		/* obtain SAPI from address field */
+		mctx.link_id |= LAPDm_ADDR_SAPI(msg->l2h[0]);
+		/* G.2.3 EA bit set to "0" is not allowed in GSM */
+		if (!LAPDm_ADDR_EA(msg->l2h[0])) {
+			LOGP(DLLAPD, LOGL_NOTICE, "EA bit 0 is not allowed in "
+				"GSM\n");
+			msgb_free(msg);
+			rsl_rll_error(RLL_CAUSE_FRM_UNIMPL, &mctx);
+			return -EINVAL;
+		}
+		/* adress field */
+		lctx.lpd = LAPDm_ADDR_LPD(msg->l2h[0]);
+		lctx.sapi = LAPDm_ADDR_SAPI(msg->l2h[0]);
+		lctx.cr = LAPDm_ADDR_CR(msg->l2h[0]);
+		/* command field */
+		if (LAPDm_CTRL_is_I(msg->l2h[1])) {
+			lctx.format = LAPD_FORM_I;
+			lctx.n_send = LAPDm_CTRL_I_Ns(msg->l2h[1]);
+			lctx.n_recv = LAPDm_CTRL_Nr(msg->l2h[1]);
+		} else if (LAPDm_CTRL_is_S(msg->l2h[1])) {
+			lctx.format = LAPD_FORM_S;
+			lctx.n_recv = LAPDm_CTRL_Nr(msg->l2h[1]);
+			lctx.s_u = LAPDm_CTRL_S_BITS(msg->l2h[1]);
+		} else if (LAPDm_CTRL_is_U(msg->l2h[1])) {
+			lctx.format = LAPD_FORM_U;
+			lctx.s_u = LAPDm_CTRL_U_BITS(msg->l2h[1]);
+		} else
+			lctx.format = LAPD_FORM_UKN;
+		lctx.p_f = LAPDm_CTRL_PF_BIT(msg->l2h[1]);
+		if (lctx.sapi != LAPDm_SAPI_NORMAL
+		 && lctx.sapi != LAPDm_SAPI_SMS
+		 && lctx.format == LAPD_FORM_U
+		 && lctx.s_u == LAPDm_U_UI) {
+			/* 5.3.3 UI frames with invalid SAPI values shall be
+			 * discarded
+			 */
+			LOGP(DLLAPD, LOGL_INFO, "sapi=%u (discarding)\n",
+				lctx.sapi);
+			msgb_free(msg);
+			return 0;
+		}
+		if (mctx.lapdm_fmt == LAPDm_FMT_B4) {
+			lctx.n201 = n201;
+			lctx.length = n201;
+			lctx.more = 0;
+			msg->l3h = msg->l2h + 2;
+			msgb_pull_l2h(msg);
+		} else {
+			/* length field */
+			if (!(msg->l2h[2] & LAPDm_EL)) {
+				/* G.4.1 If the EL bit is set to "0", an
+				 * MDL-ERROR-INDICATION primitive with cause
+				 * "frame not implemented" is sent to the
+				 * mobile management entity. */
+				LOGP(DLLAPD, LOGL_NOTICE, "we don't support "
+					"multi-octet length\n");
+				msgb_free(msg);
+				rsl_rll_error(RLL_CAUSE_FRM_UNIMPL, &mctx);
+				return -EINVAL;
+			}
+			lctx.n201 = n201;
+			lctx.length = msg->l2h[2] >> 2;
+			lctx.more = !!(msg->l2h[2] & LAPDm_MORE);
+			msg->l3h = msg->l2h + 3;
+			msgb_pull_l2h(msg);
+		}
+		/* store context for messages from lapd */
+		memcpy(&mctx.dl->mctx, &mctx, sizeof(mctx.dl->mctx));
+		/* send to LAPD */
+		rc = lapd_ph_data_ind(msg, &lctx);
+		break;
+	case LAPDm_FMT_Bter:
+		/* FIXME */
+		msgb_free(msg);
+		break;
+	case LAPDm_FMT_Bbis:
+		/* directly pass up to layer3 */
+		LOGP(DLLAPD, LOGL_INFO, "fmt=Bbis UI\n");
+		msg->l3h = msg->l2h;
+		msgb_pull_l2h(msg);
+		rc = send_rslms_rll_l3(RSL_MT_UNIT_DATA_IND, &mctx, msg);
+		break;
+	default:
+		msgb_free(msg);
+	}
+
+	return rc;
+}
+
+/* input into layer2 (from layer 1) */
+static int l2_ph_rach_ind(struct lapdm_entity *le, uint8_t ra, uint32_t fn, uint8_t acc_delay)
+{
+	struct abis_rsl_cchan_hdr *ch;
+	struct gsm48_req_ref req_ref;
+	struct gsm_time gt;
+	struct msgb *msg = msgb_alloc_headroom(512, 64, "RSL CHAN RQD");
+
+	msg->l2h = msgb_push(msg, sizeof(*ch));
+	ch = (struct abis_rsl_cchan_hdr *)msg->l2h;
+	rsl_init_cchan_hdr(ch, RSL_MT_CHAN_RQD);
+	ch->chan_nr = RSL_CHAN_RACH;
+
+	/* generate a RSL CHANNEL REQUIRED message */
+	gsm_fn2gsmtime(&gt, fn);
+	req_ref.ra = ra;
+	req_ref.t1 = gt.t1; /* FIXME: modulo? */
+	req_ref.t2 = gt.t2;
+	req_ref.t3_low = gt.t3 & 7;
+	req_ref.t3_high = gt.t3 >> 3;
+
+	msgb_tv_fixed_put(msg, RSL_IE_REQ_REFERENCE, 3, (uint8_t *) &req_ref);
+	msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, acc_delay);
+
+	return rslms_sendmsg(msg, le);
+}
+
+static int l2_ph_chan_conf(struct msgb *msg, struct lapdm_entity *le, uint32_t frame_nr);
+
+/*! \brief Receive a PH-SAP primitive from L1 */
+int lapdm_phsap_up(struct osmo_prim_hdr *oph, struct lapdm_entity *le)
+{
+	struct osmo_phsap_prim *pp = (struct osmo_phsap_prim *) oph;
+	int rc = 0;
+
+	if (oph->sap != SAP_GSM_PH) {
+		LOGP(DLLAPD, LOGL_ERROR, "primitive for unknown SAP %u\n",
+			oph->sap);
+		return -ENODEV;
+	}
+
+	switch (oph->primitive) {
+	case PRIM_PH_DATA:
+		if (oph->operation != PRIM_OP_INDICATION) {
+			LOGP(DLLAPD, LOGL_ERROR, "PH_DATA is not INDICATION %u\n",
+				oph->operation);
+			return -ENODEV;
+		}
+		rc = l2_ph_data_ind(oph->msg, le, pp->u.data.chan_nr,
+				    pp->u.data.link_id);
+		break;
+	case PRIM_PH_RTS:
+		if (oph->operation != PRIM_OP_INDICATION) {
+			LOGP(DLLAPD, LOGL_ERROR, "PH_RTS is not INDICATION %u\n",
+				oph->operation);
+			return -ENODEV;
+		}
+		rc = l2_ph_data_conf(oph->msg, le);
+		break;
+	case PRIM_PH_RACH:
+		switch (oph->operation) {
+		case PRIM_OP_INDICATION:
+			rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn,
+					    pp->u.rach_ind.acc_delay);
+			break;
+		case PRIM_OP_CONFIRM:
+			rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn);
+			break;
+		default:
+			return -EIO;
+		}
+		break;
+	}
+
+	return rc;
+}
+
+
+/* L3 -> L2 / RSLMS -> LAPDm */
+
+/* Set LAPDm context for established connection */
+static int set_lapdm_context(struct lapdm_datalink *dl, uint8_t chan_nr,
+	uint8_t link_id, int n201, uint8_t sapi)
+{
+	memset(&dl->mctx, 0, sizeof(dl->mctx));
+	dl->mctx.dl = dl;
+	dl->mctx.chan_nr = chan_nr;
+	dl->mctx.link_id = link_id;
+	dl->dl.lctx.dl = &dl->dl;
+	dl->dl.lctx.n201 = n201;
+	dl->dl.lctx.sapi = sapi;
+
+	return 0;
+}
+
+/* L3 requests establishment of data link */
+static int rslms_rx_rll_est_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	uint8_t chan_nr = rllh->chan_nr;
+	uint8_t link_id = rllh->link_id;
+	uint8_t sapi = rllh->link_id & 7;
+	struct tlv_parsed tv;
+	uint8_t length;
+	uint8_t n201 = (rllh->link_id & 0x40) ? N201_AB_SACCH : N201_AB_SDCCH;
+	struct osmo_dlsap_prim dp;
+
+	/* Set LAPDm context for established connection */
+	set_lapdm_context(dl, chan_nr, link_id, n201, sapi);
+
+	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh));
+	if (TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) {
+		msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO);
+		/* contention resolution establishment procedure */
+		if (sapi != 0) {
+			/* According to clause 6, the contention resolution
+			 * procedure is only permitted with SAPI value 0 */
+			LOGP(DLLAPD, LOGL_ERROR, "SAPI != 0 but contention"
+				"resolution (discarding)\n");
+			msgb_free(msg);
+			return send_rll_simple(RSL_MT_REL_IND, &dl->mctx);
+		}
+		/* transmit a SABM command with the P bit set to "1". The SABM
+		 * command shall contain the layer 3 message unit */
+		length = TLVP_LEN(&tv, RSL_IE_L3_INFO);
+	} else {
+		/* normal establishment procedure */
+		msg->l3h = msg->l2h + sizeof(*rllh);
+		length = 0;
+	}
+
+	/* check if the layer3 message length exceeds N201 */
+	if (length > n201) {
+		LOGP(DLLAPD, LOGL_ERROR, "frame too large: %d > N201(%d) "
+			"(discarding)\n", length, n201);
+		msgb_free(msg);
+		return send_rll_simple(RSL_MT_REL_IND, &dl->mctx);
+	}
+
+	/* Remove RLL header from msgb and set length to L3-info */
+	msgb_pull_l2h(msg);
+	msg->len = length;
+	msg->tail = msg->l3h + length;
+
+	/* prepare prim */
+	osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg);
+
+	/* send to L2 */
+	return lapd_recv_dlsap(&dp, &dl->dl.lctx);
+}
+
+/* L3 requests transfer of unnumbered information */
+static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct lapdm_entity *le = dl->entity;
+	int ui_bts = (le->mode == LAPDM_MODE_BTS);
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	uint8_t chan_nr = rllh->chan_nr;
+	uint8_t link_id = rllh->link_id;
+	uint8_t sapi = link_id & 7;
+	struct tlv_parsed tv;
+	int length;
+
+	/* check if the layer3 message length exceeds N201 */
+
+	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh));
+
+	if (TLVP_PRESENT(&tv, RSL_IE_TIMING_ADVANCE)) {
+		le->ta = *TLVP_VAL(&tv, RSL_IE_TIMING_ADVANCE);
+	}
+	if (TLVP_PRESENT(&tv, RSL_IE_MS_POWER)) {
+		le->tx_power = *TLVP_VAL(&tv, RSL_IE_MS_POWER);
+	}
+	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) {
+		LOGP(DLLAPD, LOGL_ERROR, "unit data request without message "
+			"error\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO);
+	length = TLVP_LEN(&tv, RSL_IE_L3_INFO);
+	/* check if the layer3 message length exceeds N201 */
+	if (length + 4 + !ui_bts > 23) {
+		LOGP(DLLAPD, LOGL_ERROR, "frame too large: %d > N201(%d) "
+			"(discarding)\n", length, 18 + ui_bts);
+		msgb_free(msg);
+		return -EIO;
+	}
+
+	LOGP(DLLAPD, LOGL_INFO, "sending unit data (tx_power=%d, ta=%d)\n",
+		le->tx_power, le->ta);
+
+	/* Remove RLL header from msgb and set length to L3-info */
+	msgb_pull_l2h(msg);
+	msg->len = length;
+	msg->tail = msg->l3h + length;
+
+	/* Push L1 + LAPDm header on msgb */
+	msg->l2h = msgb_push(msg, 4 + !ui_bts);
+	msg->l2h[0] = le->tx_power;
+	msg->l2h[1] = le->ta;
+	msg->l2h[2] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd);
+	msg->l2h[3] = LAPDm_CTRL_U(LAPDm_U_UI, 0);
+	if (!ui_bts)
+		msg->l2h[4] = LAPDm_LEN(length);
+
+	/* Tramsmit */
+	return tx_ph_data_enqueue(dl, msg, chan_nr, link_id, 23);
+}
+
+/* L3 requests transfer of acknowledged information */
+static int rslms_rx_rll_data_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	struct tlv_parsed tv;
+	int length;
+	struct osmo_dlsap_prim dp;
+
+	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh));
+	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) {
+		LOGP(DLLAPD, LOGL_ERROR, "data request without message "
+			"error\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO);
+	length = TLVP_LEN(&tv, RSL_IE_L3_INFO);
+
+	/* Remove RLL header from msgb and set length to L3-info */
+	msgb_pull_l2h(msg);
+	msg->len = length;
+	msg->tail = msg->l3h + length;
+
+	/* prepare prim */
+	osmo_prim_init(&dp.oph, 0, PRIM_DL_DATA, PRIM_OP_REQUEST, msg);
+
+	/* send to L2 */
+	return lapd_recv_dlsap(&dp, &dl->dl.lctx);
+}
+
+/* L3 requests suspension of data link */
+static int rslms_rx_rll_susp_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	uint8_t sapi = rllh->link_id & 7;
+	struct osmo_dlsap_prim dp;
+
+	if (sapi != 0) {
+		LOGP(DLLAPD, LOGL_ERROR, "SAPI != 0 while suspending\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	/* prepare prim */
+	osmo_prim_init(&dp.oph, 0, PRIM_DL_SUSP, PRIM_OP_REQUEST, msg);
+
+	/* send to L2 */
+	return lapd_recv_dlsap(&dp, &dl->dl.lctx);
+}
+
+/* L3 requests resume of data link */
+static int rslms_rx_rll_res_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int msg_type = rllh->c.msg_type;
+	uint8_t chan_nr = rllh->chan_nr;
+	uint8_t link_id = rllh->link_id;
+	uint8_t sapi = rllh->link_id & 7;
+	struct tlv_parsed tv;
+	uint8_t length;
+	uint8_t n201 = (rllh->link_id & 0x40) ? N201_AB_SACCH : N201_AB_SDCCH;
+	struct osmo_dlsap_prim dp;
+
+	/* Set LAPDm context for established connection */
+	set_lapdm_context(dl, chan_nr, link_id, n201, sapi);
+
+	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh));
+	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) {
+		LOGP(DLLAPD, LOGL_ERROR, "resume without message error\n");
+		msgb_free(msg);
+		return send_rll_simple(RSL_MT_REL_IND, &dl->mctx);
+	}
+	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO);
+	length = TLVP_LEN(&tv, RSL_IE_L3_INFO);
+
+	/* Remove RLL header from msgb and set length to L3-info */
+	msgb_pull_l2h(msg);
+	msg->len = length;
+	msg->tail = msg->l3h + length;
+
+	/* prepare prim */
+	osmo_prim_init(&dp.oph, 0, (msg_type == RSL_MT_RES_REQ) ? PRIM_DL_RES
+		: PRIM_DL_RECON, PRIM_OP_REQUEST, msg);
+
+	/* send to L2 */
+	return lapd_recv_dlsap(&dp, &dl->dl.lctx);
+}
+
+/* L3 requests release of data link */
+static int rslms_rx_rll_rel_req(struct msgb *msg, struct lapdm_datalink *dl)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	uint8_t mode = 0;
+	struct osmo_dlsap_prim dp;
+
+	/* get release mode */
+	if (rllh->data[0] == RSL_IE_RELEASE_MODE)
+		mode = rllh->data[1] & 1;
+
+	/* Pull rllh */
+	msgb_pull_l2h(msg);
+
+	/* 04.06 3.8.3: No information field is permitted with the DISC
+	 * command. */
+	msg->len = 0;
+	msg->tail = msg->l3h = msg->data;
+
+	/* prepare prim */
+	osmo_prim_init(&dp.oph, 0, PRIM_DL_REL, PRIM_OP_REQUEST, msg);
+	dp.u.rel_req.mode = mode;
+
+	/* send to L2 */
+	return lapd_recv_dlsap(&dp, &dl->dl.lctx);
+}
+
+/* L3 requests channel in idle state */
+static int rslms_rx_chan_rqd(struct lapdm_channel *lc, struct msgb *msg)
+{
+	struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+	void *l1ctx = lc->lapdm_dcch.l1_ctx;
+	struct osmo_phsap_prim pp;
+
+	osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_RACH,
+			PRIM_OP_REQUEST, NULL);
+
+	if (msgb_l2len(msg) < sizeof(*cch) + 4 + 2 + 2) {
+		LOGP(DLLAPD, LOGL_ERROR, "Message too short for CHAN RQD!\n");
+		return -EINVAL;
+	}
+	if (cch->data[0] != RSL_IE_REQ_REFERENCE) {
+		LOGP(DLLAPD, LOGL_ERROR, "Missing REQ REFERENCE IE\n");
+		return -EINVAL;
+	}
+	pp.u.rach_req.ra = cch->data[1];
+	pp.u.rach_req.offset = ((cch->data[2] & 0x7f) << 8) | cch->data[3];
+	pp.u.rach_req.is_combined_ccch = cch->data[2] >> 7;
+
+	if (cch->data[4] != RSL_IE_ACCESS_DELAY) {
+		LOGP(DLLAPD, LOGL_ERROR, "Missing ACCESS_DELAY IE\n");
+		return -EINVAL;
+	}
+	/* TA = 0 - delay */
+	pp.u.rach_req.ta = 0 - cch->data[5];
+
+	if (cch->data[6] != RSL_IE_MS_POWER) {
+		LOGP(DLLAPD, LOGL_ERROR, "Missing MS POWER IE\n");
+		return -EINVAL;
+	}
+	pp.u.rach_req.tx_power = cch->data[7];
+
+	msgb_free(msg);
+
+	return lc->lapdm_dcch.l1_prim_cb(&pp.oph, l1ctx);
+}
+
+/* L1 confirms channel request */
+static int l2_ph_chan_conf(struct msgb *msg, struct lapdm_entity *le, uint32_t frame_nr)
+{
+	struct abis_rsl_cchan_hdr *ch;
+	struct gsm_time tm;
+	struct gsm48_req_ref *ref;
+
+	gsm_fn2gsmtime(&tm, frame_nr);
+
+	msgb_pull_l2h(msg);
+	msg->l2h = msgb_push(msg, sizeof(*ch) + sizeof(*ref));
+	ch = (struct abis_rsl_cchan_hdr *)msg->l2h;
+	rsl_init_cchan_hdr(ch, RSL_MT_CHAN_CONF);
+	ch->chan_nr = RSL_CHAN_RACH;
+	ch->data[0] = RSL_IE_REQ_REFERENCE;
+	ref = (struct gsm48_req_ref *) (ch->data + 1);
+	ref->t1 = tm.t1;
+	ref->t2 = tm.t2;
+	ref->t3_low = tm.t3 & 0x7;
+	ref->t3_high = tm.t3 >> 3;
+	
+	return rslms_sendmsg(msg, le);
+}
+
+/* incoming RSLms RLL message from L3 */
+static int rslms_rx_rll(struct msgb *msg, struct lapdm_channel *lc)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int msg_type = rllh->c.msg_type;
+	uint8_t sapi = rllh->link_id & 7;
+	struct lapdm_entity *le;
+	struct lapdm_datalink *dl;
+	int rc = 0;
+
+	if (msgb_l2len(msg) < sizeof(*rllh)) {
+		LOGP(DLLAPD, LOGL_ERROR, "Message too short for RLL hdr!\n");
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	if (rllh->link_id & 0x40)
+		le = &lc->lapdm_acch;
+	else
+		le = &lc->lapdm_dcch;
+
+	/* G.2.1 No action schall be taken on frames containing an unallocated
+	 * SAPI.
+	 */
+	dl = datalink_for_sapi(le, sapi);
+	if (!dl) {
+		LOGP(DLLAPD, LOGL_ERROR, "No instance for SAPI %d!\n", sapi);
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	LOGP(DLLAPD, LOGL_INFO, "(%p) RLL Message '%s' received. (sapi %d)\n",
+		lc->name, rsl_msg_name(msg_type), sapi);
+
+	switch (msg_type) {
+	case RSL_MT_UNIT_DATA_REQ:
+		rc = rslms_rx_rll_udata_req(msg, dl);
+		break;
+	case RSL_MT_EST_REQ:
+		rc = rslms_rx_rll_est_req(msg, dl);
+		break;
+	case RSL_MT_DATA_REQ:
+		rc = rslms_rx_rll_data_req(msg, dl);
+		break;
+	case RSL_MT_SUSP_REQ:
+		rc = rslms_rx_rll_susp_req(msg, dl);
+		break;
+	case RSL_MT_RES_REQ:
+		rc = rslms_rx_rll_res_req(msg, dl);
+		break;
+	case RSL_MT_RECON_REQ:
+		rc = rslms_rx_rll_res_req(msg, dl);
+		break;
+	case RSL_MT_REL_REQ:
+		rc = rslms_rx_rll_rel_req(msg, dl);
+		break;
+	default:
+		LOGP(DLLAPD, LOGL_NOTICE, "Message unsupported.\n");
+		msgb_free(msg);
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+/* incoming RSLms COMMON CHANNEL message from L3 */
+static int rslms_rx_com_chan(struct msgb *msg, struct lapdm_channel *lc)
+{
+	struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+	int msg_type = cch->c.msg_type;
+	int rc = 0;
+
+	if (msgb_l2len(msg) < sizeof(*cch)) {
+		LOGP(DLLAPD, LOGL_ERROR, "Message too short for COM CHAN hdr!\n");
+		return -EINVAL;
+	}
+
+	switch (msg_type) {
+	case RSL_MT_CHAN_RQD:
+		/* create and send RACH request */
+		rc = rslms_rx_chan_rqd(lc, msg);
+		break;
+	default:
+		LOGP(DLLAPD, LOGL_NOTICE, "Unknown COMMON CHANNEL msg %d!\n",
+			msg_type);
+		msgb_free(msg);
+		return 0;
+	}
+
+	return rc;
+}
+
+/*! \brief Receive a RSLms \ref msgb from Layer 3 */
+int lapdm_rslms_recvmsg(struct msgb *msg, struct lapdm_channel *lc)
+{
+	struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	if (msgb_l2len(msg) < sizeof(*rslh)) {
+		LOGP(DLLAPD, LOGL_ERROR, "Message too short RSL hdr!\n");
+		return -EINVAL;
+	}
+
+	switch (rslh->msg_discr & 0xfe) {
+	case ABIS_RSL_MDISC_RLL:
+		rc = rslms_rx_rll(msg, lc);
+		break;
+	case ABIS_RSL_MDISC_COM_CHAN:
+		rc = rslms_rx_com_chan(msg, lc);
+		break;
+	default:
+		LOGP(DLLAPD, LOGL_ERROR, "unknown RSLms message "
+			"discriminator 0x%02x", rslh->msg_discr);
+		msgb_free(msg);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+/*! \brief Set the \ref lapdm_mode of a LAPDm entity */
+int lapdm_entity_set_mode(struct lapdm_entity *le, enum lapdm_mode mode)
+{
+	int i;
+	enum lapd_mode lm;
+
+	switch (mode) {
+	case LAPDM_MODE_MS:
+		lm = LAPD_MODE_USER;
+		break;
+	case LAPDM_MODE_BTS:
+		lm = LAPD_MODE_NETWORK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
+		lapd_set_mode(&le->datalink[i].dl, lm);
+	}
+
+	le->mode = mode;
+
+	return 0;
+}
+
+/*! \brief Set the \ref lapdm_mode of a LAPDm channel*/
+int lapdm_channel_set_mode(struct lapdm_channel *lc, enum lapdm_mode mode)
+{
+	int rc;
+
+	rc = lapdm_entity_set_mode(&lc->lapdm_dcch, mode);
+	if (rc < 0)
+		return rc;
+
+	return lapdm_entity_set_mode(&lc->lapdm_acch, mode);
+}
+
+/*! \brief Set the L1 callback and context of a LAPDm channel */
+void lapdm_channel_set_l1(struct lapdm_channel *lc, osmo_prim_cb cb, void *ctx)
+{
+	lc->lapdm_dcch.l1_prim_cb = cb;
+	lc->lapdm_acch.l1_prim_cb = cb;
+	lc->lapdm_dcch.l1_ctx = ctx;
+	lc->lapdm_acch.l1_ctx = ctx;
+}
+
+/*! \brief Set the L3 callback and context of a LAPDm channel */
+void lapdm_channel_set_l3(struct lapdm_channel *lc, lapdm_cb_t cb, void *ctx)
+{
+	lc->lapdm_dcch.l3_cb = cb;
+	lc->lapdm_acch.l3_cb = cb;
+	lc->lapdm_dcch.l3_ctx = ctx;
+	lc->lapdm_acch.l3_ctx = ctx;
+}
+
+/*! \brief Reset an entire LAPDm entity and all its datalinks */
+void lapdm_entity_reset(struct lapdm_entity *le)
+{
+	struct lapdm_datalink *dl;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
+		dl = &le->datalink[i];
+		lapd_dl_reset(&dl->dl);
+	}
+}
+
+/*! \brief Reset a LAPDm channel with all its entities */
+void lapdm_channel_reset(struct lapdm_channel *lc)
+{
+	lapdm_entity_reset(&lc->lapdm_dcch);
+	lapdm_entity_reset(&lc->lapdm_acch);
+}
+
+/*! \brief Set the flags of a LAPDm entity */
+void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags)
+{
+	le->flags = flags;
+}
+
+/*! \brief Set the flags of all LAPDm entities in a LAPDm channel */
+void lapdm_channel_set_flags(struct lapdm_channel *lc, unsigned int flags)
+{
+	lapdm_entity_set_flags(&lc->lapdm_dcch, flags);
+	lapdm_entity_set_flags(&lc->lapdm_acch, flags);
+}
+
+/*! @} */
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
new file mode 100644
index 0000000..4583de8
--- /dev/null
+++ b/src/gsm/libosmogsm.map
@@ -0,0 +1,231 @@
+LIBOSMOGSM_1.0 {
+global:
+
+abis_nm_adm_state_names;
+abis_nm_att_settable;
+abis_nm_avail_name;
+abis_nm_chcomb4pchan;
+abis_nm_debugp_foh;
+abis_nm_event_type_name;
+abis_nm_nack_cause_name;
+abis_nm_nack_name;
+abis_nm_att_tlvdef;
+abis_nm_obj_class_names;
+abis_nm_opstate_name;
+abis_nm_nacks;
+abis_nm_no_ack_nack;
+abis_nm_pchan4chcomb;
+abis_nm_reports;
+abis_nm_severity_name;
+abis_nm_sw_load_msgs;
+abis_nm_test_name;
+
+osmo_sitype_strs;
+
+comp128;
+dbm2rxlev;
+
+gprs_cipher_gen_input_i;
+gprs_cipher_gen_input_ui;
+gprs_cipher_load;
+gprs_cipher_register;
+gprs_cipher_run;
+gprs_cipher_supported;
+gprs_tlli_type;
+gprs_tmsi2tlli;
+
+gsm0480_create_notifySS;
+gsm0480_create_unstructuredSS_Notify;
+gsm0480_create_ussd_resp;
+gsm0480_decode_ussd_request;
+gsm0480_wrap_facility;
+gsm0480_wrap_invoke;
+
+gsm0502_calc_paging_group;
+
+gsm0808_att_tlvdef;
+gsm0808_bssap_name;
+gsm0808_bssmap_name;
+gsm0808_create_assignment_completed;
+gsm0808_create_assignment_failure;
+gsm0808_create_cipher_complete;
+gsm0808_create_cipher_reject;
+gsm0808_create_classmark_update;
+gsm0808_create_clear_command;
+gsm0808_create_clear_complete;
+gsm0808_create_clear_rqst;
+gsm0808_create_dtap;
+gsm0808_create_layer3;
+gsm0808_create_reset;
+gsm0808_create_sapi_reject;
+gsm0808_prepend_dtap_header;
+
+gsm338_get_sms_alphabet;
+
+gsm340_gen_oa;
+gsm340_gen_scts;
+gsm340_scts;
+gsm340_validity_period;
+
+gsm411_bcdify;
+gsm411_msgb_alloc;
+gsm411_push_cp_header;
+gsm411_push_rp_header;
+gsm411_smc_clear;
+gsm411_smc_init;
+gsm411_smc_recv;
+gsm411_smc_send;
+gsm411_smr_clear;
+gsm411_smr_init;
+gsm411_smr_recv;
+gsm411_smr_send;
+gsm411_unbcdify;
+gsm411_cp_cause_strs;
+gsm411_rp_cause_strs;
+
+gsm48_att_tlvdef;
+gsm48_cc_msg_name;
+gsm48_cc_state_name;
+gsm48_construct_ra;
+gsm48_decode_bcd_number;
+gsm48_decode_bearer_cap;
+gsm48_decode_called;
+gsm48_decode_callerid;
+gsm48_decode_calling;
+gsm48_decode_cause;
+gsm48_decode_cccap;
+gsm48_decode_connected;
+gsm48_decode_facility;
+gsm48_decode_freq_list;
+gsm48_decode_keypad;
+gsm48_decode_notify;
+gsm48_decode_progress;
+gsm48_decode_redirecting;
+gsm48_decode_signal;
+gsm48_decode_ssversion;
+gsm48_decode_useruser;
+gsm48_encode_bcd_number;
+gsm48_encode_bearer_cap;
+gsm48_encode_called;
+gsm48_encode_callerid;
+gsm48_encode_calling;
+gsm48_encode_cause;
+gsm48_encode_cccap;
+gsm48_encode_connected;
+gsm48_encode_facility;
+gsm48_encode_keypad;
+gsm48_encode_more;
+gsm48_encode_notify;
+gsm48_encode_progress;
+gsm48_encode_redirecting;
+gsm48_encode_signal;
+gsm48_encode_ssversion;
+gsm48_encode_useruser;
+gsm48_generate_lai;
+gsm48_generate_mid_from_imsi;
+gsm48_generate_mid_from_tmsi;
+gsm48_mi_to_string;
+gsm48_mm_att_tlvdef;
+gsm48_number_of_paging_subchannels;
+gsm48_parse_ra;
+gsm48_rr_att_tlvdef;
+
+gsm_7bit_decode;
+gsm_7bit_decode_hdr;
+gsm_7bit_encode;
+
+gsm_arfcn2band;
+gsm_arfcn2freq10;
+gsm_band_name;
+gsm_band_parse;
+gsm_fn2gsmtime;
+gsm_get_octet_len;
+gsm_gsmtime2fn;
+
+gsm_milenage;
+gsm_septet_encode;
+gsm_septets2octets;
+
+lapd_dl_exit;
+lapd_dl_init;
+lapd_dl_reset;
+lapd_msgb_alloc;
+lapd_ph_data_ind;
+lapd_recv_dlsap;
+lapd_set_mode;
+lapd_state_names;
+
+lapdm_channel_exit;
+lapdm_channel_init;
+lapdm_channel_reset;
+lapdm_channel_set_flags;
+lapdm_channel_set_l1;
+lapdm_channel_set_l3;
+lapdm_channel_set_mode;
+lapdm_entity_exit;
+lapdm_entity_init;
+lapdm_entity_reset;
+lapdm_entity_set_flags;
+lapdm_entity_set_mode;
+lapdm_phsap_dequeue_prim;
+lapdm_phsap_up;
+lapdm_rslms_recvmsg;
+
+milenage_auts;
+milenage_check;
+milenage_f1;
+milenage_f2345;
+milenage_generate;
+milenage_opc_gen;
+
+ms_class_gmsk_dbm;
+ms_pwr_ctl_lvl;
+ms_pwr_dbm;
+
+osmo_a5;
+osmo_a5_1;
+osmo_a5_2;
+
+osmo_auth_alg_name;
+osmo_auth_alg_parse;
+osmo_auth_gen_vec;
+osmo_auth_gen_vec_auts;
+osmo_auth_load;
+osmo_auth_register;
+osmo_auth_supported;
+
+osmo_rsl2sitype;
+osmo_sitype2rsl;
+
+rr_cause_name;
+
+rsl_att_tlvdef;
+rsl_ccch_conf_to_bs_cc_chans;
+rsl_ccch_conf_to_bs_ccch_sdcch_comb;
+rsl_chan_nr_str;
+rsl_dec_chan_nr;
+rsl_enc_chan_nr;
+rsl_err_name;
+rsl_init_cchan_hdr;
+rsl_init_rll_hdr;
+rsl_ipac_msg_name;
+rsl_msg_name;
+rsl_rll_push_hdr;
+rsl_rll_push_l3;
+rsl_rll_simple;
+rsl_rlm_cause_name;
+
+rxlev2dbm;
+rxlev_stat_dump;
+rxlev_stat_get_next;
+rxlev_stat_input;
+rxlev_stat_reset;
+
+tlv_def_patch;
+tlv_dump;
+tlv_parse;
+tlv_parse_one;
+tvlv_att_def;
+
+local: *;
+};
diff --git a/src/gsm/milenage/aes-encblock.c b/src/gsm/milenage/aes-encblock.c
new file mode 100644
index 0000000..8f35caa
--- /dev/null
+++ b/src/gsm/milenage/aes-encblock.c
@@ -0,0 +1,38 @@
+/*
+ * AES encrypt_block
+ *
+ * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "aes.h"
+#include "aes_wrap.h"
+
+/**
+ * aes_128_encrypt_block - Perform one AES 128-bit block operation
+ * @key: Key for AES
+ * @in: Input data (16 bytes)
+ * @out: Output of the AES block operation (16 bytes)
+ * Returns: 0 on success, -1 on failure
+ */
+int aes_128_encrypt_block(const u8 *key, const u8 *in, u8 *out)
+{
+	void *ctx;
+	ctx = aes_encrypt_init(key, 16);
+	if (ctx == NULL)
+		return -1;
+	aes_encrypt(ctx, in, out);
+	aes_encrypt_deinit(ctx);
+	return 0;
+}
diff --git a/src/gsm/milenage/aes-internal-enc.c b/src/gsm/milenage/aes-internal-enc.c
new file mode 100644
index 0000000..8726aa7
--- /dev/null
+++ b/src/gsm/milenage/aes-internal-enc.c
@@ -0,0 +1,121 @@
+/*
+ * AES (Rijndael) cipher - encrypt
+ *
+ * Modifications to public domain implementation:
+ * - support only 128-bit keys
+ * - cleanup
+ * - use C pre-processor to make it easier to change S table access
+ * - added option (AES_SMALL_TABLES) for reducing code size by about 8 kB at
+ *   cost of reduced throughput (quite small difference on Pentium 4,
+ *   10-25% when using -O1 or -O2 optimization)
+ *
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto.h"
+#include "aes_i.h"
+
+static void rijndaelEncrypt(const u32 rk[/*44*/], const u8 pt[16], u8 ct[16])
+{
+	u32 s0, s1, s2, s3, t0, t1, t2, t3;
+	const int Nr = 10;
+#ifndef FULL_UNROLL
+	int r;
+#endif /* ?FULL_UNROLL */
+
+	/*
+	 * map byte array block to cipher state
+	 * and add initial round key:
+	 */
+	s0 = GETU32(pt     ) ^ rk[0];
+	s1 = GETU32(pt +  4) ^ rk[1];
+	s2 = GETU32(pt +  8) ^ rk[2];
+	s3 = GETU32(pt + 12) ^ rk[3];
+
+#define ROUND(i,d,s) \
+d##0 = TE0(s##0) ^ TE1(s##1) ^ TE2(s##2) ^ TE3(s##3) ^ rk[4 * i]; \
+d##1 = TE0(s##1) ^ TE1(s##2) ^ TE2(s##3) ^ TE3(s##0) ^ rk[4 * i + 1]; \
+d##2 = TE0(s##2) ^ TE1(s##3) ^ TE2(s##0) ^ TE3(s##1) ^ rk[4 * i + 2]; \
+d##3 = TE0(s##3) ^ TE1(s##0) ^ TE2(s##1) ^ TE3(s##2) ^ rk[4 * i + 3]
+
+#ifdef FULL_UNROLL
+
+	ROUND(1,t,s);
+	ROUND(2,s,t);
+	ROUND(3,t,s);
+	ROUND(4,s,t);
+	ROUND(5,t,s);
+	ROUND(6,s,t);
+	ROUND(7,t,s);
+	ROUND(8,s,t);
+	ROUND(9,t,s);
+
+	rk += Nr << 2;
+
+#else  /* !FULL_UNROLL */
+
+	/* Nr - 1 full rounds: */
+	r = Nr >> 1;
+	for (;;) {
+		ROUND(1,t,s);
+		rk += 8;
+		if (--r == 0)
+			break;
+		ROUND(0,s,t);
+	}
+
+#endif /* ?FULL_UNROLL */
+
+#undef ROUND
+
+	/*
+	 * apply last round and
+	 * map cipher state to byte array block:
+	 */
+	s0 = TE41(t0) ^ TE42(t1) ^ TE43(t2) ^ TE44(t3) ^ rk[0];
+	PUTU32(ct     , s0);
+	s1 = TE41(t1) ^ TE42(t2) ^ TE43(t3) ^ TE44(t0) ^ rk[1];
+	PUTU32(ct +  4, s1);
+	s2 = TE41(t2) ^ TE42(t3) ^ TE43(t0) ^ TE44(t1) ^ rk[2];
+	PUTU32(ct +  8, s2);
+	s3 = TE41(t3) ^ TE42(t0) ^ TE43(t1) ^ TE44(t2) ^ rk[3];
+	PUTU32(ct + 12, s3);
+}
+
+
+void * aes_encrypt_init(const u8 *key, size_t len)
+{
+	u32 *rk;
+	if (len != 16)
+		return NULL;
+	rk = os_malloc(AES_PRIV_SIZE);
+	if (rk == NULL)
+		return NULL;
+	rijndaelKeySetupEnc(rk, key);
+	return rk;
+}
+
+
+void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt)
+{
+	rijndaelEncrypt(ctx, plain, crypt);
+}
+
+
+void aes_encrypt_deinit(void *ctx)
+{
+	os_memset(ctx, 0, AES_PRIV_SIZE);
+	os_free(ctx);
+}
diff --git a/src/gsm/milenage/aes-internal.c b/src/gsm/milenage/aes-internal.c
new file mode 100644
index 0000000..4161220
--- /dev/null
+++ b/src/gsm/milenage/aes-internal.c
@@ -0,0 +1,805 @@
+/*
+ * AES (Rijndael) cipher
+ *
+ * Modifications to public domain implementation:
+ * - support only 128-bit keys
+ * - cleanup
+ * - use C pre-processor to make it easier to change S table access
+ * - added option (AES_SMALL_TABLES) for reducing code size by about 8 kB at
+ *   cost of reduced throughput (quite small difference on Pentium 4,
+ *   10-25% when using -O1 or -O2 optimization)
+ *
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto.h"
+#include "aes_i.h"
+
+/*
+ * rijndael-alg-fst.c
+ *
+ * @version 3.0 (December 2000)
+ *
+ * Optimised ANSI C code for the Rijndael cipher (now AES)
+ *
+ * @author Vincent Rijmen <vincent.rijmen@esat.kuleuven.ac.be>
+ * @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be>
+ * @author Paulo Barreto <paulo.barreto@terra.com.br>
+ *
+ * This code is hereby placed in the public domain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+Te0[x] = S [x].[02, 01, 01, 03];
+Te1[x] = S [x].[03, 02, 01, 01];
+Te2[x] = S [x].[01, 03, 02, 01];
+Te3[x] = S [x].[01, 01, 03, 02];
+Te4[x] = S [x].[01, 01, 01, 01];
+
+Td0[x] = Si[x].[0e, 09, 0d, 0b];
+Td1[x] = Si[x].[0b, 0e, 09, 0d];
+Td2[x] = Si[x].[0d, 0b, 0e, 09];
+Td3[x] = Si[x].[09, 0d, 0b, 0e];
+Td4[x] = Si[x].[01, 01, 01, 01];
+*/
+
+const u32 Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+#ifndef AES_SMALL_TABLES
+const u32 Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+const u32 Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+const u32 Te3[256] = {
+
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+const u32 Te4[256] = {
+    0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+    0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+    0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+    0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+    0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+    0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+    0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+    0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+    0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+    0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+    0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+    0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+    0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+    0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+    0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+    0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+    0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+    0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+    0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+    0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+    0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+    0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+    0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+    0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+    0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+    0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+    0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+    0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+    0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+    0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+    0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+    0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+    0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+    0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+    0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+    0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+    0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+    0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+    0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+    0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+    0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+    0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+    0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+    0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+    0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+    0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+    0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+    0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+    0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+    0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+    0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+    0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+    0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+    0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+    0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+    0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+    0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+    0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+    0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+    0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+    0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+    0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+    0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+    0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+#endif /* AES_SMALL_TABLES */
+const u32 Td0[256] = {
+    0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+    0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+    0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+    0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+    0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+    0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+    0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+    0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+    0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+    0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+    0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+    0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+    0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+    0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+    0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+    0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+    0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+    0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+    0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+    0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+    0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+    0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+    0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+    0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+    0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+    0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+    0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+    0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+    0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+    0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+    0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+    0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+    0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+    0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+    0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+    0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+    0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+    0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+    0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+    0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+    0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+    0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+    0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+    0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+    0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+    0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+    0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+    0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+    0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+    0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+    0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+    0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+    0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+    0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+    0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+    0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+    0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+    0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+    0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+    0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+    0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+    0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+    0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+    0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+#ifndef AES_SMALL_TABLES
+const u32 Td1[256] = {
+    0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+    0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+    0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+    0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+    0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+    0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+    0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+    0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+    0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+    0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+    0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+    0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+    0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+    0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+    0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+    0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+    0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+    0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+    0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+    0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+    0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+    0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+    0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+    0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+    0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+    0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+    0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+    0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+    0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+    0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+    0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+    0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+    0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+    0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+    0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+    0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+    0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+    0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+    0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+    0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+    0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+    0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+    0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+    0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+    0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+    0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+    0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+    0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+    0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+    0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+    0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+    0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+    0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+    0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+    0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+    0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+    0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+    0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+    0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+    0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+    0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+    0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+    0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+    0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+const u32 Td2[256] = {
+    0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+    0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+    0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+    0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+    0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+    0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+    0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+    0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+    0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+    0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+    0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+    0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+    0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+    0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+    0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+    0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+    0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+    0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+    0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+    0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+
+    0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+    0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+    0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+    0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+    0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+    0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+    0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+    0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+    0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+    0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+    0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+    0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+    0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+    0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+    0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+    0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+    0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+    0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+    0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+    0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+    0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+    0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+    0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+    0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+    0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+    0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+    0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+    0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+    0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+    0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+    0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+    0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+    0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+    0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+    0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+    0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+    0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+    0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+    0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+    0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+    0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+    0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+    0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+    0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+const u32 Td3[256] = {
+    0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+    0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+    0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+    0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+    0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+    0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+    0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+    0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+    0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+    0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+    0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+    0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+    0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+    0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+    0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+    0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+    0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+    0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+    0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+    0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+    0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+    0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+    0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+    0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+    0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+    0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+    0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+    0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+    0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+    0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+    0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+    0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+    0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+    0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+    0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+    0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+    0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+    0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+    0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+    0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+    0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+    0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+    0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+    0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+    0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+    0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+    0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+    0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+    0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+    0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+    0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+    0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+    0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+    0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+    0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+    0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+    0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+    0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+    0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+    0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+    0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+    0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+    0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+    0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+const u32 Td4[256] = {
+    0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U,
+    0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U,
+    0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU,
+    0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU,
+    0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U,
+    0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U,
+    0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U,
+    0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU,
+    0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U,
+    0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU,
+    0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU,
+    0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU,
+    0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U,
+    0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U,
+    0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U,
+    0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U,
+    0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U,
+    0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U,
+    0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU,
+    0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U,
+    0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U,
+    0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU,
+    0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U,
+    0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U,
+    0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U,
+    0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU,
+    0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U,
+    0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U,
+    0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU,
+    0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U,
+    0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U,
+    0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU,
+    0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U,
+    0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU,
+    0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU,
+    0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U,
+    0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U,
+    0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U,
+    0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U,
+    0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU,
+    0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U,
+    0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U,
+    0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU,
+    0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU,
+    0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU,
+    0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U,
+    0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU,
+    0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U,
+    0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U,
+    0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U,
+    0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U,
+    0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU,
+    0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U,
+    0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU,
+    0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU,
+    0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU,
+    0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU,
+    0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U,
+    0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU,
+    0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U,
+    0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU,
+    0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U,
+    0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U,
+    0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU,
+};
+const u32 rcon[] = {
+	0x01000000, 0x02000000, 0x04000000, 0x08000000,
+	0x10000000, 0x20000000, 0x40000000, 0x80000000,
+	0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+#else /* AES_SMALL_TABLES */
+const u8 Td4s[256] = {
+    0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U,
+    0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU,
+    0x7cU, 0xe3U, 0x39U, 0x82U, 0x9bU, 0x2fU, 0xffU, 0x87U,
+    0x34U, 0x8eU, 0x43U, 0x44U, 0xc4U, 0xdeU, 0xe9U, 0xcbU,
+    0x54U, 0x7bU, 0x94U, 0x32U, 0xa6U, 0xc2U, 0x23U, 0x3dU,
+    0xeeU, 0x4cU, 0x95U, 0x0bU, 0x42U, 0xfaU, 0xc3U, 0x4eU,
+    0x08U, 0x2eU, 0xa1U, 0x66U, 0x28U, 0xd9U, 0x24U, 0xb2U,
+    0x76U, 0x5bU, 0xa2U, 0x49U, 0x6dU, 0x8bU, 0xd1U, 0x25U,
+    0x72U, 0xf8U, 0xf6U, 0x64U, 0x86U, 0x68U, 0x98U, 0x16U,
+    0xd4U, 0xa4U, 0x5cU, 0xccU, 0x5dU, 0x65U, 0xb6U, 0x92U,
+    0x6cU, 0x70U, 0x48U, 0x50U, 0xfdU, 0xedU, 0xb9U, 0xdaU,
+    0x5eU, 0x15U, 0x46U, 0x57U, 0xa7U, 0x8dU, 0x9dU, 0x84U,
+    0x90U, 0xd8U, 0xabU, 0x00U, 0x8cU, 0xbcU, 0xd3U, 0x0aU,
+    0xf7U, 0xe4U, 0x58U, 0x05U, 0xb8U, 0xb3U, 0x45U, 0x06U,
+    0xd0U, 0x2cU, 0x1eU, 0x8fU, 0xcaU, 0x3fU, 0x0fU, 0x02U,
+    0xc1U, 0xafU, 0xbdU, 0x03U, 0x01U, 0x13U, 0x8aU, 0x6bU,
+    0x3aU, 0x91U, 0x11U, 0x41U, 0x4fU, 0x67U, 0xdcU, 0xeaU,
+    0x97U, 0xf2U, 0xcfU, 0xceU, 0xf0U, 0xb4U, 0xe6U, 0x73U,
+    0x96U, 0xacU, 0x74U, 0x22U, 0xe7U, 0xadU, 0x35U, 0x85U,
+    0xe2U, 0xf9U, 0x37U, 0xe8U, 0x1cU, 0x75U, 0xdfU, 0x6eU,
+    0x47U, 0xf1U, 0x1aU, 0x71U, 0x1dU, 0x29U, 0xc5U, 0x89U,
+    0x6fU, 0xb7U, 0x62U, 0x0eU, 0xaaU, 0x18U, 0xbeU, 0x1bU,
+    0xfcU, 0x56U, 0x3eU, 0x4bU, 0xc6U, 0xd2U, 0x79U, 0x20U,
+    0x9aU, 0xdbU, 0xc0U, 0xfeU, 0x78U, 0xcdU, 0x5aU, 0xf4U,
+    0x1fU, 0xddU, 0xa8U, 0x33U, 0x88U, 0x07U, 0xc7U, 0x31U,
+    0xb1U, 0x12U, 0x10U, 0x59U, 0x27U, 0x80U, 0xecU, 0x5fU,
+    0x60U, 0x51U, 0x7fU, 0xa9U, 0x19U, 0xb5U, 0x4aU, 0x0dU,
+    0x2dU, 0xe5U, 0x7aU, 0x9fU, 0x93U, 0xc9U, 0x9cU, 0xefU,
+    0xa0U, 0xe0U, 0x3bU, 0x4dU, 0xaeU, 0x2aU, 0xf5U, 0xb0U,
+    0xc8U, 0xebU, 0xbbU, 0x3cU, 0x83U, 0x53U, 0x99U, 0x61U,
+    0x17U, 0x2bU, 0x04U, 0x7eU, 0xbaU, 0x77U, 0xd6U, 0x26U,
+    0xe1U, 0x69U, 0x14U, 0x63U, 0x55U, 0x21U, 0x0cU, 0x7dU,
+};
+const u8 rcons[] = {
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36
+	/* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+#endif /* AES_SMALL_TABLES */
+/**
+ * Expand the cipher key into the encryption key schedule.
+ *
+ * @return	the number of rounds for the given cipher key size.
+ */
+void rijndaelKeySetupEnc(u32 rk[/*44*/], const u8 cipherKey[])
+{
+	int i;
+	u32 temp;
+
+	rk[0] = GETU32(cipherKey     );
+	rk[1] = GETU32(cipherKey +  4);
+	rk[2] = GETU32(cipherKey +  8);
+	rk[3] = GETU32(cipherKey + 12);
+	for (i = 0; i < 10; i++) {
+		temp  = rk[3];
+		rk[4] = rk[0] ^
+			TE421(temp) ^ TE432(temp) ^ TE443(temp) ^ TE414(temp) ^
+			RCON(i);
+		rk[5] = rk[1] ^ rk[4];
+		rk[6] = rk[2] ^ rk[5];
+		rk[7] = rk[3] ^ rk[6];
+		rk += 4;
+	}
+}
diff --git a/src/gsm/milenage/aes.h b/src/gsm/milenage/aes.h
new file mode 100644
index 0000000..ba384a9
--- /dev/null
+++ b/src/gsm/milenage/aes.h
@@ -0,0 +1,27 @@
+/*
+ * AES functions
+ * Copyright (c) 2003-2006, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef AES_H
+#define AES_H
+
+#define AES_BLOCK_SIZE 16
+
+void * aes_encrypt_init(const u8 *key, size_t len);
+void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt);
+void aes_encrypt_deinit(void *ctx);
+void * aes_decrypt_init(const u8 *key, size_t len);
+void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain);
+void aes_decrypt_deinit(void *ctx);
+
+#endif /* AES_H */
diff --git a/src/gsm/milenage/aes_i.h b/src/gsm/milenage/aes_i.h
new file mode 100644
index 0000000..6b40bc7
--- /dev/null
+++ b/src/gsm/milenage/aes_i.h
@@ -0,0 +1,122 @@
+/*
+ * AES (Rijndael) cipher
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef AES_I_H
+#define AES_I_H
+
+#include "aes.h"
+
+/* #define FULL_UNROLL */
+#define AES_SMALL_TABLES
+
+extern const u32 Te0[256];
+extern const u32 Te1[256];
+extern const u32 Te2[256];
+extern const u32 Te3[256];
+extern const u32 Te4[256];
+extern const u32 Td0[256];
+extern const u32 Td1[256];
+extern const u32 Td2[256];
+extern const u32 Td3[256];
+extern const u32 Td4[256];
+extern const u32 rcon[10];
+extern const u8 Td4s[256];
+extern const u8 rcons[10];
+
+#ifndef AES_SMALL_TABLES
+
+#define RCON(i) rcon[(i)]
+
+#define TE0(i) Te0[((i) >> 24) & 0xff]
+#define TE1(i) Te1[((i) >> 16) & 0xff]
+#define TE2(i) Te2[((i) >> 8) & 0xff]
+#define TE3(i) Te3[(i) & 0xff]
+#define TE41(i) (Te4[((i) >> 24) & 0xff] & 0xff000000)
+#define TE42(i) (Te4[((i) >> 16) & 0xff] & 0x00ff0000)
+#define TE43(i) (Te4[((i) >> 8) & 0xff] & 0x0000ff00)
+#define TE44(i) (Te4[(i) & 0xff] & 0x000000ff)
+#define TE421(i) (Te4[((i) >> 16) & 0xff] & 0xff000000)
+#define TE432(i) (Te4[((i) >> 8) & 0xff] & 0x00ff0000)
+#define TE443(i) (Te4[(i) & 0xff] & 0x0000ff00)
+#define TE414(i) (Te4[((i) >> 24) & 0xff] & 0x000000ff)
+#define TE4(i) (Te4[(i)] & 0x000000ff)
+
+#define TD0(i) Td0[((i) >> 24) & 0xff]
+#define TD1(i) Td1[((i) >> 16) & 0xff]
+#define TD2(i) Td2[((i) >> 8) & 0xff]
+#define TD3(i) Td3[(i) & 0xff]
+#define TD41(i) (Td4[((i) >> 24) & 0xff] & 0xff000000)
+#define TD42(i) (Td4[((i) >> 16) & 0xff] & 0x00ff0000)
+#define TD43(i) (Td4[((i) >> 8) & 0xff] & 0x0000ff00)
+#define TD44(i) (Td4[(i) & 0xff] & 0x000000ff)
+#define TD0_(i) Td0[(i) & 0xff]
+#define TD1_(i) Td1[(i) & 0xff]
+#define TD2_(i) Td2[(i) & 0xff]
+#define TD3_(i) Td3[(i) & 0xff]
+
+#else /* AES_SMALL_TABLES */
+
+#define RCON(i) (rcons[(i)] << 24)
+
+static inline u32 rotr(u32 val, int bits)
+{
+	return (val >> bits) | (val << (32 - bits));
+}
+
+#define TE0(i) Te0[((i) >> 24) & 0xff]
+#define TE1(i) rotr(Te0[((i) >> 16) & 0xff], 8)
+#define TE2(i) rotr(Te0[((i) >> 8) & 0xff], 16)
+#define TE3(i) rotr(Te0[(i) & 0xff], 24)
+#define TE41(i) ((Te0[((i) >> 24) & 0xff] << 8) & 0xff000000)
+#define TE42(i) (Te0[((i) >> 16) & 0xff] & 0x00ff0000)
+#define TE43(i) (Te0[((i) >> 8) & 0xff] & 0x0000ff00)
+#define TE44(i) ((Te0[(i) & 0xff] >> 8) & 0x000000ff)
+#define TE421(i) ((Te0[((i) >> 16) & 0xff] << 8) & 0xff000000)
+#define TE432(i) (Te0[((i) >> 8) & 0xff] & 0x00ff0000)
+#define TE443(i) (Te0[(i) & 0xff] & 0x0000ff00)
+#define TE414(i) ((Te0[((i) >> 24) & 0xff] >> 8) & 0x000000ff)
+#define TE4(i) ((Te0[(i)] >> 8) & 0x000000ff)
+
+#define TD0(i) Td0[((i) >> 24) & 0xff]
+#define TD1(i) rotr(Td0[((i) >> 16) & 0xff], 8)
+#define TD2(i) rotr(Td0[((i) >> 8) & 0xff], 16)
+#define TD3(i) rotr(Td0[(i) & 0xff], 24)
+#define TD41(i) (Td4s[((i) >> 24) & 0xff] << 24)
+#define TD42(i) (Td4s[((i) >> 16) & 0xff] << 16)
+#define TD43(i) (Td4s[((i) >> 8) & 0xff] << 8)
+#define TD44(i) (Td4s[(i) & 0xff])
+#define TD0_(i) Td0[(i) & 0xff]
+#define TD1_(i) rotr(Td0[(i) & 0xff], 8)
+#define TD2_(i) rotr(Td0[(i) & 0xff], 16)
+#define TD3_(i) rotr(Td0[(i) & 0xff], 24)
+
+#endif /* AES_SMALL_TABLES */
+
+#ifdef _MSC_VER
+#define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00)
+#define GETU32(p) SWAP(*((u32 *)(p)))
+#define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); }
+#else
+#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ \
+((u32)(pt)[2] <<  8) ^ ((u32)(pt)[3]))
+#define PUTU32(ct, st) { \
+(ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); \
+(ct)[2] = (u8)((st) >>  8); (ct)[3] = (u8)(st); }
+#endif
+
+#define AES_PRIV_SIZE (4 * 44)
+
+void rijndaelKeySetupEnc(u32 rk[/*44*/], const u8 cipherKey[]);
+
+#endif /* AES_I_H */
diff --git a/src/gsm/milenage/aes_wrap.h b/src/gsm/milenage/aes_wrap.h
new file mode 100644
index 0000000..4b1c7b0
--- /dev/null
+++ b/src/gsm/milenage/aes_wrap.h
@@ -0,0 +1,48 @@
+/*
+ * AES-based functions
+ *
+ * - AES Key Wrap Algorithm (128-bit KEK) (RFC3394)
+ * - One-Key CBC MAC (OMAC1) hash with AES-128
+ * - AES-128 CTR mode encryption
+ * - AES-128 EAX mode encryption/decryption
+ * - AES-128 CBC
+ *
+ * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef AES_WRAP_H
+#define AES_WRAP_H
+
+int __must_check aes_wrap(const u8 *kek, int n, const u8 *plain, u8 *cipher);
+int __must_check aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain);
+int __must_check omac1_aes_128_vector(const u8 *key, size_t num_elem,
+				      const u8 *addr[], const size_t *len,
+				      u8 *mac);
+int __must_check omac1_aes_128(const u8 *key, const u8 *data, size_t data_len,
+			       u8 *mac);
+int __must_check aes_128_encrypt_block(const u8 *key, const u8 *in, u8 *out);
+int __must_check aes_128_ctr_encrypt(const u8 *key, const u8 *nonce,
+				     u8 *data, size_t data_len);
+int __must_check aes_128_eax_encrypt(const u8 *key,
+				     const u8 *nonce, size_t nonce_len,
+				     const u8 *hdr, size_t hdr_len,
+				     u8 *data, size_t data_len, u8 *tag);
+int __must_check aes_128_eax_decrypt(const u8 *key,
+				     const u8 *nonce, size_t nonce_len,
+				     const u8 *hdr, size_t hdr_len,
+				     u8 *data, size_t data_len, const u8 *tag);
+int __must_check aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data,
+				     size_t data_len);
+int __must_check aes_128_cbc_decrypt(const u8 *key, const u8 *iv, u8 *data,
+				     size_t data_len);
+
+#endif /* AES_WRAP_H */
diff --git a/src/gsm/milenage/common.h b/src/gsm/milenage/common.h
new file mode 100644
index 0000000..aaf82b9
--- /dev/null
+++ b/src/gsm/milenage/common.h
@@ -0,0 +1,20 @@
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MSG_DEBUG
+#define wpa_hexdump(x, args...)
+#define wpa_hexdump_key(x, args...)
+#define wpa_printf(x, args...)
+
+#define os_memcpy(x, y, z)  memcpy(x, y, z)
+#define os_memcmp(x, y, z)  memcmp(x, y, z)
+#define os_memset(x, y, z)  memset(x, y, z)
+#define os_malloc(x)  malloc(x)
+#define os_free(x)  free(x)
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+
+#define __must_check
diff --git a/src/gsm/milenage/crypto.h b/src/gsm/milenage/crypto.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gsm/milenage/crypto.h
diff --git a/src/gsm/milenage/includes.h b/src/gsm/milenage/includes.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gsm/milenage/includes.h
diff --git a/src/gsm/milenage/milenage.c b/src/gsm/milenage/milenage.c
new file mode 100644
index 0000000..b43f986
--- /dev/null
+++ b/src/gsm/milenage/milenage.c
@@ -0,0 +1,344 @@
+/*
+ * 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
+ * Copyright (c) 2006-2007 <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ *
+ * This file implements an example authentication algorithm defined for 3GPP
+ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
+ * EAP-AKA to be tested properly with real USIM cards.
+ *
+ * This implementations assumes that the r1..r5 and c1..c5 constants defined in
+ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
+ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
+ * be AES (Rijndael).
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "aes_wrap.h"
+#include "milenage.h"
+
+
+/**
+ * milenage_f1 - Milenage f1 and f1* algorithms
+ * @opc: OPc = 128-bit value derived from OP and K
+ * @k: K = 128-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @sqn: SQN = 48-bit sequence number
+ * @amf: AMF = 16-bit authentication management field
+ * @mac_a: Buffer for MAC-A = 64-bit network authentication code, or %NULL
+ * @mac_s: Buffer for MAC-S = 64-bit resync authentication code, or %NULL
+ * Returns: 0 on success, -1 on failure
+ */
+int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand,
+		const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s)
+{
+	u8 tmp1[16], tmp2[16], tmp3[16];
+	int i;
+
+	/* tmp1 = TEMP = E_K(RAND XOR OP_C) */
+	for (i = 0; i < 16; i++)
+		tmp1[i] = _rand[i] ^ opc[i];
+	if (aes_128_encrypt_block(k, tmp1, tmp1))
+		return -1;
+
+	/* tmp2 = IN1 = SQN || AMF || SQN || AMF */
+	os_memcpy(tmp2, sqn, 6);
+	os_memcpy(tmp2 + 6, amf, 2);
+	os_memcpy(tmp2 + 8, tmp2, 8);
+
+	/* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */
+
+	/* rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) */
+	for (i = 0; i < 16; i++)
+		tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i];
+	/* XOR with TEMP = E_K(RAND XOR OP_C) */
+	for (i = 0; i < 16; i++)
+		tmp3[i] ^= tmp1[i];
+	/* XOR with c1 (= ..00, i.e., NOP) */
+
+	/* f1 || f1* = E_K(tmp3) XOR OP_c */
+	if (aes_128_encrypt_block(k, tmp3, tmp1))
+		return -1;
+	for (i = 0; i < 16; i++)
+		tmp1[i] ^= opc[i];
+	if (mac_a)
+		os_memcpy(mac_a, tmp1, 8); /* f1 */
+	if (mac_s)
+		os_memcpy(mac_s, tmp1 + 8, 8); /* f1* */
+	return 0;
+}
+
+
+/**
+ * milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms
+ * @opc: OPc = 128-bit value derived from OP and K
+ * @k: K = 128-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @res: Buffer for RES = 64-bit signed response (f2), or %NULL
+ * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
+ * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
+ * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL
+ * @akstar: Buffer for AK = 48-bit anonymity key (f5*), or %NULL
+ * Returns: 0 on success, -1 on failure
+ */
+int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand,
+		   u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar)
+{
+	u8 tmp1[16], tmp2[16], tmp3[16];
+	int i;
+
+	/* tmp2 = TEMP = E_K(RAND XOR OP_C) */
+	for (i = 0; i < 16; i++)
+		tmp1[i] = _rand[i] ^ opc[i];
+	if (aes_128_encrypt_block(k, tmp1, tmp2))
+		return -1;
+
+	/* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */
+	/* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */
+	/* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */
+	/* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */
+
+	/* f2 and f5 */
+	/* rotate by r2 (= 0, i.e., NOP) */
+	for (i = 0; i < 16; i++)
+		tmp1[i] = tmp2[i] ^ opc[i];
+	tmp1[15] ^= 1; /* XOR c2 (= ..01) */
+	/* f5 || f2 = E_K(tmp1) XOR OP_c */
+	if (aes_128_encrypt_block(k, tmp1, tmp3))
+		return -1;
+	for (i = 0; i < 16; i++)
+		tmp3[i] ^= opc[i];
+	if (res)
+		os_memcpy(res, tmp3 + 8, 8); /* f2 */
+	if (ak)
+		os_memcpy(ak, tmp3, 6); /* f5 */
+
+	/* f3 */
+	if (ck) {
+		/* rotate by r3 = 0x20 = 4 bytes */
+		for (i = 0; i < 16; i++)
+			tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 2; /* XOR c3 (= ..02) */
+		if (aes_128_encrypt_block(k, tmp1, ck))
+			return -1;
+		for (i = 0; i < 16; i++)
+			ck[i] ^= opc[i];
+	}
+
+	/* f4 */
+	if (ik) {
+		/* rotate by r4 = 0x40 = 8 bytes */
+		for (i = 0; i < 16; i++)
+			tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 4; /* XOR c4 (= ..04) */
+		if (aes_128_encrypt_block(k, tmp1, ik))
+			return -1;
+		for (i = 0; i < 16; i++)
+			ik[i] ^= opc[i];
+	}
+
+	/* f5* */
+	if (akstar) {
+		/* rotate by r5 = 0x60 = 12 bytes */
+		for (i = 0; i < 16; i++)
+			tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i];
+		tmp1[15] ^= 8; /* XOR c5 (= ..08) */
+		if (aes_128_encrypt_block(k, tmp1, tmp1))
+			return -1;
+		for (i = 0; i < 6; i++)
+			akstar[i] = tmp1[i] ^ opc[i];
+	}
+
+	return 0;
+}
+
+
+/**
+ * milenage_generate - Generate AKA AUTN,IK,CK,RES
+ * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
+ * @amf: AMF = 16-bit authentication management field
+ * @k: K = 128-bit subscriber key
+ * @sqn: SQN = 48-bit sequence number
+ * @_rand: RAND = 128-bit random challenge
+ * @autn: Buffer for AUTN = 128-bit authentication token
+ * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
+ * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
+ * @res: Buffer for RES = 64-bit signed response (f2), or %NULL
+ * @res_len: Max length for res; set to used length or 0 on failure
+ */
+void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k,
+		       const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik,
+		       u8 *ck, u8 *res, size_t *res_len)
+{
+	int i;
+	u8 mac_a[8], ak[6];
+
+	if (*res_len < 8) {
+		*res_len = 0;
+		return;
+	}
+	if (milenage_f1(opc, k, _rand, sqn, amf, mac_a, NULL) ||
+	    milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) {
+		*res_len = 0;
+		return;
+	}
+	*res_len = 8;
+
+	/* AUTN = (SQN ^ AK) || AMF || MAC */
+	for (i = 0; i < 6; i++)
+		autn[i] = sqn[i] ^ ak[i];
+	os_memcpy(autn + 6, amf, 2);
+	os_memcpy(autn + 8, mac_a, 8);
+}
+
+
+/**
+ * milenage_auts - Milenage AUTS validation
+ * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
+ * @k: K = 128-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @auts: AUTS = 112-bit authentication token from client
+ * @sqn: Buffer for SQN = 48-bit sequence number
+ * Returns: 0 = success (sqn filled), -1 on failure
+ */
+int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts,
+		  u8 *sqn)
+{
+	u8 amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+	u8 ak[6], mac_s[8];
+	int i;
+
+	if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
+		return -1;
+	for (i = 0; i < 6; i++)
+		sqn[i] = auts[i] ^ ak[i];
+	if (milenage_f1(opc, k, _rand, sqn, amf, NULL, mac_s) ||
+	    memcmp(mac_s, auts + 6, 8) != 0)
+		return -1;
+	return 0;
+}
+
+
+/**
+ * gsm_milenage - Generate GSM-Milenage (3GPP TS 55.205) authentication triplet
+ * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
+ * @k: K = 128-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @sres: Buffer for SRES = 32-bit SRES
+ * @kc: Buffer for Kc = 64-bit Kc
+ * Returns: 0 on success, -1 on failure
+ */
+int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc)
+{
+	u8 res[8], ck[16], ik[16];
+	int i;
+
+	if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL))
+		return -1;
+
+	for (i = 0; i < 8; i++)
+		kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8];
+
+#ifdef GSM_MILENAGE_ALT_SRES
+	os_memcpy(sres, res, 4);
+#else /* GSM_MILENAGE_ALT_SRES */
+	for (i = 0; i < 4; i++)
+		sres[i] = res[i] ^ res[i + 4];
+#endif /* GSM_MILENAGE_ALT_SRES */
+	return 0;
+}
+
+
+/**
+ * milenage_generate - Generate AKA AUTN,IK,CK,RES
+ * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
+ * @k: K = 128-bit subscriber key
+ * @sqn: SQN = 48-bit sequence number
+ * @_rand: RAND = 128-bit random challenge
+ * @autn: AUTN = 128-bit authentication token
+ * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
+ * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
+ * @res: Buffer for RES = 64-bit signed response (f2), or %NULL
+ * @res_len: Variable that will be set to RES length
+ * @auts: 112-bit buffer for AUTS
+ * Returns: 0 on success, -1 on failure, or -2 on synchronization failure
+ */
+int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand,
+		   const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len,
+		   u8 *auts)
+{
+	int i;
+	u8 mac_a[8], ak[6], rx_sqn[6];
+	const u8 *amf;
+
+	wpa_hexdump(MSG_DEBUG, "Milenage: AUTN", autn, 16);
+	wpa_hexdump(MSG_DEBUG, "Milenage: RAND", _rand, 16);
+
+	if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL))
+		return -1;
+
+	*res_len = 8;
+	wpa_hexdump_key(MSG_DEBUG, "Milenage: RES", res, *res_len);
+	wpa_hexdump_key(MSG_DEBUG, "Milenage: CK", ck, 16);
+	wpa_hexdump_key(MSG_DEBUG, "Milenage: IK", ik, 16);
+	wpa_hexdump_key(MSG_DEBUG, "Milenage: AK", ak, 6);
+
+	/* AUTN = (SQN ^ AK) || AMF || MAC */
+	for (i = 0; i < 6; i++)
+		rx_sqn[i] = autn[i] ^ ak[i];
+	wpa_hexdump(MSG_DEBUG, "Milenage: SQN", rx_sqn, 6);
+
+	if (os_memcmp(rx_sqn, sqn, 6) <= 0) {
+		u8 auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+		if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
+			return -1;
+		wpa_hexdump_key(MSG_DEBUG, "Milenage: AK*", ak, 6);
+		for (i = 0; i < 6; i++)
+			auts[i] = sqn[i] ^ ak[i];
+		if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6))
+			return -1;
+		wpa_hexdump(MSG_DEBUG, "Milenage: AUTS", auts, 14);
+		return -2;
+	}
+
+	amf = autn + 6;
+	wpa_hexdump(MSG_DEBUG, "Milenage: AMF", amf, 2);
+	if (milenage_f1(opc, k, _rand, rx_sqn, amf, mac_a, NULL))
+		return -1;
+
+	wpa_hexdump(MSG_DEBUG, "Milenage: MAC_A", mac_a, 8);
+
+	if (os_memcmp(mac_a, autn + 8, 8) != 0) {
+		wpa_printf(MSG_DEBUG, "Milenage: MAC mismatch");
+		wpa_hexdump(MSG_DEBUG, "Milenage: Received MAC_A",
+			    autn + 8, 8);
+		return -1;
+	}
+
+	return 0;
+}
+
+int milenage_opc_gen(u8 *opc, const u8 *k, const u8 *op)
+{
+	int i;
+
+	/* Encrypt OP using K */
+	if (aes_128_encrypt_block(k, op, opc))
+		return -1;
+
+	/* XOR the resulting Ek(OP) with OP */
+	for (i = 0; i < 16; i++)
+		opc[i] = opc[i] ^ op[i];
+
+	return 0;
+}
diff --git a/src/gsm/milenage/milenage.h b/src/gsm/milenage/milenage.h
new file mode 100644
index 0000000..a91e946
--- /dev/null
+++ b/src/gsm/milenage/milenage.h
@@ -0,0 +1,35 @@
+/*
+ * UMTS AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
+ * Copyright (c) 2006-2007 <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef MILENAGE_H
+#define MILENAGE_H
+
+void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k,
+		       const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik,
+		       u8 *ck, u8 *res, size_t *res_len);
+int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts,
+		  u8 *sqn);
+int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres,
+		 u8 *kc);
+int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand,
+		   const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len,
+		   u8 *auts);
+int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand,
+		const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s);
+int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand,
+		   u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar);
+
+int milenage_opc_gen(u8 *opc, const u8 *k, const u8 *op);
+
+#endif /* MILENAGE_H */
diff --git a/src/gsm/rsl.c b/src/gsm/rsl.c
new file mode 100644
index 0000000..5693b4f
--- /dev/null
+++ b/src/gsm/rsl.c
@@ -0,0 +1,507 @@
+/* GSM Radio Signalling Link messages on the A-bis interface 
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/rsl.h>
+
+/*! \addtogroup rsl
+ *  @{
+ */
+
+/*! \file rsl.c */
+
+/*! \brief Size for RSL \ref msgb_alloc */
+#define RSL_ALLOC_SIZE		200
+/*! \brief Headroom size for RSL \ref msgb_alloc */
+#define RSL_ALLOC_HEADROOM	56
+
+/*! \brief Initialize a RSL RLL header */
+void rsl_init_rll_hdr(struct abis_rsl_rll_hdr *dh, uint8_t msg_type)
+{
+	dh->c.msg_discr = ABIS_RSL_MDISC_RLL;
+	dh->c.msg_type = msg_type;
+	dh->ie_chan = RSL_IE_CHAN_NR;
+	dh->ie_link_id = RSL_IE_LINK_IDENT;
+}
+
+/*! \brief Initialize a RSL Common Channel header */
+void rsl_init_cchan_hdr(struct abis_rsl_cchan_hdr *ch, uint8_t msg_type)
+{
+	ch->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+	ch->c.msg_type = msg_type;
+	ch->ie_chan = RSL_IE_CHAN_NR;
+}
+
+/* \brief TLV parser definition for RSL */
+const struct tlv_definition rsl_att_tlvdef = {
+	.def = {
+		[RSL_IE_CHAN_NR]		= { TLV_TYPE_TV },
+		[RSL_IE_LINK_IDENT]		= { TLV_TYPE_TV },
+		[RSL_IE_ACT_TYPE]		= { TLV_TYPE_TV },
+		[RSL_IE_BS_POWER]		= { TLV_TYPE_TV },
+		[RSL_IE_CHAN_IDENT]		= { TLV_TYPE_TLV },
+		[RSL_IE_CHAN_MODE]		= { TLV_TYPE_TLV },
+		[RSL_IE_ENCR_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_FRAME_NUMBER]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_HANDO_REF]		= { TLV_TYPE_TV },
+		[RSL_IE_L1_INFO]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_L3_INFO]		= { TLV_TYPE_TL16V },
+		[RSL_IE_MS_IDENTITY]		= { TLV_TYPE_TLV },
+		[RSL_IE_MS_POWER]		= { TLV_TYPE_TV },
+		[RSL_IE_PAGING_GROUP]		= { TLV_TYPE_TV },
+		[RSL_IE_PAGING_LOAD]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_PYHS_CONTEXT]		= { TLV_TYPE_TLV },
+		[RSL_IE_ACCESS_DELAY]		= { TLV_TYPE_TV },
+		[RSL_IE_RACH_LOAD]		= { TLV_TYPE_TLV },
+		[RSL_IE_REQ_REFERENCE]		= { TLV_TYPE_FIXED, 3 },
+		[RSL_IE_RELEASE_MODE]		= { TLV_TYPE_TV },
+		[RSL_IE_RESOURCE_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_RLM_CAUSE]		= { TLV_TYPE_TLV },
+		[RSL_IE_STARTNG_TIME]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_TIMING_ADVANCE]		= { TLV_TYPE_TV },
+		[RSL_IE_UPLINK_MEAS]		= { TLV_TYPE_TLV },
+		[RSL_IE_CAUSE]			= { TLV_TYPE_TLV },
+		[RSL_IE_MEAS_RES_NR]		= { TLV_TYPE_TV },
+		[RSL_IE_MSG_ID]			= { TLV_TYPE_TV },
+		[RSL_IE_SYSINFO_TYPE]		= { TLV_TYPE_TV },
+		[RSL_IE_MS_POWER_PARAM]		= { TLV_TYPE_TLV },
+		[RSL_IE_BS_POWER_PARAM]		= { TLV_TYPE_TLV },
+		[RSL_IE_PREPROC_PARAM]		= { TLV_TYPE_TLV },
+		[RSL_IE_PREPROC_MEAS]		= { TLV_TYPE_TLV },
+		[RSL_IE_IMM_ASS_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_SMSCB_INFO]		= { TLV_TYPE_FIXED, 23 },
+		[RSL_IE_MS_TIMING_OFFSET]	= { TLV_TYPE_TV },
+		[RSL_IE_ERR_MSG]		= { TLV_TYPE_TLV },
+		[RSL_IE_FULL_BCCH_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_CHAN_NEEDED]		= { TLV_TYPE_TV },
+		[RSL_IE_CB_CMD_TYPE]		= { TLV_TYPE_TV },
+		[RSL_IE_SMSCB_MSG]		= { TLV_TYPE_TLV },
+		[RSL_IE_FULL_IMM_ASS_INFO]	= { TLV_TYPE_TLV },
+		[RSL_IE_SACCH_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_CBCH_LOAD_INFO]		= { TLV_TYPE_TV },
+		[RSL_IE_SMSCB_CHAN_INDICATOR]	= { TLV_TYPE_TV },
+		[RSL_IE_GROUP_CALL_REF]		= { TLV_TYPE_TLV },
+		[RSL_IE_CHAN_DESC]		= { TLV_TYPE_TLV },
+		[RSL_IE_NCH_DRX_INFO]		= { TLV_TYPE_TLV },
+		[RSL_IE_CMD_INDICATOR]		= { TLV_TYPE_TLV },
+		[RSL_IE_EMLPP_PRIO]		= { TLV_TYPE_TV },
+		[RSL_IE_UIC]			= { TLV_TYPE_TLV },
+		[RSL_IE_MAIN_CHAN_REF]		= { TLV_TYPE_TV },
+		[RSL_IE_MR_CONFIG]		= { TLV_TYPE_TLV },
+		[RSL_IE_MR_CONTROL]		= { TLV_TYPE_TV },
+		[RSL_IE_SUP_CODEC_TYPES]	= { TLV_TYPE_TLV },
+		[RSL_IE_CODEC_CONFIG]		= { TLV_TYPE_TLV },
+		[RSL_IE_RTD]			= { TLV_TYPE_TV },
+		[RSL_IE_TFO_STATUS]		= { TLV_TYPE_TV },
+		[RSL_IE_LLP_APDU]		= { TLV_TYPE_TLV },
+		[RSL_IE_SIEMENS_MRPCI]		= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_PROXY_UDP]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_IPAC_BSCMPL_TOUT]	= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_REMOTE_IP]		= { TLV_TYPE_FIXED, 4 },
+		[RSL_IE_IPAC_REMOTE_PORT]	= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_IPAC_RTP_PAYLOAD]	= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_LOCAL_PORT]	= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_IPAC_SPEECH_MODE]	= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_LOCAL_IP]		= { TLV_TYPE_FIXED, 4 },
+		[RSL_IE_IPAC_CONN_ID]		= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_IPAC_RTP_CSD_FMT]	= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_RTP_JIT_BUF]	= { TLV_TYPE_FIXED, 2 },
+		[RSL_IE_IPAC_RTP_COMPR]		= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_RTP_PAYLOAD2]	= { TLV_TYPE_TV },
+		[RSL_IE_IPAC_RTP_MPLEX]		= { TLV_TYPE_FIXED, 8 },
+		[RSL_IE_IPAC_RTP_MPLEX_ID]	= { TLV_TYPE_TV },
+	},
+};
+
+/*! \brief Encode channel number as per Section 9.3.1 */
+uint8_t rsl_enc_chan_nr(uint8_t type, uint8_t subch, uint8_t timeslot)
+{
+	uint8_t ret;
+
+	ret = (timeslot & 0x07) | type;
+
+	switch (type) {
+	case RSL_CHAN_Lm_ACCHs:
+		subch &= 0x01;
+		break;
+	case RSL_CHAN_SDCCH4_ACCH:
+		subch &= 0x03;
+		break;
+	case RSL_CHAN_SDCCH8_ACCH:
+		subch &= 0x07;
+		break;
+	default:
+		/* no subchannels allowed */
+		subch = 0x00;
+		break;
+	}
+	ret |= (subch << 3);
+
+	return ret;
+}
+
+/*! \brief Decode RSL channel number
+ *  \param[in] chan_nr Channel Number
+ *  \param[out] type Channel Type
+ *  \param[out] subch Sub-channel Number
+ *  \param[out] timeslot Timeslot
+ */
+int rsl_dec_chan_nr(uint8_t chan_nr, uint8_t *type, uint8_t *subch, uint8_t *timeslot)
+{
+	*timeslot = chan_nr & 0x7;
+
+	if ((chan_nr & 0xf8) == RSL_CHAN_Bm_ACCHs) {
+		*type = RSL_CHAN_Bm_ACCHs;
+		*subch = 0;
+	} else if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) {
+		*type = RSL_CHAN_Lm_ACCHs;
+		*subch = (chan_nr >> 3) & 0x1;
+	} else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) {
+		*type = RSL_CHAN_SDCCH4_ACCH;
+		*subch = (chan_nr >> 3) & 0x3;
+	} else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) {
+		*type = RSL_CHAN_SDCCH8_ACCH;
+		*subch = (chan_nr >> 3) & 0x7;
+	} else if ((chan_nr & 0xf8) == RSL_CHAN_BCCH) {
+		*type = RSL_CHAN_BCCH;
+		*subch = 0;
+	} else if ((chan_nr & 0xf8) == RSL_CHAN_RACH) {
+		*type = RSL_CHAN_RACH;
+		*subch = 0;
+	} else if ((chan_nr & 0xf8) == RSL_CHAN_PCH_AGCH) {
+		*type = RSL_CHAN_PCH_AGCH;
+		*subch = 0;
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+/*! \brief Get human-readable string for RSL channel number */
+const char *rsl_chan_nr_str(uint8_t chan_nr)
+{
+	static char str[20];
+	int ts = chan_nr & 7;
+	uint8_t cbits = chan_nr >> 3;
+
+	if (cbits == 0x01)
+		sprintf(str, "TCH/F on TS%d", ts);
+	else if ((cbits & 0x1e) == 0x02)
+		sprintf(str, "TCH/H(%u) on TS%d", cbits & 0x01, ts);
+	else if ((cbits & 0x1c) == 0x04)
+		sprintf(str, "SDCCH/4(%u) on TS%d", cbits & 0x03, ts);
+	else if ((cbits & 0x18) == 0x08)
+		sprintf(str, "SDCCH/8(%u) on TS%d", cbits & 0x07, ts);
+	else if (cbits == 0x10)
+		sprintf(str, "BCCH on TS%d", ts);
+	else if (cbits == 0x11)
+		sprintf(str, "RACH on TS%d", ts);
+	else if (cbits == 0x12)
+		sprintf(str, "PCH/AGCH on TS%d", ts);
+	else
+		sprintf(str, "UNKNOWN on TS%d", ts);
+
+        return str;
+}
+
+static const struct value_string rsl_err_vals[] = {
+	{ RSL_ERR_RADIO_IF_FAIL,	"Radio Interface Failure" },
+	{ RSL_ERR_RADIO_LINK_FAIL,	"Radio Link Failure" },
+	{ RSL_ERR_HANDOVER_ACC_FAIL,	"Handover Access Failure" },
+	{ RSL_ERR_TALKER_ACC_FAIL,	"Talker Access Failure" },
+	{ RSL_ERR_OM_INTERVENTION,	"O&M Intervention" },
+	{ RSL_ERR_NORMAL_UNSPEC,	"Normal event, unspecified" },
+	{ RSL_ERR_T_MSRFPCI_EXP,	"Siemens: T_MSRFPCI Expired" },
+	{ RSL_ERR_EQUIPMENT_FAIL,	"Equipment Failure" },
+	{ RSL_ERR_RR_UNAVAIL,		"Radio Resource not available" },
+	{ RSL_ERR_TERR_CH_FAIL,		"Terrestrial Channel Failure" },
+	{ RSL_ERR_CCCH_OVERLOAD,	"CCCH Overload" },
+	{ RSL_ERR_ACCH_OVERLOAD,	"ACCH Overload" },
+	{ RSL_ERR_PROCESSOR_OVERLOAD,	"Processor Overload" },
+	{ RSL_ERR_RES_UNAVAIL,		"Resource not available, unspecified" },
+	{ RSL_ERR_TRANSC_UNAVAIL,	"Transcoding not available" },
+	{ RSL_ERR_SERV_OPT_UNAVAIL,	"Service or Option not available" },
+	{ RSL_ERR_ENCR_UNIMPL,		"Encryption algorithm not implemented" },
+	{ RSL_ERR_SERV_OPT_UNIMPL,	"Service or Option not implemented" },
+	{ RSL_ERR_RCH_ALR_ACTV_ALLOC,	"Radio channel already activated" },
+	{ RSL_ERR_INVALID_MESSAGE,	"Invalid Message, unspecified" },
+	{ RSL_ERR_MSG_DISCR,		"Message Discriminator Error" },
+	{ RSL_ERR_MSG_TYPE,		"Message Type Error" },
+	{ RSL_ERR_MSG_SEQ,		"Message Sequence Error" },
+	{ RSL_ERR_IE_ERROR,		"General IE error" },
+	{ RSL_ERR_MAND_IE_ERROR,	"Mandatory IE error" },
+	{ RSL_ERR_OPT_IE_ERROR,		"Optional IE error" },
+	{ RSL_ERR_IE_NONEXIST,		"IE non-existent" },
+	{ RSL_ERR_IE_LENGTH,		"IE length error" },
+	{ RSL_ERR_IE_CONTENT,		"IE content error" },
+	{ RSL_ERR_PROTO,		"Protocol error, unspecified" },
+	{ RSL_ERR_INTERWORKING,		"Interworking error, unspecified" },
+	{ 0,				NULL }
+};
+
+/*! \brief Get human-readable name for RSL Error */
+const char *rsl_err_name(uint8_t err)
+{
+	return get_value_string(rsl_err_vals, err);
+}
+
+/* Names for Radio Link Layer Management */
+static const struct value_string rsl_msgt_names[] = {
+	{ RSL_MT_DATA_REQ,		"DATA_REQ" },
+	{ RSL_MT_DATA_IND,		"DATA_IND" },
+	{ RSL_MT_ERROR_IND,		"ERROR_IND" },
+	{ RSL_MT_EST_REQ,		"EST_REQ" },
+	{ RSL_MT_EST_CONF,		"EST_CONF" },
+	{ RSL_MT_EST_IND,		"EST_IND" },
+	{ RSL_MT_REL_REQ,		"REL_REQ" },
+	{ RSL_MT_REL_CONF,		"REL_CONF" },
+	{ RSL_MT_REL_IND,		"REL_IND" },
+	{ RSL_MT_UNIT_DATA_REQ,		"UNIT_DATA_REQ" },
+	{ RSL_MT_UNIT_DATA_IND,		"UNIT_DATA_IND" },
+	{ RSL_MT_SUSP_REQ,		"SUSP_REQ" },
+	{ RSL_MT_SUSP_CONF,		"SUSP_CONF" },
+	{ RSL_MT_RES_REQ,		"RES_REQ" },
+	{ RSL_MT_RECON_REQ,		"RECON_REQ" },
+
+	{ RSL_MT_BCCH_INFO,		"BCCH_INFO" },
+	{ RSL_MT_CCCH_LOAD_IND,		"CCCH_LOAD_IND" },
+	{ RSL_MT_CHAN_RQD,		"CHAN_RQD" },
+	{ RSL_MT_DELETE_IND,		"DELETE_IND" },
+	{ RSL_MT_PAGING_CMD,		"PAGING_CMD" },
+	{ RSL_MT_IMMEDIATE_ASSIGN_CMD,	"IMM_ASS_CMD" },
+	{ RSL_MT_SMS_BC_REQ,		"SMS_BC_REQ" },
+	{ RSL_MT_CHAN_CONF,		"CHAN_CONF" },
+
+	{ RSL_MT_RF_RES_IND,		"RF_RES_IND" },
+	{ RSL_MT_SACCH_FILL,		"SACCH_FILL" },
+	{ RSL_MT_OVERLOAD,		"OVERLOAD" },
+	{ RSL_MT_ERROR_REPORT,		"ERROR_REPORT" },
+	{ RSL_MT_SMS_BC_CMD,		"SMS_BC_CMD" },
+	{ RSL_MT_CBCH_LOAD_IND,		"CBCH_LOAD_IND" },
+	{ RSL_MT_NOT_CMD,		"NOTIFY_CMD" },
+
+	{ RSL_MT_CHAN_ACTIV,		"CHAN_ACTIV" },
+	{ RSL_MT_CHAN_ACTIV_ACK,	"CHAN_ACTIV_ACK" },
+	{ RSL_MT_CHAN_ACTIV_NACK,	"CHAN_ACTIV_NACK" },
+	{ RSL_MT_CONN_FAIL,		"CONN_FAIL" },
+	{ RSL_MT_DEACTIVATE_SACCH,	"DEACTIVATE_SACCH" },
+	{ RSL_MT_ENCR_CMD,		"ENCR_CMD" },
+	{ RSL_MT_HANDO_DET,		"HANDOVER_DETECT" },
+	{ RSL_MT_MEAS_RES,		"MEAS_RES" },
+	{ RSL_MT_MODE_MODIFY_REQ,	"MODE_MODIFY_REQ" },
+	{ RSL_MT_MODE_MODIFY_ACK,	"MODE_MODIFY_ACK" },
+	{ RSL_MT_MODE_MODIFY_NACK,	"MODE_MODIFY_NACK" },
+	{ RSL_MT_PHY_CONTEXT_REQ,	"PHY_CONTEXT_REQ" },
+	{ RSL_MT_PHY_CONTEXT_CONF,	"PHY_CONTEXT_CONF" },
+	{ RSL_MT_RF_CHAN_REL,		"RF_CHAN_REL" },
+	{ RSL_MT_MS_POWER_CONTROL,	"MS_POWER_CONTROL" },
+	{ RSL_MT_BS_POWER_CONTROL,	"BS_POWER_CONTROL" },
+	{ RSL_MT_PREPROC_CONFIG,	"PREPROC_CONFIG" },
+	{ RSL_MT_PREPROC_MEAS_RES,	"PREPROC_MEAS_RES" },
+	{ RSL_MT_RF_CHAN_REL_ACK,	"RF_CHAN_REL_ACK" },
+	{ RSL_MT_SACCH_INFO_MODIFY,	"SACCH_INFO_MODIFY" },
+	{ RSL_MT_TALKER_DET,		"TALKER_DETECT" },
+	{ RSL_MT_LISTENER_DET,		"LISTENER_DETECT" },
+	{ RSL_MT_REMOTE_CODEC_CONF_REP,	"REM_CODEC_CONF_REP" },
+	{ RSL_MT_RTD_REP,		"RTD_REQ" },
+	{ RSL_MT_PRE_HANDO_NOTIF,	"HANDO_NOTIF" },
+	{ RSL_MT_MR_CODEC_MOD_REQ,	"CODEC_MOD_REQ" },
+	{ RSL_MT_MR_CODEC_MOD_ACK,	"CODEC_MOD_ACK" },
+	{ RSL_MT_MR_CODEC_MOD_NACK,	"CODEC_MOD_NACK" },
+	{ RSL_MT_MR_CODEC_MOD_PER,	"CODEC_MODE_PER" },
+	{ RSL_MT_TFO_REP,		"TFO_REP" },
+	{ RSL_MT_TFO_MOD_REQ,		"TFO_MOD_REQ" },
+	{ RSL_MT_LOCATION_INFO,		"LOCATION_INFO" },
+	{ 0,				NULL }
+};
+
+
+/*! \brief Get human-readable string for RSL Message Type */
+const char *rsl_msg_name(uint8_t msg_type)
+{
+	return get_value_string(rsl_msgt_names, msg_type);
+}
+
+/*! \brief ip.access specific */
+static const struct value_string rsl_ipac_msgt_names[] = {
+	{ RSL_MT_IPAC_PDCH_ACT,		"IPAC_PDCH_ACT" },
+	{ RSL_MT_IPAC_PDCH_ACT_ACK,	"IPAC_PDCH_ACT_ACK" },
+	{ RSL_MT_IPAC_PDCH_ACT_NACK,	"IPAC_PDCH_ACT_NACK" },
+	{ RSL_MT_IPAC_PDCH_DEACT,	"IPAC_PDCH_DEACT" },
+	{ RSL_MT_IPAC_PDCH_DEACT_ACK,	"IPAC_PDCH_DEACT_ACK" },
+	{ RSL_MT_IPAC_PDCH_DEACT_NACK,	"IPAC_PDCH_DEACT_NACK" },
+	{ RSL_MT_IPAC_CONNECT_MUX,	"IPAC_CONNECT_MUX" },
+	{ RSL_MT_IPAC_CONNECT_MUX_ACK,	"IPAC_CONNECT_MUX_ACK" },
+	{ RSL_MT_IPAC_CONNECT_MUX_NACK,	"IPAC_CONNECT_MUX_NACK" },
+	{ RSL_MT_IPAC_BIND_MUX,		"IPAC_BIND_MUX" },
+	{ RSL_MT_IPAC_BIND_MUX_ACK,	"IPAC_BIND_MUX_ACK" },
+	{ RSL_MT_IPAC_BIND_MUX_NACK,	"IPAC_BIND_MUX_NACK" },
+	{ RSL_MT_IPAC_DISC_MUX,		"IPAC_DISC_MUX" },
+	{ RSL_MT_IPAC_DISC_MUX_ACK,	"IPAC_DISC_MUX_ACK" },
+	{ RSL_MT_IPAC_DISC_MUX_NACK,	"IPAC_DISC_MUX_NACK" },
+	{ RSL_MT_IPAC_CRCX,		"IPAC_CRCX" },
+	{ RSL_MT_IPAC_CRCX_ACK,		"IPAC_CRCX_ACK" },
+	{ RSL_MT_IPAC_CRCX_NACK,	"IPAC_CRCX_NACK" },
+	{ RSL_MT_IPAC_MDCX,		"IPAC_MDCX" },
+	{ RSL_MT_IPAC_MDCX_ACK,		"IPAC_MDCX_ACK" },
+	{ RSL_MT_IPAC_MDCX_NACK,	"IPAC_MDCX_NACK" },
+	{ RSL_MT_IPAC_DLCX_IND,		"IPAC_DLCX_IND" },
+	{ RSL_MT_IPAC_DLCX,		"IPAC_DLCX" },
+	{ RSL_MT_IPAC_DLCX_ACK,		"IPAC_DLCX_ACK" },
+	{ RSL_MT_IPAC_DLCX_NACK,	"IPAC_DLCX_NACK" },
+	{ 0, NULL }
+};
+
+/*! \brief Get human-readable name of ip.access RSL msg type */
+const char *rsl_ipac_msg_name(uint8_t msg_type)
+{
+	return get_value_string(rsl_ipac_msgt_names, msg_type);
+}
+
+static const struct value_string rsl_rlm_cause_strs[] = {
+	{ RLL_CAUSE_T200_EXPIRED,	"Timer T200 expired (N200+1) times" },
+	{ RLL_CAUSE_REEST_REQ,		"Re-establishment request" },
+	{ RLL_CAUSE_UNSOL_UA_RESP,	"Unsolicited UA response" },
+	{ RLL_CAUSE_UNSOL_DM_RESP,	"Unsolicited DM response" },
+	{ RLL_CAUSE_UNSOL_DM_RESP_MF,	"Unsolicited DM response, multiple frame" },
+	{ RLL_CAUSE_UNSOL_SPRV_RESP,	"Unsolicited supervisory response" },
+	{ RLL_CAUSE_SEQ_ERR,		"Sequence Error" },
+	{ RLL_CAUSE_UFRM_INC_PARAM,	"U-Frame with incorrect parameters" },
+	{ RLL_CAUSE_SFRM_INC_PARAM,	"S-Frame with incorrect parameters" },
+	{ RLL_CAUSE_IFRM_INC_MBITS,	"I-Frame with incorrect use of M bit" },
+	{ RLL_CAUSE_IFRM_INC_LEN,	"I-Frame with incorrect length" },
+	{ RLL_CAUSE_FRM_UNIMPL,		"Fraeme not implemented" },
+	{ RLL_CAUSE_SABM_MF,		"SABM command, multiple frame established state" },
+	{ RLL_CAUSE_SABM_INFO_NOTALL,	"SABM frame with information not allowed in this state" },
+	{ 0,				NULL },
+};
+
+/*! \brief Get human-readable string for RLM cause */
+const char *rsl_rlm_cause_name(uint8_t err)
+{
+	return get_value_string(rsl_rlm_cause_strs, err);
+}
+
+/* Section 3.3.2.3 TS 05.02. I think this looks like a table */
+int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf)
+{
+	switch (ccch_conf) {
+	case RSL_BCCH_CCCH_CONF_1_NC:
+		return 1;
+	case RSL_BCCH_CCCH_CONF_1_C:
+		return 1;
+	case RSL_BCCH_CCCH_CONF_2_NC:
+		return 2;
+	case RSL_BCCH_CCCH_CONF_3_NC:
+		return 3;
+	case RSL_BCCH_CCCH_CONF_4_NC:
+		return 4;
+	default:
+		return -1;
+	}
+}
+
+/* Section 3.3.2.3 TS 05.02 */
+int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf)
+{
+	switch (ccch_conf) {
+	case RSL_BCCH_CCCH_CONF_1_NC:
+		return 0;
+	case RSL_BCCH_CCCH_CONF_1_C:
+		return 1;
+	case RSL_BCCH_CCCH_CONF_2_NC:
+		return 0;
+	case RSL_BCCH_CCCH_CONF_3_NC:
+		return 0;
+	case RSL_BCCH_CCCH_CONF_4_NC:
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+/*! \brief Push a RSL RLL header onto an existing msgb */
+void rsl_rll_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr,
+		      uint8_t link_id, int transparent)
+{
+	struct abis_rsl_rll_hdr *rh;
+
+	rh = (struct abis_rsl_rll_hdr *) msgb_push(msg, sizeof(*rh));
+	rsl_init_rll_hdr(rh, msg_type);
+	if (transparent)
+		rh->c.msg_discr |= ABIS_RSL_MDISC_TRANSP;
+	rh->chan_nr = chan_nr;
+	rh->link_id = link_id;
+
+	/* set the l2 header pointer */
+	msg->l2h = (uint8_t *)rh;
+}
+
+/*! \brief Push a RSL RLL header with L3_INFO IE */
+void rsl_rll_push_l3(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr,
+		     uint8_t link_id, int transparent)
+{
+	uint8_t l3_len = msg->tail - (uint8_t *)msgb_l3(msg);
+
+	/* construct a RSLms RLL message (DATA INDICATION, UNIT DATA
+	 * INDICATION) and send it off via RSLms */
+
+	/* Push the L3 IE tag and lengh */
+	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+
+	/* Then push the RSL header */
+	rsl_rll_push_hdr(msg, msg_type, chan_nr, link_id, transparent);
+}
+
+/*! \brief Create msgb with RSL RLL header */
+struct msgb *rsl_rll_simple(uint8_t msg_type, uint8_t chan_nr,
+			    uint8_t link_id, int transparent)
+{
+	struct abis_rsl_rll_hdr *rh;
+	struct msgb *msg;
+
+	msg = msgb_alloc_headroom(RSL_ALLOC_SIZE+RSL_ALLOC_HEADROOM,
+				  RSL_ALLOC_HEADROOM, "rsl_rll_simple");
+
+	if (!msg)
+		return NULL;
+
+	/* put the RSL header */
+	rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+	rsl_init_rll_hdr(rh, msg_type);
+	if (transparent)
+		rh->c.msg_discr |= ABIS_RSL_MDISC_TRANSP;
+	rh->chan_nr = chan_nr;
+	rh->link_id = link_id;
+
+	/* set the l2 header pointer */
+	msg->l2h = (uint8_t *)rh;
+
+	return msg;
+}
+
+/*! @} */
diff --git a/src/gsm/rxlev_stat.c b/src/gsm/rxlev_stat.c
new file mode 100644
index 0000000..d226861
--- /dev/null
+++ b/src/gsm/rxlev_stat.c
@@ -0,0 +1,82 @@
+/* Rx Level statistics */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/gsm/rxlev_stat.h>
+
+void rxlev_stat_input(struct rxlev_stats *st, uint16_t arfcn, uint8_t rxlev)
+{
+	struct bitvec bv;
+
+	if (rxlev >= NUM_RXLEVS)
+		rxlev = NUM_RXLEVS-1;
+
+	bv.data_len = NUM_ARFCNS/8;
+	bv.data = st->rxlev_buckets[rxlev];
+
+	bitvec_set_bit_pos(&bv, arfcn, ONE);
+}
+
+/* get the next ARFCN that has the specified Rxlev */
+int16_t rxlev_stat_get_next(const struct rxlev_stats *st, uint8_t rxlev, int16_t arfcn)
+{
+	struct bitvec bv;
+
+	if (rxlev >= NUM_RXLEVS)
+		rxlev = NUM_RXLEVS-1;
+
+	bv.data_len = NUM_ARFCNS/8;
+
+	if (arfcn < 0)
+		arfcn = -1;
+
+	bv.data = (uint8_t *) st->rxlev_buckets[rxlev];
+
+	return bitvec_find_bit_pos(&bv, arfcn+1, ONE);
+}
+
+void rxlev_stat_reset(struct rxlev_stats *st)
+{
+	memset(st, 0, sizeof(*st));
+}
+
+void rxlev_stat_dump(const struct rxlev_stats *st)
+{
+	int i;
+
+	for (i = NUM_RXLEVS-1; i >= 0; i--) {
+		int16_t arfcn = -1;
+
+		printf("ARFCN with RxLev %u: ", i);
+		while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) {
+			printf("%u ", arfcn);
+		}
+		printf("\n");
+	}
+}
diff --git a/src/gsm/sysinfo.c b/src/gsm/sysinfo.c
new file mode 100644
index 0000000..1408f6b
--- /dev/null
+++ b/src/gsm/sysinfo.c
@@ -0,0 +1,136 @@
+/* GSM 04.08 System Information (SI) encoding and decoding
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+/* verify the sizes of the system information type structs */
+
+/* rest octets are not part of the struct */
+osmo_static_assert(sizeof(struct gsm48_system_information_type_header) == 3, _si_header_size);
+osmo_static_assert(sizeof(struct gsm48_rach_control) == 3, _si_rach_control);
+osmo_static_assert(sizeof(struct gsm48_system_information_type_1) == 22, _si1_size);
+osmo_static_assert(sizeof(struct gsm48_system_information_type_2) == 23, _si2_size);
+osmo_static_assert(sizeof(struct gsm48_system_information_type_3) == 19, _si3_size);
+osmo_static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_size);
+
+/* bs11 forgot the l2 len, 0-6 rest octets */
+osmo_static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
+osmo_static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
+
+osmo_static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
+
+static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = {
+	[SYSINFO_TYPE_1]	= RSL_SYSTEM_INFO_1,
+	[SYSINFO_TYPE_2]	= RSL_SYSTEM_INFO_2,
+	[SYSINFO_TYPE_3]	= RSL_SYSTEM_INFO_3,
+	[SYSINFO_TYPE_4]	= RSL_SYSTEM_INFO_4,
+	[SYSINFO_TYPE_5]	= RSL_SYSTEM_INFO_5,
+	[SYSINFO_TYPE_6]	= RSL_SYSTEM_INFO_6,
+	[SYSINFO_TYPE_7]	= RSL_SYSTEM_INFO_7,
+	[SYSINFO_TYPE_8]	= RSL_SYSTEM_INFO_8,
+	[SYSINFO_TYPE_9]	= RSL_SYSTEM_INFO_9,
+	[SYSINFO_TYPE_10]	= RSL_SYSTEM_INFO_10,
+	[SYSINFO_TYPE_13]	= RSL_SYSTEM_INFO_13,
+	[SYSINFO_TYPE_16]	= RSL_SYSTEM_INFO_16,
+	[SYSINFO_TYPE_17]	= RSL_SYSTEM_INFO_17,
+	[SYSINFO_TYPE_18]	= RSL_SYSTEM_INFO_18,
+	[SYSINFO_TYPE_19]	= RSL_SYSTEM_INFO_19,
+	[SYSINFO_TYPE_20]	= RSL_SYSTEM_INFO_20,
+	[SYSINFO_TYPE_2bis]	= RSL_SYSTEM_INFO_2bis,
+	[SYSINFO_TYPE_2ter]	= RSL_SYSTEM_INFO_2ter,
+	[SYSINFO_TYPE_2quater]	= RSL_SYSTEM_INFO_2quater,
+	[SYSINFO_TYPE_5bis]	= RSL_SYSTEM_INFO_5bis,
+	[SYSINFO_TYPE_5ter]	= RSL_SYSTEM_INFO_5ter,
+	[SYSINFO_TYPE_EMO]	= RSL_EXT_MEAS_ORDER,
+	[SYSINFO_TYPE_MEAS_INFO]= RSL_MEAS_INFO,
+};
+
+static const uint8_t rsl2sitype[256] = {
+	[RSL_SYSTEM_INFO_1] = SYSINFO_TYPE_1,
+	[RSL_SYSTEM_INFO_2] = SYSINFO_TYPE_2,
+	[RSL_SYSTEM_INFO_3] = SYSINFO_TYPE_3,
+	[RSL_SYSTEM_INFO_4] = SYSINFO_TYPE_4,
+	[RSL_SYSTEM_INFO_5] = SYSINFO_TYPE_5,
+	[RSL_SYSTEM_INFO_6] = SYSINFO_TYPE_6,
+	[RSL_SYSTEM_INFO_7] = SYSINFO_TYPE_7,
+	[RSL_SYSTEM_INFO_8] = SYSINFO_TYPE_8,
+	[RSL_SYSTEM_INFO_9] = SYSINFO_TYPE_9,
+	[RSL_SYSTEM_INFO_10] = SYSINFO_TYPE_10,
+	[RSL_SYSTEM_INFO_13] = SYSINFO_TYPE_13,
+	[RSL_SYSTEM_INFO_16] = SYSINFO_TYPE_16,
+	[RSL_SYSTEM_INFO_17] = SYSINFO_TYPE_17,
+	[RSL_SYSTEM_INFO_18] = SYSINFO_TYPE_18,
+	[RSL_SYSTEM_INFO_19] = SYSINFO_TYPE_19,
+	[RSL_SYSTEM_INFO_20] = SYSINFO_TYPE_20,
+	[RSL_SYSTEM_INFO_2bis] = SYSINFO_TYPE_2bis,
+	[RSL_SYSTEM_INFO_2ter] = SYSINFO_TYPE_2ter,
+	[RSL_SYSTEM_INFO_2quater] = SYSINFO_TYPE_2quater,
+	[RSL_SYSTEM_INFO_5bis] = SYSINFO_TYPE_5bis,
+	[RSL_SYSTEM_INFO_5ter] = SYSINFO_TYPE_5ter,
+	[RSL_EXT_MEAS_ORDER] = SYSINFO_TYPE_EMO,
+	[RSL_MEAS_INFO] = SYSINFO_TYPE_MEAS_INFO,
+};
+
+const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE] = {
+	{ SYSINFO_TYPE_1,	"1" },
+	{ SYSINFO_TYPE_2,	"2" },
+	{ SYSINFO_TYPE_3,	"3" },
+	{ SYSINFO_TYPE_4,	"4" },
+	{ SYSINFO_TYPE_5,	"5" },
+	{ SYSINFO_TYPE_6,	"6" },
+	{ SYSINFO_TYPE_7,	"7" },
+	{ SYSINFO_TYPE_8,	"8" },
+	{ SYSINFO_TYPE_9,	"9" },
+	{ SYSINFO_TYPE_10,	"10" },
+	{ SYSINFO_TYPE_13,	"13" },
+	{ SYSINFO_TYPE_16,	"16" },
+	{ SYSINFO_TYPE_17,	"17" },
+	{ SYSINFO_TYPE_18,	"18" },
+	{ SYSINFO_TYPE_19,	"19" },
+	{ SYSINFO_TYPE_20,	"20" },
+	{ SYSINFO_TYPE_2bis,	"2bis" },
+	{ SYSINFO_TYPE_2ter,	"2ter" },
+	{ SYSINFO_TYPE_2quater,	"2quater" },
+	{ SYSINFO_TYPE_5bis,	"5bis" },
+	{ SYSINFO_TYPE_5ter,	"5ter" },
+	{ SYSINFO_TYPE_EMO,	"EMO" },
+	{ SYSINFO_TYPE_MEAS_INFO, "MI" },
+	{ 0, NULL }
+};
+
+uint8_t osmo_sitype2rsl(enum osmo_sysinfo_type si_type)
+{
+	return sitype2rsl[si_type];
+}
+
+enum osmo_sysinfo_type osmo_rsl2sitype(uint8_t rsl_si)
+{
+	return rsl2sitype[rsl_si];
+}
diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c
new file mode 100644
index 0000000..0ac9092
--- /dev/null
+++ b/src/gsm/tlv_parser.c
@@ -0,0 +1,189 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/tlv.h>
+
+/*! \addtogroup tlv
+ *  @{
+ */
+/*! \file tlv.c */
+
+struct tlv_definition tvlv_att_def;
+
+/*! \brief Dump pasred TLV structure to stdout */
+int tlv_dump(struct tlv_parsed *dec)
+{
+	int i;
+
+	for (i = 0; i <= 0xff; i++) {
+		if (!dec->lv[i].val)
+			continue;
+		printf("T=%02x L=%d\n", i, dec->lv[i].len);
+	}
+	return 0;
+}
+
+/*! \brief Parse a single TLV encoded IE
+ *  \param[out] o_tag the tag of the IE that was found
+ *  \param[out] o_len length of the IE that was found
+ *  \param[out] o_val pointer to the data of the IE that was found
+ *  \param[in] def structure defining the valid TLV tags / configurations
+ *  \param[in] buf the input data buffer to be parsed
+ *  \param[in] buf_len length of the input data buffer
+ *  \returns number of bytes consumed by the TLV entry / IE parsed
+ */
+int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
+		  const struct tlv_definition *def,
+		  const uint8_t *buf, int buf_len)
+{
+	uint8_t tag;
+	int len;
+
+	tag = *buf;
+	*o_tag = tag;
+
+	/* single octet TV IE */
+	if (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV) {
+		*o_tag = tag & 0xf0;
+		*o_val = buf;
+		*o_len = 1;
+		return 1;
+	}
+
+	/* FIXME: use tables for knwon IEI */
+	switch (def->def[tag].type) {
+	case TLV_TYPE_T:
+		/* GSM TS 04.07 11.2.4: Type 1 TV or Type 2 T */
+		*o_val = buf;
+		*o_len = 0;
+		len = 1;
+		break;
+	case TLV_TYPE_TV:
+		*o_val = buf+1;
+		*o_len = 1;
+		len = 2;
+		break;
+	case TLV_TYPE_FIXED:
+		*o_val = buf+1;
+		*o_len = def->def[tag].fixed_len;
+		len = def->def[tag].fixed_len + 1;
+		break;
+	case TLV_TYPE_TLV:
+		/* GSM TS 04.07 11.2.4: Type 4 TLV */
+		if (buf + 1 > buf + buf_len)
+			return -1;
+		*o_val = buf+2;
+		*o_len = *(buf+1);
+		len = *o_len + 2;
+		if (len > buf_len)
+			return -2;
+		break;
+	case TLV_TYPE_TvLV:
+		if (*(buf+1) & 0x80) {
+			/* like TLV, but without highest bit of len */
+			if (buf + 1 > buf + buf_len)
+				return -1;
+			*o_val = buf+2;
+			*o_len = *(buf+1) & 0x7f;
+			len = *o_len + 2;
+			if (len > buf_len)
+				return -2;
+			break;
+		}
+		/* like TL16V, fallthrough */
+	case TLV_TYPE_TL16V:
+		if (2 > buf_len)
+			return -1;
+		*o_val = buf+3;
+		*o_len = *(buf+1) << 8 | *(buf+2);
+		len = *o_len + 3;
+		if (len > buf_len)
+			return -2;
+		break;
+	default:
+		return -3;
+	}
+
+	return len;
+}
+
+/*! \brief Parse an entire buffer of TLV encoded Information Eleemnts
+ *  \param[out] dec caller-allocated pointer to \ref tlv_parsed
+ *  \param[in] def structure defining the valid TLV tags / configurations
+ *  \param[in] buf the input data buffer to be parsed
+ *  \param[in] buf_len length of the input data buffer
+ *  \param[in] lv_tag an initial LV tag at the start of the buffer
+ *  \param[in] lv_tag2 a second initial LV tag following the \a lv_tag
+ *  \returns number of bytes consumed by the TLV entry / IE parsed
+ */
+int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def,
+	      const uint8_t *buf, int buf_len, uint8_t lv_tag,
+	      uint8_t lv_tag2)
+{
+	int ofs = 0, num_parsed = 0;
+	uint16_t len;
+
+	memset(dec, 0, sizeof(*dec));
+
+	if (lv_tag) {
+		if (ofs > buf_len)
+			return -1;
+		dec->lv[lv_tag].val = &buf[ofs+1];
+		dec->lv[lv_tag].len = buf[ofs];
+		len = dec->lv[lv_tag].len + 1;
+		if (ofs + len > buf_len)
+			return -2;
+		num_parsed++;
+		ofs += len;
+	}
+	if (lv_tag2) {
+		if (ofs > buf_len)
+			return -1;
+		dec->lv[lv_tag2].val = &buf[ofs+1];
+		dec->lv[lv_tag2].len = buf[ofs];
+		len = dec->lv[lv_tag2].len + 1;
+		if (ofs + len > buf_len)
+			return -2;
+		num_parsed++;
+		ofs += len;
+	}
+
+	while (ofs < buf_len) {
+		int rv;
+		uint8_t tag;
+		const uint8_t *val;
+
+		rv = tlv_parse_one(&tag, &len, &val, def,
+		                   &buf[ofs], buf_len-ofs);
+		if (rv < 0)
+			return rv;
+		dec->lv[tag].val = val;
+		dec->lv[tag].len = len;
+		ofs += rv;
+		num_parsed++;
+	}
+	//tlv_dump(dec);
+	return num_parsed;
+}
+
+/*! \brief take a master (src) tlvdev and fill up all empty slots in 'dst' */
+void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dst->def); i++) {
+		if (src->def[i].type == TLV_TYPE_NONE)
+			continue;
+		if (dst->def[i].type == TLV_TYPE_NONE)
+			dst->def[i] = src->def[i];
+	}
+}
+
+static __attribute__((constructor)) void on_dso_load_tlv(void)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(tvlv_att_def.def); i++)
+		tvlv_att_def.def[i].type = TLV_TYPE_TvLV;
+}
+
+/*! @} */
diff --git a/src/gsmtap_util.c b/src/gsmtap_util.c
new file mode 100644
index 0000000..ce722da
--- /dev/null
+++ b/src/gsmtap_util.c
@@ -0,0 +1,362 @@
+/* GSMTAP support code in libmsomcore */
+/*
+ * (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "../config.h"
+
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+/*! \addtogroup gsmtap
+ *  @{
+ */
+/*! \file gsmtap_util.c */
+
+
+/*! \brief convert RSL channel number to GSMTAP channel type
+ *  \param[in] rsl_cantype RSL channel type
+ *  \param[in] link_id RSL link identifier
+ *  \returns GSMTAP channel type
+ */
+uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t link_id)
+{
+	uint8_t ret = GSMTAP_CHANNEL_UNKNOWN;
+
+	switch (rsl_chantype) {
+	case RSL_CHAN_Bm_ACCHs:
+		ret = GSMTAP_CHANNEL_TCH_F;
+		break;
+	case RSL_CHAN_Lm_ACCHs:
+		ret = GSMTAP_CHANNEL_TCH_H;
+		break;
+	case RSL_CHAN_SDCCH4_ACCH:
+		ret = GSMTAP_CHANNEL_SDCCH4;
+		break;
+	case RSL_CHAN_SDCCH8_ACCH:
+		ret = GSMTAP_CHANNEL_SDCCH8;
+		break;
+	case RSL_CHAN_BCCH:
+		ret = GSMTAP_CHANNEL_BCCH;
+		break;
+	case RSL_CHAN_RACH:
+		ret = GSMTAP_CHANNEL_RACH;
+		break;
+	case RSL_CHAN_PCH_AGCH:
+		/* it could also be AGCH... */
+		ret = GSMTAP_CHANNEL_PCH;
+		break;
+	}
+
+	if (link_id & 0x40)
+		ret |= GSMTAP_CHANNEL_ACCH;
+
+	return ret;
+}
+
+/*! \brief create an arbitrary type GSMTAP message
+ *  \param[in] type The GSMTAP_TYPE_xxx constant of the message to create
+ *  \param[in] arfcn GSM ARFCN (Channel Number)
+ *  \param[in] ts GSM time slot
+ *  \param[in] chan_type Channel Type
+ *  \param[in] ss Sub-slot
+ *  \param[in] fn GSM Frame Number
+ *  \param[in] signal_dbm Signal Strength (dBm)
+ *  \param[in] snr Signal/Noise Ratio (SNR)
+ *  \param[in] data Pointer to data buffer
+ *  \param[in] len Length of \ref data
+ *
+ * This function will allocate a new msgb and fill it with a GSMTAP
+ * header containing the information
+ */
+struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
+			    uint8_t ss, uint32_t fn, int8_t signal_dbm,
+			    uint8_t snr, const uint8_t *data, unsigned int len)
+{
+	struct msgb *msg;
+	struct gsmtap_hdr *gh;
+	uint8_t *dst;
+
+	msg = msgb_alloc(sizeof(*gh) + len, "gsmtap_tx");
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsmtap_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->version = GSMTAP_VERSION;
+	gh->hdr_len = sizeof(*gh)/4;
+	gh->type = type;
+	gh->timeslot = ts;
+	gh->sub_slot = ss;
+	gh->arfcn = htons(arfcn);
+	gh->snr_db = snr;
+	gh->signal_dbm = signal_dbm;
+	gh->frame_number = htonl(fn);
+	gh->sub_type = chan_type;
+	gh->antenna_nr = 0;
+
+	dst = msgb_put(msg, len);
+	memcpy(dst, data, len);
+
+	return msg;
+}
+
+/*! \brief create L1/L2 data and put it into GSMTAP
+ *  \param[in] arfcn GSM ARFCN (Channel Number)
+ *  \param[in] ts GSM time slot
+ *  \param[in] chan_type Channel Type
+ *  \param[in] ss Sub-slot
+ *  \param[in] fn GSM Frame Number
+ *  \param[in] signal_dbm Signal Strength (dBm)
+ *  \param[in] snr Signal/Noise Ratio (SNR)
+ *  \param[in] data Pointer to data buffer
+ *  \param[in] len Length of \ref data
+ *
+ * This function will allocate a new msgb and fill it with a GSMTAP
+ * header containing the information
+ */
+struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
+			    uint8_t ss, uint32_t fn, int8_t signal_dbm,
+			    uint8_t snr, const uint8_t *data, unsigned int len)
+{
+	return gsmtap_makemsg_ex(GSMTAP_TYPE_UM, arfcn, ts, chan_type,
+		ss, fn, signal_dbm, snr, data, len);
+}
+
+#ifdef HAVE_SYS_SOCKET_H
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/*! \brief Create a new (sending) GSMTAP source socket 
+ *  \param[in] host host name or IP address in string format
+ *  \param[in] port UDP port number in host byte order
+ *
+ * Opens a GSMTAP source (sending) socket, conncet it to host/port and
+ * return resulting fd.  If \a host is NULL, the destination address
+ * will be localhost.  If \a port is 0, the default \ref
+ * GSMTAP_UDP_PORT will be used.
+ * */
+int gsmtap_source_init_fd(const char *host, uint16_t port)
+{
+	if (port == 0)
+		port = GSMTAP_UDP_PORT;
+	if (host == NULL)
+		host = "localhost";
+
+	return osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host, port,
+				OSMO_SOCK_F_CONNECT);
+}
+
+/*! \brief Add a local sink to an existing GSMTAP source and return fd */
+int gsmtap_source_add_sink_fd(int gsmtap_fd)
+{
+	struct sockaddr_storage ss;
+	socklen_t ss_len = sizeof(ss);
+	int rc;
+
+	rc = getpeername(gsmtap_fd, (struct sockaddr *)&ss, &ss_len);
+	if (rc < 0)
+		return rc;
+
+	if (osmo_sockaddr_is_local((struct sockaddr *)&ss, ss_len) == 1) {
+		rc = osmo_sock_init_sa((struct sockaddr *)&ss, SOCK_DGRAM,
+					IPPROTO_UDP, OSMO_SOCK_F_BIND);
+		if (rc >= 0)
+			return rc;
+	}
+
+	return -ENODEV;
+}
+
+/*! \brief Send a \ref msgb through a GSMTAP source
+ *  \param[in] gti GSMTAP instance
+ *  \param[in] msgb message buffer
+ */
+int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
+{
+	if (!gti)
+		return -ENODEV;
+
+	if (gti->ofd_wq_mode)
+		return osmo_wqueue_enqueue(&gti->wq, msg);
+	else {
+		/* try immediate send and return error if any */
+		int rc;
+
+		rc = write(gsmtap_inst_fd(gti), msg->data, msg->len);
+		if (rc <= 0) {
+			return rc;
+		} else if (rc >= msg->len) {
+			msgb_free(msg);
+			return 0;
+		} else {
+			/* short write */
+			return -EIO;
+		}
+	}
+}
+
+/*! \brief send an arbitrary type through GSMTAP.
+ *  See \ref gsmtap_makemsg_ex for arguments
+ */
+int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len)
+{
+	struct msgb *msg;
+
+	if (!gti)
+		return -ENODEV;
+
+	msg = gsmtap_makemsg_ex(type, arfcn, ts, chan_type, ss, fn, signal_dbm,
+			     snr, data, len);
+	if (!msg)
+		return -ENOMEM;
+
+	return gsmtap_sendmsg(gti, msg);
+}
+
+/*! \brief send a message from L1/L2 through GSMTAP.
+ *  See \ref gsmtap_makemsg for arguments
+ */
+int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len)
+{
+	return gsmtap_send_ex(gti, GSMTAP_TYPE_UM, arfcn, ts, chan_type, ss, fn,
+		signal_dbm, snr, data, len);
+}
+
+/* Callback from select layer if we can write to the socket */
+static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+	int rc;
+
+	rc = write(ofd->fd, msg->data, msg->len);
+	if (rc < 0) {
+		perror("writing msgb to gsmtap fd");
+		return rc;
+	}
+	if (rc != msg->len) {
+		perror("short write to gsmtap fd");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/* Callback from select layer if we can read from the sink socket */
+static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
+{
+	int rc;
+	uint8_t buf[4096];
+
+	if (!(flags & BSC_FD_READ))
+		return 0;
+
+	rc = read(fd->fd, buf, sizeof(buf));
+	if (rc < 0) {
+		perror("reading from gsmtap sink fd");
+		return rc;
+	}
+	/* simply discard any data arriving on the socket */
+
+	return 0;
+}
+
+/*! \brief Add a local sink to an existing GSMTAP source instance */
+int gsmtap_source_add_sink(struct gsmtap_inst *gti)
+{
+	int fd;
+
+	fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
+	if (fd < 0)
+		return fd;
+
+	if (gti->ofd_wq_mode) {
+		struct osmo_fd *sink_ofd;
+
+		sink_ofd = &gti->sink_ofd;
+		sink_ofd->fd = fd;
+		sink_ofd->when = BSC_FD_READ;
+		sink_ofd->cb = gsmtap_sink_fd_cb;
+
+		osmo_fd_register(sink_ofd);
+	}
+
+	return fd;
+}
+
+
+/*! \brief Open GSMTAP source socket, connect and register osmo_fd
+ *  \param[in] host host name or IP address in string format
+ *  \param[in] port UDP port number in host byte order
+ *  \param[in] osmo_wq_mode Register \ref osmo_wqueue (1) or not (0)
+ *
+ * Open GSMTAP source (sending) socket, connect it to host/port,
+ * allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue
+ * registration.  This means it is like \ref gsmtap_init2 but integrated
+ * with libosmocore \ref select */
+struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
+					int ofd_wq_mode)
+{
+	struct gsmtap_inst *gti;
+	int fd;
+
+	fd = gsmtap_source_init_fd(host, port);
+	if (fd < 0)
+		return NULL;
+
+	gti = talloc_zero(NULL, struct gsmtap_inst);
+	gti->ofd_wq_mode = ofd_wq_mode;
+	gti->wq.bfd.fd = fd;
+	gti->sink_ofd.fd = -1;
+
+	if (ofd_wq_mode) {
+		osmo_wqueue_init(&gti->wq, 64);
+		gti->wq.write_cb = &gsmtap_wq_w_cb;
+
+		osmo_fd_register(&gti->wq.bfd);
+	}
+
+	return gti;
+}
+
+#endif /* HAVE_SYS_SOCKET_H */
diff --git a/src/logging.c b/src/logging.c
new file mode 100644
index 0000000..8dfc31e
--- /dev/null
+++ b/src/logging.c
@@ -0,0 +1,757 @@
+/* Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* \addtogroup logging
+ * @{
+ */
+
+/* \file logging.c */
+
+#include "../config.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <time.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/vty/logging.h>	/* for LOGGING_STR. */
+
+struct log_info *osmo_log_info;
+
+static struct log_context log_context;
+static void *tall_log_ctx = NULL;
+LLIST_HEAD(osmo_log_target_list);
+
+#define LOGLEVEL_DEFS	6	/* Number of loglevels.*/
+
+static const struct value_string loglevel_strs[LOGLEVEL_DEFS+1] = {
+	{ 0,		"EVERYTHING" },
+	{ LOGL_DEBUG,	"DEBUG" },
+	{ LOGL_INFO,	"INFO" },
+	{ LOGL_NOTICE,	"NOTICE" },
+	{ LOGL_ERROR,	"ERROR" },
+	{ LOGL_FATAL,	"FATAL" },
+	{ 0, NULL },
+};
+
+#define INT2IDX(x)	(-1*(x)-1)
+static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
+	[INT2IDX(DLGLOBAL)] = {	/* -1 becomes 0 */
+		.name = "DLGLOBAL",
+		.description = "Library-internal global log family",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
+	[INT2IDX(DLLAPD)] = {	/* -2 becomes 1 */
+		.name = "DLLAPD",
+		.description = "LAPD in libosmogsm",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
+	[INT2IDX(DLINP)] = {
+		.name = "DLINP",
+		.description = "A-bis Intput Subsystem",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
+	[INT2IDX(DLMUX)] = {
+		.name = "DLMUX",
+		.description = "A-bis B-Subchannel TRAU Frame Multiplex",
+		.loglevel = LOGL_NOTICE,
+		.enabled = 1,
+	},
+	[INT2IDX(DLMI)] = {
+		.name = "DLMI",
+		.description = "A-bis Input Driver for Signalling",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[INT2IDX(DLMIB)] = {
+		.name = "DLMIB",
+		.description = "A-bis Input Driver for B-Channels (voice)",
+		.enabled = 0, .loglevel = LOGL_NOTICE,
+	},
+	[INT2IDX(DLSMS)] = {
+		.name = "DLSMS",
+		.description = "Layer3 Short Message Service (SMS)",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
+		.color = "\033[1;38m",
+	},
+};
+
+/* You have to keep this in sync with the structure loglevel_strs. */
+const char *loglevel_descriptions[LOGLEVEL_DEFS+1] = {
+	"Log simply everything",
+	"Log debug messages and higher levels",
+	"Log informational messages and higher levels",
+	"Log noticable messages and higher levels",
+	"Log error messages and higher levels",
+	"Log only fatal messages",
+	NULL,
+};
+
+/* special magic for negative (library-internal) log subsystem numbers */
+static int subsys_lib2index(int subsys)
+{
+	return (subsys * -1) + (osmo_log_info->num_cat_user-1);
+}
+
+/*! \brief Parse a human-readable log level into a numeric value */
+int log_parse_level(const char *lvl)
+{
+	return get_string_value(loglevel_strs, lvl);
+}
+
+/*! \brief convert a numeric log level into human-readable string */
+const char *log_level_str(unsigned int lvl)
+{
+	return get_value_string(loglevel_strs, lvl);
+}
+
+/*! \brief parse a human-readable log category into numeric form
+ *  \param[in] category human-readable log category name
+ *  \returns numeric category value, or -EINVAL otherwise
+ */
+int log_parse_category(const char *category)
+{
+	int i;
+
+	for (i = 0; i < osmo_log_info->num_cat; ++i) {
+		if (osmo_log_info->cat[i].name == NULL)
+			continue;
+		if (!strcasecmp(osmo_log_info->cat[i].name+1, category))
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+/*! \brief parse the log category mask
+ *  \param[in] target log target to be configured
+ *  \param[in] _mask log category mask string
+ *
+ * The format can be this: category1:category2:category3
+ * or category1,2:category2,3:...
+ */
+void log_parse_category_mask(struct log_target* target, const char *_mask)
+{
+	int i = 0;
+	char *mask = strdup(_mask);
+	char *category_token = NULL;
+
+	/* Disable everything to enable it afterwards */
+	for (i = 0; i < osmo_log_info->num_cat; ++i)
+		target->categories[i].enabled = 0;
+
+	category_token = strtok(mask, ":");
+	do {
+		for (i = 0; i < osmo_log_info->num_cat; ++i) {
+			char* colon = strstr(category_token, ",");
+			int length = strlen(category_token);
+			int cat_length = strlen(osmo_log_info->cat[i].name);
+
+			/* Use longest length not to match subocurrences. */
+			if (cat_length > length)
+				length = cat_length;
+
+			if (!osmo_log_info->cat[i].name)
+				continue;
+
+			if (colon)
+			    length = colon - category_token;
+
+			if (strncasecmp(osmo_log_info->cat[i].name,
+					category_token, length) == 0) {
+				int level = 0;
+
+				if (colon)
+					level = atoi(colon+1);
+
+				target->categories[i].enabled = 1;
+				target->categories[i].loglevel = level;
+			}
+		}
+	} while ((category_token = strtok(NULL, ":")));
+
+	free(mask);
+}
+
+static const char* color(int subsys)
+{
+	if (subsys < osmo_log_info->num_cat)
+		return osmo_log_info->cat[subsys].color;
+
+	return NULL;
+}
+
+static void _output(struct log_target *target, unsigned int subsys,
+		    unsigned int level, char *file, int line, int cont,
+		    const char *format, va_list ap)
+{
+	char buf[4096];
+	int ret, len = 0, offset = 0, rem = sizeof(buf);
+
+	/* are we using color */
+	if (target->use_color) {
+		const char *c = color(subsys);
+		if (c) {
+			ret = snprintf(buf + offset, rem, "%s", color(subsys));
+			if (ret < 0)
+				goto err;
+			OSMO_SNPRINTF_RET(ret, rem, offset, len);
+		}
+	}
+	if (!cont) {
+		if (target->print_timestamp) {
+			char *timestr;
+			time_t tm;
+			tm = time(NULL);
+			timestr = ctime(&tm);
+			timestr[strlen(timestr)-1] = '\0';
+			ret = snprintf(buf + offset, rem, "%s ", timestr);
+			if (ret < 0)
+				goto err;
+			OSMO_SNPRINTF_RET(ret, rem, offset, len);
+		}
+		ret = snprintf(buf + offset, rem, "<%4.4x> %s:%d ",
+				subsys, file, line);
+		if (ret < 0)
+			goto err;
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+	ret = vsnprintf(buf + offset, rem, format, ap);
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	ret = snprintf(buf + offset, rem, "%s",
+			target->use_color ? "\033[0;m" : "");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+err:
+	buf[sizeof(buf)-1] = '\0';
+	target->output(target, level, buf);
+}
+
+/*! \brief vararg version of logging function */
+void osmo_vlogp(int subsys, int level, char *file, int line,
+		int cont, const char *format, va_list ap)
+{
+	struct log_target *tar;
+
+	if (subsys < 0)
+		subsys = subsys_lib2index(subsys);
+
+	if (subsys > osmo_log_info->num_cat)
+		subsys = DLGLOBAL;
+
+	llist_for_each_entry(tar, &osmo_log_target_list, entry) {
+		struct log_category *category;
+		int output = 0;
+		va_list bp;
+
+		category = &tar->categories[subsys];
+		/* subsystem is not supposed to be logged */
+		if (!category->enabled)
+			continue;
+
+		/* Check the global log level */
+		if (tar->loglevel != 0 && level < tar->loglevel)
+			continue;
+
+		/* Check the category log level */
+		if (tar->loglevel == 0 && category->loglevel != 0 &&
+		    level < category->loglevel)
+			continue;
+
+		/* Apply filters here... if that becomes messy we will
+		 * need to put filters in a list and each filter will
+		 * say stop, continue, output */
+		if ((tar->filter_map & LOG_FILTER_ALL) != 0)
+			output = 1;
+		else if (osmo_log_info->filter_fn)
+			output = osmo_log_info->filter_fn(&log_context,
+						       tar);
+		if (!output)
+			continue;
+
+		/* According to the manpage, vsnprintf leaves the value of ap
+		 * in undefined state. Since _output uses vsnprintf and it may
+		 * be called several times, we have to pass a copy of ap. */
+		va_copy(bp, ap);
+		_output(tar, subsys, level, file, line, cont, format, bp);
+		va_end(bp);
+	}
+}
+
+void logp(int subsys, char *file, int line, int cont,
+	  const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	osmo_vlogp(subsys, LOGL_DEBUG, file, line, cont, format, ap);
+	va_end(ap);
+}
+
+void logp2(int subsys, unsigned int level, char *file, int line, int cont, const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	osmo_vlogp(subsys, level, file, line, cont, format, ap);
+	va_end(ap);
+}
+
+/*! \brief Register a new log target with the logging core
+ *  \param[in] target Log target to be registered
+ */
+void log_add_target(struct log_target *target)
+{
+	llist_add_tail(&target->entry, &osmo_log_target_list);
+}
+
+/*! \brief Unregister a log target from the logging core
+ *  \param[in] target Log target to be unregistered
+ */
+void log_del_target(struct log_target *target)
+{
+	llist_del(&target->entry);
+}
+
+/*! \brief Reset (clear) the logging context */
+void log_reset_context(void)
+{
+	memset(&log_context, 0, sizeof(log_context));
+}
+
+/*! \brief Set the logging context
+ *  \param[in] ctx_nr logging context number
+ *  \param[in] value value to which the context is to be set
+ *
+ * A logging context is something like the subscriber identity to which
+ * the currently processed message relates, or the BTS through which it
+ * was received.  As soon as this data is known, it can be set using
+ * this function.  The main use of context information is for logging
+ * filters.
+ */
+int log_set_context(uint8_t ctx_nr, void *value)
+{
+	if (ctx_nr > LOG_MAX_CTX)
+		return -EINVAL;
+
+	log_context.ctx[ctx_nr] = value;
+
+	return 0;
+}
+
+/*! \brief Enable the \ref LOG_FILTER_ALL log filter
+ *  \param[in] target Log target to be affected
+ *  \param[in] all enable (1) or disable (0) the ALL filter
+ *
+ * When the \ref LOG_FILTER_ALL filter is enabled, all log messages will
+ * be printed.  It acts as a wildcard.  Setting it to \a 1 means there
+ * is no filtering.
+ */
+void log_set_all_filter(struct log_target *target, int all)
+{
+	if (all)
+		target->filter_map |= LOG_FILTER_ALL;
+	else
+		target->filter_map &= ~LOG_FILTER_ALL;
+}
+
+/*! \brief Enable or disable the use of colored output
+ *  \param[in] target Log target to be affected
+ *  \param[in] use_color Use color (1) or don't use color (0)
+ */
+void log_set_use_color(struct log_target *target, int use_color)
+{
+	target->use_color = use_color;
+}
+
+/*! \brief Enable or disable printing of timestamps while logging
+ *  \param[in] target Log target to be affected
+ *  \param[in] print_timestamp Enable (1) or disable (0) timestamps
+ */
+void log_set_print_timestamp(struct log_target *target, int print_timestamp)
+{
+	target->print_timestamp = print_timestamp;
+}
+
+/*! \brief Set the global log level for a given log target
+ *  \param[in] target Log target to be affected
+ *  \param[in] log_level New global log level
+ */
+void log_set_log_level(struct log_target *target, int log_level)
+{
+	target->loglevel = log_level;
+}
+
+void log_set_category_filter(struct log_target *target, int category,
+			       int enable, int level)
+{
+	if (category >= osmo_log_info->num_cat)
+		return;
+	target->categories[category].enabled = !!enable;
+	target->categories[category].loglevel = level;
+}
+
+static void _file_output(struct log_target *target, unsigned int level,
+			 const char *log)
+{
+	fprintf(target->tgt_file.out, "%s", log);
+	fflush(target->tgt_file.out);
+}
+
+/*! \brief Create a new log target skeleton */
+struct log_target *log_target_create(void)
+{
+	struct log_target *target;
+	unsigned int i;
+
+	target = talloc_zero(tall_log_ctx, struct log_target);
+	if (!target)
+		return NULL;
+
+	target->categories = talloc_zero_array(target, 
+						struct log_category,
+						osmo_log_info->num_cat);
+	if (!target->categories) {
+		talloc_free(target);
+		return NULL;
+	}
+
+	INIT_LLIST_HEAD(&target->entry);
+
+	/* initialize the per-category enabled/loglevel from defaults */
+	for (i = 0; i < osmo_log_info->num_cat; i++) {
+		struct log_category *cat = &target->categories[i];
+		cat->enabled = osmo_log_info->cat[i].enabled;
+		cat->loglevel = osmo_log_info->cat[i].loglevel;
+	}
+
+	/* global settings */
+	target->use_color = 1;
+	target->print_timestamp = 0;
+
+	/* global log level */
+	target->loglevel = 0;
+	return target;
+}
+
+/*! \brief Create the STDERR log target */
+struct log_target *log_target_create_stderr(void)
+{
+/* since C89/C99 says stderr is a macro, we can safely do this! */
+#ifdef stderr
+	struct log_target *target;
+
+	target = log_target_create();
+	if (!target)
+		return NULL;
+
+	target->type = LOG_TGT_TYPE_STDERR;
+	target->tgt_file.out = stderr;
+	target->output = _file_output;
+	return target;
+#else
+	return NULL;
+#endif /* stderr */
+}
+
+/*! \brief Create a new file-based log target
+ *  \param[in] fname File name of the new log file
+ *  \returns Log target in case of success, NULL otherwise
+ */
+struct log_target *log_target_create_file(const char *fname)
+{
+	struct log_target *target;
+
+	target = log_target_create();
+	if (!target)
+		return NULL;
+
+	target->type = LOG_TGT_TYPE_FILE;
+	target->tgt_file.out = fopen(fname, "a");
+	if (!target->tgt_file.out)
+		return NULL;
+
+	target->output = _file_output;
+
+	target->tgt_file.fname = talloc_strdup(target, fname);
+
+	return target;
+}
+
+/*! \brief Find a registered log target
+ *  \param[in] type Log target type
+ *  \param[in] fname File name
+ *  \returns Log target (if found), NULL otherwise
+ */
+struct log_target *log_target_find(int type, const char *fname)
+{
+	struct log_target *tgt;
+
+	llist_for_each_entry(tgt, &osmo_log_target_list, entry) {
+		if (tgt->type != type)
+			continue;
+		if (tgt->type == LOG_TGT_TYPE_FILE) {
+			if (!strcmp(fname, tgt->tgt_file.fname))
+				return tgt;
+		} else
+			return tgt;
+	}
+	return NULL;
+}
+
+/*! \brief Unregister, close and delete a log target */
+void log_target_destroy(struct log_target *target)
+{
+
+	/* just in case, to make sure we don't have any references */
+	log_del_target(target);
+
+	if (target->output == &_file_output) {
+/* since C89/C99 says stderr is a macro, we can safely do this! */
+#ifdef stderr
+		/* don't close stderr */
+		if (target->tgt_file.out != stderr)
+#endif
+		{
+			fclose(target->tgt_file.out);
+			target->tgt_file.out = NULL;
+		}
+	}
+
+	talloc_free(target);
+}
+
+/*! \brief close and re-open a log file (for log file rotation) */
+int log_target_file_reopen(struct log_target *target)
+{
+	fclose(target->tgt_file.out);
+
+	target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+	if (!target->tgt_file.out)
+		return -errno;
+
+	/* we assume target->output already to be set */
+
+	return 0;
+}
+
+/*! \brief Generates the logging command string for VTY
+ *  \param[in] unused_info Deprecated parameter, no longer used!
+ */
+const char *log_vty_command_string(const struct log_info *unused_info)
+{
+	struct log_info *info = osmo_log_info;
+	int len = 0, offset = 0, ret, i, rem;
+	int size = strlen("logging level () ()") + 1;
+	char *str;
+
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
+		size += strlen(info->cat[i].name) + 1;
+	}
+
+	for (i = 0; i < LOGLEVEL_DEFS; i++)
+		size += strlen(loglevel_strs[i].str) + 1;
+
+	rem = size;
+	str = talloc_zero_size(tall_log_ctx, size);
+	if (!str)
+		return NULL;
+
+	ret = snprintf(str + offset, rem, "logging level (all|");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name) {
+			int j, name_len = strlen(info->cat[i].name)+1;
+			char name[name_len];
+
+			for (j = 0; j < name_len; j++)
+				name[j] = tolower(info->cat[i].name[j]);
+
+			name[name_len-1] = '\0';
+			ret = snprintf(str + offset, rem, "%s|", name+1);
+			if (ret < 0)
+				goto err;
+			OSMO_SNPRINTF_RET(ret, rem, offset, len);
+		}
+	}
+	offset--;	/* to remove the trailing | */
+	rem++;
+
+	ret = snprintf(str + offset, rem, ") (");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	for (i = 0; i < LOGLEVEL_DEFS; i++) {
+		int j, loglevel_str_len = strlen(loglevel_strs[i].str)+1;
+		char loglevel_str[loglevel_str_len];
+
+		for (j = 0; j < loglevel_str_len; j++)
+			loglevel_str[j] = tolower(loglevel_strs[i].str[j]);
+
+		loglevel_str[loglevel_str_len-1] = '\0';
+		ret = snprintf(str + offset, rem, "%s|", loglevel_str);
+		if (ret < 0)
+			goto err;
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+	offset--;	/* to remove the trailing | */
+	rem++;
+
+	ret = snprintf(str + offset, rem, ")");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+err:
+	str[size-1] = '\0';
+	return str;
+}
+
+/*! \brief Generates the logging command description for VTY
+ *  \param[in] unused_info Deprecated parameter, no longer used!
+ */
+const char *log_vty_command_description(const struct log_info *unused_info)
+{
+	struct log_info *info = osmo_log_info;
+	char *str;
+	int i, ret, len = 0, offset = 0, rem;
+	unsigned int size =
+		strlen(LOGGING_STR
+		       "Set the log level for a specified category\n") + 1;
+
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
+		size += strlen(info->cat[i].description) + 1;
+	}
+
+	for (i = 0; i < LOGLEVEL_DEFS; i++)
+		size += strlen(loglevel_descriptions[i]) + 1;
+
+	size += strlen("Global setting for all subsystems") + 1;
+	rem = size;
+	str = talloc_zero_size(tall_log_ctx, size);
+	if (!str)
+		return NULL;
+
+	ret = snprintf(str + offset, rem, LOGGING_STR
+			"Set the log level for a specified category\n");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	ret = snprintf(str + offset, rem,
+			"Global setting for all subsystems\n");
+	if (ret < 0)
+		goto err;
+	OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+	for (i = 0; i < info->num_cat; i++) {
+		if (info->cat[i].name == NULL)
+			continue;
+		ret = snprintf(str + offset, rem, "%s\n",
+				info->cat[i].description);
+		if (ret < 0)
+			goto err;
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+	for (i = 0; i < LOGLEVEL_DEFS; i++) {
+		ret = snprintf(str + offset, rem, "%s\n",
+				loglevel_descriptions[i]);
+		if (ret < 0)
+			goto err;
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+err:
+	str[size-1] = '\0';
+	return str;
+}
+
+/*! \brief Initialize the Osmocom logging core
+ *  \param[in] inf Information regarding logging categories
+ *  \param[in] ctx \ref talloc context for logging allocations
+ *  \returns 0 in case of success, negative in case of error
+ */
+int log_init(const struct log_info *inf, void *ctx)
+{
+	int i;
+
+	tall_log_ctx = talloc_named_const(ctx, 1, "logging");
+	if (!tall_log_ctx)
+		return -ENOMEM;
+
+	osmo_log_info = talloc_zero(tall_log_ctx, struct log_info);
+	if (!osmo_log_info)
+		return -ENOMEM;
+
+	osmo_log_info->num_cat_user = inf->num_cat;
+	/* total number = number of user cat + library cat */
+	osmo_log_info->num_cat = inf->num_cat + ARRAY_SIZE(internal_cat);
+
+	osmo_log_info->cat = talloc_zero_array(osmo_log_info,
+					struct log_info_cat,
+					osmo_log_info->num_cat);
+	if (!osmo_log_info->cat) {
+		talloc_free(osmo_log_info);
+		osmo_log_info = NULL;
+		return -ENOMEM;
+	}
+
+	/* copy over the user part */
+	for (i = 0; i < inf->num_cat; i++) {
+		memcpy(&osmo_log_info->cat[i], &inf->cat[i],
+			sizeof(struct log_info_cat));
+	}
+
+	/* copy over the library part */
+	for (i = 0; i < ARRAY_SIZE(internal_cat); i++) {
+		unsigned int cn = osmo_log_info->num_cat_user + i;
+		memcpy(&osmo_log_info->cat[cn],
+			&internal_cat[i], sizeof(struct log_info_cat));
+	}
+
+	return 0;
+}
+
+/*! @} */
diff --git a/src/logging_syslog.c b/src/logging_syslog.c
new file mode 100644
index 0000000..5b0ae5f
--- /dev/null
+++ b/src/logging_syslog.c
@@ -0,0 +1,92 @@
+/* Syslog logging support code */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup logging
+ *  @{
+ */
+
+/*! \file logging_syslog.c */
+
+#include "../config.h"
+
+#ifdef HAVE_SYSLOG_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+static int logp2syslog_level(unsigned int level)
+{
+	if (level >= LOGL_FATAL)
+		return LOG_CRIT;
+	else if (level >= LOGL_ERROR)
+		return LOG_ERR;
+	else if (level >= LOGL_NOTICE)
+		return LOG_NOTICE;
+	else if (level >= LOGL_INFO)
+		return LOG_INFO;
+	else
+		return LOG_DEBUG;
+}
+
+static void _syslog_output(struct log_target *target,
+			   unsigned int level, const char *log)
+{
+	syslog(logp2syslog_level(level), "%s", log);
+}
+
+/*! \brief Create a new logging target for syslog logging
+ *  \param[in] ident syslog string identifier
+ *  \param[in] option syslog options
+ *  \param[in] facility syslog facility
+ *  \returns Log target in case of success, NULL in case of error
+ */
+struct log_target *log_target_create_syslog(const char *ident, int option,
+					    int facility)
+{
+	struct log_target *target;
+
+	target = log_target_create();
+	if (!target)
+		return NULL;
+
+	target->tgt_syslog.facility = facility;
+	target->type = LOG_TGT_TYPE_SYSLOG;
+	target->output = _syslog_output;
+
+	openlog(ident, option, facility);
+
+	return target;
+}
+
+#endif /* HAVE_SYSLOG_H */
+
+/* @} */
diff --git a/src/msgb.c b/src/msgb.c
new file mode 100644
index 0000000..c8564db
--- /dev/null
+++ b/src/msgb.c
@@ -0,0 +1,156 @@
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup msgb
+ *  @{
+ */
+
+/*! \file msgb.c
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <osmocom/core/msgb.h>
+//#include <openbsc/gsm_data.h>
+#include <osmocom/core/talloc.h>
+//#include <openbsc/debug.h>
+
+void *tall_msgb_ctx;
+
+/*! \brief Allocate a new message buffer
+ * \param[in] size Length in octets, including headroom
+ * \param[in] name Human-readable name to be associated with msgb
+ *
+ * This function allocates a 'struct msgb' as well as the underlying
+ * memory buffer for the actual message data (size specified by \a size)
+ * using the talloc memory context previously set by \ref msgb_set_talloc_ctx
+ */
+struct msgb *msgb_alloc(uint16_t size, const char *name)
+{
+	struct msgb *msg;
+
+	msg = _talloc_zero(tall_msgb_ctx, sizeof(*msg) + size, name);
+
+	if (!msg) {
+		//LOGP(DRSL, LOGL_FATAL, "unable to allocate msgb\n");
+		return NULL;
+	}
+
+	msg->data_len = size;
+	msg->len = 0;
+	msg->data = msg->_data;
+	msg->head = msg->_data;
+	msg->tail = msg->_data;
+
+	return msg;
+}
+
+/*! \brief Release given message buffer
+ * \param[in] m Message buffer to be free'd
+ */
+void msgb_free(struct msgb *m)
+{
+	talloc_free(m);
+}
+
+/*! \brief Enqueue message buffer to tail of a queue
+ * \param[in] queue linked list header of queue
+ * \param[in] msgb message buffer to be added to the queue
+ *
+ * The function will append the specified message buffer \a msg to the
+ * queue implemented by \ref llist_head \a queue
+ */
+void msgb_enqueue(struct llist_head *queue, struct msgb *msg)
+{
+	llist_add_tail(&msg->list, queue);
+}
+
+/*! \brief Dequeue message buffer from head of queue
+ * \param[in] queue linked list header of queue
+ * \returns message buffer (if any) or NULL if queue empty
+ *
+ * The function will remove the first message buffer from the queue
+ * implemented by 'ref llist_head \a queue.
+ */
+struct msgb *msgb_dequeue(struct llist_head *queue)
+{
+	struct llist_head *lh;
+
+	if (llist_empty(queue))
+		return NULL;
+
+	lh = queue->next;
+	llist_del(lh);
+	
+	return llist_entry(lh, struct msgb, list);
+}
+
+/*! \brief Re-set all message buffer pointers
+ *  \param[in] m message buffer that is to be resetted
+ *
+ * This will re-set the various internal pointers into the underlying
+ * message buffer, i.e. remvoe all headroom and treat the msgb as
+ * completely empty.  It also initializes the control buffer to zero.
+ */
+void msgb_reset(struct msgb *msg)
+{
+	msg->len = 0;
+	msg->data = msg->_data;
+	msg->head = msg->_data;
+	msg->tail = msg->_data;
+
+	msg->trx = NULL;
+	msg->lchan = NULL;
+	msg->l2h = NULL;
+	msg->l3h = NULL;
+	msg->l4h = NULL;
+
+	memset(&msg->cb, 0, sizeof(msg->cb));
+}
+
+/*! \brief get pointer to data section of message buffer
+ *  \param[in] msg message buffer
+ *  \returns pointer to data section of message buffer
+ */
+uint8_t *msgb_data(const struct msgb *msg)
+{
+	return msg->data;
+}
+
+/*! \brief get length of message buffer
+ *  \param[in] msg message buffer
+ *  \returns length of data section in message buffer
+ */
+uint16_t msgb_length(const struct msgb *msg)
+{
+	return msg->len;
+}
+
+/*! \brief Set the talloc context for \ref msgb_alloc
+ *  \param[in] ctx talloc context to be used as root for msgb allocations
+ */
+void msgb_set_talloc_ctx(void *ctx)
+{
+	tall_msgb_ctx = ctx;
+}
+
+/*! @} */
diff --git a/src/msgfile.c b/src/msgfile.c
new file mode 100644
index 0000000..d2b180d
--- /dev/null
+++ b/src/msgfile.c
@@ -0,0 +1,123 @@
+/*
+ * Parse a simple file with messages, e.g used for USSD messages
+ *
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/msgfile.h>
+#include <osmocom/core/talloc.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+static struct osmo_config_entry *
+alloc_entry(struct osmo_config_list *entries,
+	    const char *mcc, const char *mnc,
+	    const char *option, const char *text)
+{
+	struct osmo_config_entry *entry =
+		talloc_zero(entries, struct osmo_config_entry);
+	if (!entry)
+		return NULL;
+
+	entry->mcc = talloc_strdup(entry, mcc);
+	entry->mnc = talloc_strdup(entry, mnc);
+	entry->option = talloc_strdup(entry, option);
+	entry->text = talloc_strdup(entry, text);
+
+	llist_add_tail(&entry->list, &entries->entry);
+	return entry;
+}
+
+static struct osmo_config_list *alloc_entries(void *ctx)
+{
+	struct osmo_config_list *entries;
+
+	entries = talloc_zero(ctx, struct osmo_config_list);
+	if (!entries)
+		return NULL;
+
+	INIT_LLIST_HEAD(&entries->entry);
+	return entries;
+}
+
+/*
+ * split a line like 'foo:Text'.
+ */
+static void handle_line(struct osmo_config_list *entries, char *line)
+{
+	int i;
+	const int len = strlen(line);
+
+	char *items[3];
+	int last_item = 0;
+
+	/* Skip comments from the file */
+	if (line[0] == '#')
+		return;
+
+	for (i = 0; i < len; ++i) {
+		if (line[i] == '\n' || line[i] == '\r')
+			line[i] = '\0';
+		else if (line[i] == ':' && last_item < 3) {
+			line[i] = '\0';
+
+			items[last_item++] = &line[i + 1];
+		}
+	}
+
+	if (last_item == 3) {
+		alloc_entry(entries, &line[0] , items[0], items[1], items[2]);
+		return;
+	}
+
+	/* nothing found */
+}
+
+struct osmo_config_list *osmo_config_list_parse(void *ctx, const char *filename)
+{
+	struct osmo_config_list *entries;
+	size_t n;
+	char *line;
+	FILE *file;
+
+	file = fopen(filename, "r");
+	if (!file)
+		return NULL;
+
+	entries = alloc_entries(ctx);
+	if (!entries) {
+		fclose(file);
+		return NULL;
+	}
+
+	n = 2342;
+	line = NULL;
+        while (getline(&line, &n, file) != -1) {
+		handle_line(entries, line);
+		free(line);
+		line = NULL;
+	}
+
+	fclose(file);
+	return entries;
+}
diff --git a/src/panic.c b/src/panic.c
new file mode 100644
index 0000000..be644ff
--- /dev/null
+++ b/src/panic.c
@@ -0,0 +1,82 @@
+/* Panic handling */
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup utils
+ *  @{
+ */
+
+/*! \file panic.c */
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/panic.h>
+#include <osmocom/core/backtrace.h>
+
+#include "../config.h"
+
+
+static osmo_panic_handler_t osmo_panic_handler = (void*)0;
+
+
+#ifndef PANIC_INFLOOP
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static void osmo_panic_default(const char *fmt, va_list args)
+{
+	vfprintf(stderr, fmt, args);
+	osmo_generate_backtrace();
+	abort();
+}
+
+#else
+
+static void osmo_panic_default(const char *fmt, va_list args)
+{
+	while (1);
+}
+
+#endif
+
+
+/*! \brief Terminate the current program with a panic */
+void osmo_panic(const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if (osmo_panic_handler)
+		osmo_panic_handler(fmt, args);
+	else
+		osmo_panic_default(fmt, args);
+
+	va_end(args);
+}
+ 
+
+/*! \brief Set the panic handler */
+void osmo_set_panic_handler(osmo_panic_handler_t h)
+{
+	osmo_panic_handler = h;
+}
+
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 0000000..998bca3
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,62 @@
+/* plugin infrastructure */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "../config.h"
+
+#if HAVE_DLFCN_H
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <osmocom/core/plugin.h>
+
+int osmo_plugin_load_all(const char *directory)
+{
+	unsigned int num = 0;
+	char fname[PATH_MAX];
+	DIR *dir;
+	struct dirent *entry;
+
+	dir = opendir(directory);
+	if (!dir)
+		return -errno;
+
+	while ((entry = readdir(dir))) {
+		snprintf(fname, sizeof(fname), "%s/%s", directory,
+			entry->d_name);
+		if (dlopen(fname, RTLD_NOW))
+			num++;
+	}
+
+	closedir(dir);
+
+	return num;
+}
+#else
+int osmo_plugin_load_all(const char *directory)
+{
+	return 0;
+}
+#endif /* HAVE_DLFCN_H */
diff --git a/src/rate_ctr.c b/src/rate_ctr.c
new file mode 100644
index 0000000..8a232e8
--- /dev/null
+++ b/src/rate_ctr.c
@@ -0,0 +1,180 @@
+/* utility routines for keeping conters about events and the event rates */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/*! \addtogroup rate_ctr
+ *  @{
+ */
+
+/*! \file rate_ctr.c */
+
+
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+
+static LLIST_HEAD(rate_ctr_groups);
+
+static void *tall_rate_ctr_ctx;
+
+/*! \brief Allocate a new group of counters according to description
+ *  \param[in] ctx \ref talloc context
+ *  \param[in] desc Rate counter group description
+ *  \param[in] idx Index of new counter group
+ */
+struct rate_ctr_group *rate_ctr_group_alloc(void *ctx,
+					    const struct rate_ctr_group_desc *desc,
+					    unsigned int idx)
+{
+	unsigned int size;
+	struct rate_ctr_group *group;
+
+	size = sizeof(struct rate_ctr_group) +
+			desc->num_ctr * sizeof(struct rate_ctr);
+
+	if (!ctx)
+		ctx = tall_rate_ctr_ctx;
+
+	group = talloc_zero_size(ctx, size);
+	if (!group)
+		return NULL;
+
+	group->desc = desc;
+	group->idx = idx;
+
+	llist_add(&group->list, &rate_ctr_groups);
+
+	return group;
+}
+
+/*! \brief Free the memory for the specified group of counters */
+void rate_ctr_group_free(struct rate_ctr_group *grp)
+{
+	llist_del(&grp->list);
+	talloc_free(grp);
+}
+
+/*! \brief Add a number to the counter */
+void rate_ctr_add(struct rate_ctr *ctr, int inc)
+{
+	ctr->current += inc;
+}
+
+static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
+{
+	/* calculate rate over last interval */
+	ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
+	/* save current counter for next interval */
+	ctr->intv[intv].last = ctr->current;
+
+	/* update the rate of the next bigger interval.  This will
+	 * be overwritten when that next larger interval expires */
+	if (intv + 1 < ARRAY_SIZE(ctr->intv))
+		ctr->intv[intv+1].rate += ctr->intv[intv].rate;
+}
+
+static struct osmo_timer_list rate_ctr_timer;
+static uint64_t timer_ticks;
+
+/* The one-second interval has expired */
+static void rate_ctr_group_intv(struct rate_ctr_group *grp)
+{
+	unsigned int i;
+
+	for (i = 0; i < grp->desc->num_ctr; i++) {
+		struct rate_ctr *ctr = &grp->ctr[i];
+
+		interval_expired(ctr, RATE_CTR_INTV_SEC);
+		if ((timer_ticks % 60) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_MIN);
+		if ((timer_ticks % (60*60)) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_HOUR);
+		if ((timer_ticks % (24*60*60)) == 0)
+			interval_expired(ctr, RATE_CTR_INTV_DAY);
+	}
+}
+
+static void rate_ctr_timer_cb(void *data)
+{
+	struct rate_ctr_group *ctrg;
+
+	/* Increment number of ticks before we calculate intervals,
+	 * as a counter value of 0 would already wrap all counters */
+	timer_ticks++;
+
+	llist_for_each_entry(ctrg, &rate_ctr_groups, list)
+		rate_ctr_group_intv(ctrg);
+
+	osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+}
+
+/*! \brief Initialize the counter module */
+int rate_ctr_init(void *tall_ctx)
+{
+	tall_rate_ctr_ctx = tall_ctx;
+	rate_ctr_timer.cb = rate_ctr_timer_cb;
+	osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+
+	return 0;
+}
+
+/*! \brief Search for counter group based on group name and index */
+struct rate_ctr_group *rate_ctr_get_group_by_name_idx(const char *name, const unsigned int idx)
+{
+	struct rate_ctr_group *ctrg;
+
+	llist_for_each_entry(ctrg, &rate_ctr_groups, list) {
+		if (!ctrg->desc)
+			continue;
+
+		if (!strcmp(ctrg->desc->group_name_prefix, name) &&
+				ctrg->idx == idx) {
+			return ctrg;
+		}
+	}
+	return NULL;
+}
+
+/*! \brief Search for counter group based on group name */
+const struct rate_ctr *rate_ctr_get_by_name(const struct rate_ctr_group *ctrg, const char *name)
+{
+	int i;
+	const struct rate_ctr_desc *ctr_desc;
+
+	if (!ctrg->desc)
+		return NULL;
+
+	for (i = 0; i < ctrg->desc->num_ctr; i++) {
+		ctr_desc = &ctrg->desc->ctr_desc[i];
+
+		if (!strcmp(ctr_desc->name, name)) {
+			return &ctrg->ctr[i];
+		}
+	}
+	return NULL;
+}
+
+/*! @} */
diff --git a/src/rbtree.c b/src/rbtree.c
new file mode 100644
index 0000000..4e7c0f3
--- /dev/null
+++ b/src/rbtree.c
@@ -0,0 +1,383 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  (C) 2002  David Woodhouse <dwmw2@infradead.org>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/lib/rbtree.c
+*/
+
+#include <osmocom/core/linuxrbtree.h>
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *right = node->rb_right;
+	struct rb_node *parent = rb_parent(node);
+
+	if ((node->rb_right = right->rb_left))
+		rb_set_parent(right->rb_left, node);
+	right->rb_left = node;
+
+	rb_set_parent(right, parent);
+
+	if (parent)
+	{
+		if (node == parent->rb_left)
+			parent->rb_left = right;
+		else
+			parent->rb_right = right;
+	}
+	else
+		root->rb_node = right;
+	rb_set_parent(node, right);
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *left = node->rb_left;
+	struct rb_node *parent = rb_parent(node);
+
+	if ((node->rb_left = left->rb_right))
+		rb_set_parent(left->rb_right, node);
+	left->rb_right = node;
+
+	rb_set_parent(left, parent);
+
+	if (parent)
+	{
+		if (node == parent->rb_right)
+			parent->rb_right = left;
+		else
+			parent->rb_left = left;
+	}
+	else
+		root->rb_node = left;
+	rb_set_parent(node, left);
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *parent, *gparent;
+
+	while ((parent = rb_parent(node)) && rb_is_red(parent))
+	{
+		gparent = rb_parent(parent);
+
+		if (parent == gparent->rb_left)
+		{
+			{
+				register struct rb_node *uncle = gparent->rb_right;
+				if (uncle && rb_is_red(uncle))
+				{
+					rb_set_black(uncle);
+					rb_set_black(parent);
+					rb_set_red(gparent);
+					node = gparent;
+					continue;
+				}
+			}
+
+			if (parent->rb_right == node)
+			{
+				register struct rb_node *tmp;
+				__rb_rotate_left(parent, root);
+				tmp = parent;
+				parent = node;
+				node = tmp;
+			}
+
+			rb_set_black(parent);
+			rb_set_red(gparent);
+			__rb_rotate_right(gparent, root);
+		} else {
+			{
+				register struct rb_node *uncle = gparent->rb_left;
+				if (uncle && rb_is_red(uncle))
+				{
+					rb_set_black(uncle);
+					rb_set_black(parent);
+					rb_set_red(gparent);
+					node = gparent;
+					continue;
+				}
+			}
+
+			if (parent->rb_left == node)
+			{
+				register struct rb_node *tmp;
+				__rb_rotate_right(parent, root);
+				tmp = parent;
+				parent = node;
+				node = tmp;
+			}
+
+			rb_set_black(parent);
+			rb_set_red(gparent);
+			__rb_rotate_left(gparent, root);
+		}
+	}
+
+	rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+			     struct rb_root *root)
+{
+	struct rb_node *other;
+
+	while ((!node || rb_is_black(node)) && node != root->rb_node)
+	{
+		if (parent->rb_left == node)
+		{
+			other = parent->rb_right;
+			if (rb_is_red(other))
+			{
+				rb_set_black(other);
+				rb_set_red(parent);
+				__rb_rotate_left(parent, root);
+				other = parent->rb_right;
+			}
+			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+			    (!other->rb_right || rb_is_black(other->rb_right)))
+			{
+				rb_set_red(other);
+				node = parent;
+				parent = rb_parent(node);
+			}
+			else
+			{
+				if (!other->rb_right || rb_is_black(other->rb_right))
+				{
+					rb_set_black(other->rb_left);
+					rb_set_red(other);
+					__rb_rotate_right(other, root);
+					other = parent->rb_right;
+				}
+				rb_set_color(other, rb_color(parent));
+				rb_set_black(parent);
+				rb_set_black(other->rb_right);
+				__rb_rotate_left(parent, root);
+				node = root->rb_node;
+				break;
+			}
+		}
+		else
+		{
+			other = parent->rb_left;
+			if (rb_is_red(other))
+			{
+				rb_set_black(other);
+				rb_set_red(parent);
+				__rb_rotate_right(parent, root);
+				other = parent->rb_left;
+			}
+			if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+			    (!other->rb_right || rb_is_black(other->rb_right)))
+			{
+				rb_set_red(other);
+				node = parent;
+				parent = rb_parent(node);
+			}
+			else
+			{
+				if (!other->rb_left || rb_is_black(other->rb_left))
+				{
+					rb_set_black(other->rb_right);
+					rb_set_red(other);
+					__rb_rotate_left(other, root);
+					other = parent->rb_left;
+				}
+				rb_set_color(other, rb_color(parent));
+				rb_set_black(parent);
+				rb_set_black(other->rb_left);
+				__rb_rotate_right(parent, root);
+				node = root->rb_node;
+				break;
+			}
+		}
+	}
+	if (node)
+		rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+	struct rb_node *child, *parent;
+	int color;
+
+	if (!node->rb_left)
+		child = node->rb_right;
+	else if (!node->rb_right)
+		child = node->rb_left;
+	else
+	{
+		struct rb_node *old = node, *left;
+
+		node = node->rb_right;
+		while ((left = node->rb_left) != NULL)
+			node = left;
+
+		if (rb_parent(old)) {
+			if (rb_parent(old)->rb_left == old)
+				rb_parent(old)->rb_left = node;
+			else
+				rb_parent(old)->rb_right = node;
+		} else
+			root->rb_node = node;
+
+		child = node->rb_right;
+		parent = rb_parent(node);
+		color = rb_color(node);
+
+		if (parent == old) {
+			parent = node;
+		} else {
+			if (child)
+				rb_set_parent(child, parent);
+			parent->rb_left = child;
+
+			node->rb_right = old->rb_right;
+			rb_set_parent(old->rb_right, node);
+		}
+
+		node->rb_parent_color = old->rb_parent_color;
+		node->rb_left = old->rb_left;
+		rb_set_parent(old->rb_left, node);
+
+		goto color;
+	}
+
+	parent = rb_parent(node);
+	color = rb_color(node);
+
+	if (child)
+		rb_set_parent(child, parent);
+	if (parent)
+	{
+		if (parent->rb_left == node)
+			parent->rb_left = child;
+		else
+			parent->rb_right = child;
+	}
+	else
+		root->rb_node = child;
+
+ color:
+	if (color == RB_BLACK)
+		__rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(const struct rb_root *root)
+{
+	struct rb_node	*n;
+
+	n = root->rb_node;
+	if (!n)
+		return NULL;
+	while (n->rb_left)
+		n = n->rb_left;
+	return n;
+}
+
+struct rb_node *rb_last(const struct rb_root *root)
+{
+	struct rb_node	*n;
+
+	n = root->rb_node;
+	if (!n)
+		return NULL;
+	while (n->rb_right)
+		n = n->rb_right;
+	return n;
+}
+
+struct rb_node *rb_next(const struct rb_node *node)
+{
+	struct rb_node *parent;
+
+	if (rb_parent(node) == node)
+		return NULL;
+
+	/* If we have a right-hand child, go down and then left as far
+	   as we can. */
+	if (node->rb_right) {
+		node = node->rb_right; 
+		while (node->rb_left)
+			node=node->rb_left;
+		return (struct rb_node *)node;
+	}
+
+	/* No right-hand children.  Everything down and left is
+	   smaller than us, so any 'next' node must be in the general
+	   direction of our parent. Go up the tree; any time the
+	   ancestor is a right-hand child of its parent, keep going
+	   up. First time it's a left-hand child of its parent, said
+	   parent is our 'next' node. */
+	while ((parent = rb_parent(node)) && node == parent->rb_right)
+		node = parent;
+
+	return parent;
+}
+
+struct rb_node *rb_prev(const struct rb_node *node)
+{
+	struct rb_node *parent;
+
+	if (rb_parent(node) == node)
+		return NULL;
+
+	/* If we have a left-hand child, go down and then right as far
+	   as we can. */
+	if (node->rb_left) {
+		node = node->rb_left; 
+		while (node->rb_right)
+			node=node->rb_right;
+		return (struct rb_node *)node;
+	}
+
+	/* No left-hand children. Go up till we find an ancestor which
+	   is a right-hand child of its parent */
+	while ((parent = rb_parent(node)) && node == parent->rb_left)
+		node = parent;
+
+	return parent;
+}
+
+void rb_replace_node(struct rb_node *victim, struct rb_node *new,
+		     struct rb_root *root)
+{
+	struct rb_node *parent = rb_parent(victim);
+
+	/* Set the surrounding nodes to point to the replacement */
+	if (parent) {
+		if (victim == parent->rb_left)
+			parent->rb_left = new;
+		else
+			parent->rb_right = new;
+	} else {
+		root->rb_node = new;
+	}
+	if (victim->rb_left)
+		rb_set_parent(victim->rb_left, new);
+	if (victim->rb_right)
+		rb_set_parent(victim->rb_right, new);
+
+	/* Copy the pointers/colour from the victim to the replacement */
+	*new = *victim;
+}
diff --git a/src/select.c b/src/select.c
new file mode 100644
index 0000000..6b73377
--- /dev/null
+++ b/src/select.c
@@ -0,0 +1,172 @@
+/* select filedescriptor handling, taken from:
+ * userspace logging daemon for the iptables ULOG target
+ * of the linux 2.4 netfilter subsystem.
+ *
+ * (C) 2000-2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+
+#include "../config.h"
+
+#ifdef HAVE_SYS_SELECT_H
+
+/*! \addtogroup select
+ *  @{
+ */
+
+/*! \file select.c
+ *  \brief select loop abstraction
+ */
+
+static int maxfd = 0;
+static LLIST_HEAD(osmo_fds);
+static int unregistered_count;
+
+/*! \brief Register a new file descriptor with select loop abstraction
+ *  \param[in] fd osmocom file descriptor to be registered
+ */
+int osmo_fd_register(struct osmo_fd *fd)
+{
+	int flags;
+
+	/* make FD nonblocking */
+	flags = fcntl(fd->fd, F_GETFL);
+	if (flags < 0)
+		return flags;
+	flags |= O_NONBLOCK;
+	flags = fcntl(fd->fd, F_SETFL, flags);
+	if (flags < 0)
+		return flags;
+
+	/* set close-on-exec flag */
+	flags = fcntl(fd->fd, F_GETFD);
+	if (flags < 0)
+		return flags;
+	flags |= FD_CLOEXEC;
+	flags = fcntl(fd->fd, F_SETFD, flags);
+	if (flags < 0)
+		return flags;
+
+	/* Register FD */
+	if (fd->fd > maxfd)
+		maxfd = fd->fd;
+
+#ifdef BSC_FD_CHECK
+	struct osmo_fd *entry;
+	llist_for_each_entry(entry, &osmo_fds, list) {
+		if (entry == fd) {
+			fprintf(stderr, "Adding a osmo_fd that is already in the list.\n");
+			return 0;
+		}
+	}
+#endif
+
+	llist_add_tail(&fd->list, &osmo_fds);
+
+	return 0;
+}
+
+/*! \brief Unregister a file descriptor from select loop abstraction
+ *  \param[in] fd osmocom file descriptor to be unregistered
+ */
+void osmo_fd_unregister(struct osmo_fd *fd)
+{
+	unregistered_count++;
+	llist_del(&fd->list);
+}
+
+/*! \brief select main loop integration
+ *  \param[in] polling should we pollonly (1) or block on select (0)
+ */
+int osmo_select_main(int polling)
+{
+	struct osmo_fd *ufd, *tmp;
+	fd_set readset, writeset, exceptset;
+	int work = 0, rc;
+	struct timeval no_time = {0, 0};
+
+	FD_ZERO(&readset);
+	FD_ZERO(&writeset);
+	FD_ZERO(&exceptset);
+
+	/* prepare read and write fdsets */
+	llist_for_each_entry(ufd, &osmo_fds, list) {
+		if (ufd->when & BSC_FD_READ)
+			FD_SET(ufd->fd, &readset);
+
+		if (ufd->when & BSC_FD_WRITE)
+			FD_SET(ufd->fd, &writeset);
+
+		if (ufd->when & BSC_FD_EXCEPT)
+			FD_SET(ufd->fd, &exceptset);
+	}
+
+	osmo_timers_check();
+
+	if (!polling)
+		osmo_timers_prepare();
+	rc = select(maxfd+1, &readset, &writeset, &exceptset, polling ? &no_time : osmo_timers_nearest());
+	if (rc < 0)
+		return 0;
+
+	/* fire timers */
+	osmo_timers_update();
+
+	/* call registered callback functions */
+restart:
+	unregistered_count = 0;
+	llist_for_each_entry_safe(ufd, tmp, &osmo_fds, list) {
+		int flags = 0;
+
+		if (FD_ISSET(ufd->fd, &readset)) {
+			flags |= BSC_FD_READ;
+			FD_CLR(ufd->fd, &readset);
+		}
+
+		if (FD_ISSET(ufd->fd, &writeset)) {
+			flags |= BSC_FD_WRITE;
+			FD_CLR(ufd->fd, &writeset);
+		}
+
+		if (FD_ISSET(ufd->fd, &exceptset)) {
+			flags |= BSC_FD_EXCEPT;
+			FD_CLR(ufd->fd, &exceptset);
+		}
+
+		if (flags) {
+			work = 1;
+			ufd->cb(ufd, flags);
+		}
+		/* ugly, ugly hack. If more than one filedescriptors were
+		 * unregistered, they might have been consecutive and
+		 * llist_for_each_entry_safe() is no longer safe */
+		/* this seems to happen with the last element of the list as well */
+		if (unregistered_count >= 1)
+			goto restart;
+	}
+	return work;
+}
+
+/*! @} */
+
+#endif /* _HAVE_SYS_SELECT_H */
diff --git a/src/serial.c b/src/serial.c
new file mode 100644
index 0000000..11ba503
--- /dev/null
+++ b/src/serial.c
@@ -0,0 +1,229 @@
+/*
+ * serial.c
+ *
+ * Utility functions to deal with serial ports
+ *
+ * Copyright (C) 2011  Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \addtogroup serial
+ *  @{
+ */
+
+/*! \file serial.c
+ *  \file Osmocom serial port helpers
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef __linux__
+#include <linux/serial.h>
+#endif
+
+#include <osmocom/core/serial.h>
+
+
+#if 0
+# define dbg_perror(x) perror(x)
+#else
+# define dbg_perror(x) do { } while (0)
+#endif
+
+/*! \brief Open serial device and does base init
+ *  \param[in] dev Path to the device node to open
+ *  \param[in] baudrate Baudrate constant (speed_t: B9600, B...)
+ *  \returns >=0 file descriptor in case of success or negative errno.
+ */
+int
+osmo_serial_init(const char *dev, speed_t baudrate)
+{
+	int rc, fd=0, v24;
+	struct termios tio;
+
+	/* Open device */
+	fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY);
+	if (fd < 0) {
+		dbg_perror("open");
+		return -errno;
+	}
+
+	/* Configure serial interface */
+	rc = tcgetattr(fd, &tio);
+	if (rc < 0) {
+		dbg_perror("tcgetattr()");
+		rc = -errno;
+		goto error;
+	}
+
+	cfsetispeed(&tio, baudrate);
+	cfsetospeed(&tio, baudrate);
+
+	tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
+	tio.c_cflag |=  (CREAD | CLOCAL | CS8);
+	tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+	tio.c_iflag |=  (INPCK | ISTRIP);
+	tio.c_iflag &= ~(ISTRIP | IXON | IXOFF | IGNBRK | INLCR | ICRNL | IGNCR);
+	tio.c_oflag &= ~(OPOST | ONLCR);
+
+	rc = tcsetattr(fd, TCSANOW, &tio);
+	if (rc < 0) {
+		dbg_perror("tcsetattr()");
+		rc = -errno;
+		goto error;
+	}
+
+	/* Set ready to read/write */
+	v24 = TIOCM_DTR | TIOCM_RTS;
+	rc = ioctl(fd, TIOCMBIS, &v24);
+	if (rc < 0) {
+		dbg_perror("ioctl(TIOCMBIS)");
+		rc = -errno;
+		goto error;
+	}
+
+	return fd;
+
+error:
+	if (fd)
+		close(fd);
+	return rc;
+}
+
+static int
+_osmo_serial_set_baudrate(int fd, speed_t baudrate)
+{
+	int rc;
+	struct termios tio;
+
+	rc = tcgetattr(fd, &tio);
+	if (rc < 0) {
+		dbg_perror("tcgetattr()");
+		return -errno;
+	}
+	cfsetispeed(&tio, baudrate);
+	cfsetospeed(&tio, baudrate);
+
+	rc = tcsetattr(fd, TCSANOW, &tio);
+	if (rc < 0) {
+		dbg_perror("tcgetattr()");
+		return -errno;
+	}
+
+	return 0;
+}
+
+/*! \brief Change current baudrate
+ *  \param[in] fd File descriptor of the open device
+ *  \param[in] baudrate Baudrate constant (speed_t: B9600, B...)
+ *  \returns 0 for success or negative errno.
+ */
+int
+osmo_serial_set_baudrate(int fd, speed_t baudrate)
+{
+	osmo_serial_clear_custom_baudrate(fd);
+	return _osmo_serial_set_baudrate(fd, baudrate);
+}
+
+/*! \brief Change current baudrate to a custom one using OS specific method
+ *  \param[in] fd File descriptor of the open device
+ *  \param[in] baudrate Baudrate as integer
+ *  \returns 0 for success or negative errno.
+ *
+ *  This function might not work on all OS or with all type of serial adapters
+ */
+int
+osmo_serial_set_custom_baudrate(int fd, int baudrate)
+{
+#ifdef __linux__
+	int rc;
+	struct serial_struct ser_info;
+
+	rc = ioctl(fd, TIOCGSERIAL, &ser_info);
+	if (rc < 0) {
+		dbg_perror("ioctl(TIOCGSERIAL)");
+		return -errno;
+	}
+
+	ser_info.flags = ASYNC_SPD_CUST | ASYNC_LOW_LATENCY;
+	ser_info.custom_divisor = ser_info.baud_base / baudrate;
+
+	rc = ioctl(fd, TIOCSSERIAL, &ser_info);
+	if (rc < 0) {
+		dbg_perror("ioctl(TIOCSSERIAL)");
+		return -errno;
+	}
+
+	return _osmo_serial_set_baudrate(fd, B38400); /* 38400 is a kind of magic ... */
+#elif defined(__APPLE__)
+#ifndef IOSSIOSPEED
+#define IOSSIOSPEED    _IOW('T', 2, speed_t)
+#endif
+	int rc;
+
+	unsigned int speed = baudrate;
+	rc = ioctl(fd, IOSSIOSPEED, &speed);
+	if (rc < 0) {
+		dbg_perror("ioctl(IOSSIOSPEED)");
+		return -errno;
+	}
+	return 0;
+#else
+#warning osmo_serial_set_custom_baudrate: unsupported platform
+	return 0;
+#endif
+}
+
+/*! \brief Clear any custom baudrate
+ *  \param[in] fd File descriptor of the open device
+ *  \returns 0 for success or negative errno.
+ *
+ *  This function might not work on all OS or with all type of serial adapters
+ */
+int
+osmo_serial_clear_custom_baudrate(int fd)
+{
+#ifdef __linux__
+	int rc;
+	struct serial_struct ser_info;
+
+	rc = ioctl(fd, TIOCGSERIAL, &ser_info);
+	if (rc < 0) {
+		dbg_perror("ioctl(TIOCGSERIAL)");
+		return -errno;
+	}
+
+	ser_info.flags = ASYNC_LOW_LATENCY;
+	ser_info.custom_divisor = 0;
+
+	rc = ioctl(fd, TIOCSSERIAL, &ser_info);
+	if (rc < 0) {
+		dbg_perror("ioctl(TIOCSSERIAL)");
+		return -errno;
+	}
+#endif
+	return 0;
+}
+
+/*! @} */
diff --git a/src/signal.c b/src/signal.c
new file mode 100644
index 0000000..6384384
--- /dev/null
+++ b/src/signal.c
@@ -0,0 +1,109 @@
+/* Generic signalling/notification infrastructure */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/signal.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/*! \addtogroup signal
+ *  @{
+ */
+/*! \file signal.c */
+
+
+void *tall_sigh_ctx;
+static LLIST_HEAD(signal_handler_list);
+
+struct signal_handler {
+	struct llist_head entry;
+	unsigned int subsys;
+	osmo_signal_cbfn *cbfn;
+	void *data;
+};
+
+
+/*! \brief Register a new signal handler
+ *  \param[in] subsys Subsystem number
+ *  \param[in] cbfn Callback function
+ *  \param[in] data Data passed through to callback
+ */
+int osmo_signal_register_handler(unsigned int subsys,
+				 osmo_signal_cbfn *cbfn, void *data)
+{
+	struct signal_handler *sig_data;
+
+	sig_data = talloc(tall_sigh_ctx, struct signal_handler);
+	if (!sig_data)
+		return -ENOMEM;
+
+	memset(sig_data, 0, sizeof(*sig_data));
+
+	sig_data->subsys = subsys;
+	sig_data->data = data;
+	sig_data->cbfn = cbfn;
+
+	/* FIXME: check if we already have a handler for this subsys/cbfn/data */
+
+	llist_add_tail(&sig_data->entry, &signal_handler_list);
+
+	return 0;
+}
+
+/*! \brief Unregister signal handler
+ *  \param[in] subsys Subsystem number
+ *  \param[in] cbfn Callback function
+ *  \param[in] data Data passed through to callback
+ */
+void osmo_signal_unregister_handler(unsigned int subsys,
+				    osmo_signal_cbfn *cbfn, void *data)
+{
+	struct signal_handler *handler;
+
+	llist_for_each_entry(handler, &signal_handler_list, entry) {
+		if (handler->cbfn == cbfn && handler->data == data 
+		    && subsys == handler->subsys) {
+			llist_del(&handler->entry);
+			talloc_free(handler);
+			break;
+		}
+	}
+}
+
+/*! \brief dispatch (deliver) a new signal to all registered handlers
+ *  \param[in] subsys Subsystem number
+ *  \param[in] signal Signal number,
+ *  \param[in] signal_data Data to be passed along to handlers
+ */
+void osmo_signal_dispatch(unsigned int subsys, unsigned int signal,
+			  void *signal_data)
+{
+	struct signal_handler *handler;
+
+	llist_for_each_entry(handler, &signal_handler_list, entry) {
+		if (handler->subsys != subsys)
+			continue;
+		(*handler->cbfn)(subsys, signal, handler->data, signal_data);
+	}
+}
+
+/*! @} */
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..53205cd
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,249 @@
+#include "../config.h"
+
+/*! \addtogroup socket
+ *  @{
+ */
+
+/*! \file socket.c
+ *  \brief Osmocom socket convenience functions
+ */
+
+#ifdef HAVE_SYS_SOCKET_H
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+/*! \brief Initialize a socket (including bind/connect)
+ *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ *  \param[in] host remote host name or IP address in string form
+ *  \param[in] port remote port number in host byte order
+ *  \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ *
+ * This function creates a new socket of the designated \a family, \a
+ * type and \a proto and optionally binds or connects it, depending on
+ * the value of \a flags parameter.
+ */
+int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
+		   const char *host, uint16_t port, unsigned int flags)
+{
+	struct addrinfo hints, *result, *rp;
+	int sfd, rc, on = 1;
+	char portbuf[16];
+
+	if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
+		     (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT))
+		return -EINVAL;
+
+	sprintf(portbuf, "%u", port);
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = family;
+	hints.ai_socktype = type;
+	hints.ai_flags = 0;
+	hints.ai_protocol = proto;
+
+	if (flags & OSMO_SOCK_F_BIND)
+		hints.ai_flags |= AI_PASSIVE;
+
+	rc = getaddrinfo(host, portbuf, &hints, &result);
+	if (rc != 0) {
+		perror("getaddrinfo returned NULL");
+		return -EINVAL;
+	}
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+		if (sfd == -1)
+			continue;
+		if (flags & OSMO_SOCK_F_NONBLOCK) {
+			if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
+				perror("cannot set this socket unblocking");
+				close(sfd);
+				return -EINVAL;
+			}
+		}
+		if (flags & OSMO_SOCK_F_CONNECT) {
+			rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);
+			if (rc != -1 || (rc == -1 && errno == EINPROGRESS))
+				break;
+		} else {
+			rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
+							&on, sizeof(on));
+			if (rc < 0) {
+				perror("cannot setsockopt socket");
+				break;
+			}
+			if (bind(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+				break;
+		}
+		close(sfd);
+	}
+	freeaddrinfo(result);
+
+	if (rp == NULL) {
+		perror("unable to connect/bind socket");
+		return -ENODEV;
+	}
+
+	setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	/* Make sure to call 'listen' on a bound, connection-oriented sock */
+	if (flags & OSMO_SOCK_F_BIND) {
+		switch (type) {
+		case SOCK_STREAM:
+		case SOCK_SEQPACKET:
+			listen(sfd, 10);
+			break;
+		}
+	}
+	return sfd;
+}
+
+/*! \brief Initialize a socket and fill \ref osmo_fd
+ *  \param[out] osmocom file descriptor (will be filled in)
+ *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ *  \param[in] host remote host name or IP address in string form
+ *  \param[in] port remote port number in host byte order
+ *  \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ *
+ * This function creates (and optionall binds/connects) a socket using
+ * \ref osmo_sock_init, but also fills the \a ofd structure.
+ */
+int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
+			const char *host, uint16_t port, unsigned int flags)
+{
+	int sfd, rc;
+
+	sfd = osmo_sock_init(family, type, proto, host, port, flags);
+	if (sfd < 0)
+		return sfd;
+
+	ofd->fd = sfd;
+	ofd->when = BSC_FD_READ;
+
+	rc = osmo_fd_register(ofd);
+	if (rc < 0) {
+		close(sfd);
+		return rc;
+	}
+
+	return sfd;
+}
+
+/*! \brief Initialize a socket and fill \ref sockaddr
+ *  \param[out] ss socket address (will be filled in)
+ *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ *  \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ *
+ * This function creates (and optionall binds/connects) a socket using
+ * \ref osmo_sock_init, but also fills the \a ss structure.
+ */
+int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
+		      uint8_t proto, unsigned int flags)
+{
+	char host[NI_MAXHOST];
+	uint16_t port;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+	int s, sa_len;
+
+	/* determine port and host from ss */
+	switch (ss->sa_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *) ss;
+		sa_len = sizeof(struct sockaddr_in);
+		port = ntohs(sin->sin_port);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *) ss;
+		sa_len = sizeof(struct sockaddr_in6);
+		port = ntohs(sin6->sin6_port);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	s = getnameinfo(ss, sa_len, host, NI_MAXHOST,
+			NULL, 0, NI_NUMERICHOST);
+	if (s != 0) {
+		perror("getnameinfo failed");
+		return s;
+	}
+
+	return osmo_sock_init(ss->sa_family, type, proto, host, port, flags);
+}
+
+static int sockaddr_equal(const struct sockaddr *a,
+			  const struct sockaddr *b, unsigned int len)
+{
+	struct sockaddr_in *sin_a, *sin_b;
+	struct sockaddr_in6 *sin6_a, *sin6_b;
+
+	if (a->sa_family != b->sa_family)
+		return 0;
+
+	switch (a->sa_family) {
+	case AF_INET:
+		sin_a = (struct sockaddr_in *)a;
+		sin_b = (struct sockaddr_in *)b;
+		if (!memcmp(&sin_a->sin_addr, &sin_b->sin_addr,
+			    sizeof(struct in_addr)))
+			return 1;
+		break;
+	case AF_INET6:
+		sin6_a = (struct sockaddr_in6 *)a;
+		sin6_b = (struct sockaddr_in6 *)b;
+		if (!memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr,
+			    sizeof(struct in6_addr)))
+			return 1;
+		break;
+	}
+	return 0;
+}
+
+/*! \brief Determine if the given address is a local address
+ *  \param[in] addr Socket Address
+ *  \param[in] addrlen Length of socket address in bytes
+ *  \returns 1 if address is local, 0 otherwise.
+ */
+int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
+{
+	struct ifaddrs *ifaddr, *ifa;
+
+	if (getifaddrs(&ifaddr) == -1) {
+		perror("getifaddrs");
+		return -EIO;
+	}
+
+	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+		if (!ifa->ifa_addr)
+			continue;
+		if (sockaddr_equal(ifa->ifa_addr, addr, addrlen))
+			return 1;
+	}
+
+	return 0;
+}
+
+#endif /* HAVE_SYS_SOCKET_H */
+
+/*! @} */
diff --git a/src/statistics.c b/src/statistics.c
new file mode 100644
index 0000000..e28541b
--- /dev/null
+++ b/src/statistics.c
@@ -0,0 +1,76 @@
+/* utility routines for keeping some statistics */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/statistics.h>
+
+static LLIST_HEAD(counters);
+
+void *tall_ctr_ctx;
+
+struct osmo_counter *osmo_counter_alloc(const char *name)
+{
+	struct osmo_counter *ctr = talloc_zero(tall_ctr_ctx, struct osmo_counter);
+
+	if (!ctr)
+		return NULL;
+
+	ctr->name = name;
+	llist_add_tail(&ctr->list, &counters);
+
+	return ctr;
+}
+
+void osmo_counter_free(struct osmo_counter *ctr)
+{
+	llist_del(&ctr->list);
+	talloc_free(ctr);
+}
+
+int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *),
+			   void *data)
+{
+	struct osmo_counter *ctr;
+	int rc = 0;
+
+	llist_for_each_entry(ctr, &counters, list) {
+		rc = handle_counter(ctr, data);
+		if (rc < 0)
+			return rc;
+	}
+
+	return rc;
+}
+
+struct osmo_counter *osmo_counter_get_by_name(const char *name)
+{
+	struct osmo_counter *ctr;
+
+	llist_for_each_entry(ctr, &counters, list) {
+		if (!strcmp(ctr->name, name))
+			return ctr;
+	}
+	return NULL;
+}
diff --git a/src/talloc.c b/src/talloc.c
new file mode 100644
index 0000000..d3a0690
--- /dev/null
+++ b/src/talloc.c
@@ -0,0 +1,1804 @@
+/* 
+   Samba Unix SMB/CIFS implementation.
+
+   Samba trivial allocation library - new interface
+
+   NOTE: Please read talloc_guide.txt for full documentation
+
+   Copyright (C) Andrew Tridgell 2004
+   Copyright (C) Stefan Metzmacher 2006
+   
+     ** NOTE! The following LGPL license applies to the talloc
+     ** library. This does NOT imply that all of Samba is released
+     ** under the LGPL
+   
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 3 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+  inspired by http://swapped.cc/halloc/
+*/
+
+#ifdef _SAMBA_BUILD_
+#include "version.h"
+#if (SAMBA_VERSION_MAJOR<4)
+#include "includes.h"
+/* This is to circumvent SAMBA3's paranoid malloc checker. Here in this file
+ * we trust ourselves... */
+#ifdef malloc
+#undef malloc
+#endif
+#ifdef realloc
+#undef realloc
+#endif
+#define _TALLOC_SAMBA3
+#endif /* (SAMBA_VERSION_MAJOR<4) */
+#endif /* _SAMBA_BUILD_ */
+
+#ifndef _TALLOC_SAMBA3
+//#include "replace.h"
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#define __USE_GNU
+#include <string.h>
+#undef __USE_GNU
+#include <osmocom/core/talloc.h>
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#endif /* not _TALLOC_SAMBA3 */
+
+/* use this to force every realloc to change the pointer, to stress test
+   code that might not cope */
+#define ALWAYS_REALLOC 0
+
+
+#define MAX_TALLOC_SIZE 0x10000000
+#define TALLOC_MAGIC 0xe814ec70
+#define TALLOC_FLAG_FREE 0x01
+#define TALLOC_FLAG_LOOP 0x02
+#define TALLOC_FLAG_POOL 0x04		/* This is a talloc pool */
+#define TALLOC_FLAG_POOLMEM 0x08	/* This is allocated in a pool */
+#define TALLOC_MAGIC_REFERENCE ((const char *)1)
+
+/* by default we abort when given a bad pointer (such as when talloc_free() is called 
+   on a pointer that came from malloc() */
+#ifndef TALLOC_ABORT
+#define TALLOC_ABORT(reason) abort()
+#endif
+
+#ifndef discard_const_p
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr)))
+#else
+# define discard_const_p(type, ptr) ((type *)(ptr))
+#endif
+#endif
+
+/* these macros gain us a few percent of speed on gcc */
+#if (__GNUC__ >= 3)
+/* the strange !! is to ensure that __builtin_expect() takes either 0 or 1
+   as its first argument */
+#ifndef likely
+#define likely(x)   __builtin_expect(!!(x), 1)
+#endif
+#ifndef unlikely
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#endif
+#else
+#ifndef likely
+#define likely(x) (x)
+#endif
+#ifndef unlikely
+#define unlikely(x) (x)
+#endif
+#endif
+
+#ifdef __APPLE__
+/* taken from http://insanecoding.blogspot.com/2007/03/methods-for-safe-string-handling.html */
+size_t strnlen(const char *s, size_t n)
+{
+  const char *p = (const char *)memchr(s, 0, n);
+  return(p ? p-s : n);
+}
+#endif
+
+/* this null_context is only used if talloc_enable_leak_report() or
+   talloc_enable_leak_report_full() is called, otherwise it remains
+   NULL
+*/
+static void *null_context;
+static void *autofree_context;
+
+struct talloc_reference_handle {
+	struct talloc_reference_handle *next, *prev;
+	void *ptr;
+};
+
+typedef int (*talloc_destructor_t)(void *);
+
+struct talloc_chunk {
+	struct talloc_chunk *next, *prev;
+	struct talloc_chunk *parent, *child;
+	struct talloc_reference_handle *refs;
+	talloc_destructor_t destructor;
+	const char *name;
+	size_t size;
+	unsigned flags;
+
+	/*
+	 * "pool" has dual use:
+	 *
+	 * For the talloc pool itself (i.e. TALLOC_FLAG_POOL is set), "pool"
+	 * marks the end of the currently allocated area.
+	 *
+	 * For members of the pool (i.e. TALLOC_FLAG_POOLMEM is set), "pool"
+	 * is a pointer to the struct talloc_chunk of the pool that it was
+	 * allocated from. This way children can quickly find the pool to chew
+	 * from.
+	 */
+	void *pool;
+};
+
+/* 16 byte alignment seems to keep everyone happy */
+#define TC_HDR_SIZE ((sizeof(struct talloc_chunk)+15)&~15)
+#define TC_PTR_FROM_CHUNK(tc) ((void *)(TC_HDR_SIZE + (char*)tc))
+
+static void (*talloc_abort_fn)(const char *reason);
+
+void talloc_set_abort_fn(void (*abort_fn)(const char *reason))
+{
+	talloc_abort_fn = abort_fn;
+}
+
+static void talloc_abort(const char *reason)
+{
+	if (!talloc_abort_fn) {
+		TALLOC_ABORT(reason);
+	}
+
+	talloc_abort_fn(reason);
+}
+
+static void talloc_abort_double_free(void)
+{
+	talloc_abort("Bad talloc magic value - double free");
+}
+
+static void talloc_abort_unknown_value(void)
+{
+	talloc_abort("Bad talloc magic value - unknown value");
+}
+
+/* panic if we get a bad magic value */
+static inline struct talloc_chunk *talloc_chunk_from_ptr(const void *ptr)
+{
+	const char *pp = (const char *)ptr;
+	struct talloc_chunk *tc = discard_const_p(struct talloc_chunk, pp - TC_HDR_SIZE);
+	if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) { 
+		if (tc->flags & TALLOC_FLAG_FREE) {
+			talloc_abort_double_free();
+		} else {
+			talloc_abort_unknown_value();
+		}
+	}
+	return tc;
+}
+
+/* hook into the front of the list */
+#define _TLIST_ADD(list, p) \
+do { \
+        if (!(list)) { \
+		(list) = (p); \
+		(p)->next = (p)->prev = NULL; \
+	} else { \
+		(list)->prev = (p); \
+		(p)->next = (list); \
+		(p)->prev = NULL; \
+		(list) = (p); \
+	}\
+} while (0)
+
+/* remove an element from a list - element doesn't have to be in list. */
+#define _TLIST_REMOVE(list, p) \
+do { \
+	if ((p) == (list)) { \
+		(list) = (p)->next; \
+		if (list) (list)->prev = NULL; \
+	} else { \
+		if ((p)->prev) (p)->prev->next = (p)->next; \
+		if ((p)->next) (p)->next->prev = (p)->prev; \
+	} \
+	if ((p) && ((p) != (list))) (p)->next = (p)->prev = NULL; \
+} while (0)
+
+
+/*
+  return the parent chunk of a pointer
+*/
+static inline struct talloc_chunk *talloc_parent_chunk(const void *ptr)
+{
+	struct talloc_chunk *tc;
+
+	if (unlikely(ptr == NULL)) {
+		return NULL;
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+	while (tc->prev) tc=tc->prev;
+
+	return tc->parent;
+}
+
+void *talloc_parent(const void *ptr)
+{
+	struct talloc_chunk *tc = talloc_parent_chunk(ptr);
+	return tc? TC_PTR_FROM_CHUNK(tc) : NULL;
+}
+
+/*
+  find parents name
+*/
+const char *talloc_parent_name(const void *ptr)
+{
+	struct talloc_chunk *tc = talloc_parent_chunk(ptr);
+	return tc? tc->name : NULL;
+}
+
+/*
+  A pool carries an in-pool object count count in the first 16 bytes.
+  bytes. This is done to support talloc_steal() to a parent outside of the
+  pool. The count includes the pool itself, so a talloc_free() on a pool will
+  only destroy the pool if the count has dropped to zero. A talloc_free() of a
+  pool member will reduce the count, and eventually also call free(3) on the
+  pool memory.
+
+  The object count is not put into "struct talloc_chunk" because it is only
+  relevant for talloc pools and the alignment to 16 bytes would increase the
+  memory footprint of each talloc chunk by those 16 bytes.
+*/
+
+#define TALLOC_POOL_HDR_SIZE 16
+
+static unsigned int *talloc_pool_objectcount(struct talloc_chunk *tc)
+{
+	return (unsigned int *)((char *)tc + sizeof(struct talloc_chunk));
+}
+
+/*
+  Allocate from a pool
+*/
+
+static struct talloc_chunk *talloc_alloc_pool(struct talloc_chunk *parent,
+					      size_t size)
+{
+	struct talloc_chunk *pool_ctx = NULL;
+	size_t space_left;
+	struct talloc_chunk *result;
+	size_t chunk_size;
+
+	if (parent == NULL) {
+		return NULL;
+	}
+
+	if (parent->flags & TALLOC_FLAG_POOL) {
+		pool_ctx = parent;
+	}
+	else if (parent->flags & TALLOC_FLAG_POOLMEM) {
+		pool_ctx = (struct talloc_chunk *)parent->pool;
+	}
+
+	if (pool_ctx == NULL) {
+		return NULL;
+	}
+
+	space_left = ((char *)pool_ctx + TC_HDR_SIZE + pool_ctx->size)
+		- ((char *)pool_ctx->pool);
+
+	/*
+	 * Align size to 16 bytes
+	 */
+	chunk_size = ((size + 15) & ~15);
+
+	if (space_left < chunk_size) {
+		return NULL;
+	}
+
+	result = (struct talloc_chunk *)pool_ctx->pool;
+
+#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_UNDEFINED)
+	VALGRIND_MAKE_MEM_UNDEFINED(result, size);
+#endif
+
+	pool_ctx->pool = (void *)((char *)result + chunk_size);
+
+	result->flags = TALLOC_MAGIC | TALLOC_FLAG_POOLMEM;
+	result->pool = pool_ctx;
+
+	*talloc_pool_objectcount(pool_ctx) += 1;
+
+	return result;
+}
+
+/* 
+   Allocate a bit of memory as a child of an existing pointer
+*/
+static inline void *__talloc(const void *context, size_t size)
+{
+	struct talloc_chunk *tc = NULL;
+
+	if (unlikely(context == NULL)) {
+		context = null_context;
+	}
+
+	if (unlikely(size >= MAX_TALLOC_SIZE)) {
+		return NULL;
+	}
+
+	if (context != NULL) {
+		tc = talloc_alloc_pool(talloc_chunk_from_ptr(context),
+				       TC_HDR_SIZE+size);
+	}
+
+	if (tc == NULL) {
+		tc = (struct talloc_chunk *)malloc(TC_HDR_SIZE+size);
+		if (unlikely(tc == NULL)) return NULL;
+		tc->flags = TALLOC_MAGIC;
+		tc->pool  = NULL;
+	}
+
+	tc->size = size;
+	tc->destructor = NULL;
+	tc->child = NULL;
+	tc->name = NULL;
+	tc->refs = NULL;
+
+	if (likely(context)) {
+		struct talloc_chunk *parent = talloc_chunk_from_ptr(context);
+
+		if (parent->child) {
+			parent->child->parent = NULL;
+			tc->next = parent->child;
+			tc->next->prev = tc;
+		} else {
+			tc->next = NULL;
+		}
+		tc->parent = parent;
+		tc->prev = NULL;
+		parent->child = tc;
+	} else {
+		tc->next = tc->prev = tc->parent = NULL;
+	}
+
+	return TC_PTR_FROM_CHUNK(tc);
+}
+
+/*
+ * Create a talloc pool
+ */
+
+void *talloc_pool(const void *context, size_t size)
+{
+	void *result = __talloc(context, size + TALLOC_POOL_HDR_SIZE);
+	struct talloc_chunk *tc;
+
+	if (unlikely(result == NULL)) {
+		return NULL;
+	}
+
+	tc = talloc_chunk_from_ptr(result);
+
+	tc->flags |= TALLOC_FLAG_POOL;
+	tc->pool = (char *)result + TALLOC_POOL_HDR_SIZE;
+
+	*talloc_pool_objectcount(tc) = 1;
+
+#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_NOACCESS)
+	VALGRIND_MAKE_MEM_NOACCESS(tc->pool, size);
+#endif
+
+	return result;
+}
+
+/*
+  setup a destructor to be called on free of a pointer
+  the destructor should return 0 on success, or -1 on failure.
+  if the destructor fails then the free is failed, and the memory can
+  be continued to be used
+*/
+void _talloc_set_destructor(const void *ptr, int (*destructor)(void *))
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	tc->destructor = destructor;
+}
+
+/*
+  increase the reference count on a piece of memory. 
+*/
+int talloc_increase_ref_count(const void *ptr)
+{
+	if (unlikely(!talloc_reference(null_context, ptr))) {
+		return -1;
+	}
+	return 0;
+}
+
+/*
+  helper for talloc_reference()
+
+  this is referenced by a function pointer and should not be inline
+*/
+static int talloc_reference_destructor(struct talloc_reference_handle *handle)
+{
+	struct talloc_chunk *ptr_tc = talloc_chunk_from_ptr(handle->ptr);
+	_TLIST_REMOVE(ptr_tc->refs, handle);
+	return 0;
+}
+
+/*
+   more efficient way to add a name to a pointer - the name must point to a 
+   true string constant
+*/
+static inline void _talloc_set_name_const(const void *ptr, const char *name)
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	tc->name = name;
+}
+
+/*
+  internal talloc_named_const()
+*/
+static inline void *_talloc_named_const(const void *context, size_t size, const char *name)
+{
+	void *ptr;
+
+	ptr = __talloc(context, size);
+	if (unlikely(ptr == NULL)) {
+		return NULL;
+	}
+
+	_talloc_set_name_const(ptr, name);
+
+	return ptr;
+}
+
+/*
+  make a secondary reference to a pointer, hanging off the given context.
+  the pointer remains valid until both the original caller and this given
+  context are freed.
+  
+  the major use for this is when two different structures need to reference the 
+  same underlying data, and you want to be able to free the two instances separately,
+  and in either order
+*/
+void *_talloc_reference(const void *context, const void *ptr)
+{
+	struct talloc_chunk *tc;
+	struct talloc_reference_handle *handle;
+	if (unlikely(ptr == NULL)) return NULL;
+
+	tc = talloc_chunk_from_ptr(ptr);
+	handle = (struct talloc_reference_handle *)_talloc_named_const(context,
+						   sizeof(struct talloc_reference_handle),
+						   TALLOC_MAGIC_REFERENCE);
+	if (unlikely(handle == NULL)) return NULL;
+
+	/* note that we hang the destructor off the handle, not the
+	   main context as that allows the caller to still setup their
+	   own destructor on the context if they want to */
+	talloc_set_destructor(handle, talloc_reference_destructor);
+	handle->ptr = discard_const_p(void, ptr);
+	_TLIST_ADD(tc->refs, handle);
+	return handle->ptr;
+}
+
+
+/* 
+   internal talloc_free call
+*/
+static inline int _talloc_free(void *ptr)
+{
+	struct talloc_chunk *tc;
+
+	if (unlikely(ptr == NULL)) {
+		return -1;
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	if (unlikely(tc->refs)) {
+		int is_child;
+		/* check this is a reference from a child or grantchild
+		 * back to it's parent or grantparent
+		 *
+		 * in that case we need to remove the reference and
+		 * call another instance of talloc_free() on the current
+		 * pointer.
+		 */
+		is_child = talloc_is_parent(tc->refs, ptr);
+		_talloc_free(tc->refs);
+		if (is_child) {
+			return _talloc_free(ptr);
+		}
+		return -1;
+	}
+
+	if (unlikely(tc->flags & TALLOC_FLAG_LOOP)) {
+		/* we have a free loop - stop looping */
+		return 0;
+	}
+
+	if (unlikely(tc->destructor)) {
+		talloc_destructor_t d = tc->destructor;
+		if (d == (talloc_destructor_t)-1) {
+			return -1;
+		}
+		tc->destructor = (talloc_destructor_t)-1;
+		if (d(ptr) == -1) {
+			tc->destructor = d;
+			return -1;
+		}
+		tc->destructor = NULL;
+	}
+
+	if (tc->parent) {
+		_TLIST_REMOVE(tc->parent->child, tc);
+		if (tc->parent->child) {
+			tc->parent->child->parent = tc->parent;
+		}
+	} else {
+		if (tc->prev) tc->prev->next = tc->next;
+		if (tc->next) tc->next->prev = tc->prev;
+	}
+
+	tc->flags |= TALLOC_FLAG_LOOP;
+
+	while (tc->child) {
+		/* we need to work out who will own an abandoned child
+		   if it cannot be freed. In priority order, the first
+		   choice is owner of any remaining reference to this
+		   pointer, the second choice is our parent, and the
+		   final choice is the null context. */
+		void *child = TC_PTR_FROM_CHUNK(tc->child);
+		const void *new_parent = null_context;
+		if (unlikely(tc->child->refs)) {
+			struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs);
+			if (p) new_parent = TC_PTR_FROM_CHUNK(p);
+		}
+		if (unlikely(_talloc_free(child) == -1)) {
+			if (new_parent == null_context) {
+				struct talloc_chunk *p = talloc_parent_chunk(ptr);
+				if (p) new_parent = TC_PTR_FROM_CHUNK(p);
+			}
+			talloc_steal(new_parent, child);
+		}
+	}
+
+	tc->flags |= TALLOC_FLAG_FREE;
+
+	if (tc->flags & (TALLOC_FLAG_POOL|TALLOC_FLAG_POOLMEM)) {
+		struct talloc_chunk *pool;
+		unsigned int *pool_object_count;
+
+		pool = (tc->flags & TALLOC_FLAG_POOL)
+			? tc : (struct talloc_chunk *)tc->pool;
+
+		pool_object_count = talloc_pool_objectcount(pool);
+
+		if (*pool_object_count == 0) {
+			talloc_abort("Pool object count zero!");
+		}
+
+		*pool_object_count -= 1;
+
+		if (*pool_object_count == 0) {
+			free(pool);
+		}
+	}
+	else {
+		free(tc);
+	}
+	return 0;
+}
+
+/* 
+   move a lump of memory from one talloc context to another return the
+   ptr on success, or NULL if it could not be transferred.
+   passing NULL as ptr will always return NULL with no side effects.
+*/
+void *_talloc_steal(const void *new_ctx, const void *ptr)
+{
+	struct talloc_chunk *tc, *new_tc;
+
+	if (unlikely(!ptr)) {
+		return NULL;
+	}
+
+	if (unlikely(new_ctx == NULL)) {
+		new_ctx = null_context;
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	if (unlikely(new_ctx == NULL)) {
+		if (tc->parent) {
+			_TLIST_REMOVE(tc->parent->child, tc);
+			if (tc->parent->child) {
+				tc->parent->child->parent = tc->parent;
+			}
+		} else {
+			if (tc->prev) tc->prev->next = tc->next;
+			if (tc->next) tc->next->prev = tc->prev;
+		}
+		
+		tc->parent = tc->next = tc->prev = NULL;
+		return discard_const_p(void, ptr);
+	}
+
+	new_tc = talloc_chunk_from_ptr(new_ctx);
+
+	if (unlikely(tc == new_tc || tc->parent == new_tc)) {
+		return discard_const_p(void, ptr);
+	}
+
+	if (tc->parent) {
+		_TLIST_REMOVE(tc->parent->child, tc);
+		if (tc->parent->child) {
+			tc->parent->child->parent = tc->parent;
+		}
+	} else {
+		if (tc->prev) tc->prev->next = tc->next;
+		if (tc->next) tc->next->prev = tc->prev;
+	}
+
+	tc->parent = new_tc;
+	if (new_tc->child) new_tc->child->parent = NULL;
+	_TLIST_ADD(new_tc->child, tc);
+
+	return discard_const_p(void, ptr);
+}
+
+
+
+/*
+  remove a secondary reference to a pointer. This undo's what
+  talloc_reference() has done. The context and pointer arguments
+  must match those given to a talloc_reference()
+*/
+static inline int talloc_unreference(const void *context, const void *ptr)
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	struct talloc_reference_handle *h;
+
+	if (unlikely(context == NULL)) {
+		context = null_context;
+	}
+
+	for (h=tc->refs;h;h=h->next) {
+		struct talloc_chunk *p = talloc_parent_chunk(h);
+		if (p == NULL) {
+			if (context == NULL) break;
+		} else if (TC_PTR_FROM_CHUNK(p) == context) {
+			break;
+		}
+	}
+	if (h == NULL) {
+		return -1;
+	}
+
+	return _talloc_free(h);
+}
+
+/*
+  remove a specific parent context from a pointer. This is a more
+  controlled varient of talloc_free()
+*/
+int talloc_unlink(const void *context, void *ptr)
+{
+	struct talloc_chunk *tc_p, *new_p;
+	void *new_parent;
+
+	if (ptr == NULL) {
+		return -1;
+	}
+
+	if (context == NULL) {
+		context = null_context;
+	}
+
+	if (talloc_unreference(context, ptr) == 0) {
+		return 0;
+	}
+
+	if (context == NULL) {
+		if (talloc_parent_chunk(ptr) != NULL) {
+			return -1;
+		}
+	} else {
+		if (talloc_chunk_from_ptr(context) != talloc_parent_chunk(ptr)) {
+			return -1;
+		}
+	}
+	
+	tc_p = talloc_chunk_from_ptr(ptr);
+
+	if (tc_p->refs == NULL) {
+		return _talloc_free(ptr);
+	}
+
+	new_p = talloc_parent_chunk(tc_p->refs);
+	if (new_p) {
+		new_parent = TC_PTR_FROM_CHUNK(new_p);
+	} else {
+		new_parent = NULL;
+	}
+
+	if (talloc_unreference(new_parent, ptr) != 0) {
+		return -1;
+	}
+
+	talloc_steal(new_parent, ptr);
+
+	return 0;
+}
+
+/*
+  add a name to an existing pointer - va_list version
+*/
+static inline const char *talloc_set_name_v(const void *ptr, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);
+
+static inline const char *talloc_set_name_v(const void *ptr, const char *fmt, va_list ap)
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	tc->name = talloc_vasprintf(ptr, fmt, ap);
+	if (likely(tc->name)) {
+		_talloc_set_name_const(tc->name, ".name");
+	}
+	return tc->name;
+}
+
+/*
+  add a name to an existing pointer
+*/
+const char *talloc_set_name(const void *ptr, const char *fmt, ...)
+{
+	const char *name;
+	va_list ap;
+	va_start(ap, fmt);
+	name = talloc_set_name_v(ptr, fmt, ap);
+	va_end(ap);
+	return name;
+}
+
+
+/*
+  create a named talloc pointer. Any talloc pointer can be named, and
+  talloc_named() operates just like talloc() except that it allows you
+  to name the pointer.
+*/
+void *talloc_named(const void *context, size_t size, const char *fmt, ...)
+{
+	va_list ap;
+	void *ptr;
+	const char *name;
+
+	ptr = __talloc(context, size);
+	if (unlikely(ptr == NULL)) return NULL;
+
+	va_start(ap, fmt);
+	name = talloc_set_name_v(ptr, fmt, ap);
+	va_end(ap);
+
+	if (unlikely(name == NULL)) {
+		_talloc_free(ptr);
+		return NULL;
+	}
+
+	return ptr;
+}
+
+/*
+  return the name of a talloc ptr, or "UNNAMED"
+*/
+const char *talloc_get_name(const void *ptr)
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	if (unlikely(tc->name == TALLOC_MAGIC_REFERENCE)) {
+		return ".reference";
+	}
+	if (likely(tc->name)) {
+		return tc->name;
+	}
+	return "UNNAMED";
+}
+
+
+/*
+  check if a pointer has the given name. If it does, return the pointer,
+  otherwise return NULL
+*/
+void *talloc_check_name(const void *ptr, const char *name)
+{
+	const char *pname;
+	if (unlikely(ptr == NULL)) return NULL;
+	pname = talloc_get_name(ptr);
+	if (likely(pname == name || strcmp(pname, name) == 0)) {
+		return discard_const_p(void, ptr);
+	}
+	return NULL;
+}
+
+static void talloc_abort_type_missmatch(const char *location,
+					const char *name,
+					const char *expected)
+{
+	const char *reason;
+
+	reason = talloc_asprintf(NULL,
+				 "%s: Type mismatch: name[%s] expected[%s]",
+				 location,
+				 name?name:"NULL",
+				 expected);
+	if (!reason) {
+		reason = "Type mismatch";
+	}
+
+	talloc_abort(reason);
+}
+
+void *_talloc_get_type_abort(const void *ptr, const char *name, const char *location)
+{
+	const char *pname;
+
+	if (unlikely(ptr == NULL)) {
+		talloc_abort_type_missmatch(location, NULL, name);
+		return NULL;
+	}
+
+	pname = talloc_get_name(ptr);
+	if (likely(pname == name || strcmp(pname, name) == 0)) {
+		return discard_const_p(void, ptr);
+	}
+
+	talloc_abort_type_missmatch(location, pname, name);
+	return NULL;
+}
+
+/*
+  this is for compatibility with older versions of talloc
+*/
+void *talloc_init(const char *fmt, ...)
+{
+	va_list ap;
+	void *ptr;
+	const char *name;
+
+	/*
+	 * samba3 expects talloc_report_depth_cb(NULL, ...)
+	 * reports all talloc'ed memory, so we need to enable
+	 * null_tracking
+	 */
+	talloc_enable_null_tracking();
+
+	ptr = __talloc(NULL, 0);
+	if (unlikely(ptr == NULL)) return NULL;
+
+	va_start(ap, fmt);
+	name = talloc_set_name_v(ptr, fmt, ap);
+	va_end(ap);
+
+	if (unlikely(name == NULL)) {
+		_talloc_free(ptr);
+		return NULL;
+	}
+
+	return ptr;
+}
+
+/*
+  this is a replacement for the Samba3 talloc_destroy_pool functionality. It
+  should probably not be used in new code. It's in here to keep the talloc
+  code consistent across Samba 3 and 4.
+*/
+void talloc_free_children(void *ptr)
+{
+	struct talloc_chunk *tc;
+
+	if (unlikely(ptr == NULL)) {
+		return;
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	while (tc->child) {
+		/* we need to work out who will own an abandoned child
+		   if it cannot be freed. In priority order, the first
+		   choice is owner of any remaining reference to this
+		   pointer, the second choice is our parent, and the
+		   final choice is the null context. */
+		void *child = TC_PTR_FROM_CHUNK(tc->child);
+		const void *new_parent = null_context;
+		if (unlikely(tc->child->refs)) {
+			struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs);
+			if (p) new_parent = TC_PTR_FROM_CHUNK(p);
+		}
+		if (unlikely(_talloc_free(child) == -1)) {
+			if (new_parent == null_context) {
+				struct talloc_chunk *p = talloc_parent_chunk(ptr);
+				if (p) new_parent = TC_PTR_FROM_CHUNK(p);
+			}
+			talloc_steal(new_parent, child);
+		}
+	}
+
+	if ((tc->flags & TALLOC_FLAG_POOL)
+	    && (*talloc_pool_objectcount(tc) == 1)) {
+		tc->pool = ((char *)tc + TC_HDR_SIZE + TALLOC_POOL_HDR_SIZE);
+#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_NOACCESS)
+		VALGRIND_MAKE_MEM_NOACCESS(
+			tc->pool, tc->size - TALLOC_POOL_HDR_SIZE);
+#endif
+	}
+}
+
+/* 
+   Allocate a bit of memory as a child of an existing pointer
+*/
+void *_talloc(const void *context, size_t size)
+{
+	return __talloc(context, size);
+}
+
+/*
+  externally callable talloc_set_name_const()
+*/
+void talloc_set_name_const(const void *ptr, const char *name)
+{
+	_talloc_set_name_const(ptr, name);
+}
+
+/*
+  create a named talloc pointer. Any talloc pointer can be named, and
+  talloc_named() operates just like talloc() except that it allows you
+  to name the pointer.
+*/
+void *talloc_named_const(const void *context, size_t size, const char *name)
+{
+	return _talloc_named_const(context, size, name);
+}
+
+/* 
+   free a talloc pointer. This also frees all child pointers of this 
+   pointer recursively
+
+   return 0 if the memory is actually freed, otherwise -1. The memory
+   will not be freed if the ref_count is > 1 or the destructor (if
+   any) returns non-zero
+*/
+int talloc_free(void *ptr)
+{
+	return _talloc_free(ptr);
+}
+
+
+
+/*
+  A talloc version of realloc. The context argument is only used if
+  ptr is NULL
+*/
+void *_talloc_realloc(const void *context, void *ptr, size_t size, const char *name)
+{
+	struct talloc_chunk *tc;
+	void *new_ptr;
+	bool malloced = false;
+
+	/* size zero is equivalent to free() */
+	if (unlikely(size == 0)) {
+		_talloc_free(ptr);
+		return NULL;
+	}
+
+	if (unlikely(size >= MAX_TALLOC_SIZE)) {
+		return NULL;
+	}
+
+	/* realloc(NULL) is equivalent to malloc() */
+	if (ptr == NULL) {
+		return _talloc_named_const(context, size, name);
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	/* don't allow realloc on referenced pointers */
+	if (unlikely(tc->refs)) {
+		return NULL;
+	}
+
+	/* don't let anybody try to realloc a talloc_pool */
+	if (unlikely(tc->flags & TALLOC_FLAG_POOL)) {
+		return NULL;
+	}
+
+	/* don't shrink if we have less than 1k to gain */
+	if ((size < tc->size) && ((tc->size - size) < 1024)) {
+		tc->size = size;
+		return ptr;
+	}
+
+	/* by resetting magic we catch users of the old memory */
+	tc->flags |= TALLOC_FLAG_FREE;
+
+#if ALWAYS_REALLOC
+	new_ptr = malloc(size + TC_HDR_SIZE);
+	if (new_ptr) {
+		memcpy(new_ptr, tc, tc->size + TC_HDR_SIZE);
+		free(tc);
+	}
+#else
+	if (tc->flags & TALLOC_FLAG_POOLMEM) {
+
+		new_ptr = talloc_alloc_pool(tc, size + TC_HDR_SIZE);
+		*talloc_pool_objectcount((struct talloc_chunk *)
+					 (tc->pool)) -= 1;
+
+		if (new_ptr == NULL) {
+			new_ptr = malloc(TC_HDR_SIZE+size);
+			malloced = true;
+		}
+
+		if (new_ptr) {
+			memcpy(new_ptr, tc, MIN(tc->size,size) + TC_HDR_SIZE);
+		}
+	}
+	else {
+		new_ptr = realloc(tc, size + TC_HDR_SIZE);
+	}
+#endif
+	if (unlikely(!new_ptr)) {	
+		tc->flags &= ~TALLOC_FLAG_FREE; 
+		return NULL; 
+	}
+
+	tc = (struct talloc_chunk *)new_ptr;
+	tc->flags &= ~TALLOC_FLAG_FREE;
+	if (malloced) {
+		tc->flags &= ~TALLOC_FLAG_POOLMEM;
+	}
+	if (tc->parent) {
+		tc->parent->child = tc;
+	}
+	if (tc->child) {
+		tc->child->parent = tc;
+	}
+
+	if (tc->prev) {
+		tc->prev->next = tc;
+	}
+	if (tc->next) {
+		tc->next->prev = tc;
+	}
+
+	tc->size = size;
+	_talloc_set_name_const(TC_PTR_FROM_CHUNK(tc), name);
+
+	return TC_PTR_FROM_CHUNK(tc);
+}
+
+/*
+  a wrapper around talloc_steal() for situations where you are moving a pointer
+  between two structures, and want the old pointer to be set to NULL
+*/
+void *_talloc_move(const void *new_ctx, const void *_pptr)
+{
+	const void **pptr = discard_const_p(const void *,_pptr);
+	void *ret = _talloc_steal(new_ctx, *pptr);
+	(*pptr) = NULL;
+	return ret;
+}
+
+/*
+  return the total size of a talloc pool (subtree)
+*/
+size_t talloc_total_size(const void *ptr)
+{
+	size_t total = 0;
+	struct talloc_chunk *c, *tc;
+
+	if (ptr == NULL) {
+		ptr = null_context;
+	}
+	if (ptr == NULL) {
+		return 0;
+	}
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	if (tc->flags & TALLOC_FLAG_LOOP) {
+		return 0;
+	}
+
+	tc->flags |= TALLOC_FLAG_LOOP;
+
+	total = tc->size;
+	for (c=tc->child;c;c=c->next) {
+		total += talloc_total_size(TC_PTR_FROM_CHUNK(c));
+	}
+
+	tc->flags &= ~TALLOC_FLAG_LOOP;
+
+	return total;
+}
+
+/*
+  return the total number of blocks in a talloc pool (subtree)
+*/
+size_t talloc_total_blocks(const void *ptr)
+{
+	size_t total = 0;
+	struct talloc_chunk *c, *tc = talloc_chunk_from_ptr(ptr);
+
+	if (tc->flags & TALLOC_FLAG_LOOP) {
+		return 0;
+	}
+
+	tc->flags |= TALLOC_FLAG_LOOP;
+
+	total++;
+	for (c=tc->child;c;c=c->next) {
+		total += talloc_total_blocks(TC_PTR_FROM_CHUNK(c));
+	}
+
+	tc->flags &= ~TALLOC_FLAG_LOOP;
+
+	return total;
+}
+
+/*
+  return the number of external references to a pointer
+*/
+size_t talloc_reference_count(const void *ptr)
+{
+	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
+	struct talloc_reference_handle *h;
+	size_t ret = 0;
+
+	for (h=tc->refs;h;h=h->next) {
+		ret++;
+	}
+	return ret;
+}
+
+/*
+  report on memory usage by all children of a pointer, giving a full tree view
+*/
+void talloc_report_depth_cb(const void *ptr, int depth, int max_depth,
+			    void (*callback)(const void *ptr,
+			  		     int depth, int max_depth,
+					     int is_ref,
+					     void *private_data),
+			    void *private_data)
+{
+	struct talloc_chunk *c, *tc;
+
+	if (ptr == NULL) {
+		ptr = null_context;
+	}
+	if (ptr == NULL) return;
+
+	tc = talloc_chunk_from_ptr(ptr);
+
+	if (tc->flags & TALLOC_FLAG_LOOP) {
+		return;
+	}
+
+	callback(ptr, depth, max_depth, 0, private_data);
+
+	if (max_depth >= 0 && depth >= max_depth) {
+		return;
+	}
+
+	tc->flags |= TALLOC_FLAG_LOOP;
+	for (c=tc->child;c;c=c->next) {
+		if (c->name == TALLOC_MAGIC_REFERENCE) {
+			struct talloc_reference_handle *h = (struct talloc_reference_handle *)TC_PTR_FROM_CHUNK(c);
+			callback(h->ptr, depth + 1, max_depth, 1, private_data);
+		} else {
+			talloc_report_depth_cb(TC_PTR_FROM_CHUNK(c), depth + 1, max_depth, callback, private_data);
+		}
+	}
+	tc->flags &= ~TALLOC_FLAG_LOOP;
+}
+
+static void talloc_report_depth_FILE_helper(const void *ptr, int depth, int max_depth, int is_ref, void *_f)
+{
+	const char *name = talloc_get_name(ptr);
+	FILE *f = (FILE *)_f;
+
+	if (is_ref) {
+		fprintf(f, "%*sreference to: %s\n", depth*4, "", name);
+		return;
+	}
+
+	if (depth == 0) {
+		fprintf(f,"%stalloc report on '%s' (total %6lu bytes in %3lu blocks)\n", 
+			(max_depth < 0 ? "full " :""), name,
+			(unsigned long)talloc_total_size(ptr),
+			(unsigned long)talloc_total_blocks(ptr));
+		return;
+	}
+
+	fprintf(f, "%*s%-30s contains %6lu bytes in %3lu blocks (ref %d) %p\n", 
+		depth*4, "",
+		name,
+		(unsigned long)talloc_total_size(ptr),
+		(unsigned long)talloc_total_blocks(ptr),
+		(int)talloc_reference_count(ptr), ptr);
+
+#if 0
+	fprintf(f, "content: ");
+	if (talloc_total_size(ptr)) {
+		int tot = talloc_total_size(ptr);
+		int i;
+
+		for (i = 0; i < tot; i++) {
+			if ((((char *)ptr)[i] > 31) && (((char *)ptr)[i] < 126)) {
+				fprintf(f, "%c", ((char *)ptr)[i]);
+			} else {
+				fprintf(f, "~%02x", ((char *)ptr)[i]);
+			}
+		}
+	}
+	fprintf(f, "\n");
+#endif
+}
+
+/*
+  report on memory usage by all children of a pointer, giving a full tree view
+*/
+void talloc_report_depth_file(const void *ptr, int depth, int max_depth, FILE *f)
+{
+	talloc_report_depth_cb(ptr, depth, max_depth, talloc_report_depth_FILE_helper, f);
+	fflush(f);
+}
+
+/*
+  report on memory usage by all children of a pointer, giving a full tree view
+*/
+void talloc_report_full(const void *ptr, FILE *f)
+{
+	talloc_report_depth_file(ptr, 0, -1, f);
+}
+
+/*
+  report on memory usage by all children of a pointer
+*/
+void talloc_report(const void *ptr, FILE *f)
+{
+	talloc_report_depth_file(ptr, 0, 1, f);
+}
+
+/*
+  report on any memory hanging off the null context
+*/
+static void talloc_report_null(void)
+{
+	if (talloc_total_size(null_context) != 0) {
+		talloc_report(null_context, stderr);
+	}
+}
+
+/*
+  report on any memory hanging off the null context
+*/
+static void talloc_report_null_full(void)
+{
+	if (talloc_total_size(null_context) != 0) {
+		talloc_report_full(null_context, stderr);
+	}
+}
+
+/*
+  enable tracking of the NULL context
+*/
+void talloc_enable_null_tracking(void)
+{
+	if (null_context == NULL) {
+		null_context = _talloc_named_const(NULL, 0, "null_context");
+	}
+}
+
+/*
+  disable tracking of the NULL context
+*/
+void talloc_disable_null_tracking(void)
+{
+	_talloc_free(null_context);
+	null_context = NULL;
+}
+
+/*
+  enable leak reporting on exit
+*/
+void talloc_enable_leak_report(void)
+{
+	talloc_enable_null_tracking();
+	atexit(talloc_report_null);
+}
+
+/*
+  enable full leak reporting on exit
+*/
+void talloc_enable_leak_report_full(void)
+{
+	talloc_enable_null_tracking();
+	atexit(talloc_report_null_full);
+}
+
+/* 
+   talloc and zero memory. 
+*/
+void *_talloc_zero(const void *ctx, size_t size, const char *name)
+{
+	void *p = _talloc_named_const(ctx, size, name);
+
+	if (p) {
+		memset(p, '\0', size);
+	}
+
+	return p;
+}
+
+/*
+  memdup with a talloc. 
+*/
+void *_talloc_memdup(const void *t, const void *p, size_t size, const char *name)
+{
+	void *newp = _talloc_named_const(t, size, name);
+
+	if (likely(newp)) {
+		memcpy(newp, p, size);
+	}
+
+	return newp;
+}
+
+static inline char *__talloc_strlendup(const void *t, const char *p, size_t len)
+{
+	char *ret;
+
+	ret = (char *)__talloc(t, len + 1);
+	if (unlikely(!ret)) return NULL;
+
+	memcpy(ret, p, len);
+	ret[len] = 0;
+
+	_talloc_set_name_const(ret, ret);
+	return ret;
+}
+
+/*
+  strdup with a talloc
+*/
+char *talloc_strdup(const void *t, const char *p)
+{
+	if (unlikely(!p)) return NULL;
+	return __talloc_strlendup(t, p, strlen(p));
+}
+
+/*
+  strndup with a talloc
+*/
+char *talloc_strndup(const void *t, const char *p, size_t n)
+{
+	if (unlikely(!p)) return NULL;
+	return __talloc_strlendup(t, p, strnlen(p, n));
+}
+
+static inline char *__talloc_strlendup_append(char *s, size_t slen,
+					      const char *a, size_t alen)
+{
+	char *ret;
+
+	ret = talloc_realloc(NULL, s, char, slen + alen + 1);
+	if (unlikely(!ret)) return NULL;
+
+	/* append the string and the trailing \0 */
+	memcpy(&ret[slen], a, alen);
+	ret[slen+alen] = 0;
+
+	_talloc_set_name_const(ret, ret);
+	return ret;
+}
+
+/*
+ * Appends at the end of the string.
+ */
+char *talloc_strdup_append(char *s, const char *a)
+{
+	if (unlikely(!s)) {
+		return talloc_strdup(NULL, a);
+	}
+
+	if (unlikely(!a)) {
+		return s;
+	}
+
+	return __talloc_strlendup_append(s, strlen(s), a, strlen(a));
+}
+
+/*
+ * Appends at the end of the talloc'ed buffer,
+ * not the end of the string.
+ */
+char *talloc_strdup_append_buffer(char *s, const char *a)
+{
+	size_t slen;
+
+	if (unlikely(!s)) {
+		return talloc_strdup(NULL, a);
+	}
+
+	if (unlikely(!a)) {
+		return s;
+	}
+
+	slen = talloc_get_size(s);
+	if (likely(slen > 0)) {
+		slen--;
+	}
+
+	return __talloc_strlendup_append(s, slen, a, strlen(a));
+}
+
+/*
+ * Appends at the end of the string.
+ */
+char *talloc_strndup_append(char *s, const char *a, size_t n)
+{
+	if (unlikely(!s)) {
+		return talloc_strdup(NULL, a);
+	}
+
+	if (unlikely(!a)) {
+		return s;
+	}
+
+	return __talloc_strlendup_append(s, strlen(s), a, strnlen(a, n));
+}
+
+/*
+ * Appends at the end of the talloc'ed buffer,
+ * not the end of the string.
+ */
+char *talloc_strndup_append_buffer(char *s, const char *a, size_t n)
+{
+	size_t slen;
+
+	if (unlikely(!s)) {
+		return talloc_strdup(NULL, a);
+	}
+
+	if (unlikely(!a)) {
+		return s;
+	}
+
+	slen = talloc_get_size(s);
+	if (likely(slen > 0)) {
+		slen--;
+	}
+
+	return __talloc_strlendup_append(s, slen, a, strnlen(a, n));
+}
+
+#ifndef HAVE_VA_COPY
+#ifdef HAVE___VA_COPY
+#define va_copy(dest, src) __va_copy(dest, src)
+#else
+#define va_copy(dest, src) (dest) = (src)
+#endif
+#endif
+
+char *talloc_vasprintf(const void *t, const char *fmt, va_list ap)
+{
+	int len;
+	char *ret;
+	va_list ap2;
+	char c;
+
+	/* this call looks strange, but it makes it work on older solaris boxes */
+	va_copy(ap2, ap);
+	len = vsnprintf(&c, 1, fmt, ap2);
+	va_end(ap2);
+	if (unlikely(len < 0)) {
+		return NULL;
+	}
+
+	ret = (char *)__talloc(t, len+1);
+	if (unlikely(!ret)) return NULL;
+
+	va_copy(ap2, ap);
+	vsnprintf(ret, len+1, fmt, ap2);
+	va_end(ap2);
+
+	_talloc_set_name_const(ret, ret);
+	return ret;
+}
+
+
+/*
+  Perform string formatting, and return a pointer to newly allocated
+  memory holding the result, inside a memory pool.
+ */
+char *talloc_asprintf(const void *t, const char *fmt, ...)
+{
+	va_list ap;
+	char *ret;
+
+	va_start(ap, fmt);
+	ret = talloc_vasprintf(t, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+
+static inline char *__talloc_vaslenprintf_append(char *s, size_t slen,
+						 const char *fmt, va_list ap)
+						 PRINTF_ATTRIBUTE(3,0);
+
+static inline char *__talloc_vaslenprintf_append(char *s, size_t slen,
+						 const char *fmt, va_list ap)
+{
+	ssize_t alen;
+	va_list ap2;
+	char c;
+
+	va_copy(ap2, ap);
+	alen = vsnprintf(&c, 1, fmt, ap2);
+	va_end(ap2);
+
+	if (alen <= 0) {
+		/* Either the vsnprintf failed or the format resulted in
+		 * no characters being formatted. In the former case, we
+		 * ought to return NULL, in the latter we ought to return
+		 * the original string. Most current callers of this
+		 * function expect it to never return NULL.
+		 */
+		return s;
+	}
+
+	s = talloc_realloc(NULL, s, char, slen + alen + 1);
+	if (!s) return NULL;
+
+	va_copy(ap2, ap);
+	vsnprintf(s + slen, alen + 1, fmt, ap2);
+	va_end(ap2);
+
+	_talloc_set_name_const(s, s);
+	return s;
+}
+
+/**
+ * Realloc @p s to append the formatted result of @p fmt and @p ap,
+ * and return @p s, which may have moved.  Good for gradually
+ * accumulating output into a string buffer. Appends at the end
+ * of the string.
+ **/
+char *talloc_vasprintf_append(char *s, const char *fmt, va_list ap)
+{
+	if (unlikely(!s)) {
+		return talloc_vasprintf(NULL, fmt, ap);
+	}
+
+	return __talloc_vaslenprintf_append(s, strlen(s), fmt, ap);
+}
+
+/**
+ * Realloc @p s to append the formatted result of @p fmt and @p ap,
+ * and return @p s, which may have moved. Always appends at the
+ * end of the talloc'ed buffer, not the end of the string.
+ **/
+char *talloc_vasprintf_append_buffer(char *s, const char *fmt, va_list ap)
+{
+	size_t slen;
+
+	if (unlikely(!s)) {
+		return talloc_vasprintf(NULL, fmt, ap);
+	}
+
+	slen = talloc_get_size(s);
+	if (likely(slen > 0)) {
+		slen--;
+	}
+
+	return __talloc_vaslenprintf_append(s, slen, fmt, ap);
+}
+
+/*
+  Realloc @p s to append the formatted result of @p fmt and return @p
+  s, which may have moved.  Good for gradually accumulating output
+  into a string buffer.
+ */
+char *talloc_asprintf_append(char *s, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	s = talloc_vasprintf_append(s, fmt, ap);
+	va_end(ap);
+	return s;
+}
+
+/*
+  Realloc @p s to append the formatted result of @p fmt and return @p
+  s, which may have moved.  Good for gradually accumulating output
+  into a buffer.
+ */
+char *talloc_asprintf_append_buffer(char *s, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	s = talloc_vasprintf_append_buffer(s, fmt, ap);
+	va_end(ap);
+	return s;
+}
+
+/*
+  alloc an array, checking for integer overflow in the array size
+*/
+void *_talloc_array(const void *ctx, size_t el_size, unsigned count, const char *name)
+{
+	if (count >= MAX_TALLOC_SIZE/el_size) {
+		return NULL;
+	}
+	return _talloc_named_const(ctx, el_size * count, name);
+}
+
+/*
+  alloc an zero array, checking for integer overflow in the array size
+*/
+void *_talloc_zero_array(const void *ctx, size_t el_size, unsigned count, const char *name)
+{
+	if (count >= MAX_TALLOC_SIZE/el_size) {
+		return NULL;
+	}
+	return _talloc_zero(ctx, el_size * count, name);
+}
+
+/*
+  realloc an array, checking for integer overflow in the array size
+*/
+void *_talloc_realloc_array(const void *ctx, void *ptr, size_t el_size, unsigned count, const char *name)
+{
+	if (count >= MAX_TALLOC_SIZE/el_size) {
+		return NULL;
+	}
+	return _talloc_realloc(ctx, ptr, el_size * count, name);
+}
+
+/*
+  a function version of talloc_realloc(), so it can be passed as a function pointer
+  to libraries that want a realloc function (a realloc function encapsulates
+  all the basic capabilities of an allocation library, which is why this is useful)
+*/
+void *talloc_realloc_fn(const void *context, void *ptr, size_t size)
+{
+	return _talloc_realloc(context, ptr, size, NULL);
+}
+
+
+static int talloc_autofree_destructor(void *ptr)
+{
+	autofree_context = NULL;
+	return 0;
+}
+
+static void talloc_autofree(void)
+{
+	_talloc_free(autofree_context);
+}
+
+/*
+  return a context which will be auto-freed on exit
+  this is useful for reducing the noise in leak reports
+*/
+void *talloc_autofree_context(void)
+{
+	if (autofree_context == NULL) {
+		autofree_context = _talloc_named_const(NULL, 0, "autofree_context");
+		talloc_set_destructor(autofree_context, talloc_autofree_destructor);
+		atexit(talloc_autofree);
+	}
+	return autofree_context;
+}
+
+size_t talloc_get_size(const void *context)
+{
+	struct talloc_chunk *tc;
+
+	if (context == NULL)
+		return 0;
+
+	tc = talloc_chunk_from_ptr(context);
+
+	return tc->size;
+}
+
+/*
+  find a parent of this context that has the given name, if any
+*/
+void *talloc_find_parent_byname(const void *context, const char *name)
+{
+	struct talloc_chunk *tc;
+
+	if (context == NULL) {
+		return NULL;
+	}
+
+	tc = talloc_chunk_from_ptr(context);
+	while (tc) {
+		if (tc->name && strcmp(tc->name, name) == 0) {
+			return TC_PTR_FROM_CHUNK(tc);
+		}
+		while (tc && tc->prev) tc = tc->prev;
+		if (tc) {
+			tc = tc->parent;
+		}
+	}
+	return NULL;
+}
+
+/*
+  show the parentage of a context
+*/
+void talloc_show_parents(const void *context, FILE *file)
+{
+	struct talloc_chunk *tc;
+
+	if (context == NULL) {
+		fprintf(file, "talloc no parents for NULL\n");
+		return;
+	}
+
+	tc = talloc_chunk_from_ptr(context);
+	fprintf(file, "talloc parents of '%s'\n", talloc_get_name(context));
+	while (tc) {
+		fprintf(file, "\t'%s'\n", talloc_get_name(TC_PTR_FROM_CHUNK(tc)));
+		while (tc && tc->prev) tc = tc->prev;
+		if (tc) {
+			tc = tc->parent;
+		}
+	}
+	fflush(file);
+}
+
+/*
+  return 1 if ptr is a parent of context
+*/
+int talloc_is_parent(const void *context, const void *ptr)
+{
+	struct talloc_chunk *tc;
+
+	if (context == NULL) {
+		return 0;
+	}
+
+	tc = talloc_chunk_from_ptr(context);
+	while (tc) {
+		if (TC_PTR_FROM_CHUNK(tc) == ptr) return 1;
+		while (tc && tc->prev) tc = tc->prev;
+		if (tc) {
+			tc = tc->parent;
+		}
+	}
+	return 0;
+}
diff --git a/src/timer.c b/src/timer.c
new file mode 100644
index 0000000..cca2a23
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,236 @@
+/*
+ * (C) 2008,2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * Authors: Holger Hans Peter Freyther <zecke@selfish.org>
+ *	    Harald Welte <laforge@gnumonks.org>
+ *	    Pablo Neira Ayuso <pablo@gnumonks.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* These store the amount of time that we wait until next timer expires. */
+static struct timeval nearest;
+static struct timeval *nearest_p;
+
+/*! \addtogroup timer
+ *  @{
+ */
+
+/*! \file timer.c
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <limits.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/timer_compat.h>
+#include <osmocom/core/linuxlist.h>
+
+static struct rb_root timer_root = RB_ROOT;
+
+static void __add_timer(struct osmo_timer_list *timer)
+{
+	struct rb_node **new = &(timer_root.rb_node);
+	struct rb_node *parent = NULL;
+
+	while (*new) {
+		struct osmo_timer_list *this;
+
+		this = container_of(*new, struct osmo_timer_list, node);
+
+		parent = *new;
+		if (timercmp(&timer->timeout, &this->timeout, <))
+			new = &((*new)->rb_left);
+		else
+			new = &((*new)->rb_right);
+        }
+
+        rb_link_node(&timer->node, parent, new);
+        rb_insert_color(&timer->node, &timer_root);
+}
+
+/*! \brief add a new timer to the timer management
+ *  \param[in] timer the timer that should be added
+ */
+void osmo_timer_add(struct osmo_timer_list *timer)
+{
+	osmo_timer_del(timer);
+	timer->active = 1;
+	INIT_LLIST_HEAD(&timer->list);
+	__add_timer(timer);
+}
+
+/*! \brief schedule a timer at a given future relative time
+ *  \param[in] timer the to-be-added timer
+ *  \param[in] seconds number of seconds from now
+ *  \param[in] microseconds number of microseconds from now
+ *
+ * This function can be used to (re-)schedule a given timer at a
+ * specified number of seconds+microseconds in the future.  It will
+ * internally add it to the timer management data structures, thus
+ * osmo_timer_add() is automatically called.
+ */
+void
+osmo_timer_schedule(struct osmo_timer_list *timer, int seconds, int microseconds)
+{
+	struct timeval current_time;
+
+	gettimeofday(&current_time, NULL);
+	timer->timeout.tv_sec = seconds;
+	timer->timeout.tv_usec = microseconds;
+	timeradd(&timer->timeout, &current_time, &timer->timeout);
+	osmo_timer_add(timer);
+}
+
+/*! \brief delete a timer from timer management
+ *  \param[in] timer the to-be-deleted timer
+ *
+ * This function can be used to delete a previously added/scheduled
+ * timer from the timer management code.
+ */
+void osmo_timer_del(struct osmo_timer_list *timer)
+{
+	if (timer->active) {
+		timer->active = 0;
+		rb_erase(&timer->node, &timer_root);
+		/* make sure this is not already scheduled for removal. */
+		if (!llist_empty(&timer->list))
+			llist_del_init(&timer->list);
+	}
+}
+
+/*! \brief check if given timer is still pending
+ *  \param[in] timer the to-be-checked timer
+ *  \return 1 if pending, 0 otherwise
+ *
+ * This function can be used to determine whether a given timer
+ * has alredy expired (returns 0) or is still pending (returns 1)
+ */
+int osmo_timer_pending(struct osmo_timer_list *timer)
+{
+	return timer->active;
+}
+
+/*
+ * if we have a nearest time return the delta between the current
+ * time and the time of the nearest timer.
+ * If the nearest timer timed out return NULL and then we will
+ * dispatch everything after the select
+ */
+struct timeval *osmo_timers_nearest(void)
+{
+	/* nearest_p is exactly what we need already: NULL if nothing is
+	 * waiting, {0,0} if we must dispatch immediately, and the correct
+	 * delay if we need to wait */
+	return nearest_p;
+}
+
+static void update_nearest(struct timeval *cand, struct timeval *current)
+{
+	if (cand->tv_sec != LONG_MAX) {
+		if (timercmp(cand, current, >))
+			timersub(cand, current, &nearest);
+		else {
+			/* loop again inmediately */
+			nearest.tv_sec = 0;
+			nearest.tv_usec = 0;
+		}
+		nearest_p = &nearest;
+	} else {
+		nearest_p = NULL;
+	}
+}
+
+/*
+ * Find the nearest time and update s_nearest_time
+ */
+void osmo_timers_prepare(void)
+{
+	struct rb_node *node;
+	struct timeval current;
+
+	gettimeofday(&current, NULL);
+
+	node = rb_first(&timer_root);
+	if (node) {
+		struct osmo_timer_list *this;
+		this = container_of(node, struct osmo_timer_list, node);
+		update_nearest(&this->timeout, &current);
+	} else {
+		nearest_p = NULL;
+	}
+}
+
+/*
+ * fire all timers... and remove them
+ */
+int osmo_timers_update(void)
+{
+	struct timeval current_time;
+	struct rb_node *node;
+	struct llist_head timer_eviction_list;
+	struct osmo_timer_list *this;
+	int work = 0;
+
+	gettimeofday(&current_time, NULL);
+
+	INIT_LLIST_HEAD(&timer_eviction_list);
+	for (node = rb_first(&timer_root); node; node = rb_next(node)) {
+		this = container_of(node, struct osmo_timer_list, node);
+
+		if (timercmp(&this->timeout, &current_time, >))
+			break;
+
+		llist_add(&this->list, &timer_eviction_list);
+	}
+
+	/*
+	 * The callbacks might mess with our list and in this case
+	 * even llist_for_each_entry_safe is not safe to use. To allow
+	 * osmo_timer_del to be called from within the callback we need
+	 * to restart the iteration for each element scheduled for removal.
+	 *
+	 * The problematic scenario is the following: Given two timers A
+	 * and B that have expired at the same time. Thus, they are both
+	 * in the eviction list in this order: A, then B. If we remove
+	 * timer B from the A's callback, we continue with B in the next
+	 * iteration step, leading to an access-after-release.
+	 */
+restart:
+	llist_for_each_entry(this, &timer_eviction_list, list) {
+		osmo_timer_del(this);
+		this->cb(this->data);
+		work = 1;
+		goto restart;
+	}
+
+	return work;
+}
+
+int osmo_timers_check(void)
+{
+	struct rb_node *node;
+	int i = 0;
+
+	for (node = rb_first(&timer_root); node; node = rb_next(node)) {
+		i++;
+	}
+	return i;
+}
+
+/*! @} */
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..cf0c934
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,215 @@
+
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include <osmocom/core/utils.h>
+
+/*! \addtogroup utils
+ * @{
+ */
+
+/*! \file utils.c */
+
+static char namebuf[255];
+
+/*! \brief get human-readable string for given value
+ *  \param[in] vs Array of value_string tuples
+ *  \param[in] val Value to be converted
+ *  \returns pointer to human-readable string
+ */
+const char *get_value_string(const struct value_string *vs, uint32_t val)
+{
+	int i;
+
+	for (i = 0;; i++) {
+		if (vs[i].value == 0 && vs[i].str == NULL)
+			break;
+		if (vs[i].value == val)
+			return vs[i].str;
+	}
+
+	snprintf(namebuf, sizeof(namebuf), "unknown 0x%x", val);
+	return namebuf;
+}
+
+/*! \brief get numeric value for given human-readable string
+ *  \param[in] vs Array of value_string tuples
+ *  \param[in] str human-readable string
+ *  \returns numeric value (>0) or negative numer in case of error
+ */
+int get_string_value(const struct value_string *vs, const char *str)
+{
+	int i;
+
+	for (i = 0;; i++) {
+		if (vs[i].value == 0 && vs[i].str == NULL)
+			break;
+		if (!strcasecmp(vs[i].str, str))
+			return vs[i].value;
+	}
+	return -EINVAL;
+}
+
+/*! \brief Convert BCD-encoded digit into printable character
+ *  \param[in] bcd A single BCD-encoded digit
+ *  \returns single printable character
+ */
+char osmo_bcd2char(uint8_t bcd)
+{
+	if (bcd < 0xa)
+		return '0' + bcd;
+	else
+		return 'A' + (bcd - 0xa);
+}
+
+/* only works for numbers in ascii */
+uint8_t osmo_char2bcd(char c)
+{
+	return c - 0x30;
+}
+
+int osmo_hexparse(const char *str, uint8_t *b, int max_len)
+
+{
+	int i, l, v;
+
+	l = strlen(str);
+	if ((l&1) || ((l>>1) > max_len))
+		return -1;
+
+	memset(b, 0x00, max_len);
+
+	for (i=0; i<l; i++) {
+		char c = str[i];
+		if (c >= '0' && c <= '9')
+			v = c - '0';
+		else if (c >= 'a' && c <= 'f')
+			v = 10 + (c - 'a');
+		else if (c >= 'A' && c <= 'F')
+			v = 10 + (c - 'A');
+		else
+			return -1;
+		b[i>>1] |= v << (i&1 ? 0 : 4);
+	}
+
+	return i>>1;
+}
+
+static char hexd_buff[4096];
+
+static char *_osmo_hexdump(const unsigned char *buf, int len, char *delim)
+{
+	int i;
+	char *cur = hexd_buff;
+
+	hexd_buff[0] = 0;
+	for (i = 0; i < len; i++) {
+		int len_remain = sizeof(hexd_buff) - (cur - hexd_buff);
+		if (len_remain <= 0)
+			break;
+		int rc = snprintf(cur, len_remain, "%02x%s", buf[i], delim);
+		if (rc <= 0)
+			break;
+		cur += rc;
+	}
+	hexd_buff[sizeof(hexd_buff)-1] = 0;
+	return hexd_buff;
+}
+
+/*! \brief Convert a sequence of unpacked bits to ASCII string
+ * \param[in] bits A sequence of unpacked bits
+ * \param[in] len Length of bits
+ */
+char *osmo_ubit_dump(const uint8_t *bits, unsigned int len)
+{
+	int i;
+
+	if (len > sizeof(hexd_buff)-1)
+		len = sizeof(hexd_buff)-1;
+	memset(hexd_buff, 0, sizeof(hexd_buff));
+
+	for (i = 0; i < len; i++) {
+		char outch;
+		switch (bits[i]) {
+		case 0:
+			outch = '0';
+			break;
+		case 0xff:
+			outch = '?';
+			break;
+		case 1:
+			outch = '1';
+			break;
+		default:
+			outch = 'E';
+			break;
+		}
+		hexd_buff[i] = outch;
+	}
+	hexd_buff[sizeof(hexd_buff)-1] = 0;
+	return hexd_buff;
+}
+
+/*! \brief Convert binary sequence to hexadecimal ASCII string
+ *  \param[in] buf pointer to sequence of bytes
+ *  \param[in] len length of buf in number of bytes
+ *  \returns pointer to zero-terminated string
+ *
+ * This function will print a sequence of bytes as hexadecimal numbers,
+ * adding one space character between each byte (e.g. "1a ef d9")
+ */
+char *osmo_hexdump(const unsigned char *buf, int len)
+{
+	return _osmo_hexdump(buf, len, " ");
+}
+
+/*! \brief Convert binary sequence to hexadecimal ASCII string
+ *  \param[in] buf pointer to sequence of bytes
+ *  \param[in] len length of buf in number of bytes
+ *  \returns pointer to zero-terminated string
+ *
+ * This function will print a sequence of bytes as hexadecimal numbers,
+ * without any space character between each byte (e.g. "1aefd9")
+ */
+char *osmo_hexdump_nospc(const unsigned char *buf, int len)
+{
+	return _osmo_hexdump(buf, len, "");
+}
+
+	/* Compat with previous typo to preserve abi */
+char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len)
+	__attribute__((weak, alias("osmo_hexdump_nospc")));
+
+#include "../config.h"
+#ifdef HAVE_CTYPE_H
+#include <ctype.h>
+/*! \brief Convert an entire string to lower case
+ *  \param[out] out output string, caller-allocated
+ *  \param[in] in input string
+ */
+void osmo_str2lower(char *out, const char *in)
+{
+	unsigned int i;
+
+	for (i = 0; i < strlen(in); i++)
+		out[i] = tolower(in[i]);
+	out[strlen(in)] = '\0';
+}
+
+/*! \brief Convert an entire string to upper case
+ *  \param[out] out output string, caller-allocated
+ *  \param[in] in input string
+ */
+void osmo_str2upper(char *out, const char *in)
+{
+	unsigned int i;
+
+	for (i = 0; i < strlen(in); i++)
+		out[i] = toupper(in[i]);
+	out[strlen(in)] = '\0';
+}
+#endif /* HAVE_CTYPE_H */
+
+/*! @} */
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
new file mode 100644
index 0000000..b7be6f8
--- /dev/null
+++ b/src/vty/Makefile.am
@@ -0,0 +1,15 @@
+# This is _NOT_ the library release version, it's an API version.
+# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
+LIBVERSION=0:0:0
+
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -fPIC -Wall
+
+if ENABLE_VTY
+lib_LTLIBRARIES = libosmovty.la
+
+libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
+			telnet_interface.c logging_vty.c
+libosmovty_la_LDFLAGS = -version-info $(LIBVERSION)
+libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la
+endif
diff --git a/src/vty/buffer.c b/src/vty/buffer.c
new file mode 100644
index 0000000..e385f9f
--- /dev/null
+++ b/src/vty/buffer.c
@@ -0,0 +1,463 @@
+/*
+ * Buffering of output and input.
+ * Copyright (C) 1998 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+/* Buffer master. */
+struct buffer {
+	/* Data list. */
+	struct buffer_data *head;
+	struct buffer_data *tail;
+
+	/* Size of each buffer_data chunk. */
+	size_t size;
+};
+
+/* Data container. */
+struct buffer_data {
+	struct buffer_data *next;
+
+	/* Location to add new data. */
+	size_t cp;
+
+	/* Pointer to data not yet flushed. */
+	size_t sp;
+
+	/* Actual data stream (variable length). */
+	unsigned char data[0];	/* real dimension is buffer->size */
+};
+
+/* It should always be true that: 0 <= sp <= cp <= size */
+
+/* Default buffer size (used if none specified).  It is rounded up to the
+   next page boundery. */
+#define BUFFER_SIZE_DEFAULT		4096
+
+#define BUFFER_DATA_FREE(D) talloc_free((D))
+
+/* Make new buffer. */
+struct buffer *buffer_new(void *ctx, size_t size)
+{
+	struct buffer *b;
+
+	b = talloc_zero(ctx, struct buffer);
+
+	if (size)
+		b->size = size;
+	else {
+		static size_t default_size;
+		if (!default_size) {
+			long pgsz = sysconf(_SC_PAGESIZE);
+			default_size =
+			    ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1) * pgsz);
+		}
+		b->size = default_size;
+	}
+
+	return b;
+}
+
+/* Free buffer. */
+void buffer_free(struct buffer *b)
+{
+	buffer_reset(b);
+	talloc_free(b);
+}
+
+/* Make string clone. */
+char *buffer_getstr(struct buffer *b)
+{
+	size_t totlen = 0;
+	struct buffer_data *data;
+	char *s;
+	char *p;
+
+	for (data = b->head; data; data = data->next)
+		totlen += data->cp - data->sp;
+	if (!(s = _talloc_zero(tall_vty_ctx, (totlen + 1), "buffer_getstr")))
+		return NULL;
+	p = s;
+	for (data = b->head; data; data = data->next) {
+		memcpy(p, data->data + data->sp, data->cp - data->sp);
+		p += data->cp - data->sp;
+	}
+	*p = '\0';
+	return s;
+}
+
+/* Return 1 if buffer is empty. */
+int buffer_empty(struct buffer *b)
+{
+	return (b->head == NULL);
+}
+
+/* Clear and free all allocated data. */
+void buffer_reset(struct buffer *b)
+{
+	struct buffer_data *data;
+	struct buffer_data *next;
+
+	for (data = b->head; data; data = next) {
+		next = data->next;
+		BUFFER_DATA_FREE(data);
+	}
+	b->head = b->tail = NULL;
+}
+
+/* Add buffer_data to the end of buffer. */
+static struct buffer_data *buffer_add(struct buffer *b)
+{
+	struct buffer_data *d;
+
+	d = _talloc_zero(b,
+			 offsetof(struct buffer_data, data[b->size]),
+			 "buffer_add");
+	if (!d)
+		return NULL;
+	d->cp = d->sp = 0;
+	d->next = NULL;
+
+	if (b->tail)
+		b->tail->next = d;
+	else
+		b->head = d;
+	b->tail = d;
+
+	return d;
+}
+
+/* Write data to buffer. */
+void buffer_put(struct buffer *b, const void *p, size_t size)
+{
+	struct buffer_data *data = b->tail;
+	const char *ptr = p;
+
+	/* We use even last one byte of data buffer. */
+	while (size) {
+		size_t chunk;
+
+		/* If there is no data buffer add it. */
+		if (data == NULL || data->cp == b->size)
+			data = buffer_add(b);
+
+		chunk =
+		    ((size <=
+		      (b->size - data->cp)) ? size : (b->size - data->cp));
+		memcpy((data->data + data->cp), ptr, chunk);
+		size -= chunk;
+		ptr += chunk;
+		data->cp += chunk;
+	}
+}
+
+/* Insert character into the buffer. */
+void buffer_putc(struct buffer *b, u_char c)
+{
+	buffer_put(b, &c, 1);
+}
+
+/* Put string to the buffer. */
+void buffer_putstr(struct buffer *b, const char *c)
+{
+	buffer_put(b, c, strlen(c));
+}
+
+/* Keep flushing data to the fd until the buffer is empty or an error is
+   encountered or the operation would block. */
+buffer_status_t buffer_flush_all(struct buffer *b, int fd)
+{
+	buffer_status_t ret;
+	struct buffer_data *head;
+	size_t head_sp;
+
+	if (!b->head)
+		return BUFFER_EMPTY;
+	head_sp = (head = b->head)->sp;
+	/* Flush all data. */
+	while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) {
+		if ((b->head == head) && (head_sp == head->sp)
+		    && (errno != EINTR))
+			/* No data was flushed, so kernel buffer must be full. */
+			return ret;
+		head_sp = (head = b->head)->sp;
+	}
+
+	return ret;
+}
+
+#if 0
+/* Flush enough data to fill a terminal window of the given scene (used only
+   by vty telnet interface). */
+buffer_status_t
+buffer_flush_window(struct buffer * b, int fd, int width, int height,
+		    int erase_flag, int no_more_flag)
+{
+	int nbytes;
+	int iov_alloc;
+	int iov_index;
+	struct iovec *iov;
+	struct iovec small_iov[3];
+	char more[] = " --More-- ";
+	char erase[] =
+	    { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+		' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+		0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
+	};
+	struct buffer_data *data;
+	int column;
+
+	if (!b->head)
+		return BUFFER_EMPTY;
+
+	if (height < 1) {
+		zlog_warn
+		    ("%s called with non-positive window height %d, forcing to 1",
+		     __func__, height);
+		height = 1;
+	} else if (height >= 2)
+		height--;
+	if (width < 1) {
+		zlog_warn
+		    ("%s called with non-positive window width %d, forcing to 1",
+		     __func__, width);
+		width = 1;
+	}
+
+	/* For erase and more data add two to b's buffer_data count. */
+	if (b->head->next == NULL) {
+		iov_alloc = sizeof(small_iov) / sizeof(small_iov[0]);
+		iov = small_iov;
+	} else {
+		iov_alloc = ((height * (width + 2)) / b->size) + 10;
+		iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov));
+	}
+	iov_index = 0;
+
+	/* Previously print out is performed. */
+	if (erase_flag) {
+		iov[iov_index].iov_base = erase;
+		iov[iov_index].iov_len = sizeof erase;
+		iov_index++;
+	}
+
+	/* Output data. */
+	column = 1;		/* Column position of next character displayed. */
+	for (data = b->head; data && (height > 0); data = data->next) {
+		size_t cp;
+
+		cp = data->sp;
+		while ((cp < data->cp) && (height > 0)) {
+			/* Calculate lines remaining and column position after displaying
+			   this character. */
+			if (data->data[cp] == '\r')
+				column = 1;
+			else if ((data->data[cp] == '\n') || (column == width)) {
+				column = 1;
+				height--;
+			} else
+				column++;
+			cp++;
+		}
+		iov[iov_index].iov_base = (char *)(data->data + data->sp);
+		iov[iov_index++].iov_len = cp - data->sp;
+		data->sp = cp;
+
+		if (iov_index == iov_alloc)
+			/* This should not ordinarily happen. */
+		{
+			iov_alloc *= 2;
+			if (iov != small_iov) {
+				zlog_warn("%s: growing iov array to %d; "
+					  "width %d, height %d, size %lu",
+					  __func__, iov_alloc, width, height,
+					  (u_long) b->size);
+				iov =
+				    XREALLOC(MTYPE_TMP, iov,
+					     iov_alloc * sizeof(*iov));
+			} else {
+				/* This should absolutely never occur. */
+				zlog_err
+				    ("%s: corruption detected: iov_small overflowed; "
+				     "head %p, tail %p, head->next %p",
+				     __func__, b->head, b->tail, b->head->next);
+				iov =
+				    XMALLOC(MTYPE_TMP,
+					    iov_alloc * sizeof(*iov));
+				memcpy(iov, small_iov, sizeof(small_iov));
+			}
+		}
+	}
+
+	/* In case of `more' display need. */
+	if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) {
+		iov[iov_index].iov_base = more;
+		iov[iov_index].iov_len = sizeof more;
+		iov_index++;
+	}
+#ifdef IOV_MAX
+	/* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g.
+	   example: Solaris2.6 are defined IOV_MAX size at 16.     */
+	{
+		struct iovec *c_iov = iov;
+		nbytes = 0;	/* Make sure it's initialized. */
+
+		while (iov_index > 0) {
+			int iov_size;
+
+			iov_size =
+			    ((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
+			if ((nbytes = writev(fd, c_iov, iov_size)) < 0) {
+				zlog_warn("%s: writev to fd %d failed: %s",
+					  __func__, fd, safe_strerror(errno));
+				break;
+			}
+
+			/* move pointer io-vector */
+			c_iov += iov_size;
+			iov_index -= iov_size;
+		}
+	}
+#else				/* IOV_MAX */
+	if ((nbytes = writev(fd, iov, iov_index)) < 0)
+		zlog_warn("%s: writev to fd %d failed: %s",
+			  __func__, fd, safe_strerror(errno));
+#endif				/* IOV_MAX */
+
+	/* Free printed buffer data. */
+	while (b->head && (b->head->sp == b->head->cp)) {
+		struct buffer_data *del;
+		if (!(b->head = (del = b->head)->next))
+			b->tail = NULL;
+		BUFFER_DATA_FREE(del);
+	}
+
+	if (iov != small_iov)
+		XFREE(MTYPE_TMP, iov);
+
+	return (nbytes < 0) ? BUFFER_ERROR :
+	    (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
+}
+#endif
+
+/* This function (unlike other buffer_flush* functions above) is designed
+to work with non-blocking sockets.  It does not attempt to write out
+all of the queued data, just a "big" chunk.  It returns 0 if it was
+able to empty out the buffers completely, 1 if more flushing is
+required later, or -1 on a fatal write error. */
+buffer_status_t buffer_flush_available(struct buffer * b, int fd)
+{
+
+/* These are just reasonable values to make sure a significant amount of
+data is written.  There's no need to go crazy and try to write it all
+in one shot. */
+#ifdef IOV_MAX
+#define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
+#else
+#define MAX_CHUNKS 16
+#endif
+#define MAX_FLUSH 131072
+
+	struct buffer_data *d;
+	size_t written;
+	struct iovec iov[MAX_CHUNKS];
+	size_t iovcnt = 0;
+	size_t nbyte = 0;
+
+	for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
+	     d = d->next, iovcnt++) {
+		iov[iovcnt].iov_base = d->data + d->sp;
+		nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
+	}
+
+	if (!nbyte)
+		/* No data to flush: should we issue a warning message? */
+		return BUFFER_EMPTY;
+
+	/* only place where written should be sign compared */
+	if ((ssize_t) (written = writev(fd, iov, iovcnt)) < 0) {
+		if (ERRNO_IO_RETRY(errno))
+			/* Calling code should try again later. */
+			return BUFFER_PENDING;
+		return BUFFER_ERROR;
+	}
+
+	/* Free printed buffer data. */
+	while (written > 0) {
+		struct buffer_data *d;
+		if (!(d = b->head))
+			break;
+		if (written < d->cp - d->sp) {
+			d->sp += written;
+			return BUFFER_PENDING;
+		}
+
+		written -= (d->cp - d->sp);
+		if (!(b->head = d->next))
+			b->tail = NULL;
+		BUFFER_DATA_FREE(d);
+	}
+
+	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+
+#undef MAX_CHUNKS
+#undef MAX_FLUSH
+}
+
+buffer_status_t
+buffer_write(struct buffer * b, int fd, const void *p, size_t size)
+{
+	ssize_t nbytes;
+
+#if 0
+	/* Should we attempt to drain any previously buffered data?  This could help reduce latency in pushing out the data if we are stuck in a long-running thread that is preventing the main select loop from calling the flush thread... */
+
+	if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR))
+		return BUFFER_ERROR;
+#endif
+	if (b->head)
+		/* Buffer is not empty, so do not attempt to write the new data. */
+		nbytes = 0;
+	else if ((nbytes = write(fd, p, size)) < 0) {
+		if (ERRNO_IO_RETRY(errno))
+			nbytes = 0;
+		else
+			return BUFFER_ERROR;
+	}
+	/* Add any remaining data to the buffer. */
+	{
+		size_t written = nbytes;
+		if (written < size)
+			buffer_put(b, ((const char *)p) + written,
+				   size - written);
+	}
+	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
+}
diff --git a/src/vty/command.c b/src/vty/command.c
new file mode 100644
index 0000000..c84c612
--- /dev/null
+++ b/src/vty/command.c
@@ -0,0 +1,3325 @@
+/*
+   $Id: command.c,v 1.47 2005/04/25 16:26:42 paul Exp $
+
+   Command interpreter routine for virtual terminal [aka TeletYpe]
+   Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+
+This file is part of GNU Zebra.
+
+GNU Zebra is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published
+by the Free Software Foundation; either version 2, or (at your
+option) any later version.
+
+GNU Zebra is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Zebra; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#define _XOPEN_SOURCE
+#include <unistd.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <osmocom/vty/vector.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/talloc.h>
+
+/*! \addtogroup command
+ * @{
+ */
+/*! \file command.c */
+
+#define CONFIGFILE_MASK 022
+
+void *tall_vty_cmd_ctx;
+
+/* Command vector which includes some level of command lists. Normally
+   each daemon maintains each own cmdvec. */
+vector cmdvec;
+
+/* Host information structure. */
+struct host host;
+
+/* Standard command node structures. */
+struct cmd_node auth_node = {
+	AUTH_NODE,
+	"Password: ",
+};
+
+struct cmd_node view_node = {
+	VIEW_NODE,
+	"%s> ",
+};
+
+struct cmd_node auth_enable_node = {
+	AUTH_ENABLE_NODE,
+	"Password: ",
+};
+
+struct cmd_node enable_node = {
+	ENABLE_NODE,
+	"%s# ",
+};
+
+struct cmd_node config_node = {
+	CONFIG_NODE,
+	"%s(config)# ",
+	1
+};
+
+/* Default motd string. */
+const char *default_motd = "";
+
+/*! \brief print the version (and optionally copyright) information
+ *
+ * This is called from main when a daemon is invoked with -v or --version. */
+void print_version(int print_copyright)
+{
+	printf("%s version %s\n", host.app_info->name, host.app_info->version);
+	if (print_copyright)
+		printf("\n%s\n", host.app_info->copyright);
+}
+
+/* Utility function to concatenate argv argument into a single string
+   with inserting ' ' character between each argument.  */
+char *argv_concat(const char **argv, int argc, int shift)
+{
+	int i;
+	size_t len;
+	char *str;
+	char *p;
+
+	len = 0;
+	for (i = shift; i < argc; i++)
+		len += strlen(argv[i]) + 1;
+	if (!len)
+		return NULL;
+	p = str = _talloc_zero(tall_vty_cmd_ctx, len, "arvg_concat");
+	for (i = shift; i < argc; i++) {
+		size_t arglen;
+		memcpy(p, argv[i], (arglen = strlen(argv[i])));
+		p += arglen;
+		*p++ = ' ';
+	}
+	*(p - 1) = '\0';
+	return str;
+}
+
+/*! \brief Install top node of command vector. */
+void install_node(struct cmd_node *node, int (*func) (struct vty *))
+{
+	vector_set_index(cmdvec, node->node, node);
+	node->func = func;
+	node->cmd_vector = vector_init(VECTOR_MIN_SIZE);
+}
+
+/* Compare two command's string.  Used in sort_node (). */
+static int cmp_node(const void *p, const void *q)
+{
+	struct cmd_element *a = *(struct cmd_element **)p;
+	struct cmd_element *b = *(struct cmd_element **)q;
+
+	return strcmp(a->string, b->string);
+}
+
+static int cmp_desc(const void *p, const void *q)
+{
+	struct desc *a = *(struct desc **)p;
+	struct desc *b = *(struct desc **)q;
+
+	return strcmp(a->cmd, b->cmd);
+}
+
+static int is_config(struct vty *vty)
+{
+	if (vty->node <= CONFIG_NODE)
+		return 0;
+	else if (vty->node > CONFIG_NODE && vty->node < _LAST_OSMOVTY_NODE)
+		return 1;
+	else if (host.app_info->is_config_node)
+		return host.app_info->is_config_node(vty, vty->node);
+	else
+		return vty->node > CONFIG_NODE;
+}
+
+/*! \brief Sort each node's command element according to command string. */
+void sort_node(void)
+{
+	unsigned int i, j;
+	struct cmd_node *cnode;
+	vector descvec;
+	struct cmd_element *cmd_element;
+
+	for (i = 0; i < vector_active(cmdvec); i++)
+		if ((cnode = vector_slot(cmdvec, i)) != NULL) {
+			vector cmd_vector = cnode->cmd_vector;
+			qsort(cmd_vector->index, vector_active(cmd_vector),
+			      sizeof(void *), cmp_node);
+
+			for (j = 0; j < vector_active(cmd_vector); j++)
+				if ((cmd_element =
+				     vector_slot(cmd_vector, j)) != NULL
+				    && vector_active(cmd_element->strvec)) {
+					descvec =
+					    vector_slot(cmd_element->strvec,
+							vector_active
+							(cmd_element->strvec) -
+							1);
+					qsort(descvec->index,
+					      vector_active(descvec),
+					      sizeof(void *), cmp_desc);
+				}
+		}
+}
+
+/*! Breaking up string into each command piece. I assume given
+   character is separated by a space character. Return value is a
+   vector which includes char ** data element. */
+vector cmd_make_strvec(const char *string)
+{
+	const char *cp, *start;
+	char *token;
+	int strlen;
+	vector strvec;
+
+	if (string == NULL)
+		return NULL;
+
+	cp = string;
+
+	/* Skip white spaces. */
+	while (isspace((int)*cp) && *cp != '\0')
+		cp++;
+
+	/* Return if there is only white spaces */
+	if (*cp == '\0')
+		return NULL;
+
+	if (*cp == '!' || *cp == '#')
+		return NULL;
+
+	/* Prepare return vector. */
+	strvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Copy each command piece and set into vector. */
+	while (1) {
+		start = cp;
+		while (!(isspace((int)*cp) || *cp == '\r' || *cp == '\n') &&
+		       *cp != '\0')
+			cp++;
+		strlen = cp - start;
+		token = _talloc_zero(tall_vty_cmd_ctx, strlen + 1, "make_strvec");
+		memcpy(token, start, strlen);
+		*(token + strlen) = '\0';
+		vector_set(strvec, token);
+
+		while ((isspace((int)*cp) || *cp == '\n' || *cp == '\r') &&
+		       *cp != '\0')
+			cp++;
+
+		if (*cp == '\0')
+			return strvec;
+	}
+}
+
+/*! \brief Free allocated string vector. */
+void cmd_free_strvec(vector v)
+{
+	unsigned int i;
+	char *cp;
+
+	if (!v)
+		return;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((cp = vector_slot(v, i)) != NULL)
+			talloc_free(cp);
+
+	vector_free(v);
+}
+
+/*! \brief Fetch next description.  Used in \ref cmd_make_descvec(). */
+static char *cmd_desc_str(const char **string)
+{
+	const char *cp, *start;
+	char *token;
+	int strlen;
+
+	cp = *string;
+
+	if (cp == NULL)
+		return NULL;
+
+	/* Skip white spaces. */
+	while (isspace((int)*cp) && *cp != '\0')
+		cp++;
+
+	/* Return if there is only white spaces */
+	if (*cp == '\0')
+		return NULL;
+
+	start = cp;
+
+	while (!(*cp == '\r' || *cp == '\n') && *cp != '\0')
+		cp++;
+
+	strlen = cp - start;
+	token = _talloc_zero(tall_vty_cmd_ctx, strlen + 1, "cmd_desc_str");
+	memcpy(token, start, strlen);
+	*(token + strlen) = '\0';
+
+	*string = cp;
+
+	return token;
+}
+
+/*! \brief New string vector. */
+static vector cmd_make_descvec(const char *string, const char *descstr)
+{
+	int multiple = 0;
+	const char *sp;
+	char *token;
+	int len;
+	const char *cp;
+	const char *dp;
+	vector allvec;
+	vector strvec = NULL;
+	struct desc *desc;
+
+	cp = string;
+	dp = descstr;
+
+	if (cp == NULL)
+		return NULL;
+
+	allvec = vector_init(VECTOR_MIN_SIZE);
+
+	while (1) {
+		while (isspace((int)*cp) && *cp != '\0')
+			cp++;
+
+		if (*cp == '(') {
+			multiple = 1;
+			cp++;
+		}
+		if (*cp == ')') {
+			multiple = 0;
+			cp++;
+		}
+		if (*cp == '|') {
+			if (!multiple) {
+				fprintf(stderr, "Command parse error!: %s\n",
+					string);
+				exit(1);
+			}
+			cp++;
+		}
+
+		while (isspace((int)*cp) && *cp != '\0')
+			cp++;
+
+		if (*cp == '(') {
+			multiple = 1;
+			cp++;
+		}
+
+		if (*cp == '\0')
+			return allvec;
+
+		sp = cp;
+
+		while (!
+		       (isspace((int)*cp) || *cp == '\r' || *cp == '\n'
+			|| *cp == ')' || *cp == '|') && *cp != '\0')
+			cp++;
+
+		len = cp - sp;
+
+		token = _talloc_zero(tall_vty_cmd_ctx, len + 1, "cmd_make_descvec");
+		memcpy(token, sp, len);
+		*(token + len) = '\0';
+
+		desc = talloc_zero(tall_vty_cmd_ctx, struct desc);
+		desc->cmd = token;
+		desc->str = cmd_desc_str(&dp);
+
+		if (multiple) {
+			if (multiple == 1) {
+				strvec = vector_init(VECTOR_MIN_SIZE);
+				vector_set(allvec, strvec);
+			}
+			multiple++;
+		} else {
+			strvec = vector_init(VECTOR_MIN_SIZE);
+			vector_set(allvec, strvec);
+		}
+		vector_set(strvec, desc);
+	}
+}
+
+/* Count mandantory string vector size.  This is to determine inputed
+   command has enough command length. */
+static int cmd_cmdsize(vector strvec)
+{
+	unsigned int i;
+	int size = 0;
+	vector descvec;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(strvec); i++)
+		if ((descvec = vector_slot(strvec, i)) != NULL) {
+			if ((vector_active(descvec)) == 1
+			    && (desc = vector_slot(descvec, 0)) != NULL) {
+				if (desc->cmd == NULL || CMD_OPTION(desc->cmd))
+					return size;
+				else
+					size++;
+			} else
+				size++;
+		}
+	return size;
+}
+
+/*! \brief Return prompt character of specified node. */
+const char *cmd_prompt(enum node_type node)
+{
+	struct cmd_node *cnode;
+
+	cnode = vector_slot(cmdvec, node);
+	return cnode->prompt;
+}
+
+/*! \brief Install a command into a node
+ *  \param[in] ntype Node Type
+ *  \param[cmd] element to be installed
+ */
+void install_element(enum node_type ntype, struct cmd_element *cmd)
+{
+	struct cmd_node *cnode;
+
+	cnode = vector_slot(cmdvec, ntype);
+
+	if (cnode == NULL) {
+		fprintf(stderr,
+			"Command node %d doesn't exist, please check it\n",
+			ntype);
+		exit(1);
+	}
+
+	vector_set(cnode->cmd_vector, cmd);
+
+	cmd->strvec = cmd_make_descvec(cmd->string, cmd->doc);
+	cmd->cmdsize = cmd_cmdsize(cmd->strvec);
+}
+
+/* Install a command into VIEW and ENABLE node */
+void install_element_ve(struct cmd_element *cmd)
+{
+	install_element(VIEW_NODE, cmd);
+	install_element(ENABLE_NODE, cmd);
+}
+
+#ifdef VTY_CRYPT_PW
+static unsigned char itoa64[] =
+    "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void to64(char *s, long v, int n)
+{
+	while (--n >= 0) {
+		*s++ = itoa64[v & 0x3f];
+		v >>= 6;
+	}
+}
+
+static char *zencrypt(const char *passwd)
+{
+	char salt[6];
+	struct timeval tv;
+	char *crypt(const char *, const char *);
+
+	gettimeofday(&tv, 0);
+
+	to64(&salt[0], random(), 3);
+	to64(&salt[3], tv.tv_usec, 3);
+	salt[5] = '\0';
+
+	return crypt(passwd, salt);
+}
+#endif
+
+/* This function write configuration of this host. */
+static int config_write_host(struct vty *vty)
+{
+	if (host.name)
+		vty_out(vty, "hostname %s%s", host.name, VTY_NEWLINE);
+
+	if (host.encrypt) {
+		if (host.password_encrypt)
+			vty_out(vty, "password 8 %s%s", host.password_encrypt,
+				VTY_NEWLINE);
+		if (host.enable_encrypt)
+			vty_out(vty, "enable password 8 %s%s",
+				host.enable_encrypt, VTY_NEWLINE);
+	} else {
+		if (host.password)
+			vty_out(vty, "password %s%s", host.password,
+				VTY_NEWLINE);
+		if (host.enable)
+			vty_out(vty, "enable password %s%s", host.enable,
+				VTY_NEWLINE);
+	}
+
+	if (host.advanced)
+		vty_out(vty, "service advanced-vty%s", VTY_NEWLINE);
+
+	if (host.encrypt)
+		vty_out(vty, "service password-encryption%s", VTY_NEWLINE);
+
+	if (host.lines >= 0)
+		vty_out(vty, "service terminal-length %d%s", host.lines,
+			VTY_NEWLINE);
+
+	if (host.motdfile)
+		vty_out(vty, "banner motd file %s%s", host.motdfile,
+			VTY_NEWLINE);
+	else if (!host.motd)
+		vty_out(vty, "no banner motd%s", VTY_NEWLINE);
+
+	return 1;
+}
+
+/* Utility function for getting command vector. */
+static vector cmd_node_vector(vector v, enum node_type ntype)
+{
+	struct cmd_node *cnode = vector_slot(v, ntype);
+	return cnode->cmd_vector;
+}
+
+/* Completion match types. */
+enum match_type {
+	no_match,
+	extend_match,
+	ipv4_prefix_match,
+	ipv4_match,
+	ipv6_prefix_match,
+	ipv6_match,
+	range_match,
+	vararg_match,
+	partly_match,
+	exact_match
+};
+
+static enum match_type cmd_ipv4_match(const char *str)
+{
+	const char *sp;
+	int dots = 0, nums = 0;
+	char buf[4];
+
+	if (str == NULL)
+		return partly_match;
+
+	for (;;) {
+		memset(buf, 0, sizeof(buf));
+		sp = str;
+		while (*str != '\0') {
+			if (*str == '.') {
+				if (dots >= 3)
+					return no_match;
+
+				if (*(str + 1) == '.')
+					return no_match;
+
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				dots++;
+				break;
+			}
+			if (!isdigit((int)*str))
+				return no_match;
+
+			str++;
+		}
+
+		if (str - sp > 3)
+			return no_match;
+
+		strncpy(buf, sp, str - sp);
+		if (atoi(buf) > 255)
+			return no_match;
+
+		nums++;
+
+		if (*str == '\0')
+			break;
+
+		str++;
+	}
+
+	if (nums < 4)
+		return partly_match;
+
+	return exact_match;
+}
+
+static enum match_type cmd_ipv4_prefix_match(const char *str)
+{
+	const char *sp;
+	int dots = 0;
+	char buf[4];
+
+	if (str == NULL)
+		return partly_match;
+
+	for (;;) {
+		memset(buf, 0, sizeof(buf));
+		sp = str;
+		while (*str != '\0' && *str != '/') {
+			if (*str == '.') {
+				if (dots == 3)
+					return no_match;
+
+				if (*(str + 1) == '.' || *(str + 1) == '/')
+					return no_match;
+
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				dots++;
+				break;
+			}
+
+			if (!isdigit((int)*str))
+				return no_match;
+
+			str++;
+		}
+
+		if (str - sp > 3)
+			return no_match;
+
+		strncpy(buf, sp, str - sp);
+		if (atoi(buf) > 255)
+			return no_match;
+
+		if (dots == 3) {
+			if (*str == '/') {
+				if (*(str + 1) == '\0')
+					return partly_match;
+
+				str++;
+				break;
+			} else if (*str == '\0')
+				return partly_match;
+		}
+
+		if (*str == '\0')
+			return partly_match;
+
+		str++;
+	}
+
+	sp = str;
+	while (*str != '\0') {
+		if (!isdigit((int)*str))
+			return no_match;
+
+		str++;
+	}
+
+	if (atoi(sp) > 32)
+		return no_match;
+
+	return exact_match;
+}
+
+#define IPV6_ADDR_STR		"0123456789abcdefABCDEF:.%"
+#define IPV6_PREFIX_STR		"0123456789abcdefABCDEF:.%/"
+#define STATE_START		1
+#define STATE_COLON		2
+#define STATE_DOUBLE		3
+#define STATE_ADDR		4
+#define STATE_DOT               5
+#define STATE_SLASH		6
+#define STATE_MASK		7
+
+#ifdef HAVE_IPV6
+
+static enum match_type cmd_ipv6_match(const char *str)
+{
+	int state = STATE_START;
+	int colons = 0, nums = 0, double_colon = 0;
+	const char *sp = NULL;
+	struct sockaddr_in6 sin6_dummy;
+	int ret;
+
+	if (str == NULL)
+		return partly_match;
+
+	if (strspn(str, IPV6_ADDR_STR) != strlen(str))
+		return no_match;
+
+	/* use inet_pton that has a better support,
+	 * for example inet_pton can support the automatic addresses:
+	 *  ::1.2.3.4
+	 */
+	ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
+
+	if (ret == 1)
+		return exact_match;
+
+	while (*str != '\0') {
+		switch (state) {
+		case STATE_START:
+			if (*str == ':') {
+				if (*(str + 1) != ':' && *(str + 1) != '\0')
+					return no_match;
+				colons--;
+				state = STATE_COLON;
+			} else {
+				sp = str;
+				state = STATE_ADDR;
+			}
+
+			continue;
+		case STATE_COLON:
+			colons++;
+			if (*(str + 1) == ':')
+				state = STATE_DOUBLE;
+			else {
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+			break;
+		case STATE_DOUBLE:
+			if (double_colon)
+				return no_match;
+
+			if (*(str + 1) == ':')
+				return no_match;
+			else {
+				if (*(str + 1) != '\0')
+					colons++;
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+
+			double_colon++;
+			nums++;
+			break;
+		case STATE_ADDR:
+			if (*(str + 1) == ':' || *(str + 1) == '\0') {
+				if (str - sp > 3)
+					return no_match;
+
+				nums++;
+				state = STATE_COLON;
+			}
+			if (*(str + 1) == '.')
+				state = STATE_DOT;
+			break;
+		case STATE_DOT:
+			state = STATE_ADDR;
+			break;
+		default:
+			break;
+		}
+
+		if (nums > 8)
+			return no_match;
+
+		if (colons > 7)
+			return no_match;
+
+		str++;
+	}
+
+#if 0
+	if (nums < 11)
+		return partly_match;
+#endif				/* 0 */
+
+	return exact_match;
+}
+
+static enum match_type cmd_ipv6_prefix_match(const char *str)
+{
+	int state = STATE_START;
+	int colons = 0, nums = 0, double_colon = 0;
+	int mask;
+	const char *sp = NULL;
+	char *endptr = NULL;
+
+	if (str == NULL)
+		return partly_match;
+
+	if (strspn(str, IPV6_PREFIX_STR) != strlen(str))
+		return no_match;
+
+	while (*str != '\0' && state != STATE_MASK) {
+		switch (state) {
+		case STATE_START:
+			if (*str == ':') {
+				if (*(str + 1) != ':' && *(str + 1) != '\0')
+					return no_match;
+				colons--;
+				state = STATE_COLON;
+			} else {
+				sp = str;
+				state = STATE_ADDR;
+			}
+
+			continue;
+		case STATE_COLON:
+			colons++;
+			if (*(str + 1) == '/')
+				return no_match;
+			else if (*(str + 1) == ':')
+				state = STATE_DOUBLE;
+			else {
+				sp = str + 1;
+				state = STATE_ADDR;
+			}
+			break;
+		case STATE_DOUBLE:
+			if (double_colon)
+				return no_match;
+
+			if (*(str + 1) == ':')
+				return no_match;
+			else {
+				if (*(str + 1) != '\0' && *(str + 1) != '/')
+					colons++;
+				sp = str + 1;
+
+				if (*(str + 1) == '/')
+					state = STATE_SLASH;
+				else
+					state = STATE_ADDR;
+			}
+
+			double_colon++;
+			nums += 1;
+			break;
+		case STATE_ADDR:
+			if (*(str + 1) == ':' || *(str + 1) == '.'
+			    || *(str + 1) == '\0' || *(str + 1) == '/') {
+				if (str - sp > 3)
+					return no_match;
+
+				for (; sp <= str; sp++)
+					if (*sp == '/')
+						return no_match;
+
+				nums++;
+
+				if (*(str + 1) == ':')
+					state = STATE_COLON;
+				else if (*(str + 1) == '.')
+					state = STATE_DOT;
+				else if (*(str + 1) == '/')
+					state = STATE_SLASH;
+			}
+			break;
+		case STATE_DOT:
+			state = STATE_ADDR;
+			break;
+		case STATE_SLASH:
+			if (*(str + 1) == '\0')
+				return partly_match;
+
+			state = STATE_MASK;
+			break;
+		default:
+			break;
+		}
+
+		if (nums > 11)
+			return no_match;
+
+		if (colons > 7)
+			return no_match;
+
+		str++;
+	}
+
+	if (state < STATE_MASK)
+		return partly_match;
+
+	mask = strtol(str, &endptr, 10);
+	if (*endptr != '\0')
+		return no_match;
+
+	if (mask < 0 || mask > 128)
+		return no_match;
+
+/* I don't know why mask < 13 makes command match partly.
+   Forgive me to make this comments. I Want to set static default route
+   because of lack of function to originate default in ospf6d; sorry
+       yasu
+  if (mask < 13)
+    return partly_match;
+*/
+
+	return exact_match;
+}
+
+#endif				/* HAVE_IPV6  */
+
+#define DECIMAL_STRLEN_MAX 10
+
+static int cmd_range_match(const char *range, const char *str)
+{
+	char *p;
+	char buf[DECIMAL_STRLEN_MAX + 1];
+	char *endptr = NULL;
+
+	if (str == NULL)
+		return 1;
+
+	if (range[1] == '-') {
+		signed long min = 0, max = 0, val;
+
+		val = strtol(str, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		range += 2;
+		p = strchr(range, '-');
+		if (p == NULL)
+			return 0;
+		if (p - range > DECIMAL_STRLEN_MAX)
+			return 0;
+		strncpy(buf, range, p - range);
+		buf[p - range] = '\0';
+		min = -strtol(buf, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		range = p + 1;
+		p = strchr(range, '>');
+		if (p == NULL)
+			return 0;
+		if (p - range > DECIMAL_STRLEN_MAX)
+			return 0;
+		strncpy(buf, range, p - range);
+		buf[p - range] = '\0';
+		max = strtol(buf, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		if (val < min || val > max)
+			return 0;
+	} else {
+		unsigned long min, max, val;
+
+		val = strtoul(str, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		range++;
+		p = strchr(range, '-');
+		if (p == NULL)
+			return 0;
+		if (p - range > DECIMAL_STRLEN_MAX)
+			return 0;
+		strncpy(buf, range, p - range);
+		buf[p - range] = '\0';
+		min = strtoul(buf, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		range = p + 1;
+		p = strchr(range, '>');
+		if (p == NULL)
+			return 0;
+		if (p - range > DECIMAL_STRLEN_MAX)
+			return 0;
+		strncpy(buf, range, p - range);
+		buf[p - range] = '\0';
+		max = strtoul(buf, &endptr, 10);
+		if (*endptr != '\0')
+			return 0;
+
+		if (val < min || val > max)
+			return 0;
+	}
+
+	return 1;
+}
+
+/* Make completion match and return match type flag. */
+static enum match_type
+cmd_filter_by_completion(char *command, vector v, unsigned int index)
+{
+	unsigned int i;
+	const char *str;
+	struct cmd_element *cmd_element;
+	enum match_type match_type;
+	vector descvec;
+	struct desc *desc;
+
+	match_type = no_match;
+
+	/* If command and cmd_element string does not match set NULL to vector */
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			if (index >= vector_active(cmd_element->strvec))
+				vector_slot(v, i) = NULL;
+			else {
+				unsigned int j;
+				int matched = 0;
+
+				descvec =
+				    vector_slot(cmd_element->strvec, index);
+
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						str = desc->cmd;
+
+						if (CMD_VARARG(str)) {
+							if (match_type <
+							    vararg_match)
+								match_type =
+								    vararg_match;
+							matched++;
+						} else if (CMD_RANGE(str)) {
+							if (cmd_range_match
+							    (str, command)) {
+								if (match_type <
+								    range_match)
+									match_type
+									    =
+									    range_match;
+
+								matched++;
+							}
+						}
+#ifdef HAVE_IPV6
+						else if (CMD_IPV6(str)) {
+							if (cmd_ipv6_match
+							    (command)) {
+								if (match_type <
+								    ipv6_match)
+									match_type
+									    =
+									    ipv6_match;
+
+								matched++;
+							}
+						} else if (CMD_IPV6_PREFIX(str)) {
+							if (cmd_ipv6_prefix_match(command)) {
+								if (match_type <
+								    ipv6_prefix_match)
+									match_type
+									    =
+									    ipv6_prefix_match;
+
+								matched++;
+							}
+						}
+#endif				/* HAVE_IPV6  */
+						else if (CMD_IPV4(str)) {
+							if (cmd_ipv4_match
+							    (command)) {
+								if (match_type <
+								    ipv4_match)
+									match_type
+									    =
+									    ipv4_match;
+
+								matched++;
+							}
+						} else if (CMD_IPV4_PREFIX(str)) {
+							if (cmd_ipv4_prefix_match(command)) {
+								if (match_type <
+								    ipv4_prefix_match)
+									match_type
+									    =
+									    ipv4_prefix_match;
+								matched++;
+							}
+						} else
+							/* Check is this point's argument optional ? */
+						if (CMD_OPTION(str)
+							    ||
+							    CMD_VARIABLE(str)) {
+							if (match_type <
+							    extend_match)
+								match_type =
+								    extend_match;
+							matched++;
+						} else
+						    if (strncmp
+							(command, str,
+							 strlen(command)) ==
+							0) {
+							if (strcmp(command, str)
+							    == 0)
+								match_type =
+								    exact_match;
+							else {
+								if (match_type <
+								    partly_match)
+									match_type
+									    =
+									    partly_match;
+							}
+							matched++;
+						}
+					}
+				if (!matched)
+					vector_slot(v, i) = NULL;
+			}
+		}
+	return match_type;
+}
+
+/* Filter vector by command character with index. */
+static enum match_type
+cmd_filter_by_string(char *command, vector v, unsigned int index)
+{
+	unsigned int i;
+	const char *str;
+	struct cmd_element *cmd_element;
+	enum match_type match_type;
+	vector descvec;
+	struct desc *desc;
+
+	match_type = no_match;
+
+	/* If command and cmd_element string does not match set NULL to vector */
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			/* If given index is bigger than max string vector of command,
+			   set NULL */
+			if (index >= vector_active(cmd_element->strvec))
+				vector_slot(v, i) = NULL;
+			else {
+				unsigned int j;
+				int matched = 0;
+
+				descvec =
+				    vector_slot(cmd_element->strvec, index);
+
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						str = desc->cmd;
+
+						if (CMD_VARARG(str)) {
+							if (match_type <
+							    vararg_match)
+								match_type =
+								    vararg_match;
+							matched++;
+						} else if (CMD_RANGE(str)) {
+							if (cmd_range_match
+							    (str, command)) {
+								if (match_type <
+								    range_match)
+									match_type
+									    =
+									    range_match;
+								matched++;
+							}
+						}
+#ifdef HAVE_IPV6
+						else if (CMD_IPV6(str)) {
+							if (cmd_ipv6_match
+							    (command) ==
+							    exact_match) {
+								if (match_type <
+								    ipv6_match)
+									match_type
+									    =
+									    ipv6_match;
+								matched++;
+							}
+						} else if (CMD_IPV6_PREFIX(str)) {
+							if (cmd_ipv6_prefix_match(command) == exact_match) {
+								if (match_type <
+								    ipv6_prefix_match)
+									match_type
+									    =
+									    ipv6_prefix_match;
+								matched++;
+							}
+						}
+#endif				/* HAVE_IPV6  */
+						else if (CMD_IPV4(str)) {
+							if (cmd_ipv4_match
+							    (command) ==
+							    exact_match) {
+								if (match_type <
+								    ipv4_match)
+									match_type
+									    =
+									    ipv4_match;
+								matched++;
+							}
+						} else if (CMD_IPV4_PREFIX(str)) {
+							if (cmd_ipv4_prefix_match(command) == exact_match) {
+								if (match_type <
+								    ipv4_prefix_match)
+									match_type
+									    =
+									    ipv4_prefix_match;
+								matched++;
+							}
+						} else if (CMD_OPTION(str)
+							   || CMD_VARIABLE(str))
+						{
+							if (match_type <
+							    extend_match)
+								match_type =
+								    extend_match;
+							matched++;
+						} else {
+							if (strcmp(command, str)
+							    == 0) {
+								match_type =
+								    exact_match;
+								matched++;
+							}
+						}
+					}
+				if (!matched)
+					vector_slot(v, i) = NULL;
+			}
+		}
+	return match_type;
+}
+
+/* Check ambiguous match */
+static int
+is_cmd_ambiguous(char *command, vector v, int index, enum match_type type)
+{
+	unsigned int i;
+	unsigned int j;
+	const char *str = NULL;
+	struct cmd_element *cmd_element;
+	const char *matched = NULL;
+	vector descvec;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((cmd_element = vector_slot(v, i)) != NULL) {
+			int match = 0;
+
+			descvec = vector_slot(cmd_element->strvec, index);
+
+			for (j = 0; j < vector_active(descvec); j++)
+				if ((desc = vector_slot(descvec, j))) {
+					enum match_type ret;
+
+					str = desc->cmd;
+
+					switch (type) {
+					case exact_match:
+						if (!
+						    (CMD_OPTION(str)
+						     || CMD_VARIABLE(str))
+&& strcmp(command, str) == 0)
+							match++;
+						break;
+					case partly_match:
+						if (!
+						    (CMD_OPTION(str)
+						     || CMD_VARIABLE(str))
+&& strncmp(command, str, strlen(command)) == 0) {
+							if (matched
+							    && strcmp(matched,
+								      str) != 0)
+								return 1;	/* There is ambiguous match. */
+							else
+								matched = str;
+							match++;
+						}
+						break;
+					case range_match:
+						if (cmd_range_match
+						    (str, command)) {
+							if (matched
+							    && strcmp(matched,
+								      str) != 0)
+								return 1;
+							else
+								matched = str;
+							match++;
+						}
+						break;
+#ifdef HAVE_IPV6
+					case ipv6_match:
+						if (CMD_IPV6(str))
+							match++;
+						break;
+					case ipv6_prefix_match:
+						if ((ret =
+						     cmd_ipv6_prefix_match
+						     (command)) != no_match) {
+							if (ret == partly_match)
+								return 2;	/* There is incomplete match. */
+
+							match++;
+						}
+						break;
+#endif				/* HAVE_IPV6 */
+					case ipv4_match:
+						if (CMD_IPV4(str))
+							match++;
+						break;
+					case ipv4_prefix_match:
+						if ((ret =
+						     cmd_ipv4_prefix_match
+						     (command)) != no_match) {
+							if (ret == partly_match)
+								return 2;	/* There is incomplete match. */
+
+							match++;
+						}
+						break;
+					case extend_match:
+						if (CMD_OPTION(str)
+						    || CMD_VARIABLE(str))
+							match++;
+						break;
+					case no_match:
+					default:
+						break;
+					}
+				}
+			if (!match)
+				vector_slot(v, i) = NULL;
+		}
+	return 0;
+}
+
+/* If src matches dst return dst string, otherwise return NULL */
+static const char *cmd_entry_function(const char *src, const char *dst)
+{
+	/* Skip variable arguments. */
+	if (CMD_OPTION(dst) || CMD_VARIABLE(dst) || CMD_VARARG(dst) ||
+	    CMD_IPV4(dst) || CMD_IPV4_PREFIX(dst) || CMD_RANGE(dst))
+		return NULL;
+
+	/* In case of 'command \t', given src is NULL string. */
+	if (src == NULL)
+		return dst;
+
+	/* Matched with input string. */
+	if (strncmp(src, dst, strlen(src)) == 0)
+		return dst;
+
+	return NULL;
+}
+
+/* If src matches dst return dst string, otherwise return NULL */
+/* This version will return the dst string always if it is
+   CMD_VARIABLE for '?' key processing */
+static const char *cmd_entry_function_desc(const char *src, const char *dst)
+{
+	if (CMD_VARARG(dst))
+		return dst;
+
+	if (CMD_RANGE(dst)) {
+		if (cmd_range_match(dst, src))
+			return dst;
+		else
+			return NULL;
+	}
+#ifdef HAVE_IPV6
+	if (CMD_IPV6(dst)) {
+		if (cmd_ipv6_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	if (CMD_IPV6_PREFIX(dst)) {
+		if (cmd_ipv6_prefix_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+#endif				/* HAVE_IPV6 */
+
+	if (CMD_IPV4(dst)) {
+		if (cmd_ipv4_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	if (CMD_IPV4_PREFIX(dst)) {
+		if (cmd_ipv4_prefix_match(src))
+			return dst;
+		else
+			return NULL;
+	}
+
+	/* Optional or variable commands always match on '?' */
+	if (CMD_OPTION(dst) || CMD_VARIABLE(dst))
+		return dst;
+
+	/* In case of 'command \t', given src is NULL string. */
+	if (src == NULL)
+		return dst;
+
+	if (strncmp(src, dst, strlen(src)) == 0)
+		return dst;
+	else
+		return NULL;
+}
+
+/* Check same string element existence.  If it isn't there return
+    1. */
+static int cmd_unique_string(vector v, const char *str)
+{
+	unsigned int i;
+	char *match;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((match = vector_slot(v, i)) != NULL)
+			if (strcmp(match, str) == 0)
+				return 0;
+	return 1;
+}
+
+/* Compare string to description vector.  If there is same string
+   return 1 else return 0. */
+static int desc_unique_string(vector v, const char *str)
+{
+	unsigned int i;
+	struct desc *desc;
+
+	for (i = 0; i < vector_active(v); i++)
+		if ((desc = vector_slot(v, i)) != NULL)
+			if (strcmp(desc->cmd, str) == 0)
+				return 1;
+	return 0;
+}
+
+static int cmd_try_do_shortcut(enum node_type node, char *first_word)
+{
+	if (first_word != NULL &&
+	    node != AUTH_NODE &&
+	    node != VIEW_NODE &&
+	    node != AUTH_ENABLE_NODE &&
+	    node != ENABLE_NODE && 0 == strcmp("do", first_word))
+		return 1;
+	return 0;
+}
+
+/* '?' describe command support. */
+static vector
+cmd_describe_command_real(vector vline, struct vty *vty, int *status)
+{
+	unsigned int i;
+	vector cmd_vector;
+#define INIT_MATCHVEC_SIZE 10
+	vector matchvec;
+	struct cmd_element *cmd_element;
+	unsigned int index;
+	int ret;
+	enum match_type match;
+	char *command;
+	static struct desc desc_cr = { "<cr>", "" };
+
+	/* Set index. */
+	if (vector_active(vline) == 0) {
+		*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	} else
+		index = vector_active(vline) - 1;
+
+	/* Make copy vector of current node's command vector. */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	/* Prepare match vector */
+	matchvec = vector_init(INIT_MATCHVEC_SIZE);
+
+	/* Filter commands. */
+	/* Only words precedes current word will be checked in this loop. */
+	for (i = 0; i < index; i++)
+		if ((command = vector_slot(vline, i))) {
+			match =
+			    cmd_filter_by_completion(command, cmd_vector, i);
+
+			if (match == vararg_match) {
+				struct cmd_element *cmd_element;
+				vector descvec;
+				unsigned int j, k;
+
+				for (j = 0; j < vector_active(cmd_vector); j++)
+					if ((cmd_element =
+					     vector_slot(cmd_vector, j)) != NULL
+					    &&
+					    (vector_active
+					     (cmd_element->strvec))) {
+						descvec =
+						    vector_slot(cmd_element->
+								strvec,
+								vector_active
+								(cmd_element->
+								 strvec) - 1);
+						for (k = 0;
+						     k < vector_active(descvec);
+						     k++) {
+							struct desc *desc =
+							    vector_slot(descvec,
+									k);
+							vector_set(matchvec,
+								   desc);
+						}
+					}
+
+				vector_set(matchvec, &desc_cr);
+				vector_free(cmd_vector);
+
+				return matchvec;
+			}
+
+			if ((ret =
+			     is_cmd_ambiguous(command, cmd_vector, i,
+					      match)) == 1) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_AMBIGUOUS;
+				return NULL;
+			} else if (ret == 2) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_NO_MATCH;
+				return NULL;
+			}
+		}
+
+	/* Prepare match vector */
+	/*  matchvec = vector_init (INIT_MATCHVEC_SIZE); */
+
+	/* Make sure that cmd_vector is filtered based on current word */
+	command = vector_slot(vline, index);
+	if (command)
+		match = cmd_filter_by_completion(command, cmd_vector, index);
+
+	/* Make description vector. */
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i)) != NULL) {
+			const char *string = NULL;
+			vector strvec = cmd_element->strvec;
+
+			/* if command is NULL, index may be equal to vector_active */
+			if (command && index >= vector_active(strvec))
+				vector_slot(cmd_vector, i) = NULL;
+			else {
+				/* Check if command is completed. */
+				if (command == NULL
+				    && index == vector_active(strvec)) {
+					string = "<cr>";
+					if (!desc_unique_string
+					    (matchvec, string))
+						vector_set(matchvec, &desc_cr);
+				} else {
+					unsigned int j;
+					vector descvec =
+					    vector_slot(strvec, index);
+					struct desc *desc;
+
+					for (j = 0; j < vector_active(descvec);
+					     j++)
+						if ((desc =
+						     vector_slot(descvec, j))) {
+							string =
+							    cmd_entry_function_desc
+							    (command,
+							     desc->cmd);
+							if (string) {
+								/* Uniqueness check */
+								if (!desc_unique_string(matchvec, string))
+									vector_set
+									    (matchvec,
+									     desc);
+							}
+						}
+				}
+			}
+		}
+	vector_free(cmd_vector);
+
+	if (vector_slot(matchvec, 0) == NULL) {
+		vector_free(matchvec);
+		*status = CMD_ERR_NO_MATCH;
+	} else
+		*status = CMD_SUCCESS;
+
+	return matchvec;
+}
+
+vector cmd_describe_command(vector vline, struct vty * vty, int *status)
+{
+	vector ret;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		enum node_type onode;
+		vector shifted_vline;
+		unsigned int index;
+
+		onode = vty->node;
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_describe_command_real(shifted_vline, vty, status);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	return cmd_describe_command_real(vline, vty, status);
+}
+
+/* Check LCD of matched command. */
+static int cmd_lcd(char **matched)
+{
+	int i;
+	int j;
+	int lcd = -1;
+	char *s1, *s2;
+	char c1, c2;
+
+	if (matched[0] == NULL || matched[1] == NULL)
+		return 0;
+
+	for (i = 1; matched[i] != NULL; i++) {
+		s1 = matched[i - 1];
+		s2 = matched[i];
+
+		for (j = 0; (c1 = s1[j]) && (c2 = s2[j]); j++)
+			if (c1 != c2)
+				break;
+
+		if (lcd < 0)
+			lcd = j;
+		else {
+			if (lcd > j)
+				lcd = j;
+		}
+	}
+	return lcd;
+}
+
+/* Command line completion support. */
+static char **cmd_complete_command_real(vector vline, struct vty *vty,
+					int *status)
+{
+	unsigned int i;
+	vector cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+#define INIT_MATCHVEC_SIZE 10
+	vector matchvec;
+	struct cmd_element *cmd_element;
+	unsigned int index;
+	char **match_str;
+	struct desc *desc;
+	vector descvec;
+	char *command;
+	int lcd;
+
+	if (vector_active(vline) == 0) {
+		*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	} else
+		index = vector_active(vline) - 1;
+
+	/* First, filter by preceeding command string */
+	for (i = 0; i < index; i++)
+		if ((command = vector_slot(vline, i))) {
+			enum match_type match;
+			int ret;
+
+			/* First try completion match, if there is exactly match return 1 */
+			match =
+			    cmd_filter_by_completion(command, cmd_vector, i);
+
+			/* If there is exact match then filter ambiguous match else check
+			   ambiguousness. */
+			if ((ret =
+			     is_cmd_ambiguous(command, cmd_vector, i,
+					      match)) == 1) {
+				vector_free(cmd_vector);
+				*status = CMD_ERR_AMBIGUOUS;
+				return NULL;
+			}
+			/*
+			   else if (ret == 2)
+			   {
+			   vector_free (cmd_vector);
+			   *status = CMD_ERR_NO_MATCH;
+			   return NULL;
+			   }
+			 */
+		}
+
+	/* Prepare match vector. */
+	matchvec = vector_init(INIT_MATCHVEC_SIZE);
+
+	/* Now we got into completion */
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i))) {
+			const char *string;
+			vector strvec = cmd_element->strvec;
+
+			/* Check field length */
+			if (index >= vector_active(strvec))
+				vector_slot(cmd_vector, i) = NULL;
+			else {
+				unsigned int j;
+
+				descvec = vector_slot(strvec, index);
+				for (j = 0; j < vector_active(descvec); j++)
+					if ((desc = vector_slot(descvec, j))) {
+						if ((string = cmd_entry_function(vector_slot(vline, index), desc->cmd)))
+							if (cmd_unique_string (matchvec, string))
+								vector_set (matchvec, talloc_strdup(tall_vty_cmd_ctx, string));
+					}
+			}
+		}
+
+	/* We don't need cmd_vector any more. */
+	vector_free(cmd_vector);
+
+	/* No matched command */
+	if (vector_slot(matchvec, 0) == NULL) {
+		vector_free(matchvec);
+
+		/* In case of 'command \t' pattern.  Do you need '?' command at
+		   the end of the line. */
+		if (vector_slot(vline, index) == '\0')
+			*status = CMD_ERR_NOTHING_TODO;
+		else
+			*status = CMD_ERR_NO_MATCH;
+		return NULL;
+	}
+
+	/* Only one matched */
+	if (vector_slot(matchvec, 1) == NULL) {
+		match_str = (char **)matchvec->index;
+		vector_only_wrapper_free(matchvec);
+		*status = CMD_COMPLETE_FULL_MATCH;
+		return match_str;
+	}
+	/* Make it sure last element is NULL. */
+	vector_set(matchvec, NULL);
+
+	/* Check LCD of matched strings. */
+	if (vector_slot(vline, index) != NULL) {
+		lcd = cmd_lcd((char **)matchvec->index);
+
+		if (lcd) {
+			int len = strlen(vector_slot(vline, index));
+
+			if (len < lcd) {
+				char *lcdstr;
+
+				lcdstr = _talloc_zero(tall_vty_cmd_ctx, lcd + 1,
+						      "complete-lcdstr");
+				memcpy(lcdstr, matchvec->index[0], lcd);
+				lcdstr[lcd] = '\0';
+
+				/* match_str = (char **) &lcdstr; */
+
+				/* Free matchvec. */
+				for (i = 0; i < vector_active(matchvec); i++) {
+					if (vector_slot(matchvec, i))
+						talloc_free(vector_slot(matchvec, i));
+				}
+				vector_free(matchvec);
+
+				/* Make new matchvec. */
+				matchvec = vector_init(INIT_MATCHVEC_SIZE);
+				vector_set(matchvec, lcdstr);
+				match_str = (char **)matchvec->index;
+				vector_only_wrapper_free(matchvec);
+
+				*status = CMD_COMPLETE_MATCH;
+				return match_str;
+			}
+		}
+	}
+
+	match_str = (char **)matchvec->index;
+	vector_only_wrapper_free(matchvec);
+	*status = CMD_COMPLETE_LIST_MATCH;
+	return match_str;
+}
+
+char **cmd_complete_command(vector vline, struct vty *vty, int *status)
+{
+	char **ret;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		enum node_type onode;
+		vector shifted_vline;
+		unsigned int index;
+
+		onode = vty->node;
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_complete_command_real(shifted_vline, vty, status);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	return cmd_complete_command_real(vline, vty, status);
+}
+
+/* return parent node */
+/* MUST eventually converge on CONFIG_NODE */
+enum node_type vty_go_parent(struct vty *vty)
+{
+	assert(vty->node > CONFIG_NODE);
+
+	if (host.app_info->go_parent_cb)
+		host.app_info->go_parent_cb(vty);
+	else
+		vty->node = CONFIG_NODE;
+
+	return vty->node;
+}
+
+/* Execute command by argument vline vector. */
+static int
+cmd_execute_command_real(vector vline, struct vty *vty,
+			 struct cmd_element **cmd)
+{
+	unsigned int i;
+	unsigned int index;
+	vector cmd_vector;
+	struct cmd_element *cmd_element;
+	struct cmd_element *matched_element;
+	unsigned int matched_count, incomplete_count;
+	int argc;
+	const char *argv[CMD_ARGC_MAX];
+	enum match_type match = 0;
+	int varflag;
+	char *command;
+
+	/* Make copy of command elements. */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	for (index = 0; index < vector_active(vline); index++)
+		if ((command = vector_slot(vline, index))) {
+			int ret;
+
+			match =
+			    cmd_filter_by_completion(command, cmd_vector,
+						     index);
+
+			if (match == vararg_match)
+				break;
+
+			ret =
+			    is_cmd_ambiguous(command, cmd_vector, index, match);
+
+			if (ret == 1) {
+				vector_free(cmd_vector);
+				return CMD_ERR_AMBIGUOUS;
+			} else if (ret == 2) {
+				vector_free(cmd_vector);
+				return CMD_ERR_NO_MATCH;
+			}
+		}
+
+	/* Check matched count. */
+	matched_element = NULL;
+	matched_count = 0;
+	incomplete_count = 0;
+
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if ((cmd_element = vector_slot(cmd_vector, i))) {
+			if (match == vararg_match
+			    || index >= cmd_element->cmdsize) {
+				matched_element = cmd_element;
+#if 0
+				printf("DEBUG: %s\n", cmd_element->string);
+#endif
+				matched_count++;
+			} else {
+				incomplete_count++;
+			}
+		}
+
+	/* Finish of using cmd_vector. */
+	vector_free(cmd_vector);
+
+	/* To execute command, matched_count must be 1. */
+	if (matched_count == 0) {
+		if (incomplete_count)
+			return CMD_ERR_INCOMPLETE;
+		else
+			return CMD_ERR_NO_MATCH;
+	}
+
+	if (matched_count > 1)
+		return CMD_ERR_AMBIGUOUS;
+
+	/* Argument treatment */
+	varflag = 0;
+	argc = 0;
+
+	for (i = 0; i < vector_active(vline); i++) {
+		if (varflag)
+			argv[argc++] = vector_slot(vline, i);
+		else {
+			vector descvec =
+			    vector_slot(matched_element->strvec, i);
+
+			if (vector_active(descvec) == 1) {
+				struct desc *desc = vector_slot(descvec, 0);
+
+				if (CMD_VARARG(desc->cmd))
+					varflag = 1;
+
+				if (varflag || CMD_VARIABLE(desc->cmd)
+				    || CMD_OPTION(desc->cmd))
+					argv[argc++] = vector_slot(vline, i);
+			} else
+				argv[argc++] = vector_slot(vline, i);
+		}
+
+		if (argc >= CMD_ARGC_MAX)
+			return CMD_ERR_EXEED_ARGC_MAX;
+	}
+
+	/* For vtysh execution. */
+	if (cmd)
+		*cmd = matched_element;
+
+	if (matched_element->daemon)
+		return CMD_SUCCESS_DAEMON;
+
+	/* Execute matched command. */
+	return (*matched_element->func) (matched_element, vty, argc, argv);
+}
+
+int
+cmd_execute_command(vector vline, struct vty *vty, struct cmd_element **cmd,
+		    int vtysh)
+{
+	int ret, saved_ret, tried = 0;
+	enum node_type onode;
+	void *oindex;
+
+	onode = vty->node;
+	oindex = vty->index;
+
+	if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+		vector shifted_vline;
+		unsigned int index;
+
+		vty->node = ENABLE_NODE;
+		/* We can try it on enable node, cos' the vty is authenticated */
+
+		shifted_vline = vector_init(vector_count(vline));
+		/* use memcpy? */
+		for (index = 1; index < vector_active(vline); index++) {
+			vector_set_index(shifted_vline, index - 1,
+					 vector_lookup(vline, index));
+		}
+
+		ret = cmd_execute_command_real(shifted_vline, vty, cmd);
+
+		vector_free(shifted_vline);
+		vty->node = onode;
+		return ret;
+	}
+
+	saved_ret = ret = cmd_execute_command_real(vline, vty, cmd);
+
+	if (vtysh)
+		return saved_ret;
+
+	/* Go to parent for config nodes to attempt to find the right command */
+	while (ret != CMD_SUCCESS && ret != CMD_WARNING
+	       && is_config(vty)) {
+		vty_go_parent(vty);
+		ret = cmd_execute_command_real(vline, vty, cmd);
+		tried = 1;
+		if (ret == CMD_SUCCESS || ret == CMD_WARNING) {
+			/* succesfull command, leave the node as is */
+			return ret;
+		}
+	}
+	/* no command succeeded, reset the vty to the original node and
+	   return the error for this node */
+	if (tried) {
+		vty->node = onode;
+		vty->index = oindex;
+	}
+	return saved_ret;
+}
+
+/* Execute command by argument readline. */
+int
+cmd_execute_command_strict(vector vline, struct vty *vty,
+			   struct cmd_element **cmd)
+{
+	unsigned int i;
+	unsigned int index;
+	vector cmd_vector;
+	struct cmd_element *cmd_element;
+	struct cmd_element *matched_element;
+	unsigned int matched_count, incomplete_count;
+	int argc;
+	const char *argv[CMD_ARGC_MAX];
+	int varflag;
+	enum match_type match = 0;
+	char *command;
+
+	/* Make copy of command element */
+	cmd_vector = vector_copy(cmd_node_vector(cmdvec, vty->node));
+
+	for (index = 0; index < vector_active(vline); index++)
+		if ((command = vector_slot(vline, index))) {
+			int ret;
+
+			match = cmd_filter_by_string(vector_slot(vline, index),
+						     cmd_vector, index);
+
+			/* If command meets '.VARARG' then finish matching. */
+			if (match == vararg_match)
+				break;
+
+			ret =
+			    is_cmd_ambiguous(command, cmd_vector, index, match);
+			if (ret == 1) {
+				vector_free(cmd_vector);
+				return CMD_ERR_AMBIGUOUS;
+			}
+			if (ret == 2) {
+				vector_free(cmd_vector);
+				return CMD_ERR_NO_MATCH;
+			}
+		}
+
+	/* Check matched count. */
+	matched_element = NULL;
+	matched_count = 0;
+	incomplete_count = 0;
+	for (i = 0; i < vector_active(cmd_vector); i++)
+		if (vector_slot(cmd_vector, i) != NULL) {
+			cmd_element = vector_slot(cmd_vector, i);
+
+			if (match == vararg_match
+			    || index >= cmd_element->cmdsize) {
+				matched_element = cmd_element;
+				matched_count++;
+			} else
+				incomplete_count++;
+		}
+
+	/* Finish of using cmd_vector. */
+	vector_free(cmd_vector);
+
+	/* To execute command, matched_count must be 1. */
+	if (matched_count == 0) {
+		if (incomplete_count)
+			return CMD_ERR_INCOMPLETE;
+		else
+			return CMD_ERR_NO_MATCH;
+	}
+
+	if (matched_count > 1)
+		return CMD_ERR_AMBIGUOUS;
+
+	/* Argument treatment */
+	varflag = 0;
+	argc = 0;
+
+	for (i = 0; i < vector_active(vline); i++) {
+		if (varflag)
+			argv[argc++] = vector_slot(vline, i);
+		else {
+			vector descvec =
+			    vector_slot(matched_element->strvec, i);
+
+			if (vector_active(descvec) == 1) {
+				struct desc *desc = vector_slot(descvec, 0);
+
+				if (CMD_VARARG(desc->cmd))
+					varflag = 1;
+
+				if (varflag || CMD_VARIABLE(desc->cmd)
+				    || CMD_OPTION(desc->cmd))
+					argv[argc++] = vector_slot(vline, i);
+			} else
+				argv[argc++] = vector_slot(vline, i);
+		}
+
+		if (argc >= CMD_ARGC_MAX)
+			return CMD_ERR_EXEED_ARGC_MAX;
+	}
+
+	/* For vtysh execution. */
+	if (cmd)
+		*cmd = matched_element;
+
+	if (matched_element->daemon)
+		return CMD_SUCCESS_DAEMON;
+
+	/* Now execute matched command */
+	return (*matched_element->func) (matched_element, vty, argc, argv);
+}
+
+/* Configration make from file. */
+int config_from_file(struct vty *vty, FILE * fp)
+{
+	int ret;
+	vector vline;
+
+	while (fgets(vty->buf, VTY_BUFSIZ, fp)) {
+		vline = cmd_make_strvec(vty->buf);
+
+		/* In case of comment line */
+		if (vline == NULL)
+			continue;
+		/* Execute configuration command : this is strict match */
+		ret = cmd_execute_command_strict(vline, vty, NULL);
+
+		/* Try again with setting node to CONFIG_NODE */
+		while (ret != CMD_SUCCESS && ret != CMD_WARNING
+		       && ret != CMD_ERR_NOTHING_TODO
+		       && vty->node != CONFIG_NODE && is_config(vty)) {
+			vty_go_parent(vty);
+			ret = cmd_execute_command_strict(vline, vty, NULL);
+		}
+
+		cmd_free_strvec(vline);
+
+		if (ret != CMD_SUCCESS && ret != CMD_WARNING
+		    && ret != CMD_ERR_NOTHING_TODO)
+			return ret;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Configration from terminal */
+DEFUN(config_terminal,
+      config_terminal_cmd,
+      "configure terminal",
+      "Configuration from vty interface\n" "Configuration terminal\n")
+{
+	if (vty_config_lock(vty))
+		vty->node = CONFIG_NODE;
+	else {
+		vty_out(vty, "VTY configuration is locked by other VTY%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Enable command */
+DEFUN(enable, config_enable_cmd, "enable", "Turn on privileged mode command\n")
+{
+	/* If enable password is NULL, change to ENABLE_NODE */
+	if ((host.enable == NULL && host.enable_encrypt == NULL) ||
+	    vty->type == VTY_SHELL_SERV)
+		vty->node = ENABLE_NODE;
+	else
+		vty->node = AUTH_ENABLE_NODE;
+
+	return CMD_SUCCESS;
+}
+
+/* Disable command */
+DEFUN(disable,
+      config_disable_cmd, "disable", "Turn off privileged mode command\n")
+{
+	if (vty->node == ENABLE_NODE)
+		vty->node = VIEW_NODE;
+	return CMD_SUCCESS;
+}
+
+/* Down vty node level. */
+gDEFUN(config_exit,
+      config_exit_cmd, "exit", "Exit current mode and down to previous mode\n")
+{
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		if (0)		//vty_shell (vty))
+			exit(0);
+		else
+			vty->status = VTY_CLOSE;
+		break;
+	case CONFIG_NODE:
+		vty->node = ENABLE_NODE;
+		vty_config_unlock(vty);
+		break;
+	case VTY_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	case CFG_LOG_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+/* End of configuration. */
+    gDEFUN(config_end,
+      config_end_cmd, "end", "End current mode and change to enable mode.")
+{
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CFG_LOG_NODE:
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	default:
+		break;
+	}
+	return CMD_SUCCESS;
+}
+
+/* Show version. */
+DEFUN(show_version,
+      show_version_cmd, "show version", SHOW_STR "Displays program version\n")
+{
+	vty_out(vty, "%s %s (%s).%s", host.app_info->name,
+		host.app_info->version,
+		host.app_info->name ? host.app_info->name : "", VTY_NEWLINE);
+	vty_out(vty, "%s%s", host.app_info->copyright, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+gDEFUN(config_help,
+      config_help_cmd, "help", "Description of the interactive help system\n")
+{
+	vty_out(vty,
+		"This VTY provides advanced help features.  When you need help,%s\
+anytime at the command line please press '?'.%s\
+%s\
+If nothing matches, the help list will be empty and you must backup%s\
+ until entering a '?' shows the available options.%s\
+Two styles of help are provided:%s\
+1. Full help is available when you are ready to enter a%s\
+command argument (e.g. 'show ?') and describes each possible%s\
+argument.%s\
+2. Partial help is provided when an abbreviated argument is entered%s\
+   and you want to know what arguments match the input%s\
+   (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
+		VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+gDEFUN(config_list, config_list_cmd, "list", "Print command list\n")
+{
+	unsigned int i;
+	struct cmd_node *cnode = vector_slot(cmdvec, vty->node);
+	struct cmd_element *cmd;
+
+	for (i = 0; i < vector_active(cnode->cmd_vector); i++)
+		if ((cmd = vector_slot(cnode->cmd_vector, i)) != NULL
+		    && !(cmd->attr == CMD_ATTR_DEPRECATED
+			 || cmd->attr == CMD_ATTR_HIDDEN))
+			vty_out(vty, "  %s%s", cmd->string, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+static int write_config_file(const char *config_file, char **outpath)
+{
+	unsigned int i;
+	int fd;
+	struct cmd_node *node;
+	char *config_file_tmp = NULL;
+	char *config_file_sav = NULL;
+	struct vty *file_vty;
+	struct stat st;
+
+	*outpath = NULL;
+
+	/* Check and see if we are operating under vtysh configuration */
+	config_file_sav =
+	    _talloc_zero(tall_vty_cmd_ctx,
+			 strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1,
+			 "config_file_sav");
+	strcpy(config_file_sav, config_file);
+	strcat(config_file_sav, CONF_BACKUP_EXT);
+
+	config_file_tmp = _talloc_zero(tall_vty_cmd_ctx, strlen(config_file) + 8,
+					"config_file_tmp");
+	sprintf(config_file_tmp, "%s.XXXXXX", config_file);
+
+	/* Open file to configuration write. */
+	fd = mkstemp(config_file_tmp);
+	if (fd < 0) {
+		*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file_tmp);
+		talloc_free(config_file_tmp);
+		talloc_free(config_file_sav);
+		return -1;
+	}
+
+	/* Make vty for configuration file. */
+	file_vty = vty_new();
+	file_vty->fd = fd;
+	file_vty->type = VTY_FILE;
+
+	/* Config file header print. */
+	vty_out(file_vty, "!\n! %s (%s) configuration saved from vty\n!",
+		host.app_info->name, host.app_info->version);
+	//vty_time_print (file_vty, 1);
+	vty_out(file_vty, "!\n");
+
+	for (i = 0; i < vector_active(cmdvec); i++)
+		if ((node = vector_slot(cmdvec, i)) && node->func) {
+			if ((*node->func) (file_vty))
+				vty_out(file_vty, "!\n");
+		}
+	vty_close(file_vty);
+
+	if (unlink(config_file_sav) != 0)
+		if (errno != ENOENT) {
+			*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file_sav);
+			talloc_free(config_file_sav);
+			talloc_free(config_file_tmp);
+			unlink(config_file_tmp);
+			return -2;
+		}
+
+	/* Only link the .sav file if the original file exists */
+	if (stat(config_file, &st) == 0) {
+		if (link(config_file, config_file_sav) != 0) {
+			*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file_sav);
+			talloc_free(config_file_sav);
+			talloc_free(config_file_tmp);
+			unlink(config_file_tmp);
+			return -3;
+		}
+		sync();
+		if (unlink(config_file) != 0) {
+			*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file);
+			talloc_free(config_file_sav);
+			talloc_free(config_file_tmp);
+			unlink(config_file_tmp);
+			return -4;
+		}
+	}
+	if (link(config_file_tmp, config_file) != 0) {
+		*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file);
+		talloc_free(config_file_sav);
+		talloc_free(config_file_tmp);
+		unlink(config_file_tmp);
+		return -5;
+	}
+	unlink(config_file_tmp);
+	sync();
+
+	talloc_free(config_file_sav);
+	talloc_free(config_file_tmp);
+
+	if (chmod(config_file, 0666 & ~CONFIGFILE_MASK) != 0) {
+		*outpath = talloc_strdup(tall_vty_cmd_ctx, config_file);
+		return -6;
+	}
+
+	return 0;
+}
+
+
+/* Write current configuration into file. */
+DEFUN(config_write_file,
+      config_write_file_cmd,
+      "write file",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write to configuration file\n")
+{
+	char *failed_file;
+	int rc;
+
+	if (host.config == NULL) {
+		vty_out(vty, "Can't save to configuration file, using vtysh.%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = write_config_file(host.config, &failed_file);
+	switch (rc) {
+	case -1:
+		vty_out(vty, "Can't open configuration file %s.%s",
+			failed_file, VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	case -2:
+		vty_out(vty, "Can't unlink backup configuration file %s.%s",
+			failed_file, VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	case -3:
+		vty_out(vty, "Can't backup old configuration file %s.%s",
+			failed_file, VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	case -4:
+		vty_out(vty, "Can't unlink configuration file %s.%s",
+			failed_file, VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	case -5:
+		vty_out(vty, "Can't save configuration file %s.%s", failed_file,
+			VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	case -6:
+		vty_out(vty, "Can't chmod configuration file %s: %s (%d).%s",
+			failed_file, strerror(errno), errno, VTY_NEWLINE);
+		rc = CMD_WARNING;
+		break;
+	default:
+		vty_out(vty, "Configuration saved to %s%s", host.config, VTY_NEWLINE);
+		rc = CMD_SUCCESS;
+		break;
+	}
+
+	talloc_free(failed_file);
+	return rc;
+}
+
+ALIAS(config_write_file,
+      config_write_cmd,
+      "write", "Write running configuration to memory, network, or terminal\n")
+
+    ALIAS(config_write_file,
+      config_write_memory_cmd,
+      "write memory",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write configuration to the file (same as write file)\n")
+
+    ALIAS(config_write_file,
+      copy_runningconfig_startupconfig_cmd,
+      "copy running-config startup-config",
+      "Copy configuration\n"
+      "Copy running config to... \n"
+      "Copy running config to startup config (same as write file)\n")
+
+/* Write current configuration into the terminal. */
+    DEFUN(config_write_terminal,
+      config_write_terminal_cmd,
+      "write terminal",
+      "Write running configuration to memory, network, or terminal\n"
+      "Write to terminal\n")
+{
+	unsigned int i;
+	struct cmd_node *node;
+
+	if (vty->type == VTY_SHELL_SERV) {
+		for (i = 0; i < vector_active(cmdvec); i++)
+			if ((node = vector_slot(cmdvec, i)) && node->func
+			    && node->vtysh) {
+				if ((*node->func) (vty))
+					vty_out(vty, "!%s", VTY_NEWLINE);
+			}
+	} else {
+		vty_out(vty, "%sCurrent configuration:%s", VTY_NEWLINE,
+			VTY_NEWLINE);
+		vty_out(vty, "!%s", VTY_NEWLINE);
+
+		for (i = 0; i < vector_active(cmdvec); i++)
+			if ((node = vector_slot(cmdvec, i)) && node->func) {
+				if ((*node->func) (vty))
+					vty_out(vty, "!%s", VTY_NEWLINE);
+			}
+		vty_out(vty, "end%s", VTY_NEWLINE);
+	}
+	return CMD_SUCCESS;
+}
+
+/* Write current configuration into the terminal. */
+ALIAS(config_write_terminal,
+      show_running_config_cmd,
+      "show running-config", SHOW_STR "running configuration\n")
+
+/* Write startup configuration into the terminal. */
+    DEFUN(show_startup_config,
+      show_startup_config_cmd,
+      "show startup-config", SHOW_STR "Contentes of startup configuration\n")
+{
+	char buf[BUFSIZ];
+	FILE *confp;
+
+	confp = fopen(host.config, "r");
+	if (confp == NULL) {
+		vty_out(vty, "Can't open configuration file [%s]%s",
+			host.config, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	while (fgets(buf, BUFSIZ, confp)) {
+		char *cp = buf;
+
+		while (*cp != '\r' && *cp != '\n' && *cp != '\0')
+			cp++;
+		*cp = '\0';
+
+		vty_out(vty, "%s%s", buf, VTY_NEWLINE);
+	}
+
+	fclose(confp);
+
+	return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN(config_hostname,
+      hostname_cmd,
+      "hostname WORD",
+      "Set system's network name\n" "This system's network name\n")
+{
+	if (!isalpha((int)*argv[0])) {
+		vty_out(vty, "Please specify string starting with alphabet%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.name)
+		talloc_free(host.name);
+
+	host.name = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_no_hostname,
+      no_hostname_cmd,
+      "no hostname [HOSTNAME]",
+      NO_STR "Reset system's network name\n" "Host name of this router\n")
+{
+	if (host.name)
+		talloc_free(host.name);
+	host.name = NULL;
+	return CMD_SUCCESS;
+}
+
+/* VTY interface password set. */
+DEFUN(config_password, password_cmd,
+      "password (8|) WORD",
+      "Assign the terminal connection password\n"
+      "Specifies a HIDDEN password will follow\n"
+      "dummy string \n" "The HIDDEN line password string\n")
+{
+	/* Argument check. */
+	if (argc == 0) {
+		vty_out(vty, "Please specify password.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (argc == 2) {
+		if (*argv[0] == '8') {
+			if (host.password)
+				talloc_free(host.password);
+			host.password = NULL;
+			if (host.password_encrypt)
+				talloc_free(host.password_encrypt);
+			host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, argv[1]);
+			return CMD_SUCCESS;
+		} else {
+			vty_out(vty, "Unknown encryption type.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	if (!isalnum((int)*argv[0])) {
+		vty_out(vty,
+			"Please specify string starting with alphanumeric%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.password)
+		talloc_free(host.password);
+	host.password = NULL;
+
+#ifdef VTY_CRYPT_PW
+	if (host.encrypt) {
+		if (host.password_encrypt)
+			talloc_free(host.password_encrypt);
+		host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(argv[0]));
+	} else
+#endif
+		host.password = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(config_password, password_text_cmd,
+      "password LINE",
+      "Assign the terminal connection password\n"
+      "The UNENCRYPTED (cleartext) line password\n")
+
+/* VTY enable password set. */
+    DEFUN(config_enable_password, enable_password_cmd,
+      "enable password (8|) WORD",
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n"
+      "Specifies a HIDDEN password will follow\n"
+      "dummy string \n" "The HIDDEN 'enable' password string\n")
+{
+	/* Argument check. */
+	if (argc == 0) {
+		vty_out(vty, "Please specify password.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Crypt type is specified. */
+	if (argc == 2) {
+		if (*argv[0] == '8') {
+			if (host.enable)
+				talloc_free(host.enable);
+			host.enable = NULL;
+
+			if (host.enable_encrypt)
+				talloc_free(host.enable_encrypt);
+			host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, argv[1]);
+
+			return CMD_SUCCESS;
+		} else {
+			vty_out(vty, "Unknown encryption type.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	if (!isalnum((int)*argv[0])) {
+		vty_out(vty,
+			"Please specify string starting with alphanumeric%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (host.enable)
+		talloc_free(host.enable);
+	host.enable = NULL;
+
+	/* Plain password input. */
+#ifdef VTY_CRYPT_PW
+	if (host.encrypt) {
+		if (host.enable_encrypt)
+			talloc_free(host.enable_encrypt);
+		host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(argv[0]));
+	} else
+#endif
+		host.enable = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(config_enable_password,
+      enable_password_text_cmd,
+      "enable password LINE",
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n"
+      "The UNENCRYPTED (cleartext) 'enable' password\n")
+
+/* VTY enable password delete. */
+    DEFUN(no_config_enable_password, no_enable_password_cmd,
+      "no enable password",
+      NO_STR
+      "Modify enable password parameters\n"
+      "Assign the privileged level password\n")
+{
+	if (host.enable)
+		talloc_free(host.enable);
+	host.enable = NULL;
+
+	if (host.enable_encrypt)
+		talloc_free(host.enable_encrypt);
+	host.enable_encrypt = NULL;
+
+	return CMD_SUCCESS;
+}
+
+#ifdef VTY_CRYPT_PW
+DEFUN(service_password_encrypt,
+      service_password_encrypt_cmd,
+      "service password-encryption",
+      "Set up miscellaneous service\n" "Enable encrypted passwords\n")
+{
+	if (host.encrypt)
+		return CMD_SUCCESS;
+
+	host.encrypt = 1;
+
+	if (host.password) {
+		if (host.password_encrypt)
+			talloc_free(host.password_encrypt);
+		host.password_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(host.password));
+	}
+	if (host.enable) {
+		if (host.enable_encrypt)
+			talloc_free(host.enable_encrypt);
+		host.enable_encrypt = talloc_strdup(tall_vty_cmd_ctx, zencrypt(host.enable));
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_password_encrypt,
+      no_service_password_encrypt_cmd,
+      "no service password-encryption",
+      NO_STR "Set up miscellaneous service\n" "Enable encrypted passwords\n")
+{
+	if (!host.encrypt)
+		return CMD_SUCCESS;
+
+	host.encrypt = 0;
+
+	if (host.password_encrypt)
+		talloc_free(host.password_encrypt);
+	host.password_encrypt = NULL;
+
+	if (host.enable_encrypt)
+		talloc_free(host.enable_encrypt);
+	host.enable_encrypt = NULL;
+
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(config_terminal_length, config_terminal_length_cmd,
+      "terminal length <0-512>",
+      "Set terminal line parameters\n"
+      "Set number of lines on a screen\n"
+      "Number of lines on screen (0 for no pausing)\n")
+{
+	int lines;
+	char *endptr = NULL;
+
+	lines = strtol(argv[0], &endptr, 10);
+	if (lines < 0 || lines > 512 || *endptr != '\0') {
+		vty_out(vty, "length is malformed%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	vty->lines = lines;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_terminal_no_length, config_terminal_no_length_cmd,
+      "terminal no length",
+      "Set terminal line parameters\n"
+      NO_STR "Set number of lines on a screen\n")
+{
+	vty->lines = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(service_terminal_length, service_terminal_length_cmd,
+      "service terminal-length <0-512>",
+      "Set up miscellaneous service\n"
+      "System wide terminal length configuration\n"
+      "Number of lines of VTY (0 means no line control)\n")
+{
+	int lines;
+	char *endptr = NULL;
+
+	lines = strtol(argv[0], &endptr, 10);
+	if (lines < 0 || lines > 512 || *endptr != '\0') {
+		vty_out(vty, "length is malformed%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	host.lines = lines;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_terminal_length, no_service_terminal_length_cmd,
+      "no service terminal-length [<0-512>]",
+      NO_STR
+      "Set up miscellaneous service\n"
+      "System wide terminal length configuration\n"
+      "Number of lines of VTY (0 means no line control)\n")
+{
+	host.lines = -1;
+	return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN(do_echo,
+	     echo_cmd,
+	     "echo .MESSAGE",
+	     "Echo a message back to the vty\n" "The message to echo\n")
+{
+	char *message;
+
+	vty_out(vty, "%s%s",
+		((message =
+		  argv_concat(argv, argc, 0)) ? message : ""), VTY_NEWLINE);
+	if (message)
+		talloc_free(message);
+	return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(config_logmsg,
+      config_logmsg_cmd,
+      "logmsg " LOG_LEVELS " .MESSAGE",
+      "Send a message to enabled logging destinations\n"
+      LOG_LEVEL_DESC "The message to send\n")
+{
+	int level;
+	char *message;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+
+	zlog(NULL, level,
+	     ((message = argv_concat(argv, argc, 1)) ? message : ""));
+	if (message)
+		talloc_free(message);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_logging,
+      show_logging_cmd,
+      "show logging", SHOW_STR "Show current logging configuration\n")
+{
+	struct zlog *zl = zlog_default;
+
+	vty_out(vty, "Syslog logging: ");
+	if (zl->maxlvl[ZLOG_DEST_SYSLOG] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s, facility %s, ident %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_SYSLOG]],
+			facility_name(zl->facility), zl->ident);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Stdout logging: ");
+	if (zl->maxlvl[ZLOG_DEST_STDOUT] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_STDOUT]]);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Monitor logging: ");
+	if (zl->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_MONITOR]]);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "File logging: ");
+	if ((zl->maxlvl[ZLOG_DEST_FILE] == ZLOG_DISABLED) || !zl->fp)
+		vty_out(vty, "disabled");
+	else
+		vty_out(vty, "level %s, filename %s",
+			zlog_priority[zl->maxlvl[ZLOG_DEST_FILE]],
+			zl->filename);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	vty_out(vty, "Protocol name: %s%s",
+		zlog_proto_names[zl->protocol], VTY_NEWLINE);
+	vty_out(vty, "Record priority: %s%s",
+		(zl->record_priority ? "enabled" : "disabled"), VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_stdout,
+      config_log_stdout_cmd,
+      "log stdout", "Logging control\n" "Set stdout logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_stdout_level,
+      config_log_stdout_level_cmd,
+      "log stdout " LOG_LEVELS,
+      "Logging control\n" "Set stdout logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_stdout,
+      no_config_log_stdout_cmd,
+      "no log stdout [LEVEL]",
+      NO_STR "Logging control\n" "Cancel logging to stdout\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_STDOUT, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_monitor,
+      config_log_monitor_cmd,
+      "log monitor",
+      "Logging control\n" "Set terminal line (monitor) logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_monitor_level,
+      config_log_monitor_level_cmd,
+      "log monitor " LOG_LEVELS,
+      "Logging control\n"
+      "Set terminal line (monitor) logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_monitor,
+      no_config_log_monitor_cmd,
+      "no log monitor [LEVEL]",
+      NO_STR
+      "Logging control\n"
+      "Disable terminal line (monitor) logging\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+static int set_log_file(struct vty *vty, const char *fname, int loglevel)
+{
+	int ret;
+	char *p = NULL;
+	const char *fullpath;
+
+	/* Path detection. */
+	if (!IS_DIRECTORY_SEP(*fname)) {
+		char cwd[MAXPATHLEN + 1];
+		cwd[MAXPATHLEN] = '\0';
+
+		if (getcwd(cwd, MAXPATHLEN) == NULL) {
+			zlog_err("config_log_file: Unable to alloc mem!");
+			return CMD_WARNING;
+		}
+
+		if ((p = _talloc_zero(tall_vcmd_ctx,
+				      strlen(cwd) + strlen(fname) + 2),
+				      "set_log_file")
+		    == NULL) {
+			zlog_err("config_log_file: Unable to alloc mem!");
+			return CMD_WARNING;
+		}
+		sprintf(p, "%s/%s", cwd, fname);
+		fullpath = p;
+	} else
+		fullpath = fname;
+
+	ret = zlog_set_file(NULL, fullpath, loglevel);
+
+	if (p)
+		talloc_free(p);
+
+	if (!ret) {
+		vty_out(vty, "can't open logfile %s\n", fname);
+		return CMD_WARNING;
+	}
+
+	if (host.logfile)
+		talloc_free(host.logfile);
+
+	host.logfile = talloc_strdup(tall_vty_cmd_ctx, fname);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_file,
+      config_log_file_cmd,
+      "log file FILENAME",
+      "Logging control\n" "Logging to file\n" "Logging filename\n")
+{
+	return set_log_file(vty, argv[0], zlog_default->default_lvl);
+}
+
+DEFUN(config_log_file_level,
+      config_log_file_level_cmd,
+      "log file FILENAME " LOG_LEVELS,
+      "Logging control\n"
+      "Logging to file\n" "Logging filename\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[1])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	return set_log_file(vty, argv[0], level);
+}
+
+DEFUN(no_config_log_file,
+      no_config_log_file_cmd,
+      "no log file [FILENAME]",
+      NO_STR
+      "Logging control\n" "Cancel logging to file\n" "Logging file name\n")
+{
+	zlog_reset_file(NULL);
+
+	if (host.logfile)
+		talloc_free(host.logfile);
+
+	host.logfile = NULL;
+
+	return CMD_SUCCESS;
+}
+
+ALIAS(no_config_log_file,
+      no_config_log_file_level_cmd,
+      "no log file FILENAME LEVEL",
+      NO_STR
+      "Logging control\n"
+      "Cancel logging to file\n" "Logging file name\n" "Logging level\n")
+
+    DEFUN(config_log_syslog,
+      config_log_syslog_cmd,
+      "log syslog", "Logging control\n" "Set syslog logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl);
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_syslog_level,
+      config_log_syslog_level_cmd,
+      "log syslog " LOG_LEVELS,
+      "Logging control\n" "Set syslog logging level\n" LOG_LEVEL_DESC)
+{
+	int level;
+
+	if ((level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, level);
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(config_log_syslog_facility,
+		 config_log_syslog_facility_cmd,
+		 "log syslog facility " LOG_FACILITIES,
+		 "Logging control\n"
+		 "Logging goes to syslog\n"
+		 "(Deprecated) Facility parameter for syslog messages\n"
+		 LOG_FACILITY_DESC)
+{
+	int facility;
+
+	if ((facility = facility_match(argv[0])) < 0)
+		return CMD_ERR_NO_MATCH;
+
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, zlog_default->default_lvl);
+	zlog_default->facility = facility;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_syslog,
+      no_config_log_syslog_cmd,
+      "no log syslog [LEVEL]",
+      NO_STR "Logging control\n" "Cancel logging to syslog\n" "Logging level\n")
+{
+	zlog_set_level(NULL, ZLOG_DEST_SYSLOG, ZLOG_DISABLED);
+	return CMD_SUCCESS;
+}
+
+ALIAS(no_config_log_syslog,
+      no_config_log_syslog_facility_cmd,
+      "no log syslog facility " LOG_FACILITIES,
+      NO_STR
+      "Logging control\n"
+      "Logging goes to syslog\n"
+      "Facility parameter for syslog messages\n" LOG_FACILITY_DESC)
+
+    DEFUN(config_log_facility,
+      config_log_facility_cmd,
+      "log facility " LOG_FACILITIES,
+      "Logging control\n"
+      "Facility parameter for syslog messages\n" LOG_FACILITY_DESC)
+{
+	int facility;
+
+	if ((facility = facility_match(argv[0])) < 0)
+		return CMD_ERR_NO_MATCH;
+	zlog_default->facility = facility;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_facility,
+      no_config_log_facility_cmd,
+      "no log facility [FACILITY]",
+      NO_STR
+      "Logging control\n"
+      "Reset syslog facility to default (daemon)\n" "Syslog facility\n")
+{
+	zlog_default->facility = LOG_DAEMON;
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(config_log_trap,
+		 config_log_trap_cmd,
+		 "log trap " LOG_LEVELS,
+		 "Logging control\n"
+		 "(Deprecated) Set logging level and default for all destinations\n"
+		 LOG_LEVEL_DESC)
+{
+	int new_level;
+	int i;
+
+	if ((new_level = level_match(argv[0])) == ZLOG_DISABLED)
+		return CMD_ERR_NO_MATCH;
+
+	zlog_default->default_lvl = new_level;
+	for (i = 0; i < ZLOG_NUM_DESTS; i++)
+		if (zlog_default->maxlvl[i] != ZLOG_DISABLED)
+			zlog_default->maxlvl[i] = new_level;
+	return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(no_config_log_trap,
+		 no_config_log_trap_cmd,
+		 "no log trap [LEVEL]",
+		 NO_STR
+		 "Logging control\n"
+		 "Permit all logging information\n" "Logging level\n")
+{
+	zlog_default->default_lvl = LOG_DEBUG;
+	return CMD_SUCCESS;
+}
+
+DEFUN(config_log_record_priority,
+      config_log_record_priority_cmd,
+      "log record-priority",
+      "Logging control\n"
+      "Log the priority of the message within the message\n")
+{
+	zlog_default->record_priority = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_config_log_record_priority,
+      no_config_log_record_priority_cmd,
+      "no log record-priority",
+      NO_STR
+      "Logging control\n"
+      "Do not log the priority of the message within the message\n")
+{
+	zlog_default->record_priority = 0;
+	return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(banner_motd_file,
+      banner_motd_file_cmd,
+      "banner motd file [FILE]",
+      "Set banner\n" "Banner for motd\n" "Banner from a file\n" "Filename\n")
+{
+	if (host.motdfile)
+		talloc_free(host.motdfile);
+	host.motdfile = talloc_strdup(tall_vty_cmd_ctx, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(banner_motd_default,
+      banner_motd_default_cmd,
+      "banner motd default",
+      "Set banner string\n" "Strings for motd\n" "Default string\n")
+{
+	host.motd = default_motd;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_banner_motd,
+      no_banner_motd_cmd,
+      "no banner motd", NO_STR "Set banner string\n" "Strings for motd\n")
+{
+	host.motd = NULL;
+	if (host.motdfile)
+		talloc_free(host.motdfile);
+	host.motdfile = NULL;
+	return CMD_SUCCESS;
+}
+
+/* Set config filename.  Called from vty.c */
+void host_config_set(const char *filename)
+{
+	host.config = talloc_strdup(tall_vty_cmd_ctx, filename);
+}
+
+void install_default(enum node_type node)
+{
+	install_element(node, &config_help_cmd);
+	install_element(node, &config_list_cmd);
+
+	install_element(node, &config_write_terminal_cmd);
+	install_element(node, &config_write_file_cmd);
+	install_element(node, &config_write_memory_cmd);
+	install_element(node, &config_write_cmd);
+	install_element(node, &show_running_config_cmd);
+}
+
+/**
+ * \brief Write the current running config to a given file
+ * \param[in] vty the vty of the code
+ * \param[in] filename where to store the file
+ * \return 0 in case of success.
+ *
+ * If the filename already exists create a filename.sav
+ * version with the current code.
+ *
+ */
+int osmo_vty_write_config_file(const char *filename)
+{
+	char *failed_file;
+	int rc;
+
+	rc = write_config_file(filename, &failed_file);
+	talloc_free(failed_file);
+	return rc;
+}
+
+/**
+ * \brief Save the current state to the config file
+ * \return 0 in case of success.
+ *
+ * If the filename already exists create a filename.sav
+ * version with the current code.
+ *
+ */
+int osmo_vty_save_config_file(void)
+{
+	char *failed_file;
+	int rc;
+
+	if (host.config == NULL)
+		return -7;
+
+	rc = write_config_file(host.config, &failed_file);
+	talloc_free(failed_file);
+	return rc;
+}
+
+/* Initialize command interface. Install basic nodes and commands. */
+void cmd_init(int terminal)
+{
+	/* Allocate initial top vector of commands. */
+	cmdvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Default host value settings. */
+	host.name = NULL;
+	host.password = NULL;
+	host.enable = NULL;
+	host.logfile = NULL;
+	host.config = NULL;
+	host.lines = -1;
+	host.motd = default_motd;
+	host.motdfile = NULL;
+
+	/* Install top nodes. */
+	install_node(&view_node, NULL);
+	install_node(&enable_node, NULL);
+	install_node(&auth_node, NULL);
+	install_node(&auth_enable_node, NULL);
+	install_node(&config_node, config_write_host);
+
+	/* Each node's basic commands. */
+	install_element(VIEW_NODE, &show_version_cmd);
+	if (terminal) {
+		install_element(VIEW_NODE, &config_list_cmd);
+		install_element(VIEW_NODE, &config_exit_cmd);
+		install_element(VIEW_NODE, &config_help_cmd);
+		install_element(VIEW_NODE, &config_enable_cmd);
+		install_element(VIEW_NODE, &config_terminal_length_cmd);
+		install_element(VIEW_NODE, &config_terminal_no_length_cmd);
+		install_element(VIEW_NODE, &echo_cmd);
+	}
+
+	if (terminal) {
+		install_element(ENABLE_NODE, &config_exit_cmd);
+		install_default(ENABLE_NODE);
+		install_element(ENABLE_NODE, &config_disable_cmd);
+		install_element(ENABLE_NODE, &config_terminal_cmd);
+		install_element (ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
+	}
+	install_element (ENABLE_NODE, &show_startup_config_cmd);
+	install_element(ENABLE_NODE, &show_version_cmd);
+
+	if (terminal) {
+		install_element(ENABLE_NODE, &config_terminal_length_cmd);
+		install_element(ENABLE_NODE, &config_terminal_no_length_cmd);
+		install_element(ENABLE_NODE, &echo_cmd);
+
+		install_default(CONFIG_NODE);
+		install_element(CONFIG_NODE, &config_exit_cmd);
+	}
+
+	install_element(CONFIG_NODE, &hostname_cmd);
+	install_element(CONFIG_NODE, &no_hostname_cmd);
+
+	if (terminal) {
+		install_element(CONFIG_NODE, &password_cmd);
+		install_element(CONFIG_NODE, &password_text_cmd);
+		install_element(CONFIG_NODE, &enable_password_cmd);
+		install_element(CONFIG_NODE, &enable_password_text_cmd);
+		install_element(CONFIG_NODE, &no_enable_password_cmd);
+
+#ifdef VTY_CRYPT_PW
+		install_element(CONFIG_NODE, &service_password_encrypt_cmd);
+		install_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
+#endif
+		install_element(CONFIG_NODE, &banner_motd_default_cmd);
+		install_element(CONFIG_NODE, &banner_motd_file_cmd);
+		install_element(CONFIG_NODE, &no_banner_motd_cmd);
+		install_element(CONFIG_NODE, &service_terminal_length_cmd);
+		install_element(CONFIG_NODE, &no_service_terminal_length_cmd);
+
+	}
+	srand(time(NULL));
+}
+
+/*! @} */
diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c
new file mode 100644
index 0000000..6be30b4
--- /dev/null
+++ b/src/vty/logging_vty.c
@@ -0,0 +1,604 @@
+/* OpenBSC logging helper for the VTY */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../config.h"
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+//#include <openbsc/vty.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#define LOG_STR "Configure logging sub-system\n"
+
+extern const struct log_info *osmo_log_info;
+
+static void _vty_output(struct log_target *tgt,
+			unsigned int level, const char *line)
+{
+	struct vty *vty = tgt->tgt_vty.vty;
+	vty_out(vty, "%s", line);
+	/* This is an ugly hack, but there is no easy way... */
+	if (strchr(line, '\n'))
+		vty_out(vty, "\r");
+}
+
+struct log_target *log_target_create_vty(struct vty *vty)
+{
+	struct log_target *target;
+
+	target = log_target_create();
+	if (!target)
+		return NULL;
+
+	target->tgt_vty.vty = vty;
+	target->output = _vty_output;
+	return target;
+}
+
+DEFUN(enable_logging,
+      enable_logging_cmd,
+      "logging enable",
+	LOGGING_STR
+      "Enables logging to this vty\n")
+{
+	struct telnet_connection *conn;
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (conn->dbg) {
+		vty_out(vty, "Logging already enabled.%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	conn->dbg = log_target_create_vty(vty);
+	if (!conn->dbg)
+		return CMD_WARNING;
+
+	log_add_target(conn->dbg);
+	return CMD_SUCCESS;
+}
+
+struct log_target *osmo_log_vty2tgt(struct vty *vty)
+{
+	struct telnet_connection *conn;
+
+	if (vty->node == CFG_LOG_NODE)
+		return vty->index;
+
+
+	conn = (struct telnet_connection *) vty->priv;
+	if (!conn->dbg)
+		vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+
+	return conn->dbg;
+}
+
+DEFUN(logging_fltr_all,
+      logging_fltr_all_cmd,
+      "logging filter all (0|1)",
+	LOGGING_STR FILTER_STR
+	"Do you want to log all messages?\n"
+	"Only print messages matched by other filters\n"
+	"Bypass filter and print all messages\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_set_all_filter(tgt, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_use_clr,
+      logging_use_clr_cmd,
+      "logging color (0|1)",
+	LOGGING_STR "Configure color-printing for log messages\n"
+      "Don't use color for printing messages\n"
+      "Use color for printing messages\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_set_use_color(tgt, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_prnt_timestamp,
+      logging_prnt_timestamp_cmd,
+      "logging timestamp (0|1)",
+	LOGGING_STR "Configure log message timestamping\n"
+	"Don't prefix each log message\n"
+	"Prefix each log message with current timestamp\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_set_print_timestamp(tgt, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_level,
+      logging_level_cmd,
+      NULL, /* cmdstr is dynamically set in logging_vty_add_cmds(). */
+      NULL) /* same thing for helpstr. */
+{
+	int category = log_parse_category(argv[0]);
+	int level = log_parse_level(argv[1]);
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	if (level < 0) {
+		vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* Check for special case where we want to set global log level */
+	if (!strcmp(argv[0], "all")) {
+		log_set_log_level(tgt, level);
+		return CMD_SUCCESS;
+	}
+
+	if (category < 0) {
+		vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	tgt->categories[category].enabled = 1;
+	tgt->categories[category].loglevel = level;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(logging_set_category_mask,
+      logging_set_category_mask_cmd,
+      "logging set-log-mask MASK",
+	LOGGING_STR
+      "Set the logmask of this logging target\n"
+      "The logmask to use\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_parse_category_mask(tgt, argv[0]);
+	return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(logging_set_category_mask,
+		 logging_set_category_mask_old_cmd,
+		 "logging set log mask MASK",
+		 LOGGING_STR
+		 "Decide which categories to output.\n"
+		 "Log commands\n" "Mask commands\n" "The logmask to use\n");
+
+
+DEFUN(diable_logging,
+      disable_logging_cmd,
+      "logging disable",
+	LOGGING_STR
+      "Disables logging to this vty\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+	struct telnet_connection *conn = (struct telnet_connection *) vty->priv;
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_del_target(tgt);
+	talloc_free(tgt);
+	conn->dbg = NULL;
+
+	return CMD_SUCCESS;
+}
+
+static void vty_print_logtarget(struct vty *vty, const struct log_info *info,
+				const struct log_target *tgt)
+{
+	unsigned int i;
+
+	vty_out(vty, " Global Loglevel: %s%s",
+		log_level_str(tgt->loglevel), VTY_NEWLINE);
+	vty_out(vty, " Use color: %s, Print Timestamp: %s%s",
+		tgt->use_color ? "On" : "Off",
+		tgt->print_timestamp ? "On" : "Off", VTY_NEWLINE);
+
+	vty_out(vty, " Log Level specific information:%s", VTY_NEWLINE);
+
+	for (i = 0; i < info->num_cat; i++) {
+		const struct log_category *cat = &tgt->categories[i];
+		vty_out(vty, "  %-10s %-10s %-8s %s%s",
+			info->cat[i].name+1, log_level_str(cat->loglevel),
+			cat->enabled ? "Enabled" : "Disabled",
+ 			info->cat[i].description,
+			VTY_NEWLINE);
+	}
+}
+
+#define SHOW_LOG_STR "Show current logging configuration\n"
+
+DEFUN(show_logging_vty,
+      show_logging_vty_cmd,
+      "show logging vty",
+	SHOW_STR SHOW_LOG_STR
+	"Show current logging configuration for this vty\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	vty_print_logtarget(vty, osmo_log_info, tgt);
+
+	return CMD_SUCCESS;
+}
+
+gDEFUN(cfg_description, cfg_description_cmd,
+	"description .TEXT",
+	"Save human-readable decription of the object\n")
+{
+	char **dptr = vty->index_sub;
+
+	if (!dptr) {
+		vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (*dptr)
+		talloc_free(*dptr);
+	*dptr = argv_concat(argv, argc, 0);
+	if (!dptr)
+		return CMD_WARNING;
+
+	return CMD_SUCCESS;
+}
+
+gDEFUN(cfg_no_description, cfg_no_description_cmd,
+	"no description",
+	NO_STR
+	"Remove description of the object\n")
+{
+	char **dptr = vty->index_sub;
+
+	if (!dptr) {
+		vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (*dptr) {
+		talloc_free(*dptr);
+		*dptr = NULL;
+	}
+
+	return CMD_SUCCESS;
+}
+
+/* Support for configuration of log targets != the current vty */
+
+struct cmd_node cfg_log_node = {
+	CFG_LOG_NODE,
+	"%s(config-log)# ",
+	1
+};
+
+#ifdef HAVE_SYSLOG_H
+
+#include <syslog.h>
+
+static const int local_sysl_map[] = {
+	[0] = LOG_LOCAL0,
+	[1] = LOG_LOCAL1,
+	[2] = LOG_LOCAL2,
+	[3] = LOG_LOCAL3,
+	[4] = LOG_LOCAL4,
+	[5] = LOG_LOCAL5,
+	[6] = LOG_LOCAL6,
+	[7] = LOG_LOCAL7
+};
+
+/* From VTY core code */
+extern struct host host;
+
+static int _cfg_log_syslog(struct vty *vty, int facility)
+{
+	struct log_target *tgt;
+
+	/* First delete the old syslog target, if any */
+	tgt = log_target_find(LOG_TGT_TYPE_SYSLOG, NULL);
+	if (tgt)
+		log_target_destroy(tgt);
+
+	tgt = log_target_create_syslog(host.app_info->name, 0, facility);
+	if (!tgt) {
+		vty_out(vty, "%% Unable to open syslog%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	log_add_target(tgt);
+
+	vty->index = tgt;
+	vty->node = CFG_LOG_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_log_syslog_local, cfg_log_syslog_local_cmd,
+      "log syslog local <0-7>",
+	LOG_STR "Logging via syslog\n" "Syslog LOCAL facility\n"
+	"Local facility number\n")
+{
+	int local = atoi(argv[0]);
+	int facility = local_sysl_map[local];
+
+	return _cfg_log_syslog(vty, facility);
+}
+
+static struct value_string sysl_level_names[] = {
+	{ LOG_AUTHPRIV, "authpriv" },
+	{ LOG_CRON, 	"cron" },
+	{ LOG_DAEMON,	"daemon" },
+	{ LOG_FTP,	"ftp" },
+	{ LOG_LPR,	"lpr" },
+	{ LOG_MAIL,	"mail" },
+	{ LOG_NEWS,	"news" },
+	{ LOG_USER,	"user" },
+	{ LOG_UUCP,	"uucp" },
+	/* only for value -> string conversion */
+	{ LOG_LOCAL0,	"local 0" },
+	{ LOG_LOCAL1,	"local 1" },
+	{ LOG_LOCAL2,	"local 2" },
+	{ LOG_LOCAL3,	"local 3" },
+	{ LOG_LOCAL4,	"local 4" },
+	{ LOG_LOCAL5,	"local 5" },
+	{ LOG_LOCAL6,	"local 6" },
+	{ LOG_LOCAL7,	"local 7" },
+	{ 0, NULL }
+};
+
+DEFUN(cfg_log_syslog, cfg_log_syslog_cmd,
+      "log syslog (authpriv|cron|daemon|ftp|lpr|mail|news|user|uucp)",
+	LOG_STR "Logging via syslog\n"
+	"Security/authorization messages facility\n"
+	"Clock daemon (cron/at) facility\n"
+	"General system daemon facility\n"
+	"Ftp daemon facility\n"
+	"Line printer facility\n"
+	"Mail facility\n"
+	"News facility\n"
+	"Generic facility\n"
+	"UUCP facility\n")
+{
+	int facility = get_string_value(sysl_level_names, argv[0]);
+
+	return _cfg_log_syslog(vty, facility);
+}
+
+DEFUN(cfg_no_log_syslog, cfg_no_log_syslog_cmd,
+	"no log syslog",
+	NO_STR LOG_STR "Logging via syslog\n")
+{
+	struct log_target *tgt;
+
+	tgt = log_target_find(LOG_TGT_TYPE_SYSLOG, NULL);
+	if (!tgt) {
+		vty_out(vty, "%% No syslog target found%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_target_destroy(tgt);
+
+	return CMD_SUCCESS;
+}
+#endif /* HAVE_SYSLOG_H */
+
+DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
+	"log stderr",
+	LOG_STR "Logging via STDERR of the process\n")
+{
+	struct log_target *tgt;
+
+	tgt = log_target_find(LOG_TGT_TYPE_STDERR, NULL);
+	if (!tgt) {
+		tgt = log_target_create_stderr();
+		if (!tgt) {
+			vty_out(vty, "%% Unable to create stderr log%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		log_add_target(tgt);
+	}
+
+	vty->index = tgt;
+	vty->node = CFG_LOG_NODE;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd,
+	"no log stderr",
+	NO_STR LOG_STR "Logging via STDERR of the process\n")
+{
+	struct log_target *tgt;
+
+	tgt = log_target_find(LOG_TGT_TYPE_STDERR, NULL);
+	if (!tgt) {
+		vty_out(vty, "%% No stderr logging active%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_target_destroy(tgt);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_log_file, cfg_log_file_cmd,
+	"log file .FILENAME",
+	LOG_STR "Logging to text file\n" "Filename\n")
+{
+	const char *fname = argv[0];
+	struct log_target *tgt;
+
+	tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
+	if (!tgt) {
+		tgt = log_target_create_file(fname);
+		if (!tgt) {
+			vty_out(vty, "%% Unable to create file `%s'%s",
+				fname, VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		log_add_target(tgt);
+	}
+
+	vty->index = tgt;
+	vty->node = CFG_LOG_NODE;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
+	"no log file .FILENAME",
+	NO_STR LOG_STR "Logging to text file\n" "Filename\n")
+{
+	const char *fname = argv[0];
+	struct log_target *tgt;
+
+	tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
+	if (!tgt) {
+		vty_out(vty, "%% No such log file `%s'%s",
+			fname, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	log_target_destroy(tgt);
+
+	return CMD_SUCCESS;
+}
+
+static int config_write_log_single(struct vty *vty, struct log_target *tgt)
+{
+	int i;
+	char level_lower[32];
+
+	switch (tgt->type) {
+	case LOG_TGT_TYPE_VTY:
+		return 1;
+		break;
+	case LOG_TGT_TYPE_STDERR:
+		vty_out(vty, "log stderr%s", VTY_NEWLINE);
+		break;
+	case LOG_TGT_TYPE_SYSLOG:
+#ifdef HAVE_SYSLOG_H
+		vty_out(vty, "log syslog %s%s",
+			get_value_string(sysl_level_names,
+					 tgt->tgt_syslog.facility),
+			VTY_NEWLINE);
+#endif
+		break;
+	case LOG_TGT_TYPE_FILE:
+		vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE);
+		break;
+	}
+
+	vty_out(vty, "  logging color %u%s", tgt->use_color ? 1 : 0,
+		VTY_NEWLINE);
+	vty_out(vty, "  logging timestamp %u%s", tgt->print_timestamp ? 1 : 0,
+		VTY_NEWLINE);
+
+	/* stupid old osmo logging API uses uppercase strings... */
+	osmo_str2lower(level_lower, log_level_str(tgt->loglevel));
+	vty_out(vty, "  logging level all %s%s", level_lower, VTY_NEWLINE);
+
+	for (i = 0; i < osmo_log_info->num_cat; i++) {
+		const struct log_category *cat = &tgt->categories[i];
+		char cat_lower[32];
+
+		/* stupid old osmo logging API uses uppercase strings... */
+		osmo_str2lower(cat_lower, osmo_log_info->cat[i].name+1);
+		osmo_str2lower(level_lower, log_level_str(cat->loglevel));
+
+		vty_out(vty, "  logging level %s %s%s", cat_lower, level_lower,
+			VTY_NEWLINE);
+	}
+
+	/* FIXME: levels */
+
+	return 1;
+}
+
+static int config_write_log(struct vty *vty)
+{
+	struct log_target *dbg = vty->index;
+
+	llist_for_each_entry(dbg, &osmo_log_target_list, entry)
+		config_write_log_single(vty, dbg);
+
+	return 1;
+}
+
+void logging_vty_add_cmds(const struct log_info *cat)
+{
+	install_element_ve(&enable_logging_cmd);
+	install_element_ve(&disable_logging_cmd);
+	install_element_ve(&logging_fltr_all_cmd);
+	install_element_ve(&logging_use_clr_cmd);
+	install_element_ve(&logging_prnt_timestamp_cmd);
+	install_element_ve(&logging_set_category_mask_cmd);
+	install_element_ve(&logging_set_category_mask_old_cmd);
+
+	/* Logging level strings are generated dynamically. */
+	logging_level_cmd.string = log_vty_command_string(cat);
+	logging_level_cmd.doc = log_vty_command_description(cat);
+	install_element_ve(&logging_level_cmd);
+	install_element_ve(&show_logging_vty_cmd);
+
+	install_node(&cfg_log_node, config_write_log);
+	install_element(CFG_LOG_NODE, &logging_fltr_all_cmd);
+	install_element(CFG_LOG_NODE, &logging_use_clr_cmd);
+	install_element(CFG_LOG_NODE, &logging_prnt_timestamp_cmd);
+	install_element(CFG_LOG_NODE, &logging_level_cmd);
+
+	install_element(CONFIG_NODE, &cfg_log_stderr_cmd);
+	install_element(CONFIG_NODE, &cfg_no_log_stderr_cmd);
+	install_element(CONFIG_NODE, &cfg_log_file_cmd);
+	install_element(CONFIG_NODE, &cfg_no_log_file_cmd);
+#ifdef HAVE_SYSLOG_H
+	install_element(CONFIG_NODE, &cfg_log_syslog_cmd);
+	install_element(CONFIG_NODE, &cfg_log_syslog_local_cmd);
+	install_element(CONFIG_NODE, &cfg_no_log_syslog_cmd);
+#endif
+}
diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c
new file mode 100644
index 0000000..1abf141
--- /dev/null
+++ b/src/vty/telnet_interface.c
@@ -0,0 +1,204 @@
+/* minimalistic telnet/network interface it might turn into a wire interface */
+/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+/*! \addtogroup telnet_interface
+ *  @{
+ */
+/*! \file telnet_interface.c */
+
+/* per connection data */
+LLIST_HEAD(active_connections);
+
+static void *tall_telnet_ctx;
+
+/* per network data */
+static int telnet_new_connection(struct osmo_fd *fd, unsigned int what);
+
+static struct osmo_fd server_socket = {
+	.when	    = BSC_FD_READ,
+	.cb	    = telnet_new_connection,
+	.priv_nr    = 0,
+};
+
+/*! \brief Initialize telnet based VTY interface listening to 127.0.0.1
+ *  \param[in] tall_ctx \ref talloc context
+ *  \param[in] priv private data to be passed to callback
+ *  \param[in] port UDP port number
+ */
+int telnet_init(void *tall_ctx, void *priv, int port)
+{
+	return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
+}
+
+/*! \brief Initialize telnet based VTY interface
+ *  \param[in] tall_ctx \ref talloc context
+ *  \param[in] priv private data to be passed to callback
+ *  \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
+ *  \param[in] port UDP port number
+ */
+int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+{
+	int rc;
+
+	tall_telnet_ctx = talloc_named_const(tall_ctx, 1,
+			"telnet_connection");
+
+	rc = osmo_sock_init_ofd(
+			&server_socket,
+			AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+			ip, port, OSMO_SOCK_F_BIND
+			);
+
+	server_socket.data = priv;
+
+	return (rc < 0) ? -1 : 0;
+}
+
+extern struct host host;
+
+/*! \brief close a telnet connection */
+int telnet_close_client(struct osmo_fd *fd)
+{
+	struct telnet_connection *conn = (struct telnet_connection*)fd->data;
+
+	close(fd->fd);
+	osmo_fd_unregister(fd);
+
+	if (conn->dbg) {
+		log_del_target(conn->dbg);
+		talloc_free(conn->dbg);
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+	return 0;
+}
+
+static int client_data(struct osmo_fd *fd, unsigned int what)
+{
+	struct telnet_connection *conn = fd->data;
+	int rc = 0;
+
+	if (what & BSC_FD_READ) {
+		conn->fd.when &= ~BSC_FD_READ;
+		rc = vty_read(conn->vty);
+	}
+
+	/* vty might have been closed from vithin vty_read() */
+	if (!conn->vty)
+		return rc;
+
+	if (what & BSC_FD_WRITE) {
+		rc = buffer_flush_all(conn->vty->obuf, fd->fd);
+		if (rc == BUFFER_EMPTY)
+			conn->fd.when &= ~BSC_FD_WRITE;
+	}
+
+	return rc;
+}
+
+static int telnet_new_connection(struct osmo_fd *fd, unsigned int what)
+{
+	struct telnet_connection *connection;
+	struct sockaddr_in sockaddr;
+	socklen_t len = sizeof(sockaddr);
+	int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
+
+	if (new_connection < 0) {
+		LOGP(0, LOGL_ERROR, "telnet accept failed\n");
+		return new_connection;
+	}
+
+	connection = talloc_zero(tall_telnet_ctx, struct telnet_connection);
+	connection->priv = fd->data;
+	connection->fd.data = connection;
+	connection->fd.fd = new_connection;
+	connection->fd.when = BSC_FD_READ;
+	connection->fd.cb = client_data;
+	osmo_fd_register(&connection->fd);
+	llist_add_tail(&connection->entry, &active_connections);
+
+	connection->vty = vty_create(new_connection, connection);
+	if (!connection->vty) {
+		LOGP(0, LOGL_ERROR, "couldn't create VTY\n");
+		close(new_connection);
+		talloc_free(connection);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*! \brief callback from core VTY code about VTY related events */
+void vty_event(enum event event, int sock, struct vty *vty)
+{
+	struct telnet_connection *connection = vty->priv;
+	struct osmo_fd *bfd = &connection->fd;
+
+	if (vty->type != VTY_TERM)
+		return;
+
+	switch (event) {
+	case VTY_READ:
+		bfd->when |= BSC_FD_READ;
+		break;
+	case VTY_WRITE:
+		bfd->when |= BSC_FD_WRITE;
+		break;
+	case VTY_CLOSED:
+		/* vty layer is about to free() vty */
+		connection->vty = NULL;
+		telnet_close_client(bfd);
+		break;
+	default:
+		break;
+	}
+}
+
+void telnet_exit(void) 
+{
+	struct telnet_connection *tc, *tc2;
+
+	llist_for_each_entry_safe(tc, tc2, &active_connections, entry)
+		telnet_close_client(&tc->fd);
+
+	osmo_fd_unregister(&server_socket);
+	close(server_socket.fd);
+	talloc_free(tall_telnet_ctx);
+}
+
+/*! @} */
diff --git a/src/vty/utils.c b/src/vty/utils.c
new file mode 100644
index 0000000..b3223ed
--- /dev/null
+++ b/src/vty/utils.c
@@ -0,0 +1,63 @@
+/* utility routines for printing common objects in the Osmocom world */
+
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/vty/vty.h>
+
+/* \file utils.c */
+
+/*! \addtogroup rate_ctr
+ *  @{
+ */
+
+/*! \brief print a rate counter group to given VTY
+ *  \param[in] vty The VTY to which it should be printed
+ *  \param[in] prefix Any additional log prefix ahead of each line
+ *  \param[in] ctrg Rate counter group to be printed
+ */
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+			    struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+
+	vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE);
+	for (i = 0; i < ctrg->desc->num_ctr; i++) {
+		struct rate_ctr *ctr = &ctrg->ctr[i];
+		vty_out(vty, " %s%s: %8" PRIu64 " "
+			"(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s",
+			prefix, ctrg->desc->ctr_desc[i].description, ctr->current,
+			ctr->intv[RATE_CTR_INTV_SEC].rate,
+			ctr->intv[RATE_CTR_INTV_MIN].rate,
+			ctr->intv[RATE_CTR_INTV_HOUR].rate,
+			ctr->intv[RATE_CTR_INTV_DAY].rate,
+			VTY_NEWLINE);
+	};
+}
+
+/*! @} */
diff --git a/src/vty/vector.c b/src/vty/vector.c
new file mode 100644
index 0000000..4012f24
--- /dev/null
+++ b/src/vty/vector.c
@@ -0,0 +1,192 @@
+/* Generic vector interface routine
+ * Copyright (C) 1997 Kunihiro Ishiguro
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING.  If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <osmocom/vty/vector.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/core/talloc.h>
+#include <memory.h>
+
+void *tall_vty_vec_ctx;
+
+/* Initialize vector : allocate memory and return vector. */
+vector vector_init(unsigned int size)
+{
+	vector v = talloc_zero(tall_vty_vec_ctx, struct _vector);
+	if (!v)
+		return NULL;
+
+	/* allocate at least one slot */
+	if (size == 0)
+		size = 1;
+
+	v->alloced = size;
+	v->active = 0;
+	v->index = _talloc_zero(tall_vty_vec_ctx, sizeof(void *) * size,
+				"vector_init:index");
+	if (!v->index) {
+		talloc_free(v);
+		return NULL;
+	}
+	return v;
+}
+
+void vector_only_wrapper_free(vector v)
+{
+	talloc_free(v);
+}
+
+void vector_only_index_free(void *index)
+{
+	talloc_free(index);
+}
+
+void vector_free(vector v)
+{
+	talloc_free(v->index);
+	talloc_free(v);
+}
+
+vector vector_copy(vector v)
+{
+	unsigned int size;
+	vector new = talloc_zero(tall_vty_vec_ctx, struct _vector);
+	if (!new)
+		return NULL;
+
+	new->active = v->active;
+	new->alloced = v->alloced;
+
+	size = sizeof(void *) * (v->alloced);
+	new->index = _talloc_zero(tall_vty_vec_ctx, size, "vector_copy:index");
+	if (!new->index) {
+		talloc_free(new);
+		return NULL;
+	}
+	memcpy(new->index, v->index, size);
+
+	return new;
+}
+
+/* Check assigned index, and if it runs short double index pointer */
+void vector_ensure(vector v, unsigned int num)
+{
+	if (v->alloced > num)
+		return;
+
+	v->index = talloc_realloc_size(tall_vty_vec_ctx, v->index,
+				       sizeof(void *) * (v->alloced * 2));
+	memset(&v->index[v->alloced], 0, sizeof(void *) * v->alloced);
+	v->alloced *= 2;
+
+	if (v->alloced <= num)
+		vector_ensure(v, num);
+}
+
+/* This function only returns next empty slot index.  It dose not mean
+   the slot's index memory is assigned, please call vector_ensure()
+   after calling this function. */
+int vector_empty_slot(vector v)
+{
+	unsigned int i;
+
+	if (v->active == 0)
+		return 0;
+
+	for (i = 0; i < v->active; i++)
+		if (v->index[i] == 0)
+			return i;
+
+	return i;
+}
+
+/* Set value to the smallest empty slot. */
+int vector_set(vector v, void *val)
+{
+	unsigned int i;
+
+	i = vector_empty_slot(v);
+	vector_ensure(v, i);
+
+	v->index[i] = val;
+
+	if (v->active <= i)
+		v->active = i + 1;
+
+	return i;
+}
+
+/* Set value to specified index slot. */
+int vector_set_index(vector v, unsigned int i, void *val)
+{
+	vector_ensure(v, i);
+
+	v->index[i] = val;
+
+	if (v->active <= i)
+		v->active = i + 1;
+
+	return i;
+}
+
+/* Look up vector.  */
+void *vector_lookup(vector v, unsigned int i)
+{
+	if (i >= v->active)
+		return NULL;
+	return v->index[i];
+}
+
+/* Lookup vector, ensure it. */
+void *vector_lookup_ensure(vector v, unsigned int i)
+{
+	vector_ensure(v, i);
+	return v->index[i];
+}
+
+/* Unset value at specified index slot. */
+void vector_unset(vector v, unsigned int i)
+{
+	if (i >= v->alloced)
+		return;
+
+	v->index[i] = NULL;
+
+	if (i + 1 == v->active) {
+		v->active--;
+		while (i && v->index[--i] == NULL && v->active--) ;	/* Is this ugly ? */
+	}
+}
+
+/* Count the number of not emplty slot. */
+unsigned int vector_count(vector v)
+{
+	unsigned int i;
+	unsigned count = 0;
+
+	for (i = 0; i < v->active; i++)
+		if (v->index[i] != NULL)
+			count++;
+
+	return count;
+}
diff --git a/src/vty/vty.c b/src/vty/vty.c
new file mode 100644
index 0000000..28c2266
--- /dev/null
+++ b/src/vty/vty.c
@@ -0,0 +1,1782 @@
+
+/*! \mainpage libosmovty Documentation
+ *
+ * \section sec_intro Introduction
+ * This library is a collection of common code used in various
+ * GSM related sub-projects inside the Osmocom family of projects.  It
+ * has been imported/derived from the GNU Zebra project.
+ * \n\n
+ * libosmovty implements the interactive command-line on the VTY
+ * (Virtual TTY) as well as configuration file parsing.
+ * \n\n
+ * Please note that C language projects inside Osmocom are typically
+ * single-threaded event-loop state machine designs.  As such,
+ * routines in libosmovty are not thread-safe.  If you must use them in
+ * a multi-threaded context, you have to add your own locking.
+ *
+ * \section sec_copyright Copyright and License
+ * Copyright © 1997-2007 - Kuninhiro Ishiguro\n
+ * Copyright © 2008-2011 - Harald Welte, Holger Freyther and contributors\n
+ * All rights reserved. \n\n
+ * The source code of libosmovty is licensed under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License, or (at your option) any later
+ * version.\n
+ * See <http://www.gnu.org/licenses/> or COPYING included in the source
+ * code package istelf.\n
+ * The information detailed here is provided AS IS with NO WARRANTY OF
+ * ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.
+ * \n\n
+ *
+ * \section sec_contact Contact and Support
+ * Community-based support is available at the OpenBSC mailing list
+ * <http://lists.osmocom.org/mailman/listinfo/openbsc>\n
+ * Commercial support options available upon request from
+ * <http://sysmocom.de/>
+ */
+
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <termios.h>
+
+#include <sys/utsname.h>
+#include <sys/param.h>
+
+#include <arpa/telnet.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/core/talloc.h>
+
+/* \addtogroup vty
+ * @{
+ */
+/*! \file vty.c */
+
+#define SYSCONFDIR "/usr/local/etc"
+
+/* our callback, located in telnet_interface.c */
+void vty_event(enum event event, int sock, struct vty *vty);
+
+extern struct host host;
+
+/* Vector which store each vty structure. */
+static vector vtyvec;
+
+vector Vvty_serv_thread;
+
+char *vty_cwd = NULL;
+
+/* Configure lock. */
+static int vty_config;
+
+static int no_password_check = 1;
+
+void *tall_vty_ctx;
+
+static void vty_clear_buf(struct vty *vty)
+{
+	memset(vty->buf, 0, vty->max);
+}
+
+/*! \brief Allocate a new vty interface structure */
+struct vty *vty_new(void)
+{
+	struct vty *new = talloc_zero(tall_vty_ctx, struct vty);
+
+	if (!new)
+		goto out;
+
+	new->obuf = buffer_new(new, 0);	/* Use default buffer size. */
+	if (!new->obuf)
+		goto out_new;
+	new->buf = _talloc_zero(new, VTY_BUFSIZ, "vty_new->buf");
+	if (!new->buf)
+		goto out_obuf;
+
+	new->max = VTY_BUFSIZ;
+
+	return new;
+
+out_obuf:
+	buffer_free(new->obuf);
+out_new:
+	talloc_free(new);
+	new = NULL;
+out:
+	return new;
+}
+
+/* Authentication of vty */
+static void vty_auth(struct vty *vty, char *buf)
+{
+	char *passwd = NULL;
+	enum node_type next_node = 0;
+	int fail;
+	char *crypt(const char *, const char *);
+
+	switch (vty->node) {
+	case AUTH_NODE:
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			passwd = host.password_encrypt;
+		else
+#endif
+			passwd = host.password;
+		if (host.advanced)
+			next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
+		else
+			next_node = VIEW_NODE;
+		break;
+	case AUTH_ENABLE_NODE:
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			passwd = host.enable_encrypt;
+		else
+#endif
+			passwd = host.enable;
+		next_node = ENABLE_NODE;
+		break;
+	}
+
+	if (passwd) {
+#ifdef VTY_CRYPT_PW
+		if (host.encrypt)
+			fail = strcmp(crypt(buf, passwd), passwd);
+		else
+#endif
+			fail = strcmp(buf, passwd);
+	} else
+		fail = 1;
+
+	if (!fail) {
+		vty->fail = 0;
+		vty->node = next_node;	/* Success ! */
+	} else {
+		vty->fail++;
+		if (vty->fail >= 3) {
+			if (vty->node == AUTH_NODE) {
+				vty_out(vty,
+					"%% Bad passwords, too many failures!%s",
+					VTY_NEWLINE);
+				vty->status = VTY_CLOSE;
+			} else {
+				/* AUTH_ENABLE_NODE */
+				vty->fail = 0;
+				vty_out(vty,
+					"%% Bad enable passwords, too many failures!%s",
+					VTY_NEWLINE);
+				vty->node = VIEW_NODE;
+			}
+		}
+	}
+}
+
+/*! \brief Close a given vty interface. */
+void vty_close(struct vty *vty)
+{
+	int i;
+
+	if (vty->obuf)  {
+		/* Flush buffer. */
+		buffer_flush_all(vty->obuf, vty->fd);
+
+		/* Free input buffer. */
+		buffer_free(vty->obuf);
+		vty->obuf = NULL;
+	}
+
+	/* Free command history. */
+	for (i = 0; i < VTY_MAXHIST; i++)
+		if (vty->hist[i])
+			talloc_free(vty->hist[i]);
+
+	/* Unset vector. */
+	vector_unset(vtyvec, vty->fd);
+
+	/* Close socket. */
+	if (vty->fd > 0)
+		close(vty->fd);
+
+	if (vty->buf) {
+		talloc_free(vty->buf);
+		vty->buf = NULL;
+	}
+
+	/* Check configure. */
+	vty_config_unlock(vty);
+
+	/* VTY_CLOSED is handled by the telnet_interface */
+	vty_event(VTY_CLOSED, vty->fd, vty);
+
+	/* OK free vty. */
+	talloc_free(vty);
+}
+
+/*! \brief Return if this VTY is a shell or not */
+int vty_shell(struct vty *vty)
+{
+	return vty->type == VTY_SHELL ? 1 : 0;
+}
+
+
+/*! \brief VTY standard output function
+ *  \param[in] vty VTY to which we should print
+ *  \param[in] format variable-length format string
+ */
+int vty_out(struct vty *vty, const char *format, ...)
+{
+	va_list args;
+	int len = 0;
+	int size = 1024;
+	char buf[1024];
+	char *p = NULL;
+
+	if (vty_shell(vty)) {
+		va_start(args, format);
+		vprintf(format, args);
+		va_end(args);
+	} else {
+		/* Try to write to initial buffer.  */
+		va_start(args, format);
+		len = vsnprintf(buf, sizeof buf, format, args);
+		va_end(args);
+
+		/* Initial buffer is not enough.  */
+		if (len < 0 || len >= size) {
+			while (1) {
+				if (len > -1)
+					size = len + 1;
+				else
+					size = size * 2;
+
+				p = talloc_realloc_size(vty, p, size);
+				if (!p)
+					return -1;
+
+				va_start(args, format);
+				len = vsnprintf(p, size, format, args);
+				va_end(args);
+
+				if (len > -1 && len < size)
+					break;
+			}
+		}
+
+		/* When initial buffer is enough to store all output.  */
+		if (!p)
+			p = buf;
+
+		/* Pointer p must point out buffer. */
+		buffer_put(vty->obuf, (u_char *) p, len);
+
+		/* If p is not different with buf, it is allocated buffer.  */
+		if (p != buf)
+			talloc_free(p);
+	}
+
+	vty_event(VTY_WRITE, vty->fd, vty);
+
+	return len;
+}
+
+/*! \brief print a newline on the given VTY */
+int vty_out_newline(struct vty *vty)
+{
+	char *p = vty_newline(vty);
+	buffer_put(vty->obuf, p, strlen(p));
+	return 0;
+}
+
+/*! \brief return the current index of a given VTY */
+void *vty_current_index(struct vty *vty)
+{
+	return vty->index;
+}
+
+/*! \brief return the current node of a given VTY */
+int vty_current_node(struct vty *vty)
+{
+	return vty->node;
+}
+
+/*! \brief Lock the configuration to a given VTY
+ *  \param[in] vty VTY to which the config shall be locked
+ *  \returns 1 on success, 0 on error
+ *
+ * This shall be used to make sure only one VTY at a given time has
+ * access to modify the configuration */
+int vty_config_lock(struct vty *vty)
+{
+	if (vty_config == 0) {
+		vty->config = 1;
+		vty_config = 1;
+	}
+	return vty->config;
+}
+
+/*! \brief Unlock the configuration from a given VTY
+ *  \param[in] vty VTY from which the configuration shall be unlocked
+ *  \returns 0 in case of success
+ */
+int vty_config_unlock(struct vty *vty)
+{
+	if (vty_config == 1 && vty->config == 1) {
+		vty->config = 0;
+		vty_config = 0;
+	}
+	return vty->config;
+}
+
+/* Say hello to vty interface. */
+void vty_hello(struct vty *vty)
+{
+	const char *app_name = "<unnamed>";
+
+	if (host.app_info->name)
+		app_name = host.app_info->name;
+
+	vty_out(vty, "Welcome to the %s control interface%s%s",
+		app_name, VTY_NEWLINE, VTY_NEWLINE);
+
+	if (host.app_info->copyright)
+		vty_out(vty, host.app_info->copyright);
+
+	if (host.motdfile) {
+		FILE *f;
+		char buf[4096];
+
+		f = fopen(host.motdfile, "r");
+		if (f) {
+			while (fgets(buf, sizeof(buf), f)) {
+				char *s;
+				/* work backwards to ignore trailling isspace() */
+				for (s = buf + strlen(buf);
+				     (s > buf) && isspace(*(s - 1)); s--) ;
+				*s = '\0';
+				vty_out(vty, "%s%s", buf, VTY_NEWLINE);
+			}
+			fclose(f);
+		} else
+			vty_out(vty, "MOTD file not found%s", VTY_NEWLINE);
+	} else if (host.motd)
+		vty_out(vty, "%s", host.motd);
+}
+
+/* Put out prompt and wait input from user. */
+static void vty_prompt(struct vty *vty)
+{
+	struct utsname names;
+	const char *hostname;
+
+	if (vty->type == VTY_TERM) {
+		hostname = host.app_info->name;
+		if (!hostname) {
+			uname(&names);
+			hostname = names.nodename;
+		}
+		vty_out(vty, cmd_prompt(vty->node), hostname);
+	}
+}
+
+/* Command execution over the vty interface. */
+static int vty_command(struct vty *vty, char *buf)
+{
+	int ret;
+	vector vline;
+
+	/* Split readline string up into the vector */
+	vline = cmd_make_strvec(buf);
+
+	if (vline == NULL)
+		return CMD_SUCCESS;
+
+	ret = cmd_execute_command(vline, vty, NULL, 0);
+	if (ret != CMD_SUCCESS)
+		switch (ret) {
+		case CMD_WARNING:
+			if (vty->type == VTY_FILE)
+				vty_out(vty, "Warning...%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_AMBIGUOUS:
+			vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_NO_MATCH:
+			vty_out(vty, "%% Unknown command.%s", VTY_NEWLINE);
+			break;
+		case CMD_ERR_INCOMPLETE:
+			vty_out(vty, "%% Command incomplete.%s", VTY_NEWLINE);
+			break;
+		}
+	cmd_free_strvec(vline);
+
+	return ret;
+}
+
+static const char telnet_backward_char = 0x08;
+static const char telnet_space_char = ' ';
+
+/* Basic function to write buffer to vty. */
+static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
+{
+	if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
+		return;
+
+	/* Should we do buffering here ?  And make vty_flush (vty) ? */
+	buffer_put(vty->obuf, buf, nbytes);
+}
+
+/* Ensure length of input buffer.  Is buffer is short, double it. */
+static void vty_ensure(struct vty *vty, int length)
+{
+	if (vty->max <= length) {
+		vty->max *= 2;
+		vty->buf = talloc_realloc_size(vty, vty->buf, vty->max);
+		// FIXME: check return
+	}
+}
+
+/* Basic function to insert character into vty. */
+static void vty_self_insert(struct vty *vty, char c)
+{
+	int i;
+	int length;
+
+	vty_ensure(vty, vty->length + 1);
+	length = vty->length - vty->cp;
+	memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
+	vty->buf[vty->cp] = c;
+
+	vty_write(vty, &vty->buf[vty->cp], length + 1);
+	for (i = 0; i < length; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+
+	vty->cp++;
+	vty->length++;
+}
+
+/* Self insert character 'c' in overwrite mode. */
+static void vty_self_insert_overwrite(struct vty *vty, char c)
+{
+	vty_ensure(vty, vty->length + 1);
+	vty->buf[vty->cp++] = c;
+
+	if (vty->cp > vty->length)
+		vty->length++;
+
+	if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
+		return;
+
+	vty_write(vty, &c, 1);
+}
+
+/* Insert a word into vty interface with overwrite mode. */
+static void vty_insert_word_overwrite(struct vty *vty, char *str)
+{
+	int len = strlen(str);
+	vty_write(vty, str, len);
+	strcpy(&vty->buf[vty->cp], str);
+	vty->cp += len;
+	vty->length = vty->cp;
+}
+
+/* Forward character. */
+static void vty_forward_char(struct vty *vty)
+{
+	if (vty->cp < vty->length) {
+		vty_write(vty, &vty->buf[vty->cp], 1);
+		vty->cp++;
+	}
+}
+
+/* Backward character. */
+static void vty_backward_char(struct vty *vty)
+{
+	if (vty->cp > 0) {
+		vty->cp--;
+		vty_write(vty, &telnet_backward_char, 1);
+	}
+}
+
+/* Move to the beginning of the line. */
+static void vty_beginning_of_line(struct vty *vty)
+{
+	while (vty->cp)
+		vty_backward_char(vty);
+}
+
+/* Move to the end of the line. */
+static void vty_end_of_line(struct vty *vty)
+{
+	while (vty->cp < vty->length)
+		vty_forward_char(vty);
+}
+
+/* Add current command line to the history buffer. */
+static void vty_hist_add(struct vty *vty)
+{
+	int index;
+
+	if (vty->length == 0)
+		return;
+
+	index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;
+
+	/* Ignore the same string as previous one. */
+	if (vty->hist[index])
+		if (strcmp(vty->buf, vty->hist[index]) == 0) {
+			vty->hp = vty->hindex;
+			return;
+		}
+
+	/* Insert history entry. */
+	if (vty->hist[vty->hindex])
+		talloc_free(vty->hist[vty->hindex]);
+	vty->hist[vty->hindex] = talloc_strdup(vty, vty->buf);
+
+	/* History index rotation. */
+	vty->hindex++;
+	if (vty->hindex == VTY_MAXHIST)
+		vty->hindex = 0;
+
+	vty->hp = vty->hindex;
+}
+
+/* Get telnet window size. */
+static int
+vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes)
+{
+#ifdef TELNET_OPTION_DEBUG
+  int i;
+
+  for (i = 0; i < nbytes; i++)
+    {
+      switch (buf[i])
+	{
+	case IAC:
+	  vty_out (vty, "IAC ");
+	  break;
+	case WILL:
+	  vty_out (vty, "WILL ");
+	  break;
+	case WONT:
+	  vty_out (vty, "WONT ");
+	  break;
+	case DO:
+	  vty_out (vty, "DO ");
+	  break;
+	case DONT:
+	  vty_out (vty, "DONT ");
+	  break;
+	case SB:
+	  vty_out (vty, "SB ");
+	  break;
+	case SE:
+	  vty_out (vty, "SE ");
+	  break;
+	case TELOPT_ECHO:
+	  vty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE);
+	  break;
+	case TELOPT_SGA:
+	  vty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE);
+	  break;
+	case TELOPT_NAWS:
+	  vty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE);
+	  break;
+	default:
+	  vty_out (vty, "%x ", buf[i]);
+	  break;
+	}
+    }
+  vty_out (vty, "%s", VTY_NEWLINE);
+
+#endif /* TELNET_OPTION_DEBUG */
+
+  switch (buf[0])
+    {
+    case SB:
+      vty->sb_len = 0;
+      vty->iac_sb_in_progress = 1;
+      return 0;
+      break;
+    case SE:
+      {
+	if (!vty->iac_sb_in_progress)
+	  return 0;
+
+	if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0'))
+	  {
+	    vty->iac_sb_in_progress = 0;
+	    return 0;
+	  }
+	switch (vty->sb_buf[0])
+	  {
+	  case TELOPT_NAWS:
+	    if (vty->sb_len != TELNET_NAWS_SB_LEN)
+	      vty_out(vty,"RFC 1073 violation detected: telnet NAWS option "
+			"should send %d characters, but we received %lu",
+			TELNET_NAWS_SB_LEN, (u_long)vty->sb_len);
+	    else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
+	      vty_out(vty, "Bug detected: sizeof(vty->sb_buf) %lu < %d, "
+		       "too small to handle the telnet NAWS option",
+		       (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN);
+	    else
+	      {
+		vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]);
+		vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]);
+#ifdef TELNET_OPTION_DEBUG
+		vty_out(vty, "TELNET NAWS window size negotiation completed: "
+			      "width %d, height %d%s",
+			vty->width, vty->height, VTY_NEWLINE);
+#endif
+	      }
+	    break;
+	  }
+	vty->iac_sb_in_progress = 0;
+	return 0;
+	break;
+      }
+    default:
+      break;
+    }
+  return 1;
+}
+
+/* Execute current command line. */
+static int vty_execute(struct vty *vty)
+{
+	int ret;
+
+	ret = CMD_SUCCESS;
+
+	switch (vty->node) {
+	case AUTH_NODE:
+	case AUTH_ENABLE_NODE:
+		vty_auth(vty, vty->buf);
+		break;
+	default:
+		ret = vty_command(vty, vty->buf);
+		if (vty->type == VTY_TERM)
+			vty_hist_add(vty);
+		break;
+	}
+
+	/* Clear command line buffer. */
+	vty->cp = vty->length = 0;
+	vty_clear_buf(vty);
+
+	if (vty->status != VTY_CLOSE)
+		vty_prompt(vty);
+
+	return ret;
+}
+
+/* Send WILL TELOPT_ECHO to remote server. */
+static void
+vty_will_echo (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Make suppress Go-Ahead telnet option. */
+static void
+vty_will_suppress_go_ahead (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Make don't use linemode over telnet. */
+static void
+vty_dont_linemode (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+/* Use window size. */
+static void
+vty_do_window_size (struct vty *vty)
+{
+	unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' };
+	vty_out (vty, "%s", cmd);
+}
+
+static void vty_kill_line_from_beginning(struct vty *);
+static void vty_redraw_line(struct vty *);
+
+/* Print command line history.  This function is called from
+   vty_next_line and vty_previous_line. */
+static void vty_history_print(struct vty *vty)
+{
+	int length;
+
+	vty_kill_line_from_beginning(vty);
+
+	/* Get previous line from history buffer */
+	length = strlen(vty->hist[vty->hp]);
+	memcpy(vty->buf, vty->hist[vty->hp], length);
+	vty->cp = vty->length = length;
+
+	/* Redraw current line */
+	vty_redraw_line(vty);
+}
+
+/* Show next command line history. */
+static void vty_next_line(struct vty *vty)
+{
+	int try_index;
+
+	if (vty->hp == vty->hindex)
+		return;
+
+	/* Try is there history exist or not. */
+	try_index = vty->hp;
+	if (try_index == (VTY_MAXHIST - 1))
+		try_index = 0;
+	else
+		try_index++;
+
+	/* If there is not history return. */
+	if (vty->hist[try_index] == NULL)
+		return;
+	else
+		vty->hp = try_index;
+
+	vty_history_print(vty);
+}
+
+/* Show previous command line history. */
+static void vty_previous_line(struct vty *vty)
+{
+	int try_index;
+
+	try_index = vty->hp;
+	if (try_index == 0)
+		try_index = VTY_MAXHIST - 1;
+	else
+		try_index--;
+
+	if (vty->hist[try_index] == NULL)
+		return;
+	else
+		vty->hp = try_index;
+
+	vty_history_print(vty);
+}
+
+/* This function redraw all of the command line character. */
+static void vty_redraw_line(struct vty *vty)
+{
+	vty_write(vty, vty->buf, vty->length);
+	vty->cp = vty->length;
+}
+
+/* Forward word. */
+static void vty_forward_word(struct vty *vty)
+{
+	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+		vty_forward_char(vty);
+
+	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+		vty_forward_char(vty);
+}
+
+/* Backward word without skipping training space. */
+static void vty_backward_pure_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_backward_char(vty);
+}
+
+/* Backward word. */
+static void vty_backward_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+		vty_backward_char(vty);
+
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_backward_char(vty);
+}
+
+/* When '^D' is typed at the beginning of the line we move to the down
+   level. */
+static void vty_down_level(struct vty *vty)
+{
+	vty_out(vty, "%s", VTY_NEWLINE);
+	/* call the exit function of the specific node */
+	if (vty->node > CONFIG_NODE)
+		vty_go_parent(vty);
+	else
+		(*config_exit_cmd.func) (NULL, vty, 0, NULL);
+	vty_prompt(vty);
+	vty->cp = 0;
+}
+
+/* When '^Z' is received from vty, move down to the enable mode. */
+static void vty_end_config(struct vty *vty)
+{
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	/* FIXME: we need to call the exit function of the specific node
+	 * in question, not this generic one that doesn't know all nodes */
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	case CFG_LOG_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	default:
+		/* Unknown node, we have to ignore it. */
+		break;
+	}
+
+	vty_prompt(vty);
+	vty->cp = 0;
+}
+
+/* Delete a charcter at the current point. */
+static void vty_delete_char(struct vty *vty)
+{
+	int i;
+	int size;
+
+	if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+		return;
+
+	if (vty->length == 0) {
+		vty_down_level(vty);
+		return;
+	}
+
+	if (vty->cp == vty->length)
+		return;		/* completion need here? */
+
+	size = vty->length - vty->cp;
+
+	vty->length--;
+	memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
+	vty->buf[vty->length] = '\0';
+
+	vty_write(vty, &vty->buf[vty->cp], size - 1);
+	vty_write(vty, &telnet_space_char, 1);
+
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+}
+
+/* Delete a character before the point. */
+static void vty_delete_backward_char(struct vty *vty)
+{
+	if (vty->cp == 0)
+		return;
+
+	vty_backward_char(vty);
+	vty_delete_char(vty);
+}
+
+/* Kill rest of line from current point. */
+static void vty_kill_line(struct vty *vty)
+{
+	int i;
+	int size;
+
+	size = vty->length - vty->cp;
+
+	if (size == 0)
+		return;
+
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_space_char, 1);
+	for (i = 0; i < size; i++)
+		vty_write(vty, &telnet_backward_char, 1);
+
+	memset(&vty->buf[vty->cp], 0, size);
+	vty->length = vty->cp;
+}
+
+/* Kill line from the beginning. */
+static void vty_kill_line_from_beginning(struct vty *vty)
+{
+	vty_beginning_of_line(vty);
+	vty_kill_line(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_forward_kill_word(struct vty *vty)
+{
+	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
+		vty_delete_char(vty);
+	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
+		vty_delete_char(vty);
+}
+
+/* Delete a word before the point. */
+static void vty_backward_kill_word(struct vty *vty)
+{
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
+		vty_delete_backward_char(vty);
+	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
+		vty_delete_backward_char(vty);
+}
+
+/* Transpose chars before or at the point. */
+static void vty_transpose_chars(struct vty *vty)
+{
+	char c1, c2;
+
+	/* If length is short or point is near by the beginning of line then
+	   return. */
+	if (vty->length < 2 || vty->cp < 1)
+		return;
+
+	/* In case of point is located at the end of the line. */
+	if (vty->cp == vty->length) {
+		c1 = vty->buf[vty->cp - 1];
+		c2 = vty->buf[vty->cp - 2];
+
+		vty_backward_char(vty);
+		vty_backward_char(vty);
+		vty_self_insert_overwrite(vty, c1);
+		vty_self_insert_overwrite(vty, c2);
+	} else {
+		c1 = vty->buf[vty->cp];
+		c2 = vty->buf[vty->cp - 1];
+
+		vty_backward_char(vty);
+		vty_self_insert_overwrite(vty, c1);
+		vty_self_insert_overwrite(vty, c2);
+	}
+}
+
+/* Do completion at vty interface. */
+static void vty_complete_command(struct vty *vty)
+{
+	int i;
+	int ret;
+	char **matched = NULL;
+	vector vline;
+
+	if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
+		return;
+
+	vline = cmd_make_strvec(vty->buf);
+	if (vline == NULL)
+		return;
+
+	/* In case of 'help \t'. */
+	if (isspace((int)vty->buf[vty->length - 1]))
+		vector_set(vline, '\0');
+
+	matched = cmd_complete_command(vline, vty, &ret);
+
+	cmd_free_strvec(vline);
+
+	vty_out(vty, "%s", VTY_NEWLINE);
+	switch (ret) {
+	case CMD_ERR_AMBIGUOUS:
+		vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_ERR_NO_MATCH:
+		/* vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); */
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_COMPLETE_FULL_MATCH:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		vty_backward_pure_word(vty);
+		vty_insert_word_overwrite(vty, matched[0]);
+		vty_self_insert(vty, ' ');
+		talloc_free(matched[0]);
+		break;
+	case CMD_COMPLETE_MATCH:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		vty_backward_pure_word(vty);
+		vty_insert_word_overwrite(vty, matched[0]);
+		talloc_free(matched[0]);
+		break;
+	case CMD_COMPLETE_LIST_MATCH:
+		for (i = 0; matched[i] != NULL; i++) {
+			if (i != 0 && ((i % 6) == 0))
+				vty_out(vty, "%s", VTY_NEWLINE);
+			vty_out(vty, "%-10s ", matched[i]);
+			talloc_free(matched[i]);
+		}
+		vty_out(vty, "%s", VTY_NEWLINE);
+
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	case CMD_ERR_NOTHING_TODO:
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		break;
+	default:
+		break;
+	}
+	if (matched)
+		vector_only_index_free(matched);
+}
+
+static void
+vty_describe_fold(struct vty *vty, int cmd_width,
+		  unsigned int desc_width, struct desc *desc)
+{
+	char *buf;
+	const char *cmd, *p;
+	int pos;
+
+	cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
+
+	if (desc_width <= 0) {
+		vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, desc->str,
+			VTY_NEWLINE);
+		return;
+	}
+
+	buf = _talloc_zero(vty, strlen(desc->str) + 1, "describe_fold");
+	if (!buf)
+		return;
+
+	for (p = desc->str; strlen(p) > desc_width; p += pos + 1) {
+		for (pos = desc_width; pos > 0; pos--)
+			if (*(p + pos) == ' ')
+				break;
+
+		if (pos == 0)
+			break;
+
+		strncpy(buf, p, pos);
+		buf[pos] = '\0';
+		vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, buf, VTY_NEWLINE);
+
+		cmd = "";
+	}
+
+	vty_out(vty, "  %-*s  %s%s", cmd_width, cmd, p, VTY_NEWLINE);
+
+	talloc_free(buf);
+}
+
+/* Describe matched command function. */
+static void vty_describe_command(struct vty *vty)
+{
+	int ret;
+	vector vline;
+	vector describe;
+	unsigned int i, width, desc_width;
+	struct desc *desc, *desc_cr = NULL;
+
+	vline = cmd_make_strvec(vty->buf);
+
+	/* In case of '> ?'. */
+	if (vline == NULL) {
+		vline = vector_init(1);
+		vector_set(vline, '\0');
+	} else if (isspace((int)vty->buf[vty->length - 1]))
+		vector_set(vline, '\0');
+
+	describe = cmd_describe_command(vline, vty, &ret);
+
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	/* Ambiguous error. */
+	switch (ret) {
+	case CMD_ERR_AMBIGUOUS:
+		cmd_free_strvec(vline);
+		vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		return;
+		break;
+	case CMD_ERR_NO_MATCH:
+		cmd_free_strvec(vline);
+		vty_out(vty, "%% There is no matched command.%s", VTY_NEWLINE);
+		vty_prompt(vty);
+		vty_redraw_line(vty);
+		return;
+		break;
+	}
+
+	/* Get width of command string. */
+	width = 0;
+	for (i = 0; i < vector_active(describe); i++)
+		if ((desc = vector_slot(describe, i)) != NULL) {
+			unsigned int len;
+
+			if (desc->cmd[0] == '\0')
+				continue;
+
+			len = strlen(desc->cmd);
+			if (desc->cmd[0] == '.')
+				len--;
+
+			if (width < len)
+				width = len;
+		}
+
+	/* Get width of description string. */
+	desc_width = vty->width - (width + 6);
+
+	/* Print out description. */
+	for (i = 0; i < vector_active(describe); i++)
+		if ((desc = vector_slot(describe, i)) != NULL) {
+			if (desc->cmd[0] == '\0')
+				continue;
+
+			if (strcmp(desc->cmd, "<cr>") == 0) {
+				desc_cr = desc;
+				continue;
+			}
+
+			if (!desc->str)
+				vty_out(vty, "  %-s%s",
+					desc->cmd[0] ==
+					'.' ? desc->cmd + 1 : desc->cmd,
+					VTY_NEWLINE);
+			else if (desc_width >= strlen(desc->str))
+				vty_out(vty, "  %-*s  %s%s", width,
+					desc->cmd[0] ==
+					'.' ? desc->cmd + 1 : desc->cmd,
+					desc->str, VTY_NEWLINE);
+			else
+				vty_describe_fold(vty, width, desc_width, desc);
+
+#if 0
+			vty_out(vty, "  %-*s %s%s", width
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				desc->str ? desc->str : "", VTY_NEWLINE);
+#endif				/* 0 */
+		}
+
+	if ((desc = desc_cr)) {
+		if (!desc->str)
+			vty_out(vty, "  %-s%s",
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				VTY_NEWLINE);
+		else if (desc_width >= strlen(desc->str))
+			vty_out(vty, "  %-*s  %s%s", width,
+				desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
+				desc->str, VTY_NEWLINE);
+		else
+			vty_describe_fold(vty, width, desc_width, desc);
+	}
+
+	cmd_free_strvec(vline);
+	vector_free(describe);
+
+	vty_prompt(vty);
+	vty_redraw_line(vty);
+}
+
+/* ^C stop current input and do not add command line to the history. */
+static void vty_stop_input(struct vty *vty)
+{
+	vty->cp = vty->length = 0;
+	vty_clear_buf(vty);
+	vty_out(vty, "%s", VTY_NEWLINE);
+
+	switch (vty->node) {
+	case VIEW_NODE:
+	case ENABLE_NODE:
+		/* Nothing to do. */
+		break;
+	case CONFIG_NODE:
+	case VTY_NODE:
+		vty_config_unlock(vty);
+		vty->node = ENABLE_NODE;
+		break;
+	case CFG_LOG_NODE:
+		vty->node = CONFIG_NODE;
+		break;
+	default:
+		/* Unknown node, we have to ignore it. */
+		break;
+	}
+	vty_prompt(vty);
+
+	/* Set history pointer to the latest one. */
+	vty->hp = vty->hindex;
+}
+
+#define CONTROL(X)  ((X) - '@')
+#define VTY_NORMAL     0
+#define VTY_PRE_ESCAPE 1
+#define VTY_ESCAPE     2
+
+/* Escape character command map. */
+static void vty_escape_map(unsigned char c, struct vty *vty)
+{
+	switch (c) {
+	case ('A'):
+		vty_previous_line(vty);
+		break;
+	case ('B'):
+		vty_next_line(vty);
+		break;
+	case ('C'):
+		vty_forward_char(vty);
+		break;
+	case ('D'):
+		vty_backward_char(vty);
+		break;
+	default:
+		break;
+	}
+
+	/* Go back to normal mode. */
+	vty->escape = VTY_NORMAL;
+}
+
+/* Quit print out to the buffer. */
+static void vty_buffer_reset(struct vty *vty)
+{
+	buffer_reset(vty->obuf);
+	vty_prompt(vty);
+	vty_redraw_line(vty);
+}
+
+/*! \brief Read data via vty socket. */
+int vty_read(struct vty *vty)
+{
+	int i;
+	int nbytes;
+	unsigned char buf[VTY_READ_BUFSIZ];
+
+	int vty_sock = vty->fd;
+
+	/* Read raw data from socket */
+	if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
+		if (nbytes < 0) {
+			if (ERRNO_IO_RETRY(errno)) {
+				vty_event(VTY_READ, vty_sock, vty);
+				return 0;
+			}
+		}
+		buffer_reset(vty->obuf);
+		vty->status = VTY_CLOSE;
+	}
+
+	for (i = 0; i < nbytes; i++) {
+		if (buf[i] == IAC) {
+			if (!vty->iac) {
+				vty->iac = 1;
+				continue;
+			} else {
+				vty->iac = 0;
+			}
+		}
+
+		if (vty->iac_sb_in_progress && !vty->iac) {
+			if (vty->sb_len < sizeof(vty->sb_buf))
+				vty->sb_buf[vty->sb_len] = buf[i];
+			vty->sb_len++;
+			continue;
+		}
+
+		if (vty->iac) {
+			/* In case of telnet command */
+			int ret = 0;
+			ret = vty_telnet_option(vty, buf + i, nbytes - i);
+			vty->iac = 0;
+			i += ret;
+			continue;
+		}
+
+		if (vty->status == VTY_MORE) {
+			switch (buf[i]) {
+			case CONTROL('C'):
+			case 'q':
+			case 'Q':
+				vty_buffer_reset(vty);
+				break;
+#if 0				/* More line does not work for "show ip bgp".  */
+			case '\n':
+			case '\r':
+				vty->status = VTY_MORELINE;
+				break;
+#endif
+			default:
+				break;
+			}
+			continue;
+		}
+
+		/* Escape character. */
+		if (vty->escape == VTY_ESCAPE) {
+			vty_escape_map(buf[i], vty);
+			continue;
+		}
+
+		/* Pre-escape status. */
+		if (vty->escape == VTY_PRE_ESCAPE) {
+			switch (buf[i]) {
+			case '[':
+				vty->escape = VTY_ESCAPE;
+				break;
+			case 'b':
+				vty_backward_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case 'f':
+				vty_forward_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case 'd':
+				vty_forward_kill_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			case CONTROL('H'):
+			case 0x7f:
+				vty_backward_kill_word(vty);
+				vty->escape = VTY_NORMAL;
+				break;
+			default:
+				vty->escape = VTY_NORMAL;
+				break;
+			}
+			continue;
+		}
+
+		switch (buf[i]) {
+		case CONTROL('A'):
+			vty_beginning_of_line(vty);
+			break;
+		case CONTROL('B'):
+			vty_backward_char(vty);
+			break;
+		case CONTROL('C'):
+			vty_stop_input(vty);
+			break;
+		case CONTROL('D'):
+			vty_delete_char(vty);
+			break;
+		case CONTROL('E'):
+			vty_end_of_line(vty);
+			break;
+		case CONTROL('F'):
+			vty_forward_char(vty);
+			break;
+		case CONTROL('H'):
+		case 0x7f:
+			vty_delete_backward_char(vty);
+			break;
+		case CONTROL('K'):
+			vty_kill_line(vty);
+			break;
+		case CONTROL('N'):
+			vty_next_line(vty);
+			break;
+		case CONTROL('P'):
+			vty_previous_line(vty);
+			break;
+		case CONTROL('T'):
+			vty_transpose_chars(vty);
+			break;
+		case CONTROL('U'):
+			vty_kill_line_from_beginning(vty);
+			break;
+		case CONTROL('W'):
+			vty_backward_kill_word(vty);
+			break;
+		case CONTROL('Z'):
+			vty_end_config(vty);
+			break;
+		case '\n':
+		case '\r':
+			vty_out(vty, "%s", VTY_NEWLINE);
+			vty_execute(vty);
+			break;
+		case '\t':
+			vty_complete_command(vty);
+			break;
+		case '?':
+			if (vty->node == AUTH_NODE
+			    || vty->node == AUTH_ENABLE_NODE)
+				vty_self_insert(vty, buf[i]);
+			else
+				vty_describe_command(vty);
+			break;
+		case '\033':
+			if (i + 1 < nbytes && buf[i + 1] == '[') {
+				vty->escape = VTY_ESCAPE;
+				i++;
+			} else
+				vty->escape = VTY_PRE_ESCAPE;
+			break;
+		default:
+			if (buf[i] > 31 && buf[i] < 127)
+				vty_self_insert(vty, buf[i]);
+			break;
+		}
+	}
+
+	/* Check status. */
+	if (vty->status == VTY_CLOSE)
+		vty_close(vty);
+	else {
+		vty_event(VTY_WRITE, vty_sock, vty);
+		vty_event(VTY_READ, vty_sock, vty);
+	}
+	return 0;
+}
+
+/* Read up configuration file */
+static int
+vty_read_file(FILE *confp, void *priv)
+{
+	int ret;
+	struct vty *vty;
+
+	vty = vty_new();
+	vty->fd = 0;
+	vty->type = VTY_FILE;
+	vty->node = CONFIG_NODE;
+	vty->priv = priv;
+
+	ret = config_from_file(vty, confp);
+
+	if (ret != CMD_SUCCESS) {
+		switch (ret) {
+		case CMD_ERR_AMBIGUOUS:
+			fprintf(stderr, "Ambiguous command.\n");
+			break;
+		case CMD_ERR_NO_MATCH:
+			fprintf(stderr, "There is no such command.\n");
+			break;
+		}
+		fprintf(stderr, "Error occurred during reading below "
+			"line:\n%s\n", vty->buf);
+		vty_close(vty);
+		return -EINVAL;
+	}
+
+	vty_close(vty);
+	return 0;
+}
+
+/*! \brief Create new vty structure. */
+struct vty *
+vty_create (int vty_sock, void *priv)
+{
+  struct vty *vty;
+
+	struct termios t;
+
+	tcgetattr(vty_sock, &t);
+	cfmakeraw(&t);
+	tcsetattr(vty_sock, TCSANOW, &t);
+
+  /* Allocate new vty structure and set up default values. */
+  vty = vty_new ();
+  vty->fd = vty_sock;
+  vty->priv = priv;
+  vty->type = VTY_TERM;
+  if (no_password_check)
+    {
+      if (host.advanced)
+	vty->node = ENABLE_NODE;
+      else
+	vty->node = VIEW_NODE;
+    }
+  else
+    vty->node = AUTH_NODE;
+  vty->fail = 0;
+  vty->cp = 0;
+  vty_clear_buf (vty);
+  vty->length = 0;
+  memset (vty->hist, 0, sizeof (vty->hist));
+  vty->hp = 0;
+  vty->hindex = 0;
+  vector_set_index (vtyvec, vty_sock, vty);
+  vty->status = VTY_NORMAL;
+  if (host.lines >= 0)
+    vty->lines = host.lines;
+  else
+    vty->lines = -1;
+
+  if (! no_password_check)
+    {
+      /* Vty is not available if password isn't set. */
+      if (host.password == NULL && host.password_encrypt == NULL)
+	{
+	  vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
+	  vty->status = VTY_CLOSE;
+	  vty_close (vty);
+	  return NULL;
+	}
+    }
+
+  /* Say hello to the world. */
+  vty_hello (vty);
+  if (! no_password_check)
+    vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  /* Setting up terminal. */
+  vty_will_echo (vty);
+  vty_will_suppress_go_ahead (vty);
+
+  vty_dont_linemode (vty);
+  vty_do_window_size (vty);
+  /* vty_dont_lflow_ahead (vty); */
+
+  vty_prompt (vty);
+
+  /* Add read/write thread. */
+  vty_event (VTY_WRITE, vty_sock, vty);
+  vty_event (VTY_READ, vty_sock, vty);
+
+  return vty;
+}
+
+DEFUN(config_who, config_who_cmd, "who", "Display who is on vty\n")
+{
+	unsigned int i;
+	struct vty *v;
+
+	for (i = 0; i < vector_active(vtyvec); i++)
+		if ((v = vector_slot(vtyvec, i)) != NULL)
+			vty_out(vty, "%svty[%d] %s",
+				v->config ? "*" : " ", i, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+/* Move to vty configuration mode. */
+DEFUN(line_vty,
+      line_vty_cmd,
+      "line vty", "Configure a terminal line\n" "Virtual terminal\n")
+{
+	vty->node = VTY_NODE;
+	return CMD_SUCCESS;
+}
+
+/* vty login. */
+DEFUN(vty_login, vty_login_cmd, "login", "Enable password checking\n")
+{
+	no_password_check = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_vty_login,
+      no_vty_login_cmd, "no login", NO_STR "Enable password checking\n")
+{
+	no_password_check = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(service_advanced_vty,
+      service_advanced_vty_cmd,
+      "service advanced-vty",
+      "Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
+{
+	host.advanced = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_service_advanced_vty,
+      no_service_advanced_vty_cmd,
+      "no service advanced-vty",
+      NO_STR
+      "Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
+{
+	host.advanced = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(terminal_monitor,
+      terminal_monitor_cmd,
+      "terminal monitor",
+      "Set terminal line parameters\n"
+      "Copy debug output to the current terminal line\n")
+{
+	vty->monitor = 1;
+	return CMD_SUCCESS;
+}
+
+DEFUN(terminal_no_monitor,
+      terminal_no_monitor_cmd,
+      "terminal no monitor",
+      "Set terminal line parameters\n"
+      NO_STR "Copy debug output to the current terminal line\n")
+{
+	vty->monitor = 0;
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_history,
+      show_history_cmd,
+      "show history", SHOW_STR "Display the session command history\n")
+{
+	int index;
+
+	for (index = vty->hindex + 1; index != vty->hindex;) {
+		if (index == VTY_MAXHIST) {
+			index = 0;
+			continue;
+		}
+
+		if (vty->hist[index] != NULL)
+			vty_out(vty, "  %s%s", vty->hist[index], VTY_NEWLINE);
+
+		index++;
+	}
+
+	return CMD_SUCCESS;
+}
+
+/* Display current configuration. */
+static int vty_config_write(struct vty *vty)
+{
+	vty_out(vty, "line vty%s", VTY_NEWLINE);
+
+	/* login */
+	if (no_password_check)
+		vty_out(vty, " no login%s", VTY_NEWLINE);
+
+	vty_out(vty, "!%s", VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+struct cmd_node vty_node = {
+	VTY_NODE,
+	"%s(config-line)# ",
+	1,
+};
+
+/*! \brief Reset all VTY status. */
+void vty_reset(void)
+{
+	unsigned int i;
+	struct vty *vty;
+	struct thread *vty_serv_thread;
+
+	for (i = 0; i < vector_active(vtyvec); i++)
+		if ((vty = vector_slot(vtyvec, i)) != NULL) {
+			buffer_reset(vty->obuf);
+			vty->status = VTY_CLOSE;
+			vty_close(vty);
+		}
+
+	for (i = 0; i < vector_active(Vvty_serv_thread); i++)
+		if ((vty_serv_thread =
+		     vector_slot(Vvty_serv_thread, i)) != NULL) {
+			//thread_cancel (vty_serv_thread);
+			vector_slot(Vvty_serv_thread, i) = NULL;
+			close(i);
+		}
+}
+
+static void vty_save_cwd(void)
+{
+	char cwd[MAXPATHLEN];
+	char *c ;
+
+	c = getcwd(cwd, MAXPATHLEN);
+
+	if (!c) {
+		if (chdir(SYSCONFDIR) != 0)
+		    perror("chdir failed");
+		if (getcwd(cwd, MAXPATHLEN) == NULL)
+		    perror("getcwd failed");
+	}
+
+	vty_cwd = _talloc_zero(tall_vty_ctx, strlen(cwd) + 1, "save_cwd");
+	strcpy(vty_cwd, cwd);
+}
+
+char *vty_get_cwd(void)
+{
+	return vty_cwd;
+}
+
+int vty_shell_serv(struct vty *vty)
+{
+	return vty->type == VTY_SHELL_SERV ? 1 : 0;
+}
+
+void vty_init_vtysh(void)
+{
+	vtyvec = vector_init(VECTOR_MIN_SIZE);
+}
+
+extern void *tall_bsc_ctx;
+
+/*! \brief Initialize VTY layer
+ *  \param[in] app_info application information
+ */
+/* Install vty's own commands like `who' command. */
+void vty_init(struct vty_app_info *app_info)
+{
+	tall_vty_ctx = talloc_named_const(app_info->tall_ctx, 0, "vty");
+	tall_vty_vec_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_vector");
+	tall_vty_cmd_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_command");
+
+	cmd_init(1);
+
+	host.app_info = app_info;
+
+	/* For further configuration read, preserve current directory. */
+	vty_save_cwd();
+
+	vtyvec = vector_init(VECTOR_MIN_SIZE);
+
+	/* Install bgp top node. */
+	install_node(&vty_node, vty_config_write);
+
+	install_element_ve(&config_who_cmd);
+	install_element_ve(&show_history_cmd);
+	install_element(CONFIG_NODE, &line_vty_cmd);
+	install_element(CONFIG_NODE, &service_advanced_vty_cmd);
+	install_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
+	install_element(CONFIG_NODE, &show_history_cmd);
+	install_element(ENABLE_NODE, &terminal_monitor_cmd);
+	install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
+
+	install_default(VTY_NODE);
+	install_element(VTY_NODE, &vty_login_cmd);
+	install_element(VTY_NODE, &no_vty_login_cmd);
+}
+
+/*! \brief Read the configuration file using the VTY code
+ *  \param[in] file_name file name of the configuration file
+ *  \param[in] priv private data to be passed to \ref vty_read_file
+ */
+int vty_read_config_file(const char *file_name, void *priv)
+{
+	FILE *cfile;
+	int rc;
+
+	cfile = fopen(file_name, "r");
+	if (!cfile)
+		return -ENOENT;
+
+	rc = vty_read_file(cfile, priv);
+	fclose(cfile);
+
+	host_config_set(file_name);
+
+	return rc;
+}
+
+/*! @} */
diff --git a/src/write_queue.c b/src/write_queue.c
new file mode 100644
index 0000000..cef40f8
--- /dev/null
+++ b/src/write_queue.c
@@ -0,0 +1,118 @@
+/* Generic write queue implementation */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/write_queue.h>
+
+/*! \addtogroup write_queue
+ *  @{
+ */
+
+/*! \file write_queue.c */
+
+/*! \brief Select loop function for write queue handling
+ *  \param[in] fd osmocom file descriptor
+ *  \param[in] what bit-mask of events that have happened
+ *
+ * This function is provided so that it can be registered with the
+ * select loop abstraction code (\ref osmo_fd::cb).
+ */
+int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what)
+{
+	struct osmo_wqueue *queue;
+
+	queue = container_of(fd, struct osmo_wqueue, bfd);
+
+	if (what & BSC_FD_READ)
+		queue->read_cb(fd);
+
+	if (what & BSC_FD_EXCEPT)
+		queue->except_cb(fd);
+
+	if (what & BSC_FD_WRITE) {
+		struct msgb *msg;
+
+		fd->when &= ~BSC_FD_WRITE;
+
+		/* the queue might have been emptied */
+		if (!llist_empty(&queue->msg_queue)) {
+			--queue->current_length;
+
+			msg = msgb_dequeue(&queue->msg_queue);
+			queue->write_cb(fd, msg);
+			msgb_free(msg);
+
+			if (!llist_empty(&queue->msg_queue))
+				fd->when |= BSC_FD_WRITE;
+		}
+	}
+
+	return 0;
+}
+
+/*! \brief Initialize a \ref osmo_wqueue structure
+ *  \param[in] queue Write queue to operate on
+ *  \param[in] max_length Maximum length of write queue
+ */
+void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length)
+{
+	queue->max_length = max_length;
+	queue->current_length = 0;
+	queue->read_cb = NULL;
+	queue->write_cb = NULL;
+	queue->bfd.cb = osmo_wqueue_bfd_cb;
+	INIT_LLIST_HEAD(&queue->msg_queue);
+}
+
+/*! \brief Enqueue a new \ref msgb into a write queue
+ *  \param[in] queue Write queue to be used
+ *  \param[in] data to-be-enqueued message buffer
+ */
+int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data)
+{
+//	if (queue->current_length + 1 >= queue->max_length)
+//		LOGP(DMSC, LOGL_ERROR, "The queue is full. Dropping not yet implemented.\n");
+
+	++queue->current_length;
+	msgb_enqueue(&queue->msg_queue, data);
+	queue->bfd.when |= BSC_FD_WRITE;
+
+	return 0;
+}
+
+/*! \brief Clear a \ref osmo_wqueue
+ *  \param[in] queue Write queue to be cleared
+ *
+ * This function will clear (remove/release) all messages in it.
+ */
+void osmo_wqueue_clear(struct osmo_wqueue *queue)
+{
+	while (!llist_empty(&queue->msg_queue)) {
+		struct msgb *msg = msgb_dequeue(&queue->msg_queue);
+		msgb_free(msg);
+	}
+
+	queue->current_length = 0;
+	queue->bfd.when &= ~BSC_FD_WRITE;
+}
+
+/*! @} */
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..eff1ac4
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,47 @@
+if ENABLE_TESTS
+SUBDIRS = timer sms ussd smscb bits a5 conv auth lapd gsm0808
+if ENABLE_MSGFILE
+SUBDIRS += msgfile
+endif
+
+
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	:;{ \
+               echo '# Signature of the current package.' && \
+               echo 'm4_define([AT_PACKAGE_NAME],' && \
+               echo '  [$(PACKAGE_NAME)])' && \
+               echo 'm4_define([AT_PACKAGE_TARNAME],' && \
+               echo '  [$(PACKAGE_TARNAME)])' && \
+               echo 'm4_define([AT_PACKAGE_VERSION],' && \
+               echo '  [$(PACKAGE_VERSION)])' && \
+               echo 'm4_define([AT_PACKAGE_STRING],' && \
+               echo '  [$(PACKAGE_STRING)])' && \
+               echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
+               echo '  [$(PACKAGE_BUGREPORT)])'; \
+               echo 'm4_define([AT_PACKAGE_URL],' && \
+               echo '  [$(PACKAGE_URL)])'; \
+             } >'$(srcdir)/package.m4'
+     
+EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE)
+TESTSUITE = $(srcdir)/testsuite
+     
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
+     
+installcheck-local: atconfig $(TESTSUITE)
+	$(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
+		$(TESTSUITEFLAGS)
+     
+clean-local:
+	test ! -f '$(TESTSUITE)' || \
+		$(SHELL) '$(TESTSUITE)' --clean
+	$(RM) -f atconfig
+     
+AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
+	$(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+	mv $@.tmp $@
+
+endif
diff --git a/tests/a5/Makefile.am b/tests/a5/Makefile.am
new file mode 100644
index 0000000..3c6e6ba
--- /dev/null
+++ b/tests/a5/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = a5_test
+EXTRA_DIST = a5_test.ok
+
+a5_test_SOURCES = a5_test.c
+a5_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/a5/a5_test.c b/tests/a5/a5_test.c
new file mode 100644
index 0000000..14436f1
--- /dev/null
+++ b/tests/a5/a5_test.c
@@ -0,0 +1,98 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/a5.h>
+
+static const uint8_t key[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+static const uint32_t fn = 123456;
+static const uint8_t dl[] = {
+	/* A5/0 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+	/* A5/1 */
+	0xcb, 0xa2, 0x55, 0x76, 0x17, 0x5d, 0x3b, 0x1c,
+	0x7b, 0x2f, 0x29, 0xa8, 0xc1, 0xb6, 0x00,
+
+	/* A5/2 */
+	0x45, 0x9c, 0x88, 0xc3, 0x82, 0xb7, 0xff, 0xb3,
+	0x98, 0xd2, 0xf9, 0x6e, 0x0f, 0x14, 0x80,
+};
+static const uint8_t ul[] = {
+	/* A5/0 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+	/* A5/1 */
+	0xd9, 0x03, 0x5e, 0x0f, 0x2a, 0xec, 0x13, 0x9a,
+	0x05, 0xd4, 0xa8, 0x7b, 0xb1, 0x64, 0x80,
+
+	/* A5/2 */
+	0xf0, 0x3a, 0xac, 0xde, 0xe3, 0x5b, 0x5e, 0x65,
+	0x80, 0xba, 0xab, 0xc0, 0x59, 0x26, 0x40,
+};
+
+static const char *
+binstr(ubit_t *d, int n)
+{
+	static char str[256];
+	int i;
+
+	for (i=0; i<n; i++)
+		str[i] = d[i] ? '1' : '0';
+
+	str[i] = '\0';
+
+	return str;
+}
+
+int main(int argc, char **argv)
+{
+	ubit_t exp[114];
+	ubit_t out[114];
+	int n, i;
+
+	for (n=0; n<3; n++) {
+		/* "Randomize" */
+		for (i=0; i<114; i++)
+			out[i] = i & 1;
+
+		/* DL */
+		osmo_pbit2ubit(exp, &dl[15*n], 114);
+
+		osmo_a5(n, key, fn, out, NULL);
+
+		printf("A5/%d - DL: %s", n, binstr(out, 114));
+
+		if (!memcmp(exp, out, 114))
+			printf(" => OK\n");
+		else {
+			printf(" => BAD\n");
+			printf(" Expected: %s", binstr(out, 114));
+			fprintf(stderr, "[!] A5/%d DL failed", n);
+			exit(1);
+		}
+
+		/* UL */
+		osmo_pbit2ubit(exp, &ul[15*n], 114);
+
+		osmo_a5(n, key, fn, NULL, out);
+
+		printf("A5/%d - UL: %s", n, binstr(out, 114));
+
+		if (!memcmp(exp, out, 114))
+			printf(" => OK\n");
+		else {
+			printf(" => BAD\n");
+			printf(" Expected: %s", binstr(out, 114));
+			fprintf(stderr, "[!] A5/%d UL failed", n);
+			exit(1);
+		}
+	}
+
+	return 0;
+}
diff --git a/tests/a5/a5_test.ok b/tests/a5/a5_test.ok
new file mode 100644
index 0000000..4497e14
--- /dev/null
+++ b/tests/a5/a5_test.ok
@@ -0,0 +1,6 @@
+A5/0 - DL: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 => OK
+A5/0 - UL: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 => OK
+A5/1 - DL: 110010111010001001010101011101100001011101011101001110110001110001111011001011110010100110101000110000011011011000 => OK
+A5/1 - UL: 110110010000001101011110000011110010101011101100000100111001101000000101110101001010100001111011101100010110010010 => OK
+A5/2 - DL: 010001011001110010001000110000111000001010110111111111111011001110011000110100101111100101101110000011110001010010 => OK
+A5/2 - UL: 111100000011101010101100110111101110001101011011010111100110010110000000101110101010101111000000010110010010011001 => OK
diff --git a/tests/auth/Makefile.am b/tests/auth/Makefile.am
new file mode 100644
index 0000000..52976d0
--- /dev/null
+++ b/tests/auth/Makefile.am
@@ -0,0 +1,8 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = milenage_test
+EXTRA_DIST = milenage_test.ok
+
+milenage_test_SOURCES = milenage_test.c
+milenage_test_LDADD = $(top_builddir)/src/libosmocore.la \
+			$(top_builddir)/src/gsm/libosmogsm.la
+
diff --git a/tests/auth/milenage_test.c b/tests/auth/milenage_test.c
new file mode 100644
index 0000000..7c996f0
--- /dev/null
+++ b/tests/auth/milenage_test.c
@@ -0,0 +1,98 @@
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <osmocom/crypt/auth.h>
+#include <osmocom/core/utils.h>
+
+static void dump_auth_vec(struct osmo_auth_vector *vec)
+{
+	printf("RAND:\t%s\n", osmo_hexdump(vec->rand, sizeof(vec->rand)));
+
+	if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+		printf("AUTN:\t%s\n", osmo_hexdump(vec->autn, sizeof(vec->autn)));
+		printf("IK:\t%s\n", osmo_hexdump(vec->ik, sizeof(vec->ik)));
+		printf("CK:\t%s\n", osmo_hexdump(vec->ck, sizeof(vec->ck)));
+		printf("RES:\t%s\n", osmo_hexdump(vec->res, vec->res_len));
+	}
+
+	if (vec->auth_types & OSMO_AUTH_TYPE_GSM) {
+		printf("SRES:\t%s\n", osmo_hexdump(vec->sres, sizeof(vec->sres)));
+		printf("Kc:\t%s\n", osmo_hexdump(vec->kc, sizeof(vec->kc)));
+	}
+}
+
+static struct osmo_sub_auth_data test_aud = {
+	.type = OSMO_AUTH_TYPE_UMTS,
+	.algo = OSMO_AUTH_ALG_MILENAGE,
+	.u.umts = {
+		.opc = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+			 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.k =   { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+			 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+		.amf = { 0x00, 0x00 },
+		.sqn = 0x22,
+	},
+};
+
+static int opc_test(const struct osmo_sub_auth_data *aud)
+{
+	int rc;
+	uint8_t opc[16];
+#if 0
+	const uint8_t op[16] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+				 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f };
+#else
+	const uint8_t op[16] = { 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 };
+#endif
+
+	rc = milenage_opc_gen(opc, aud->u.umts.k, op);
+
+	printf("OP:\t%s\n", osmo_hexdump(op, sizeof(op)));
+	printf("OPC:\t%s\n", osmo_hexdump(opc, sizeof(opc)));
+	return rc;
+}
+
+int main(int argc, char **argv)
+{
+	struct osmo_auth_vector _vec;
+	struct osmo_auth_vector *vec = &_vec;
+	uint8_t _rand[16];
+	int rc;
+
+#if 0
+	srand(time(NULL));
+	*(uint32_t *)&_rand[0] = rand();
+	*(uint32_t *)(&_rand[4]) = rand();
+	*(uint32_t *)(&_rand[8]) = rand();
+	*(uint32_t *)(&_rand[12]) = rand();
+#else
+	memset(_rand, 0, sizeof(_rand));
+#endif
+	memset(vec, 0, sizeof(*vec));
+
+	rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+	if (rc < 0) {
+		fprintf(stderr, "error generating auth vector\n");
+		exit(1);
+	}
+
+	dump_auth_vec(vec);
+
+	const uint8_t auts[14] = { 0x87, 0x11, 0xa0, 0xec, 0x9e, 0x16, 0x37, 0xdf,
+			     0x17, 0xf8, 0x0b, 0x38, 0x4e, 0xe4 };
+
+	rc = osmo_auth_gen_vec_auts(vec, &test_aud, auts, _rand, _rand);
+	if (rc < 0) {
+		printf("AUTS failed\n");
+	} else {
+		printf("AUTS success: SEQ.MS = %lu\n", test_aud.u.umts.sqn);
+	}
+
+	opc_test(&test_aud);
+
+	exit(0);
+
+}
diff --git a/tests/auth/milenage_test.ok b/tests/auth/milenage_test.ok
new file mode 100644
index 0000000..00ffc22
--- /dev/null
+++ b/tests/auth/milenage_test.ok
@@ -0,0 +1,10 @@
+RAND:	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+AUTN:	ec 93 20 c2 c2 12 00 00 c8 b7 de 2a 34 49 f1 bd 
+IK:	12 cb 2d d3 e0 ec 83 78 f6 fc 1d 60 6c 61 9f 47 
+CK:	72 00 a1 84 d8 f2 c7 58 fb df 87 90 0d db f2 75 
+RES:	e9 fc 88 cc c8 a3 53 81 
+SRES:	21 5f db 4d 
+Kc:	6d e8 16 a7 59 a4 29 12 
+AUTS success: SEQ.MS = 33
+OP:	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
+OPC:	c6 a1 3b 37 87 8f 5b 82 6f 4f 81 62 a1 c8 d8 79 
diff --git a/tests/bits/Makefile.am b/tests/bits/Makefile.am
new file mode 100644
index 0000000..d6fb2f2
--- /dev/null
+++ b/tests/bits/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = bitrev_test
+EXTRA_DIST = bitrev_test.ok
+
+bitrev_test_SOURCES = bitrev_test.c
+bitrev_test_LDADD = $(top_builddir)/src/libosmocore.la
+
diff --git a/tests/bits/bitrev_test.c b/tests/bits/bitrev_test.c
new file mode 100644
index 0000000..5eca990
--- /dev/null
+++ b/tests/bits/bitrev_test.c
@@ -0,0 +1,36 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
+
+static const uint8_t input[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+static const uint8_t exp_out[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+int main(int argc, char **argv)
+{
+	uint8_t out[ARRAY_SIZE(input)];
+	unsigned int offs;
+
+	for (offs = 0; offs < sizeof(out); offs++) {
+		uint8_t *start = out + offs;
+		uint8_t len = sizeof(out) - offs;
+
+		memcpy(out, input, sizeof(out));
+
+		printf("INORDER:  %s\n", osmo_hexdump(start, len));
+		osmo_revbytebits_buf(start, len);
+		printf("REVERSED: %s\n", osmo_hexdump(start, len));
+		if (memcmp(start, exp_out + offs, len)) {
+			printf("EXPECTED: %s\n", osmo_hexdump(exp_out+offs, len));
+			fprintf(stderr, "REVERSED != EXPECTED!\n");
+			exit(1);
+		}
+		printf("\n");
+	}
+
+	return 0;
+}
diff --git a/tests/bits/bitrev_test.ok b/tests/bits/bitrev_test.ok
new file mode 100644
index 0000000..47f402f
--- /dev/null
+++ b/tests/bits/bitrev_test.ok
@@ -0,0 +1,24 @@
+INORDER:  01 02 04 08 10 20 40 80 
+REVERSED: 80 40 20 10 08 04 02 01 
+
+INORDER:  02 04 08 10 20 40 80 
+REVERSED: 40 20 10 08 04 02 01 
+
+INORDER:  04 08 10 20 40 80 
+REVERSED: 20 10 08 04 02 01 
+
+INORDER:  08 10 20 40 80 
+REVERSED: 10 08 04 02 01 
+
+INORDER:  10 20 40 80 
+REVERSED: 08 04 02 01 
+
+INORDER:  20 40 80 
+REVERSED: 04 02 01 
+
+INORDER:  40 80 
+REVERSED: 02 01 
+
+INORDER:  80 
+REVERSED: 01 
+
diff --git a/tests/conv/Makefile.am b/tests/conv/Makefile.am
new file mode 100644
index 0000000..75cfec8
--- /dev/null
+++ b/tests/conv/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = conv_test
+EXTRA_DIST = conv_test.ok
+
+conv_test_SOURCES = conv_test.c
+conv_test_LDADD = $(top_builddir)/src/libosmocore.la
diff --git a/tests/conv/conv_test.c b/tests/conv/conv_test.c
new file mode 100644
index 0000000..54e043e
--- /dev/null
+++ b/tests/conv/conv_test.c
@@ -0,0 +1,486 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/conv.h>
+#include <osmocom/core/utils.h>
+
+#define MAX_LEN_BITS	512
+#define MAX_LEN_BYTES	(512/8)
+
+
+/* ------------------------------------------------------------------------ */
+/* Test codes                                                               */
+/* ------------------------------------------------------------------------ */
+
+/* GSM xCCH -> Non-recursive code, flushed, not punctured */
+static const uint8_t conv_gsm_xcch_next_output[][2] = {
+	{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+	{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+	{ 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 },
+	{ 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 },
+};
+
+static const uint8_t conv_gsm_xcch_next_state[][2] = {
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+};
+
+static const struct osmo_conv_code conv_gsm_xcch = {
+	.N = 2,
+	.K = 5,
+	.len = 224,
+	.term = CONV_TERM_FLUSH,
+	.next_output = conv_gsm_xcch_next_output,
+	.next_state  = conv_gsm_xcch_next_state,
+};
+
+
+/* GSM TCH/AFS 7.95 -> Recursive code, flushed, with puncturing */
+static const uint8_t conv_gsm_tch_afs_7_95_next_output[][2] = {
+	{ 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 },
+	{ 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 },
+	{ 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 },
+	{ 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 },
+	{ 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 },
+	{ 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 },
+	{ 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 },
+	{ 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 },
+	{ 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 },
+	{ 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 },
+	{ 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 },
+	{ 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 },
+	{ 3, 4 }, { 0, 7 }, { 1, 6 }, { 2, 5 },
+	{ 1, 6 }, { 2, 5 }, { 3, 4 }, { 0, 7 },
+	{ 0, 7 }, { 3, 4 }, { 2, 5 }, { 1, 6 },
+	{ 2, 5 }, { 1, 6 }, { 0, 7 }, { 3, 4 },
+};
+
+static const uint8_t conv_gsm_tch_afs_7_95_next_state[][2] = {
+	{  0,  1 }, {  2,  3 }, {  5,  4 }, {  7,  6 },
+	{  9,  8 }, { 11, 10 }, { 12, 13 }, { 14, 15 },
+	{ 16, 17 }, { 18, 19 }, { 21, 20 }, { 23, 22 },
+	{ 25, 24 }, { 27, 26 }, { 28, 29 }, { 30, 31 },
+	{ 33, 32 }, { 35, 34 }, { 36, 37 }, { 38, 39 },
+	{ 40, 41 }, { 42, 43 }, { 45, 44 }, { 47, 46 },
+	{ 49, 48 }, { 51, 50 }, { 52, 53 }, { 54, 55 },
+	{ 56, 57 }, { 58, 59 }, { 61, 60 }, { 63, 62 },
+	{  1,  0 }, {  3,  2 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 13, 12 }, { 15, 14 },
+	{ 17, 16 }, { 19, 18 }, { 20, 21 }, { 22, 23 },
+	{ 24, 25 }, { 26, 27 }, { 29, 28 }, { 31, 30 },
+	{ 32, 33 }, { 34, 35 }, { 37, 36 }, { 39, 38 },
+	{ 41, 40 }, { 43, 42 }, { 44, 45 }, { 46, 47 },
+	{ 48, 49 }, { 50, 51 }, { 53, 52 }, { 55, 54 },
+	{ 57, 56 }, { 59, 58 }, { 60, 61 }, { 62, 63 },
+};
+
+static const uint8_t conv_gsm_tch_afs_7_95_next_term_output[] = {
+	 0,  3,  5,  6,  5,  6,  0,  3,  3,  0,  6,  5,  6,  5,  3,  0,
+	 4,  7,  1,  2,  1,  2,  4,  7,  7,  4,  2,  1,  2,  1,  7,  4,
+	 7,  4,  2,  1,  2,  1,  7,  4,  4,  7,  1,  2,  1,  2,  4,  7,
+	 3,  0,  6,  5,  6,  5,  3,  0,  0,  3,  5,  6,  5,  6,  0,  3,
+};
+
+static const uint8_t conv_gsm_tch_afs_7_95_next_term_state[] = {
+	 0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+	32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
+	 0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+	32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
+};
+
+static int conv_gsm_tch_afs_7_95_puncture[] = {
+	  1,   2,   4,   5,   8,  22,  70, 118, 166, 214, 262, 310,
+	317, 319, 325, 332, 334, 341, 343, 349, 356, 358, 365, 367,
+	373, 380, 382, 385, 389, 391, 397, 404, 406, 409, 413, 415,
+	421, 428, 430, 433, 437, 439, 445, 452, 454, 457, 461, 463,
+	469, 476, 478, 481, 485, 487, 490, 493, 500, 502, 503, 505,
+	506, 508, 509, 511, 512,
+	-1, /* end */
+};
+
+static const struct osmo_conv_code conv_gsm_tch_afs_7_95 = {
+	.N = 3,
+	.K = 7,
+	.len = 165,
+	.term = CONV_TERM_FLUSH,
+	.next_output      = conv_gsm_tch_afs_7_95_next_output,
+	.next_state       = conv_gsm_tch_afs_7_95_next_state,
+	.next_term_output = conv_gsm_tch_afs_7_95_next_term_output,
+	.next_term_state  = conv_gsm_tch_afs_7_95_next_term_state,
+	.puncture         = conv_gsm_tch_afs_7_95_puncture,
+};
+
+
+/* GMR-1 TCH3 Speech -> Non recursive code, tail-biting, punctured */
+static const uint8_t conv_gmr1_tch3_speech_next_output[][2] = {
+	{  0,  3 }, {  1,  2 }, {  3,  0 }, {  2,  1 },
+	{  3,  0 }, {  2,  1 }, {  0,  3 }, {  1,  2 },
+	{  0,  3 }, {  1,  2 }, {  3,  0 }, {  2,  1 },
+	{  3,  0 }, {  2,  1 }, {  0,  3 }, {  1,  2 },
+	{  2,  1 }, {  3,  0 }, {  1,  2 }, {  0,  3 },
+	{  1,  2 }, {  0,  3 }, {  2,  1 }, {  3,  0 },
+	{  2,  1 }, {  3,  0 }, {  1,  2 }, {  0,  3 },
+	{  1,  2 }, {  0,  3 }, {  2,  1 }, {  3,  0 },
+	{  3,  0 }, {  2,  1 }, {  0,  3 }, {  1,  2 },
+	{  0,  3 }, {  1,  2 }, {  3,  0 }, {  2,  1 },
+	{  3,  0 }, {  2,  1 }, {  0,  3 }, {  1,  2 },
+	{  0,  3 }, {  1,  2 }, {  3,  0 }, {  2,  1 },
+	{  1,  2 }, {  0,  3 }, {  2,  1 }, {  3,  0 },
+	{  2,  1 }, {  3,  0 }, {  1,  2 }, {  0,  3 },
+	{  1,  2 }, {  0,  3 }, {  2,  1 }, {  3,  0 },
+	{  2,  1 }, {  3,  0 }, {  1,  2 }, {  0,  3 },
+};
+
+static const uint8_t conv_gmr1_tch3_speech_next_state[][2] = {
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+	{ 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 },
+	{ 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 },
+	{ 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 },
+	{ 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 },
+	{ 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 },
+	{ 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 },
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+	{ 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 },
+	{ 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 },
+	{ 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 },
+	{ 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 },
+	{ 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 },
+	{ 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 },
+};
+
+static const int conv_gmr1_tch3_speech_puncture[] = {
+	 3,  7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47,
+	51, 55, 59, 63, 67, 71, 75, 79, 83, 87, 91, 95,
+	-1, /* end */
+};
+
+static const struct osmo_conv_code conv_gmr1_tch3_speech = {
+	.N = 2,
+	.K = 7,
+	.len = 48,
+	.term = CONV_TERM_TAIL_BITING,
+	.next_output = conv_gmr1_tch3_speech_next_output,
+	.next_state  = conv_gmr1_tch3_speech_next_state,
+	.puncture = conv_gmr1_tch3_speech_puncture,
+};
+
+
+/* WiMax FCH -> Non recursive code, tail-biting, non-punctured */
+static const uint8_t conv_wimax_fch_next_output[][2] = {
+	{  0,  3 }, {  2,  1 }, {  3,  0 }, {  1,  2 },
+	{  3,  0 }, {  1,  2 }, {  0,  3 }, {  2,  1 },
+	{  0,  3 }, {  2,  1 }, {  3,  0 }, {  1,  2 },
+	{  3,  0 }, {  1,  2 }, {  0,  3 }, {  2,  1 },
+	{  1,  2 }, {  3,  0 }, {  2,  1 }, {  0,  3 },
+	{  2,  1 }, {  0,  3 }, {  1,  2 }, {  3,  0 },
+	{  1,  2 }, {  3,  0 }, {  2,  1 }, {  0,  3 },
+	{  2,  1 }, {  0,  3 }, {  1,  2 }, {  3,  0 },
+	{  3,  0 }, {  1,  2 }, {  0,  3 }, {  2,  1 },
+	{  0,  3 }, {  2,  1 }, {  3,  0 }, {  1,  2 },
+	{  3,  0 }, {  1,  2 }, {  0,  3 }, {  2,  1 },
+	{  0,  3 }, {  2,  1 }, {  3,  0 }, {  1,  2 },
+	{  2,  1 }, {  0,  3 }, {  1,  2 }, {  3,  0 },
+	{  1,  2 }, {  3,  0 }, {  2,  1 }, {  0,  3 },
+	{  2,  1 }, {  0,  3 }, {  1,  2 }, {  3,  0 },
+	{  1,  2 }, {  3,  0 }, {  2,  1 }, {  0,  3 },
+};
+
+static const uint8_t conv_wimax_fch_next_state[][2] = {
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+	{ 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 },
+	{ 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 },
+	{ 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 },
+	{ 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 },
+	{ 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 },
+	{ 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 },
+	{  0,  1 }, {  2,  3 }, {  4,  5 }, {  6,  7 },
+	{  8,  9 }, { 10, 11 }, { 12, 13 }, { 14, 15 },
+	{ 16, 17 }, { 18, 19 }, { 20, 21 }, { 22, 23 },
+	{ 24, 25 }, { 26, 27 }, { 28, 29 }, { 30, 31 },
+	{ 32, 33 }, { 34, 35 }, { 36, 37 }, { 38, 39 },
+	{ 40, 41 }, { 42, 43 }, { 44, 45 }, { 46, 47 },
+	{ 48, 49 }, { 50, 51 }, { 52, 53 }, { 54, 55 },
+	{ 56, 57 }, { 58, 59 }, { 60, 61 }, { 62, 63 },
+};
+
+static const struct osmo_conv_code conv_wimax_fch = {
+	.N = 2,
+	.K = 7,
+	.len = 48,
+	.term = CONV_TERM_TAIL_BITING,
+	.next_output = conv_wimax_fch_next_output,
+	.next_state  = conv_wimax_fch_next_state,
+};
+
+
+/* Random code -> Non recursive code, direct truncation, non-punctured */
+static const struct osmo_conv_code conv_trunc = {
+	.N = 2,
+	.K = 5,
+	.len = 224,
+	.term = CONV_TERM_TRUNCATION,
+	.next_output = conv_gsm_xcch_next_output,
+	.next_state  = conv_gsm_xcch_next_state,
+};
+
+
+/* ------------------------------------------------------------------------ */
+/* Test vectors                                                             */
+/* ------------------------------------------------------------------------ */
+
+struct conv_test_vector {
+	const char *name;
+	const struct osmo_conv_code *code;
+	int in_len;
+	int out_len;
+	int has_vec;
+	pbit_t vec_in[MAX_LEN_BYTES];
+	pbit_t vec_out[MAX_LEN_BYTES];
+};
+
+static const struct conv_test_vector tests[] = {
+	{
+		.name = "GSM xCCH (non-recursive, flushed, not punctured)",
+		.code = &conv_gsm_xcch,
+		.in_len  = 224,
+		.out_len = 456,
+		.has_vec = 1,
+		.vec_in  = { 0xf3, 0x1d, 0xb4, 0x0c, 0x4d, 0x1d, 0x9d, 0xae,
+		             0xc0, 0x0a, 0x42, 0x57, 0x13, 0x60, 0x80, 0x96,
+		             0xef, 0x23, 0x7e, 0x4c, 0x1d, 0x96, 0x24, 0x19,
+		             0x17, 0xf2, 0x44, 0x99 },
+		.vec_out = { 0xe9, 0x4d, 0x70, 0xab, 0xa2, 0x87, 0xf0, 0xe7,
+		             0x04, 0x14, 0x7c, 0xab, 0xaf, 0x6b, 0xa1, 0x16,
+		             0xeb, 0x30, 0x00, 0xde, 0xc8, 0xfd, 0x0b, 0x85,
+		             0x80, 0x41, 0x4a, 0xcc, 0xd3, 0xc0, 0xd0, 0xb6,
+		             0x26, 0xe5, 0x4e, 0x32, 0x49, 0x69, 0x38, 0x17,
+		             0x33, 0xab, 0xaf, 0xb6, 0xc1, 0x08, 0xf3, 0x9f,
+		             0x8c, 0x75, 0x6a, 0x4e, 0x08, 0xc4, 0x20, 0x5f,
+		             0x8f },
+	},
+	{
+		.name = "GSM TCH/AFS 7.95 (recursive, flushed, punctured)",
+		.code = &conv_gsm_tch_afs_7_95,
+		.in_len  = 165,
+		.out_len = 448,
+		.has_vec = 1,
+		.vec_in  = { 0x87, 0x66, 0xc3, 0x58, 0x09, 0xd4, 0x06, 0x59,
+		             0x10, 0xbf, 0x6b, 0x7f, 0xc8, 0xed, 0x72, 0xaa,
+		             0xc1, 0x3d, 0xf3, 0x1e, 0xb0 },
+		.vec_out = { 0x92, 0xbc, 0xde, 0xa0, 0xde, 0xbe, 0x01, 0x2f,
+		             0xbe, 0xe4, 0x61, 0x32, 0x4d, 0x4f, 0xdc, 0x41,
+		             0x43, 0x0d, 0x15, 0xe0, 0x23, 0xdd, 0x18, 0x91,
+		             0xe5, 0x36, 0x2d, 0xb7, 0xd9, 0x78, 0xb8, 0xb1,
+		             0xb7, 0xcb, 0x2f, 0xc0, 0x52, 0x8f, 0xe2, 0x8c,
+		             0x6f, 0xa6, 0x79, 0x88, 0xed, 0x0c, 0x2e, 0x9e,
+		             0xa1, 0x5f, 0x45, 0x4a, 0xfb, 0xe6, 0x5a, 0x9c },
+	},
+	{
+		.name = "GMR-1 TCH3 Speech (non-recursive, tail-biting, punctured)",
+		.code = &conv_gmr1_tch3_speech,
+		.in_len  = 48,
+		.out_len = 72,
+		.has_vec = 1,
+		.vec_in  = { 0x4d, 0xcb, 0xfc, 0x72, 0xf4, 0x8c },
+		.vec_out = { 0xc0, 0x86, 0x63, 0x4b, 0x8b, 0xd4, 0x6a, 0x76, 0xb2 },
+	},
+	{
+		.name = "WiMax FCH (non-recursive, tail-biting, not punctured)",
+		.code = &conv_wimax_fch,
+		.in_len  = 48,
+		.out_len = 96,
+		.has_vec = 1,
+		.vec_in  = { 0xfc, 0xa0, 0xa0, 0xfc, 0xa0, 0xa0 },
+		.vec_out = { 0x19, 0x42, 0x8a, 0xed, 0x21, 0xed, 0x19, 0x42,
+		             0x8a, 0xed, 0x21, 0xed },
+	},
+	{
+		.name = "??? (non-recursive, direct truncation, not punctured)",
+		.code = &conv_trunc,
+		.in_len  = 224,
+		.out_len = 448,
+		.has_vec = 1,
+		.vec_in  = { 0xe5, 0xe0, 0x85, 0x7e, 0xf7, 0x08, 0x19, 0x5a,
+		             0xb9, 0xad, 0x82, 0x37, 0x98, 0x8b, 0x26, 0xb9,
+		             0x81, 0x26, 0x9c, 0x75, 0xaf, 0xf3, 0xcb, 0x07,
+		             0xac, 0x63, 0xe2, 0x9c,
+		},
+		.vec_out = { 0xea, 0x3b, 0x55, 0x0c, 0xd3, 0xf7, 0x85, 0x69,
+		             0xe5, 0x79, 0x83, 0xd3, 0xc3, 0x9f, 0xb8, 0x61,
+		             0x21, 0x63, 0x51, 0x18, 0xac, 0xcd, 0x32, 0x49,
+		             0x53, 0x5c, 0x13, 0x1d, 0xbe, 0x05, 0x11, 0x63,
+		             0x5c, 0xc3, 0x42, 0x05, 0x1c, 0x68, 0x0a, 0xb4,
+		             0x61, 0x15, 0xaa, 0x4d, 0x94, 0xed, 0xb3, 0x3a,
+		             0x5d, 0x1b, 0x09, 0xc2, 0x99, 0x01, 0xec, 0x68 },
+	},
+	{ /* end */ },
+};
+
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Main                                                                     */
+/* ------------------------------------------------------------------------ */
+
+static void
+fill_random(ubit_t *b, int n)
+{
+	int i;
+	for (i=0; i<n; i++)
+		b[i] = random() & 1;
+}
+
+static void
+ubit_to_sbit(sbit_t *dst, ubit_t *src, int n)
+{
+	int i;
+	for (i=0; i<n; i++)
+		dst[i] = src[i] ? -127 : 127;
+}
+
+static void
+sbit_to_ubit(ubit_t *dst, sbit_t *src, int n)
+{
+	int i;
+	for (i=0; i<n; i++)
+		dst[i] = src[i] < 0;
+}
+
+
+int main(int argc, char argv[])
+{
+	const struct conv_test_vector *tst;
+	ubit_t *bu0, *bu1;
+	sbit_t *bs;
+
+	srandom(time(NULL));
+
+	bu0 = malloc(sizeof(ubit_t) * MAX_LEN_BITS);
+	bu1 = malloc(sizeof(ubit_t) * MAX_LEN_BITS);
+	bs  = malloc(sizeof(sbit_t) * MAX_LEN_BITS);
+
+	for (tst=tests; tst->name; tst++)
+	{
+		int i,l;
+
+		/* Test name */
+		printf("[+] Testing: %s\n", tst->name);
+
+		/* Check length */
+		l = osmo_conv_get_input_length(tst->code, 0);
+		printf("[.] Input length  : ret = %3d  exp = %3d -> %s\n",
+			l, tst->in_len, l == tst->in_len ? "OK" : "Bad !");
+
+		if (l != tst->in_len) {
+			fprintf(stderr, "[!] Failure for input length computation\n");
+			return -1;
+		}
+
+		l = osmo_conv_get_output_length(tst->code, 0);
+		printf("[.] Output length : ret = %3d  exp = %3d -> %s\n",
+			l, tst->out_len, l == tst->out_len ? "OK" : "Bad !");
+
+		if (l != tst->out_len) {
+			fprintf(stderr, "[!] Failure for output length computation\n");
+			return -1;
+		}
+
+		/* Check pre-computed vector */
+		if (tst->has_vec) {
+			printf("[.] Pre computed vector checks:\n");
+
+			printf("[..] Encoding: ");
+
+			osmo_pbit2ubit(bu0, tst->vec_in, tst->in_len);
+
+			l = osmo_conv_encode(tst->code, bu0, bu1);
+			if (l != tst->out_len) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed encoding length check\n");
+				return -1;
+			}
+
+			osmo_pbit2ubit(bu0, tst->vec_out, tst->out_len);
+
+			if (memcmp(bu0, bu1, tst->out_len)) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed encoding: Results don't match\n");
+				return -1;
+			};
+
+			printf("OK\n");
+
+
+			printf("[..] Decoding: ");
+
+			ubit_to_sbit(bs, bu0, l);
+
+			l = osmo_conv_decode(tst->code, bs, bu1);
+			if (l != 0) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed decoding: non-zero path (%d)\n", l);
+				return -1;
+			}
+
+			osmo_pbit2ubit(bu0, tst->vec_in, tst->in_len);
+
+			if (memcmp(bu0, bu1, tst->in_len)) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed decoding: Results don't match\n");
+				return -1;
+			}
+
+			printf("OK\n");
+		}
+
+		/* Check random vector */
+		printf("[.] Random vector checks:\n");
+
+		for (i=0; i<3; i++) {
+			printf("[..] Encoding / Decoding cycle : ");
+
+			fill_random(bu0, tst->in_len);
+
+			l = osmo_conv_encode(tst->code, bu0, bu1);
+			if (l != tst->out_len) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed encoding length check\n");
+				return -1;
+			}
+
+			ubit_to_sbit(bs, bu1, l);
+
+			l = osmo_conv_decode(tst->code, bs, bu1);
+			if (l != 0) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed decoding: non-zero path (%d)\n", l);
+				return -1;
+			}
+
+			if (memcmp(bu0, bu1, tst->in_len)) {
+				printf("ERROR !\n");
+				fprintf(stderr, "[!] Failed decoding: Results don't match\n");
+				return -1;
+			}
+
+			printf("OK\n");
+		}
+
+		/* Spacing */
+		printf("\n");
+	}
+
+	free(bs);
+	free(bu1);
+	free(bu0);
+
+	return 0;
+}
diff --git a/tests/conv/conv_test.ok b/tests/conv/conv_test.ok
new file mode 100644
index 0000000..2122961
--- /dev/null
+++ b/tests/conv/conv_test.ok
@@ -0,0 +1,55 @@
+[+] Testing: GSM xCCH (non-recursive, flushed, not punctured)
+[.] Input length  : ret = 224  exp = 224 -> OK
+[.] Output length : ret = 456  exp = 456 -> OK
+[.] Pre computed vector checks:
+[..] Encoding: OK
+[..] Decoding: OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: GSM TCH/AFS 7.95 (recursive, flushed, punctured)
+[.] Input length  : ret = 165  exp = 165 -> OK
+[.] Output length : ret = 448  exp = 448 -> OK
+[.] Pre computed vector checks:
+[..] Encoding: OK
+[..] Decoding: OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: GMR-1 TCH3 Speech (non-recursive, tail-biting, punctured)
+[.] Input length  : ret =  48  exp =  48 -> OK
+[.] Output length : ret =  72  exp =  72 -> OK
+[.] Pre computed vector checks:
+[..] Encoding: OK
+[..] Decoding: OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: WiMax FCH (non-recursive, tail-biting, not punctured)
+[.] Input length  : ret =  48  exp =  48 -> OK
+[.] Output length : ret =  96  exp =  96 -> OK
+[.] Pre computed vector checks:
+[..] Encoding: OK
+[..] Decoding: OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: ??? (non-recursive, direct truncation, not punctured)
+[.] Input length  : ret = 224  exp = 224 -> OK
+[.] Output length : ret = 448  exp = 448 -> OK
+[.] Pre computed vector checks:
+[..] Encoding: OK
+[..] Decoding: OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
diff --git a/tests/gsm0808/Makefile.am b/tests/gsm0808/Makefile.am
new file mode 100644
index 0000000..a238e7f
--- /dev/null
+++ b/tests/gsm0808/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = gsm0808_test
+EXTRA_DIST = gsm0808_test.ok
+
+gsm0808_test_SOURCES = gsm0808_test.c
+gsm0808_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/gsm0808/gsm0808_test.c b/tests/gsm0808/gsm0808_test.c
new file mode 100644
index 0000000..7e5e97b
--- /dev/null
+++ b/tests/gsm0808/gsm0808_test.c
@@ -0,0 +1,269 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define VERIFY(msg, data, len) 						\
+	if (msgb_l3len(msg) != len) {					\
+		printf("%s:%d Length don't match: %d vs. %d. %s\n", 	\
+			__func__, __LINE__, msgb_l3len(msg), len,	\
+			osmo_hexdump(msg->l3h, msgb_l3len(msg))); 	\
+		abort();						\
+	} else if (memcmp(msg->l3h, data, len) != 0) {			\
+		printf("%s:%d didn't match: got: %s\n",			\
+			__func__, __LINE__,				\
+			osmo_hexdump(msg->l3h, msgb_l3len(msg)));	\
+		abort();						\
+	}
+
+
+static void test_create_layer3(void)
+{
+	static const uint8_t res[] = {
+		0x00, 0x0e, 0x57, 0x05, 0x08, 0x00, 0x77, 0x62,
+		0x83, 0x33, 0x66, 0x44, 0x88, 0x17, 0x01, 0x23 };
+	struct msgb *msg, *in_msg;
+	printf("Testing creating Layer3\n");
+
+	in_msg = msgb_alloc_headroom(512, 128, "foo");
+	in_msg->l3h = in_msg->data;
+	msgb_v_put(in_msg, 0x23);
+
+	msg = gsm0808_create_layer3(in_msg, 0x1122, 0x2244, 0x3366, 0x4488);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+	msgb_free(in_msg);
+}
+
+static void test_create_reset()
+{
+	static const uint8_t res[] = { 0x00, 0x04, 0x30, 0x04, 0x01, 0x20 };
+	struct msgb *msg;
+
+	printf("Testing creating Reset\n");
+	msg = gsm0808_create_reset();
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_clear_command()
+{
+	static const uint8_t res[] = { 0x20, 0x04, 0x01, 0x23 };
+	struct msgb *msg;
+
+	printf("Testing creating Clear Command\n");
+	msg = gsm0808_create_clear_command(0x23);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_clear_complete()
+{
+	static const uint8_t res[] = { 0x00, 0x01, 0x21 };
+	struct msgb *msg;
+
+	printf("Testing creating Clear Complete\n");
+	msg = gsm0808_create_clear_complete();
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_cipher_complete()
+{
+	static const uint8_t res1[] = {
+		0x00, 0x08, 0x55, 0x20, 0x03, 0x23, 0x42, 0x21, 0x2c, 0x04 };
+	static const uint8_t res2[] = { 0x00, 0x03, 0x55, 0x2c, 0x04};
+	struct msgb *l3, *msg;
+
+	printf("Testing creating Cipher Complete\n");
+	l3 = msgb_alloc_headroom(512, 128, "l3h");
+	l3->l3h = l3->data;
+	msgb_v_put(l3, 0x23);
+	msgb_v_put(l3, 0x42);
+	msgb_v_put(l3, 0x21);
+
+	/* with l3 data */
+	msg = gsm0808_create_cipher_complete(l3, 4);
+	VERIFY(msg, res1, ARRAY_SIZE(res1));
+	msgb_free(msg);
+
+	/* with l3 data but short */
+	l3->len -= 1;
+	l3->tail -= 1;
+	msg = gsm0808_create_cipher_complete(l3, 4);
+	VERIFY(msg, res2, ARRAY_SIZE(res2));
+	msgb_free(msg);
+
+	/* without l3 data */
+	msg = gsm0808_create_cipher_complete(NULL, 4);
+	VERIFY(msg, res2, ARRAY_SIZE(res2));
+	msgb_free(msg);
+
+
+	msgb_free(l3);
+}
+
+static void test_create_cipher_reject()
+{
+	static const uint8_t res[] = { 0x00, 0x02, 0x59, 0x23 };
+	struct msgb *msg;
+
+	printf("Testing creating Cipher Reject\n");
+	msg = gsm0808_create_cipher_reject(0x23);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_cm_u()
+{
+	static const uint8_t res[] = {
+		0x00, 0x07, 0x54, 0x12, 0x01, 0x23, 0x13, 0x01, 0x42 };
+	static const uint8_t res2o[] = {
+		0x00, 0x04, 0x54, 0x12, 0x01, 0x23 };
+	struct msgb *msg;
+	const uint8_t cm2 = 0x23;
+	const uint8_t cm3 = 0x42;
+
+	printf("Testing creating CM U\n");
+	msg = gsm0808_create_classmark_update(&cm2, 1, &cm3, 1);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+
+	msg = gsm0808_create_classmark_update(&cm2, 1, NULL, 0);
+	VERIFY(msg, res2o, ARRAY_SIZE(res2o));
+
+	msgb_free(msg);
+}
+
+static void test_create_sapi_reject()
+{
+	static const uint8_t res[] = { 0x00, 0x03, 0x25, 0x03, 0x25 };
+	struct msgb *msg;
+
+	printf("Testing creating SAPI Reject\n");
+	msg = gsm0808_create_sapi_reject(3);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_ass_compl()
+{
+	static const uint8_t res1[] = {
+		0x00, 0x09, 0x02, 0x15, 0x23, 0x21, 0x42, 0x2c,
+		0x11, 0x40, 0x22 };
+	static const uint8_t res2[] = {
+		0x00, 0x07, 0x02, 0x15, 0x23, 0x21, 0x42, 0x2c, 0x11};
+	struct msgb *msg;
+
+	printf("Testing creating Assignment Complete\n");
+	msg = gsm0808_create_assignment_completed(0x23, 0x42, 0x11, 0x22);
+	VERIFY(msg, res1, ARRAY_SIZE(res1));
+	msgb_free(msg);
+
+	msg = gsm0808_create_assignment_completed(0x23, 0x42, 0x11, 0);
+	VERIFY(msg, res2, ARRAY_SIZE(res2));
+	msgb_free(msg);
+}
+
+static void test_create_ass_fail()
+{
+	static const uint8_t res1[] = { 0x00, 0x04, 0x03, 0x04, 0x01, 0x23 };
+	static const uint8_t res2[] = {
+		0x00, 0x06, 0x03, 0x04, 0x01, 0x23, 0x15, 0x02};
+	uint8_t rr_res = 2;
+	struct msgb *msg;
+
+	printf("Testing creating Assignment Failure\n");
+	msg = gsm0808_create_assignment_failure(0x23, NULL);
+	VERIFY(msg, res1, ARRAY_SIZE(res1));
+	msgb_free(msg);
+
+	msg = gsm0808_create_assignment_failure(0x23, &rr_res);
+	VERIFY(msg, res2, ARRAY_SIZE(res2));
+	msgb_free(msg);
+}
+
+static void test_create_clear_rqst()
+{
+	static const uint8_t res[] = { 0x00, 0x04, 0x22, 0x04, 0x01, 0x23 };
+	struct msgb *msg;
+
+	printf("Testing creating Clear Request\n");
+	msg = gsm0808_create_clear_rqst(0x23);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+}
+
+static void test_create_dtap()
+{
+	static const uint8_t res[] = { 0x01, 0x03, 0x02, 0x23, 0x42 };
+	struct msgb *msg, *l3;
+
+	printf("Testing creating DTAP\n");
+	l3 = msgb_alloc_headroom(512, 128, "test");
+	l3->l3h = l3->data;
+	msgb_v_put(l3, 0x23);
+	msgb_v_put(l3, 0x42);
+
+	msg = gsm0808_create_dtap(l3, 0x3);
+	VERIFY(msg, res, ARRAY_SIZE(res));
+	msgb_free(msg);
+	msgb_free(l3);
+}
+
+static void test_prepend_dtap()
+{
+	static const uint8_t res[] = { 0x01, 0x03, 0x02, 0x23, 0x42 };
+	struct msgb *in_msg;
+
+	printf("Testing prepend DTAP\n");
+
+	in_msg = msgb_alloc_headroom(512, 128, "test");
+	msgb_v_put(in_msg, 0x23);
+	msgb_v_put(in_msg, 0x42);
+
+	gsm0808_prepend_dtap_header(in_msg, 0x3);
+	in_msg->l3h = in_msg->data;
+	VERIFY(in_msg, res, ARRAY_SIZE(res));
+	msgb_free(in_msg);
+}
+
+int main(int argc, char **argv)
+{
+	printf("Testing generation of GSM0808 messages\n");
+	test_create_layer3();
+	test_create_reset();
+	test_create_clear_command();
+	test_create_clear_complete();
+	test_create_cipher_complete();
+	test_create_cipher_reject();
+	test_create_cm_u();
+	test_create_sapi_reject();
+	test_create_ass_compl();
+	test_create_ass_fail();
+	test_create_clear_rqst();
+	test_create_dtap();
+	test_prepend_dtap();
+
+	printf("Done\n");
+	return EXIT_SUCCESS;
+}
diff --git a/tests/gsm0808/gsm0808_test.ok b/tests/gsm0808/gsm0808_test.ok
new file mode 100644
index 0000000..eb43126
--- /dev/null
+++ b/tests/gsm0808/gsm0808_test.ok
@@ -0,0 +1,15 @@
+Testing generation of GSM0808 messages
+Testing creating Layer3
+Testing creating Reset
+Testing creating Clear Command
+Testing creating Clear Complete
+Testing creating Cipher Complete
+Testing creating Cipher Reject
+Testing creating CM U
+Testing creating SAPI Reject
+Testing creating Assignment Complete
+Testing creating Assignment Failure
+Testing creating Clear Request
+Testing creating DTAP
+Testing prepend DTAP
+Done
diff --git a/tests/lapd/Makefile.am b/tests/lapd/Makefile.am
new file mode 100644
index 0000000..f7e2ab0
--- /dev/null
+++ b/tests/lapd/Makefile.am
@@ -0,0 +1,9 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_FLAGS = -Wall -O0
+noinst_PROGRAMS = lapd_test
+EXTRA_DIST = lapd_test.ok
+
+lapd_test_SOURCES = lapd_test.c
+lapd_test_LDADD = \
+	$(top_builddir)/src/libosmocore.la \
+	$(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/lapd/lapd_test.c b/tests/lapd/lapd_test.c
new file mode 100644
index 0000000..d58bec6
--- /dev/null
+++ b/tests/lapd/lapd_test.c
@@ -0,0 +1,319 @@
+/*
+ * (C) 2011 by Holger Hans Peter Freyther
+ * (C) 2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <errno.h>
+
+#include <string.h>
+
+#define CHECK_RC(rc)	\
+	if (rc != 0) {	\
+		printf("Operation failed rc=%d on %s:%d\n", rc, __FILE__, __LINE__); \
+		abort(); \
+	}
+
+#define ASSERT(exp)    \
+	if (!(exp)) { \
+		printf("Assert failed %s %s:%d\n", #exp, __FILE__, __LINE__); \
+		abort(); \
+	}
+
+
+static struct log_info info = {};
+
+struct lapdm_polling_state {
+	struct lapdm_channel *bts;
+	int bts_read;
+
+	struct lapdm_channel *ms;
+	int ms_read;
+};
+
+static struct msgb *msgb_from_array(const uint8_t *data, int len)
+{
+	struct msgb *msg = msgb_alloc_headroom(4096, 128, "data");
+	msg->l3h = msgb_put(msg, len);
+	memcpy(msg->l3h, data, len);
+	return msg;
+}
+
+/*
+ * Test data is below...
+ */
+static const uint8_t cm[] = {
+	0x05, 0x24, 0x31, 0x03, 0x50, 0x18, 0x93, 0x08,
+	0x29, 0x47, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+};
+
+static const uint8_t cm_padded[] = {
+	0x05, 0x24, 0x31, 0x03, 0x50, 0x18, 0x93, 0x08,
+	0x29, 0x47, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+	0x2b, 0x2b, 0x2b, 0x2b
+};
+
+static const uint8_t mm[] = {
+	0x00, 0x0c, 0x00, 0x03, 0x01, 0x01, 0x20, 0x02,
+	0x00, 0x0b, 0x00, 0x03, 0x05, 0x04, 0x0d
+};
+
+static const uint8_t dummy1[] = {
+	0xab, 0x03, 0x30, 0x60, 0x06,
+};
+
+static struct msgb *create_cm_serv_req(void)
+{
+	struct msgb *msg;
+
+	msg = msgb_from_array(cm, sizeof(cm));
+	rsl_rll_push_l3(msg, RSL_MT_EST_REQ, 0, 0, 1);
+	return msg;
+}
+
+static struct msgb *create_mm_id_req(void)
+{
+	struct msgb *msg;
+
+	msg = msgb_from_array(mm, sizeof(mm));
+	msg->l2h = msg->data + 3;
+	ASSERT(msgb_l2len(msg) == 12);
+	msg->l3h = msg->l2h + 6;
+	ASSERT(msgb_l3len(msg) == 6);
+
+	return msg;
+}
+
+static struct msgb *create_empty_msg(void)
+{
+	struct msgb *msg;
+
+	msg = msgb_from_array(NULL, 0);
+	ASSERT(msgb_l3len(msg) == 0);
+	rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, 0, 0, 1);
+	return msg;
+}
+
+static struct msgb *create_dummy_data_req(void)
+{
+	struct msgb *msg;
+
+	msg = msgb_from_array(dummy1, sizeof(dummy1));
+	rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, 0, 0, 1);
+	return msg;
+}
+
+static int send(struct msgb *in_msg, struct lapdm_channel *chan)
+{
+	struct osmo_phsap_prim pp;
+	struct msgb *msg;
+	int rc;
+
+	msg = msgb_alloc_headroom(128, 64, "PH-DATA.ind");
+	osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA,
+			PRIM_OP_INDICATION, msg);
+	/* copy over actual MAC block */
+	msg->l2h = msgb_put(msg, msgb_l2len(in_msg));
+	memcpy(msg->l2h, in_msg->l2h, msgb_l2len(in_msg));
+
+	/* LAPDm requires those... */
+	pp.u.data.chan_nr = 0;
+	pp.u.data.link_id = 0;
+        /* feed into the LAPDm code of libosmogsm */
+        rc = lapdm_phsap_up(&pp.oph, &chan->lapdm_dcch);
+	ASSERT(rc == 0 || rc == -EBUSY);
+	return 0;
+}
+
+/*
+ * I get called from the LAPDm code when something was sent my way...
+ */
+static int bts_to_ms_tx_cb(struct msgb *in_msg, struct lapdm_entity *le, void *_ctx)
+{
+	struct lapdm_polling_state *state = _ctx;
+
+
+	printf("%s: MS->BTS(us) message %d\n", __func__, msgb_length(in_msg));
+
+
+	if (state->bts_read == 0) {
+		printf("BTS: Verifying CM request.\n");
+		ASSERT(msgb_l3len(in_msg) == ARRAY_SIZE(cm_padded));
+		ASSERT(memcmp(in_msg->l3h, cm_padded, ARRAY_SIZE(cm_padded)) == 0);
+	} else if (state->bts_read == 1) {
+		printf("BTS: Verifying dummy message.\n");
+		ASSERT(msgb_l3len(in_msg) == ARRAY_SIZE(dummy1));
+		ASSERT(memcmp(in_msg->l3h, dummy1, ARRAY_SIZE(dummy1)) == 0);
+	} else {
+		printf("BTS: Do not know to verify: %d\n", state->bts_read);
+	}
+
+	state->bts_read += 1;
+	msgb_free(in_msg);
+
+	return 0;
+}
+
+static int ms_to_bts_l1_cb(struct osmo_prim_hdr *oph, void *_ctx)
+{
+	int rc;
+	struct lapdm_polling_state *state = _ctx;
+	printf("%s: MS(us) -> BTS prim message\n", __func__);
+
+	/* i stuff it into the LAPDm channel of the BTS */
+	rc = send(oph->msg, state->bts);
+	msgb_free(oph->msg);
+}
+
+static int ms_to_bts_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *_ctx)
+{
+	struct lapdm_polling_state *state = _ctx;
+
+	printf("%s: BTS->MS(us) message %d\n", __func__, msgb_length(msg));
+
+	if (state->ms_read == 0) {
+		struct abis_rsl_rll_hdr hdr;
+
+		printf("MS: Verifying incoming primitive.\n");
+		ASSERT(msg->len == sizeof(struct abis_rsl_rll_hdr) + 3);
+
+		/* verify the header */
+		memset(&hdr, 0, sizeof(hdr));
+		rsl_init_rll_hdr(&hdr, RSL_MT_EST_CONF);
+		hdr.c.msg_discr |= ABIS_RSL_MDISC_TRANSP;
+		ASSERT(memcmp(msg->data, &hdr, sizeof(hdr)) == 0);
+
+		/* Verify the added RSL_IE_L3_INFO but we have a bug here */
+		ASSERT(msg->data[6] == RSL_IE_L3_INFO);
+		#warning "RSL_IE_L3_INFO 16 bit length is wrong"
+		/* ASSERT(msg->data[7] == 0x0 && msg->data[8] == 0x9c); */
+		/* this should be 0x0 and 0x0... but we have a bug */
+	} else if (state->ms_read == 1) {
+		printf("MS: Verifying incoming MM message: %d\n", msgb_l3len(msg));
+		ASSERT(msgb_l3len(msg) == 3);
+		ASSERT(memcmp(msg->l3h, &mm[12], msgb_l3len(msg)) == 0);
+	} else {
+		printf("MS: Do not know to verify: %d\n", state->ms_read);
+	}
+
+	state->ms_read += 1;
+	msgb_free(msg);
+	return 0;
+}
+
+static void test_lapdm_polling()
+{
+	printf("I do some very simple LAPDm test.\n");
+
+	int rc;
+	struct lapdm_polling_state test_state;
+	struct osmo_phsap_prim pp;
+
+	/* Configure LAPDm on both sides */
+	struct lapdm_channel bts_to_ms_channel;
+	struct lapdm_channel ms_to_bts_channel;
+	memset(&bts_to_ms_channel, 0, sizeof(bts_to_ms_channel));
+	memset(&ms_to_bts_channel, 0, sizeof(ms_to_bts_channel));
+
+	memset(&test_state, 0, sizeof(test_state));
+	test_state.bts = &bts_to_ms_channel;
+	test_state.ms = &ms_to_bts_channel;
+
+	/* BTS to MS in polling mode */
+	lapdm_channel_init(&bts_to_ms_channel, LAPDM_MODE_BTS);
+        lapdm_channel_set_flags(&bts_to_ms_channel, LAPDM_ENT_F_POLLING_ONLY);
+        lapdm_channel_set_l1(&bts_to_ms_channel, NULL, &test_state);
+        lapdm_channel_set_l3(&bts_to_ms_channel, bts_to_ms_tx_cb, &test_state);
+
+	/* MS to BTS in direct mode */
+	lapdm_channel_init(&ms_to_bts_channel, LAPDM_MODE_MS);
+	lapdm_channel_set_l1(&ms_to_bts_channel, ms_to_bts_l1_cb, &test_state);
+	lapdm_channel_set_l3(&ms_to_bts_channel, ms_to_bts_tx_cb, &test_state);
+
+	/*
+	 * We try to send messages from the MS to the BTS to the MS..
+	 */
+	/* 1. Start with MS -> BTS, BTS should have a pending message */
+	printf("Establishing link.\n");
+	lapdm_rslms_recvmsg(create_cm_serv_req(), &ms_to_bts_channel);
+
+	/* 2. Poll on the BTS for sending out a confirmation */
+	printf("\nConfirming\n");
+	ASSERT(test_state.bts_read == 1)
+	rc = lapdm_phsap_dequeue_prim(&bts_to_ms_channel.lapdm_dcch, &pp);
+	CHECK_RC(rc);
+	ASSERT(pp.oph.msg->data == pp.oph.msg->l2h);
+	send(pp.oph.msg, &ms_to_bts_channel);
+	msgb_free(pp.oph.msg);
+	ASSERT(test_state.ms_read == 1);
+
+	/* 3. Send some data to the MS */
+	printf("\nSending back to MS\n");
+	lapdm_rslms_recvmsg(create_mm_id_req(), &bts_to_ms_channel);
+	rc = lapdm_phsap_dequeue_prim(&bts_to_ms_channel.lapdm_dcch, &pp);
+	CHECK_RC(rc);
+	send(pp.oph.msg, &ms_to_bts_channel);
+	msgb_free(pp.oph.msg);
+	ASSERT(test_state.ms_read == 2);
+
+	/* verify that there is nothing more to poll */
+	rc = lapdm_phsap_dequeue_prim(&bts_to_ms_channel.lapdm_dcch, &pp);
+	ASSERT(rc < 0);
+
+	/* 3. And back to the BTS */
+	printf("\nSending back to BTS\n");
+	ASSERT(test_state.ms_read == 2);
+	lapdm_rslms_recvmsg(create_dummy_data_req(), &ms_to_bts_channel);
+
+
+	/* 4. And back to the MS, but let's move data/l2h apart */
+	ASSERT(test_state.bts_read == 2)
+	ASSERT(test_state.ms_read == 2);
+	rc = lapdm_phsap_dequeue_prim(&bts_to_ms_channel.lapdm_dcch, &pp);
+	CHECK_RC(rc);
+	send(pp.oph.msg, &ms_to_bts_channel);
+	ASSERT(test_state.ms_read == 2);
+	msgb_free(pp.oph.msg);
+
+	/* verify that there is nothing more to poll */
+	rc = lapdm_phsap_dequeue_prim(&bts_to_ms_channel.lapdm_dcch, &pp);
+	ASSERT(rc < 0);
+
+	/* check sending an empty L3 message fails */
+	rc = lapdm_rslms_recvmsg(create_empty_msg(), &bts_to_ms_channel);
+	ASSERT(rc == -1);
+	ASSERT(test_state.ms_read == 2);
+
+	/* clean up */
+	lapdm_channel_exit(&bts_to_ms_channel);
+	lapdm_channel_exit(&ms_to_bts_channel);
+}
+
+int main(int argc, char **argv)
+{
+	osmo_init_logging(&info);
+
+	test_lapdm_polling();
+	printf("Success.\n");
+
+	return 0;
+}
diff --git a/tests/lapd/lapd_test.ok b/tests/lapd/lapd_test.ok
new file mode 100644
index 0000000..d67a0a8
--- /dev/null
+++ b/tests/lapd/lapd_test.ok
@@ -0,0 +1,20 @@
+I do some very simple LAPDm test.
+Establishing link.
+ms_to_bts_l1_cb: MS(us) -> BTS prim message
+bts_to_ms_tx_cb: MS->BTS(us) message 29
+BTS: Verifying CM request.
+
+Confirming
+ms_to_bts_tx_cb: BTS->MS(us) message 9
+MS: Verifying incoming primitive.
+
+Sending back to MS
+ms_to_bts_tx_cb: BTS->MS(us) message 12
+MS: Verifying incoming MM message: 3
+ms_to_bts_l1_cb: MS(us) -> BTS prim message
+
+Sending back to BTS
+ms_to_bts_l1_cb: MS(us) -> BTS prim message
+bts_to_ms_tx_cb: MS->BTS(us) message 14
+BTS: Verifying dummy message.
+Success.
diff --git a/tests/msgfile/Makefile.am b/tests/msgfile/Makefile.am
new file mode 100644
index 0000000..88c355d
--- /dev/null
+++ b/tests/msgfile/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = msgfile_test
+EXTRA_DIST = msgfile_test.ok msgconfig.cfg
+
+msgfile_test_SOURCES = msgfile_test.c
+msgfile_test_LDADD = $(top_builddir)/src/libosmocore.la
diff --git a/tests/msgfile/msgconfig.cfg b/tests/msgfile/msgconfig.cfg
new file mode 100644
index 0000000..28d7432
--- /dev/null
+++ b/tests/msgfile/msgconfig.cfg
@@ -0,0 +1,2 @@
+# This is a comment
+*:*::Hello Welt
diff --git a/tests/msgfile/msgfile_test.c b/tests/msgfile/msgfile_test.c
new file mode 100644
index 0000000..ed7aa97
--- /dev/null
+++ b/tests/msgfile/msgfile_test.c
@@ -0,0 +1,50 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/msgfile.h>
+
+#include <stdio.h>
+
+static void dump_entries(struct osmo_config_list *entries)
+{
+	struct osmo_config_entry *entry;
+
+	if (!entries) {
+		fprintf(stderr, "Failed to parse the file\n");
+		return;
+	}
+
+	llist_for_each_entry(entry, &entries->entry, list) {
+		printf("Entry '%s:%s:%s:%s'\n",
+			entry->mcc, entry->mnc, entry->option, entry->text);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	struct osmo_config_list *entries;
+
+	/* todo use msgfile_test.c.in and replace the path */
+	entries = osmo_config_list_parse(NULL, "msgconfig.cfg");
+	dump_entries(entries);
+
+	return 0;
+}
diff --git a/tests/msgfile/msgfile_test.ok b/tests/msgfile/msgfile_test.ok
new file mode 100644
index 0000000..c47cd74
--- /dev/null
+++ b/tests/msgfile/msgfile_test.ok
@@ -0,0 +1 @@
+Entry '*:*::Hello Welt'
diff --git a/tests/sms/Makefile.am b/tests/sms/Makefile.am
new file mode 100644
index 0000000..02860af
--- /dev/null
+++ b/tests/sms/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = sms_test
+EXTRA_DIST = sms_test.ok
+
+sms_test_SOURCES = sms_test.c
+sms_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/sms/sms_test.c b/tests/sms/sms_test.c
new file mode 100644
index 0000000..6df4b62
--- /dev/null
+++ b/tests/sms/sms_test.c
@@ -0,0 +1,319 @@
+/*
+ * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2010 by Nico Golde <nico@ngolde.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/utils.h>
+
+struct test_case {
+	const uint8_t *input;
+	const uint16_t input_length;
+
+	const uint8_t *expected;
+	const uint16_t expected_octet_length;
+	const uint16_t expected_septet_length;
+	const uint8_t ud_hdr_ind;
+};
+
+static const char simple_text[] = "test text";
+#define simple_septet_length 9
+static const uint8_t simple_enc[] = {
+	0xf4, 0xf2, 0x9c, 0x0e, 0xa2, 0x97, 0xf1, 0x74
+};
+
+static const char escape_text[] = "!$ a more#^- complicated test@@?_%! case";
+#define escape_septet_length 41 /* note: the ^ counts as two, because it is a extension character */
+static const uint8_t escape_enc[] = {
+	0x21, 0x01, 0x28, 0x0c, 0x6a, 0xbf, 0xe5, 0xe5, 0xd1,
+	0x86, 0xd2, 0x02, 0x8d, 0xdf, 0x6d, 0x38, 0x3b, 0x3d,
+	0x0e, 0xd3, 0xcb, 0x64, 0x10, 0xbd, 0x3c, 0xa7, 0x03,
+	0x00, 0xbf, 0x48, 0x29, 0x04, 0x1a, 0x87, 0xe7, 0x65,
+};
+
+static const char enhanced_text[] = "enhanced ^ {][} test |+~ ^ test";
+#define enhanced_septet_length 39 /* note: the characters { } [ ] ^ | ~ count as two (each of them), because they are extension characters */
+static const uint8_t enhanced_enc[] = {
+	0x65, 0x37, 0x3A, 0xEC, 0x1E, 0x97, 0xC9, 0xA0, 0x0D,
+	0x05, 0xB4, 0x41, 0x6D, 0x7C, 0x1B, 0xDE, 0x26, 0x05,
+	0xA2, 0x97, 0xE7, 0x74, 0xD0, 0x06, 0xB8, 0xDA, 0xF4,
+	0x40, 0x1B, 0x0A, 0x88, 0x5E, 0x9E, 0xD3, 0x01,
+};
+
+static const char enhancedV2_text[] = "enhanced ^ {][} test |+~ ^ tests";
+#define enhancedV2_septet_length 40 /* note: number of octets are equal to the enhanced_text! */
+static const uint8_t enhancedV2_enc[] = {
+	0x65, 0x37, 0x3A, 0xEC, 0x1E, 0x97, 0xC9, 0xA0, 0x0D,
+	0x05, 0xB4, 0x41, 0x6D, 0x7C, 0x1B, 0xDE, 0x26, 0x05,
+	0xA2, 0x97, 0xE7, 0x74, 0xD0, 0x06, 0xB8, 0xDA, 0xF4,
+	0x40, 0x1B, 0x0A, 0x88, 0x5E, 0x9E, 0xD3, 0xE7,
+};
+
+
+
+static const char concatenated_text[] =
+		"this is a testmessage. this is a testmessage. this is a testmessage. this is a testmessage. "
+		"this is a testmessage. this is a testmessage. cut here .....: this is a second testmessage. end here.";
+
+static const char splitted_text_part1[] =
+		"this is a testmessage. this is a testmessage. this is a testmessage. this is a testmessage. "
+		"this is a testmessage. this is a testmessage. cut here .....:";
+#define concatenated_part1_septet_length_with_header 160
+#define concatenated_part1_septet_length 153
+static const uint8_t concatenated_part1_enc[] = {
+		0x05, 0x00, 0x03, 0x6f, 0x02, 0x01,
+		0xe8, 0xe8, 0xf4, 0x1c, 0x94, 0x9e, 0x83, 0xc2,
+		0x20, 0x7a, 0x79, 0x4e, 0x6f, 0x97, 0xe7, 0xf3,
+		0xf0, 0xb9, 0xec, 0x02, 0xd1, 0xd1, 0xe9, 0x39,
+		0x28, 0x3d, 0x07, 0x85, 0x41, 0xf4, 0xf2, 0x9c,
+		0xde, 0x2e, 0xcf, 0xe7, 0xe1, 0x73, 0xd9, 0x05,
+		0xa2, 0xa3, 0xd3, 0x73, 0x50, 0x7a, 0x0e, 0x0a,
+		0x83, 0xe8, 0xe5, 0x39, 0xbd, 0x5d, 0x9e, 0xcf,
+		0xc3, 0xe7, 0xb2, 0x0b, 0x44, 0x47, 0xa7, 0xe7,
+		0xa0, 0xf4, 0x1c, 0x14, 0x06, 0xd1, 0xcb, 0x73,
+		0x7a, 0xbb, 0x3c, 0x9f, 0x87, 0xcf, 0x65, 0x17,
+		0x88, 0x8e, 0x4e, 0xcf, 0x41, 0xe9, 0x39, 0x28,
+		0x0c, 0xa2, 0x97, 0xe7, 0xf4, 0x76, 0x79, 0x3e,
+		0x0f, 0x9f, 0xcb, 0x2e, 0x10, 0x1d, 0x9d, 0x9e,
+		0x83, 0xd2, 0x73, 0x50, 0x18, 0x44, 0x2f, 0xcf,
+		0xe9, 0xed, 0xf2, 0x7c, 0x1e, 0x3e, 0x97, 0x5d,
+		0xa0, 0x71, 0x9d, 0x0e, 0x42, 0x97, 0xe5, 0x65,
+		0x90, 0xcb, 0xe5, 0x72, 0xb9, 0x74,
+};
+
+static const char splitted_text_part2[] = " this is a second testmessage. end here.";
+#define concatenated_part2_septet_length_with_header 47
+#define concatenated_part2_septet_length 40
+static const uint8_t concatenated_part2_enc[] = {
+		0x05, 0x00, 0x03, 0x6f, 0x02, 0x02,
+		0x40, 0x74, 0x74, 0x7a, 0x0e, 0x4a, 0xcf, 0x41,
+		0x61, 0xd0, 0xbc, 0x3c, 0x7e, 0xbb, 0xc9, 0x20,
+		0x7a, 0x79, 0x4e, 0x6f, 0x97, 0xe7, 0xf3, 0xf0,
+		0xb9, 0xec, 0x02, 0x95, 0xdd, 0x64, 0x10, 0xba,
+		0x2c, 0x2f, 0xbb, 0x00,
+};
+
+static const struct test_case test_multiple_encode[] =
+{
+	{
+		.input = concatenated_text,
+		.expected = concatenated_part1_enc,
+		.expected_octet_length = sizeof(concatenated_part1_enc),
+		.expected_septet_length = concatenated_part1_septet_length,
+		.ud_hdr_ind = 1,
+	},
+	{
+		.input = concatenated_text,
+		.expected = concatenated_part2_enc,
+		.expected_octet_length = sizeof(concatenated_part2_enc),
+		.expected_septet_length = concatenated_part2_septet_length,
+		.ud_hdr_ind = 1,
+	},
+};
+
+static const struct test_case test_encode[] =
+{
+	{
+		.input = simple_text,
+		.expected = simple_enc,
+		.expected_octet_length = sizeof(simple_enc),
+		.expected_septet_length = simple_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = escape_text,
+		.expected = escape_enc,
+		.expected_octet_length = sizeof(escape_enc),
+		.expected_septet_length = escape_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = enhanced_text,
+		.expected = enhanced_enc,
+		.expected_octet_length = sizeof(enhanced_enc),
+		.expected_septet_length = enhanced_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = enhancedV2_text,
+		.expected = enhancedV2_enc,
+		.expected_octet_length = sizeof(enhancedV2_enc),
+		.expected_septet_length = enhancedV2_septet_length,
+		.ud_hdr_ind = 0,
+	},
+};
+
+static const struct test_case test_decode[] =
+{
+	{
+		.input = simple_enc,
+		.input_length = sizeof(simple_enc),
+		.expected = simple_text,
+		.expected_septet_length = simple_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = escape_enc,
+		.input_length = sizeof(escape_enc),
+		.expected = escape_text,
+		.expected_septet_length = escape_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = enhanced_enc,
+		.input_length = sizeof(enhanced_enc),
+		.expected = enhanced_text,
+		.expected_septet_length = enhanced_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = enhancedV2_enc,
+		.input_length = sizeof(enhancedV2_enc),
+		.expected = enhancedV2_text,
+		.expected_septet_length = enhancedV2_septet_length,
+		.ud_hdr_ind = 0,
+	},
+	{
+		.input = concatenated_part1_enc,
+		.input_length = sizeof(concatenated_part1_enc),
+		.expected = splitted_text_part1,
+		.expected_septet_length = concatenated_part1_septet_length_with_header,
+		.ud_hdr_ind = 1,
+	},
+	{
+		.input = concatenated_part2_enc,
+		.input_length = sizeof(concatenated_part2_enc),
+		.expected = splitted_text_part2,
+		.expected_septet_length = concatenated_part2_septet_length_with_header,
+		.ud_hdr_ind = 1,
+	},
+};
+
+int main(int argc, char** argv)
+{
+	printf("SMS testing\n");
+	struct msgb *msg;
+	uint8_t i;
+
+	uint8_t octet_length;
+	uint8_t septet_length;
+	uint8_t gsm_septet_length;
+	uint8_t coded[256];
+	uint8_t tmp[160];
+	uint8_t septet_data[256];
+	uint8_t ud_header[6];
+	char result[256];
+
+	/* test 7-bit encoding */
+	for (i = 0; i < ARRAY_SIZE(test_encode); ++i) {
+		memset(coded, 0x42, sizeof(coded));
+		septet_length = gsm_7bit_encode(coded, test_encode[i].input);
+		octet_length = gsm_get_octet_len(septet_length);
+		if (octet_length != test_encode[i].expected_octet_length) {
+			fprintf(stderr, "Encode case %d: Octet length failure. Got %d, expected %d\n",
+				i, octet_length, test_encode[i].expected_octet_length);
+			return -1;
+		}
+
+		if (septet_length != test_encode[i].expected_septet_length){
+			fprintf(stderr, "Encode case %d: Septet length failure. Got %d, expected %d\n",
+				i, septet_length, test_encode[i].expected_septet_length);
+			return -1;
+		}
+
+		if (memcmp(coded, test_encode[i].expected, octet_length) != 0) {
+			fprintf(stderr, "Encoded content does not match for case %d\n",
+				i);
+			return -1;
+		}
+	}
+
+
+	/* Test: encode multiple SMS */
+	int number_of_septets = gsm_septet_encode(septet_data, test_multiple_encode[0].input);
+
+	/* SMS part 1 */
+	memset(tmp, 0x42, sizeof(tmp));
+	memset(coded, 0x42, sizeof(coded));
+	memcpy(tmp, septet_data, concatenated_part1_septet_length);
+
+	/* In our case: test_multiple_decode[0].ud_hdr_ind equals number of padding bits*/
+	octet_length = gsm_septets2octets(coded, tmp, concatenated_part1_septet_length, test_multiple_encode[0].ud_hdr_ind);
+
+	/* copy header */
+	memset(tmp, 0x42, sizeof(tmp));
+	int udh_length = test_multiple_encode[0].expected[0] + 1;
+	memcpy(tmp, test_multiple_encode[0].expected, udh_length);
+	memcpy(tmp + udh_length, coded, octet_length);
+	memset(coded, 0x42, sizeof(coded));
+	memcpy(coded, tmp, octet_length + 6);
+
+	if (memcmp(coded, test_multiple_encode[0].expected, octet_length) != 0) {
+		fprintf(stderr, "Multiple-SMS encoded content does not match for part 1\n");
+		return -1;
+	}
+
+
+	/* SMS part 2 */
+	memset(tmp, 0x42, sizeof(tmp));
+	memset(coded, 0x42, sizeof(coded));
+	memcpy(tmp, septet_data + concatenated_part1_septet_length, concatenated_part2_septet_length);
+
+	/* In our case: test_multiple_decode[1].ud_hdr_ind equals number of padding bits*/
+	octet_length = gsm_septets2octets(coded, tmp, concatenated_part2_septet_length, test_multiple_encode[1].ud_hdr_ind);
+
+	/* copy header */
+	memset(tmp, 0x42, sizeof(tmp));
+	udh_length = test_multiple_encode[1].expected[0] + 1;
+	memcpy(tmp, test_multiple_encode[1].expected, udh_length);
+	memcpy(tmp + udh_length, coded, octet_length);
+	memset(coded, 0x42, sizeof(coded));
+	memcpy(coded, tmp, octet_length + 6);
+
+	if (memcmp(coded, test_multiple_encode[1].expected, octet_length) != 0) {
+		fprintf(stderr, "Multiple-SMS encoded content does not match for part 2\n");
+		return -1;
+	}
+
+
+
+	/* test 7-bit decoding */
+	for (i = 0; i < ARRAY_SIZE(test_decode); ++i) {
+		memset(result, 0x42, sizeof(coded));
+		septet_length = gsm_7bit_decode_hdr(result, test_decode[i].input,
+				test_decode[i].expected_septet_length, test_decode[i].ud_hdr_ind);
+
+		if (strcmp(result, test_decode[i].expected) != 0) {
+			fprintf(stderr, "Test case %d failed to decode.\n", i);
+			return -1;
+		}
+		if (septet_length != test_decode[i].expected_septet_length) {
+			fprintf(stderr, "Decode case %d: Septet length failure. Got %d, expected %d\n",
+				i, septet_length, test_decode[i].expected_septet_length);
+			return -1;
+		}
+	}
+
+	printf("OK\n");
+	return 0;
+}
diff --git a/tests/sms/sms_test.ok b/tests/sms/sms_test.ok
new file mode 100644
index 0000000..d0e0983
--- /dev/null
+++ b/tests/sms/sms_test.ok
@@ -0,0 +1,2 @@
+SMS testing
+OK
diff --git a/tests/smscb/Makefile.am b/tests/smscb/Makefile.am
new file mode 100644
index 0000000..7045ea7
--- /dev/null
+++ b/tests/smscb/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = smscb_test
+EXTRA_DIST = smscb_test.ok
+
+smscb_test_SOURCES = smscb_test.c
+smscb_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/smscb/smscb_test.c b/tests/smscb/smscb_test.c
new file mode 100644
index 0000000..e10e12d
--- /dev/null
+++ b/tests/smscb/smscb_test.c
@@ -0,0 +1,41 @@
+/*
+ * (C) 2010 Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/gsm/protocol/gsm_03_41.h>
+
+#include <stdio.h>
+
+static uint8_t smscb_msg[] = { 0x40, 0x10, 0x05, 0x0d, 0x01, 0x11 };
+
+int main(int argc, char **argv)
+{
+	struct gsm341_ms_message *msg;
+
+	msg = (struct gsm341_ms_message *) smscb_msg;
+	printf("(srl) GS: %d MSG_CODE: %d UPDATE: %d\n",
+		msg->serial.gs, GSM341_MSG_CODE(msg), msg->serial.update);
+	printf("(msg) msg_id: %d\n", htons(msg->msg_id));
+	printf("(dcs) group: %d language: %d\n",
+		msg->dcs.language, msg->dcs.group);
+	printf("(pge) page total: %d current: %d\n",
+		msg->page.total, msg->page.current);
+
+	return 0;
+}
diff --git a/tests/smscb/smscb_test.ok b/tests/smscb/smscb_test.ok
new file mode 100644
index 0000000..347037f
--- /dev/null
+++ b/tests/smscb/smscb_test.ok
@@ -0,0 +1,4 @@
+(srl) GS: 1 MSG_CODE: 1 UPDATE: 0
+(msg) msg_id: 1293
+(dcs) group: 1 language: 0
+(pge) page total: 1 current: 1
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644
index 0000000..69624c1
--- /dev/null
+++ b/tests/testsuite.at
@@ -0,0 +1,73 @@
+AT_INIT
+AT_BANNER([Regression tests.])
+
+
+# todo.. create one macro for it
+AT_SETUP([a5])
+AT_KEYWORDS([a5])
+cat $abs_srcdir/a5/a5_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/a5/a5_test], [], [expout])
+AT_CLEANUP
+
+AT_SETUP([bits])
+AT_KEYWORDS([bits])
+cat $abs_srcdir/bits/bitrev_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bits/bitrev_test], [], [expout])
+AT_CLEANUP
+
+AT_SETUP([conv])
+AT_KEYWORDS([conv])
+cat $abs_srcdir/conv/conv_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/conv/conv_test], [], [expout])
+AT_CLEANUP
+
+if ENABLE_MSGFILE
+AT_SETUP([msgfile])
+AT_KEYWORDS([msgfile])
+cp $abs_srcdir/msgfile/msgconfig.cfg .
+cat $abs_srcdir/msgfile/msgfile_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/msgfile/msgfile_test], [], [expout])
+AT_CLEANUP
+endif
+
+AT_SETUP([sms])
+AT_KEYWORDS([sms])
+cat $abs_srcdir/sms/sms_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/sms/sms_test], [], [expout])
+AT_CLEANUP
+
+AT_SETUP([smscb])
+AT_KEYWORDS([smscb])
+cat $abs_srcdir/smscb/smscb_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/smscb/smscb_test], [], [expout])
+AT_CLEANUP
+
+AT_SETUP([timer])
+AT_KEYWORDS([timer])
+cat $abs_srcdir/timer/timer_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/timer/timer_test -s 5], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([ussd])
+AT_KEYWORDS([ussd])
+cat $abs_srcdir/ussd/ussd_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/ussd/ussd_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([auth])
+AT_KEYWORDS([auth])
+cat $abs_srcdir/auth/milenage_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/auth/milenage_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([lapd])
+AT_KEYWORDS([lapd])
+cat $abs_srcdir/lapd/lapd_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/lapd/lapd_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([gsm0808])
+AT_KEYWORDS([gsm0808])
+cat $abs_srcdir/gsm0808/gsm0808_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm0808/gsm0808_test], [], [expout], [ignore])
+AT_CLEANUP
diff --git a/tests/timer/Makefile.am b/tests/timer/Makefile.am
new file mode 100644
index 0000000..062d81b
--- /dev/null
+++ b/tests/timer/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = timer_test
+EXTRA_DIST = timer_test.ok
+
+timer_test_SOURCES = timer_test.c
+timer_test_LDADD = $(top_builddir)/src/libosmocore.la
+
diff --git a/tests/timer/timer_test.c b/tests/timer/timer_test.c
new file mode 100644
index 0000000..61079bd
--- /dev/null
+++ b/tests/timer/timer_test.c
@@ -0,0 +1,191 @@
+/*
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * Authors: Holger Hans Peter Freyther <zecke@selfish.org>
+ *	    Pablo Neira Ayuso <pablo@gnumonks.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+
+#include "../../config.h"
+
+static void main_timer_fired(void *data);
+static void secondary_timer_fired(void *data);
+
+static unsigned int main_timer_step = 0;
+static struct osmo_timer_list main_timer = {
+	.cb = main_timer_fired,
+	.data = &main_timer_step,
+};
+
+static LLIST_HEAD(timer_test_list);
+
+struct test_timer {
+	struct llist_head head;
+	struct osmo_timer_list timer;
+	struct timeval start;
+	struct timeval stop;
+};
+
+/* number of test steps. We add fact(steps) timers in the whole test. */
+#define MAIN_TIMER_NSTEPS	16
+
+/* time between two steps, in secs. */
+#define TIME_BETWEEN_STEPS	1
+
+/* timer imprecision that we accept for this test: 10 milliseconds. */
+#define TIMER_PRES_SECS		0
+#define TIMER_PRES_USECS	20000
+
+static int timer_nsteps = MAIN_TIMER_NSTEPS;
+static unsigned int expired_timers = 0;
+static unsigned int total_timers = 0;
+static unsigned int too_late = 0;
+
+static void main_timer_fired(void *data)
+{
+	unsigned int *step = data;
+	unsigned int add_in_this_step;
+	int i;
+
+	if (*step == timer_nsteps) {
+		fprintf(stderr, "Main timer has finished, please, "
+				"wait a bit for the final report.\n");
+		return;
+	}
+	/* add 2^step pair of timers per step. */
+	add_in_this_step = (1 << *step);
+
+	for (i=0; i<add_in_this_step; i++) {
+		struct test_timer *v;
+
+		v = talloc_zero(NULL, struct test_timer);
+		if (v == NULL) {
+			fprintf(stderr, "timer_test: OOM!\n");
+			return;
+		}
+		gettimeofday(&v->start, NULL);
+		v->timer.cb = secondary_timer_fired;
+		v->timer.data = v;
+		unsigned int seconds = (random() % 10) + 1;
+		v->stop.tv_sec = v->start.tv_sec + seconds;
+		osmo_timer_schedule(&v->timer, seconds, 0);
+		llist_add(&v->head, &timer_test_list);
+	}
+	fprintf(stderr, "added %d timers in step %u (expired=%u)\n",
+		add_in_this_step, *step, expired_timers);
+	total_timers += add_in_this_step;
+	osmo_timer_schedule(&main_timer, TIME_BETWEEN_STEPS, 0);
+	(*step)++;
+}
+
+static void secondary_timer_fired(void *data)
+{
+	struct test_timer *v = data, *this, *tmp;
+	struct timeval current, res, precision = { 1, 0 };
+
+	gettimeofday(&current, NULL);
+
+	timersub(&current, &v->stop, &res);
+	if (timercmp(&res, &precision, >)) {
+		fprintf(stderr, "ERROR: timer %p has expired too late!\n",
+			v->timer);
+		too_late++;
+	}
+
+	llist_del(&v->head);
+	talloc_free(data);
+	expired_timers++;
+	if (expired_timers == total_timers) {
+		fprintf(stdout, "test over: added=%u expired=%u too_late=%u \n",
+			total_timers, expired_timers, too_late);
+		exit(EXIT_SUCCESS);
+	}
+
+	/* randomly (10%) deletion of timers. */
+	llist_for_each_entry_safe(this, tmp, &timer_test_list, head) {
+		if ((random() % 100) < 10) {
+			osmo_timer_del(&this->timer);
+			llist_del(&this->head);
+			talloc_free(this);
+			expired_timers++;
+		}
+	}
+}
+
+static void alarm_handler(int signum)
+{
+	fprintf(stderr, "ERROR: We took too long to run the timer test, "
+			"something seems broken, aborting.\n");
+	exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+
+	if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
+		perror("cannot register signal handler");
+		exit(EXIT_FAILURE);
+	}
+
+	while ((c = getopt_long(argc, argv, "s:", NULL, NULL)) != -1) {
+	switch(c) {
+		case 's':
+			timer_nsteps = atoi(optarg);
+			if (timer_nsteps <= 0) {
+				fprintf(stderr, "%s: steps must be > 0\n",
+					argv[0]);
+				exit(EXIT_FAILURE);
+			}
+			break;
+		default:
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	fprintf(stdout, "Running timer test for %u steps, accepting "
+		"imprecision of %u.%.6u seconds\n",
+		timer_nsteps, TIMER_PRES_SECS, TIMER_PRES_USECS);
+
+	osmo_timer_schedule(&main_timer, 1, 0);
+
+	/* if the test takes too long, we may consider that the timer scheduler
+	 * has hung. We set some maximum wait time which is the double of the
+	 * maximum timeout randomly set (10 seconds, worst case) plus the
+	 * number of steps (since some of them are reset each step). */
+	alarm(2 * (10 + timer_nsteps));
+
+#ifdef HAVE_SYS_SELECT_H
+	while (1) {
+		osmo_select_main(0);
+	}
+#else
+	fprintf(stdout, "Select not supported on this platform!\n");
+#endif
+}
diff --git a/tests/timer/timer_test.ok b/tests/timer/timer_test.ok
new file mode 100644
index 0000000..1bb382e
--- /dev/null
+++ b/tests/timer/timer_test.ok
@@ -0,0 +1,2 @@
+Running timer test for 5 steps, accepting imprecision of 0.020000 seconds
+test over: added=31 expired=31 too_late=0 
diff --git a/tests/ussd/Makefile.am b/tests/ussd/Makefile.am
new file mode 100644
index 0000000..de9ff89
--- /dev/null
+++ b/tests/ussd/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = ussd_test
+EXTRA_DIST = ussd_test.ok
+
+ussd_test_SOURCES = ussd_test.c
+ussd_test_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
diff --git a/tests/ussd/ussd_test.c b/tests/ussd/ussd_test.c
new file mode 100644
index 0000000..55384f1
--- /dev/null
+++ b/tests/ussd/ussd_test.c
@@ -0,0 +1,97 @@
+/*
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const uint8_t ussd_request[] = {
+	0x0b, 0x7b, 0x1c, 0x15, 0xa1, 0x13, 0x02, 0x01,
+	0x03, 0x02, 0x01, 0x3b, 0x30, 0x0b, 0x04, 0x01,
+	0x0f, 0x04, 0x06, 0x2a, 0xd5, 0x4c, 0x16, 0x1b,
+	0x01, 0x7f, 0x01, 0x00
+};
+
+static int parse_ussd(const uint8_t *_data, int len)
+{
+	uint8_t *data;
+	int rc;
+	struct ussd_request req;
+	struct gsm48_hdr *hdr;
+
+	data = malloc(len);
+	memcpy(data, _data, len);
+	hdr = (struct gsm48_hdr *) &data[0];
+	rc = gsm0480_decode_ussd_request(hdr, len, &req);
+	free(data);
+
+	return rc;
+}
+
+static int parse_mangle_ussd(const uint8_t *_data, int len)
+{
+	uint8_t *data;
+	int rc;
+	struct ussd_request req;
+	struct gsm48_hdr *hdr;
+
+	data = malloc(len);
+	memcpy(data, _data, len);
+	hdr = (struct gsm48_hdr *) &data[0];
+	hdr->data[1] = len - sizeof(*hdr) - 2;
+	rc = gsm0480_decode_ussd_request(hdr, len, &req);
+	free(data);
+
+	return rc;
+}
+
+struct log_info info = {};
+
+int main(int argc, char **argv)
+{
+	struct ussd_request req;
+	const int size = sizeof(ussd_request);
+	int i;
+
+	osmo_init_logging(&info);
+
+	gsm0480_decode_ussd_request((struct gsm48_hdr *) ussd_request, size, &req);
+	printf("Tested if it still works. Text was: %s\n", req.text);
+
+
+	printf("Testing parsing a USSD request and truncated versions\n");
+
+	for (i = size; i > sizeof(struct gsm48_hdr); --i) {
+		int rc = parse_ussd(&ussd_request[0], i);
+		printf("Result for %d is %d\n", rc, i);
+	}
+
+	printf("Mangling the container now\n");
+	for (i = size; i > sizeof(struct gsm48_hdr) + 2; --i) {
+		int rc = parse_mangle_ussd(&ussd_request[0], i);
+		printf("Result for %d is %d\n", rc, i);
+	}
+
+	return 0;
+}
diff --git a/tests/ussd/ussd_test.ok b/tests/ussd/ussd_test.ok
new file mode 100644
index 0000000..1b6316e
--- /dev/null
+++ b/tests/ussd/ussd_test.ok
@@ -0,0 +1,53 @@
+Tested if it still works. Text was: **321#
+Testing parsing a USSD request and truncated versions
+Result for 1 is 28
+Result for 1 is 27
+Result for 1 is 26
+Result for 1 is 25
+Result for 0 is 24
+Result for 0 is 23
+Result for 0 is 22
+Result for 0 is 21
+Result for 0 is 20
+Result for 0 is 19
+Result for 0 is 18
+Result for 0 is 17
+Result for 0 is 16
+Result for 0 is 15
+Result for 0 is 14
+Result for 0 is 13
+Result for 0 is 12
+Result for 0 is 11
+Result for 0 is 10
+Result for 0 is 9
+Result for 0 is 8
+Result for 0 is 7
+Result for 0 is 6
+Result for 0 is 5
+Result for 0 is 4
+Result for 0 is 3
+Mangling the container now
+Result for 0 is 28
+Result for 0 is 27
+Result for 1 is 26
+Result for 1 is 25
+Result for 0 is 24
+Result for 0 is 23
+Result for 0 is 22
+Result for 0 is 21
+Result for 0 is 20
+Result for 0 is 19
+Result for 0 is 18
+Result for 0 is 17
+Result for 0 is 16
+Result for 0 is 15
+Result for 0 is 14
+Result for 0 is 13
+Result for 0 is 12
+Result for 0 is 11
+Result for 0 is 10
+Result for 0 is 9
+Result for 0 is 8
+Result for 0 is 7
+Result for 0 is 6
+Result for 1 is 5
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..4e7869e
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,10 @@
+if ENABLE_UTILITIES
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+noinst_PROGRAMS = osmo-arfcn osmo-auc-gen
+
+osmo_arfcn_SOURCES = osmo-arfcn.c
+osmo_arfcn_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+
+osmo_auc_gen_SOURCES = osmo-auc-gen.c
+osmo_auc_gen_LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+endif
diff --git a/utils/gen_website_doc_tree.sh b/utils/gen_website_doc_tree.sh
new file mode 100755
index 0000000..622db56
--- /dev/null
+++ b/utils/gen_website_doc_tree.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+TOPDIR=`pwd`
+INDIR="$TOPDIR/doc"
+OUTDIR=/tmp/doxywww
+GITREV=`./git-version-gen .tarball-version`
+
+[ -f "$OUTDIR" ] || mkdir "$OUTDIR"
+
+for MOD in core gsm vty codec; do
+	TGTDIR="$OUTDIR/libosmo$MOD/$GITREV"
+	mkdir -p "$TGTDIR"
+	cp -R "$INDIR/$MOD/"* "$TGTDIR/"
+done
diff --git a/utils/osmo-arfcn.c b/utils/osmo-arfcn.c
new file mode 100644
index 0000000..15adbca
--- /dev/null
+++ b/utils/osmo-arfcn.c
@@ -0,0 +1,103 @@
+/* Utility program for ARFCN / frequency calculations */
+/*
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+enum program_mode {
+	MODE_NONE,
+	MODE_A2F,
+	MODE_F2A,
+};
+
+static int arfcn2freq(char *arfcn_str)
+{
+	int arfcn = atoi(arfcn_str);
+	uint16_t freq10u, freq10d;
+
+	if (arfcn < 0 || arfcn > 0xffff) {
+		fprintf(stderr, "Invalid ARFCN %d\n", arfcn);
+		return -EINVAL;
+	}
+
+	freq10u = gsm_arfcn2freq10(arfcn, 1);
+	freq10d = gsm_arfcn2freq10(arfcn, 0);
+	if (freq10u == 0xffff || freq10d == 0xffff) {
+		fprintf(stderr, "Error during conversion of ARFCN %d\n",
+			arfcn);
+		return -EINVAL;
+	}
+
+	printf("ARFCN %4d: Uplink %4u.%1u MHz / Downlink %4u.%1u MHz\n",
+		arfcn, freq10u/10, freq10u%10, freq10d/10, freq10d%10);
+
+	return 0;
+}
+
+static void help(const char *progname)
+{
+	printf("Usage: %s [-h] [-a arfcn] [-f freq] [-u|-d]\n",
+		progname);
+}
+
+int main(int argc, char **argv)
+{
+	int opt;
+	char *param;
+	enum program_mode mode = MODE_NONE;
+
+	while ((opt = getopt(argc, argv, "a:f:ud")) != -1) {
+		switch (opt) {
+		case 'a':
+			mode = MODE_A2F;
+			param = optarg;
+			break;
+		case 'f':
+			mode = MODE_F2A;
+			param = optarg;
+			break;
+		case 'h':
+			help(argv[0]);
+			exit(0);
+			break;
+		default:
+			break;
+		}
+	}
+
+	switch (mode) {
+	case MODE_NONE:
+		help(argv[0]);
+		exit(2);
+		break;
+	case MODE_A2F:
+		arfcn2freq(param);
+		break;
+	}
+
+	exit(0);
+}
diff --git a/utils/osmo-auc-gen.c b/utils/osmo-auc-gen.c
new file mode 100644
index 0000000..e3502b2
--- /dev/null
+++ b/utils/osmo-auc-gen.c
@@ -0,0 +1,231 @@
+/* GSM/GPRS/3G authentication testing tool */
+
+/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <osmocom/crypt/auth.h>
+#include <osmocom/core/utils.h>
+
+static void dump_auth_vec(struct osmo_auth_vector *vec)
+{
+	printf("RAND:\t%s\n", osmo_hexdump(vec->rand, sizeof(vec->rand)));
+
+	if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+		printf("AUTN:\t%s\n", osmo_hexdump(vec->autn, sizeof(vec->autn)));
+		printf("IK:\t%s\n", osmo_hexdump(vec->ik, sizeof(vec->ik)));
+		printf("CK:\t%s\n", osmo_hexdump(vec->ck, sizeof(vec->ck)));
+		printf("RES:\t%s\n", osmo_hexdump(vec->res, vec->res_len));
+	}
+
+	if (vec->auth_types & OSMO_AUTH_TYPE_GSM) {
+		printf("SRES:\t%s\n", osmo_hexdump(vec->sres, sizeof(vec->sres)));
+		printf("Kc:\t%s\n", osmo_hexdump(vec->kc, sizeof(vec->kc)));
+	}
+}
+
+static struct osmo_sub_auth_data test_aud = {
+	.type = OSMO_AUTH_TYPE_NONE,
+	.algo = OSMO_AUTH_ALG_NONE,
+};
+
+static void help()
+{
+	printf( "-2  --2g\tUse 2G (GSM) authentication\n"
+		"-3  --3g\tUse 3G (UMTS) authentication\n"
+		"-a  --algorithm\tSpecify name of the algorithm\n"
+		"-k  --key\tSpecify Ki / K\n"
+		"-o  --opc\tSpecify OPC (only for 3G)\n"
+		"-O  --op\tSpecify OP (only for 3G)\n"
+		"-a  --amf\tSpecify AMF (only for 3G)\n"
+		"-s  --sqn\tSpecify SQN (only for 3G)\n"
+		"-A  --auts\tSpecify AUTS (only for 3G)\n"
+		"-r  --rand\tSpecify random value\n");
+}
+
+int main(int argc, char **argv)
+{
+	struct osmo_auth_vector _vec;
+	struct osmo_auth_vector *vec = &_vec;
+	uint8_t _rand[16], _auts[16];
+	int rc, option_index;
+	int rand_is_set = 0;
+	int auts_is_set = 0;
+
+	printf("osmo-auc-gen (C) 2011-2012 by Harald Welte\n");
+	printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+	memset(_auts, 0, sizeof(_auts));
+
+	while (1) {
+		int c;
+		unsigned long ul;
+		static struct option long_options[] = {
+			{ "2g", 0, 0, '2' },
+			{ "3g", 0, 0, '3' },
+			{ "algorithm", 1, 0, 'a' },
+			{ "key", 1, 0, 'k' },
+			{ "opc", 1, 0, 'o' },
+			{ "op", 1, 0, 'O' },
+			{ "amf", 1, 0, 'f' },
+			{ "sqn", 1, 0, 's' },
+			{ "rand", 1, 0, 'r' },
+			{ "auts", 1, 0, 'A' },
+			{ "help", 0, 0, 'h' },
+			{ 0, 0, 0, 0 }
+		};
+
+		rc = 0;
+
+		c = getopt_long(argc, argv, "23a:k:o:f:s:r:hO:A:", long_options,
+				&option_index);
+
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case '2':
+			test_aud.type = OSMO_AUTH_TYPE_GSM;
+			break;
+		case '3':
+			test_aud.type = OSMO_AUTH_TYPE_UMTS;
+			break;
+		case 'a':
+			rc = osmo_auth_alg_parse(optarg);
+			if (rc < 0)
+				break;
+			test_aud.algo = rc;
+			break;
+		case 'k':
+			switch (test_aud.type) {
+			case OSMO_AUTH_TYPE_GSM:
+				rc = osmo_hexparse(optarg, test_aud.u.gsm.ki,
+						   sizeof(test_aud.u.gsm.ki));
+				break;
+			case OSMO_AUTH_TYPE_UMTS:
+				rc = osmo_hexparse(optarg, test_aud.u.umts.k,
+						   sizeof(test_aud.u.umts.k));
+				break;
+			default:
+				fprintf(stderr, "please specify 2g/3g first!\n");
+			}
+			break;
+		case 'o':
+			if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
+				fprintf(stderr, "Only UMTS has OPC\n");
+				exit(2);
+			}
+			rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
+					   sizeof(test_aud.u.umts.opc));
+			test_aud.u.umts.opc_is_op = 0;
+			break;
+		case 'O':
+			if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
+				fprintf(stderr, "Only UMTS has OP\n");
+				exit(2);
+			}
+			rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
+					   sizeof(test_aud.u.umts.opc));
+			test_aud.u.umts.opc_is_op = 1;
+			break;
+		case 'A':
+			if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
+				fprintf(stderr, "Only UMTS has AUTS\n");
+				exit(2);
+			}
+			rc = osmo_hexparse(optarg, _auts, sizeof(_auts));
+			auts_is_set = 1;
+			break;
+		case 'f':
+			if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
+				fprintf(stderr, "Only UMTS has AMF\n");
+				exit(2);
+			}
+			rc = osmo_hexparse(optarg, test_aud.u.umts.amf,
+					   sizeof(test_aud.u.umts.amf));
+			break;
+		case 's':
+			if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
+				fprintf(stderr, "Only UMTS has SQN\n");
+				exit(2);
+			}
+			ul = strtoul(optarg, 0, 10);
+			test_aud.u.umts.sqn = ul;
+			break;
+		case 'r':
+			rc = osmo_hexparse(optarg, _rand, sizeof(_rand));
+			rand_is_set = 1;
+			break;
+		case 'h':
+			help();
+			exit(0);
+		default:
+			help();
+			exit(1);
+		}
+
+		if (rc < 0) {
+			fprintf(stderr, "Error parsing argument of option `%c'\n", c);
+			exit(2);
+		}
+	}
+
+	if (!rand_is_set) {
+		printf("WARNING: We're using really weak random numbers!\n\n");
+		srand(time(NULL));
+		*(uint32_t *)&_rand[0] = rand();
+		*(uint32_t *)(&_rand[4]) = rand();
+		*(uint32_t *)(&_rand[8]) = rand();
+		*(uint32_t *)(&_rand[12]) = rand();
+	}
+
+	if (test_aud.type == OSMO_AUTH_TYPE_NONE ||
+	    test_aud.algo == OSMO_AUTH_ALG_NONE) {
+		help();
+		exit(2);
+	}
+
+	memset(vec, 0, sizeof(*vec));
+
+	if (!auts_is_set)
+		rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+	else
+		rc = osmo_auth_gen_vec_auts(vec, &test_aud, _auts, _rand, _rand);
+	if (rc < 0) {
+		if (!auts_is_set)
+			fprintf(stderr, "error generating auth vector\n");
+		else
+			fprintf(stderr, "AUTS from MS seems incorrect\n");
+		exit(1);
+	}
+
+	dump_auth_vec(vec);
+
+	if (auts_is_set)
+		printf("AUTS success: SEQ.MS = %lu\n", test_aud.u.umts.sqn);
+
+	exit(0);
+}