summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrobot101 <>2007-03-08 17:28:17 +0000
committerrobot101 <>2007-03-08 17:28:17 +0000
commit868dd2a46dd28b49eb2f8bfa521e5c615f594bc3 (patch)
tree0619bdef59c55ecb8a3414df610eed08b59d5e8d
parent36e7ba52d4397763aca609c368bc6f2472b644a1 (diff)
[svn-to-darcs @ 3]
-rw-r--r--AUTHORS10
-rw-r--r--COPYING504
-rw-r--r--ChangeLog1132
-rw-r--r--Makefile.am22
-rw-r--r--NEWS12
-rw-r--r--README56
-rw-r--r--TODO163
-rw-r--r--configure.ac93
-rw-r--r--data/.git-darcs-dir0
-rw-r--r--data/Makefile.am13
-rw-r--r--data/org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in4
-rw-r--r--docs/.git-darcs-dir0
-rw-r--r--docs/Makefile.am79
-rw-r--r--docs/design.txt83
-rw-r--r--m4/.git-darcs-dir0
-rw-r--r--m4/Makefile.am3
-rw-r--r--m4/as-compiler-flag.m433
-rw-r--r--m4/as-version.m466
-rw-r--r--m4/as_ac_expand.m441
-rw-r--r--src/.git-darcs-dir0
-rw-r--r--src/Makefile.am118
-rw-r--r--src/debug.c62
-rw-r--r--src/debug.h52
-rw-r--r--src/media-factory.c472
-rw-r--r--src/media-factory.h80
-rw-r--r--src/signals-marshal.list4
-rw-r--r--src/sip-connection-helpers.c304
-rw-r--r--src/sip-connection-helpers.h77
-rw-r--r--src/sip-connection-manager.c475
-rw-r--r--src/sip-connection-manager.h62
-rw-r--r--src/sip-connection-private.h73
-rw-r--r--src/sip-connection-sofia.c743
-rw-r--r--src/sip-connection-sofia.h41
-rw-r--r--src/sip-connection.c901
-rw-r--r--src/sip-connection.h77
-rw-r--r--src/sip-media-channel.c1254
-rw-r--r--src/sip-media-channel.h85
-rw-r--r--src/sip-media-session.c1049
-rw-r--r--src/sip-media-session.h141
-rw-r--r--src/sip-media-stream.c1266
-rw-r--r--src/sip-media-stream.h98
-rw-r--r--src/sip-text-channel.c787
-rw-r--r--src/sip-text-channel.h66
-rw-r--r--src/telepathy-helpers.c60
-rw-r--r--src/telepathy-helpers.h42
-rw-r--r--src/telepathy-sofiasip.c55
-rw-r--r--src/text-factory.c322
-rw-r--r--src/text-factory.h66
-rw-r--r--src/write-mgr-file.c112
-rw-r--r--tests/.git-darcs-dir0
-rw-r--r--tests/Makefile.am17
-rw-r--r--tests/tp_caller.c564
52 files changed, 11839 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..40bf3cd
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,10 @@
+Mikhail Zabaluev <first.surname@nokia.com>
+Kai Vehmanen <first.surname@nokia.com>
+Martti Mela <first.surname@nokia.com>
+Simon McVittie <simon.mcvittie@collabora.co.uk>
+Rob Taylor <rob.taylor@collabora.co.uk>
+
+Authors for original telepathy-gabble code:
+Robert McQueen <robert.mcqueen@collabora.co.uk>
+Ole André Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+Ross Burton <ross@burtonini.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5ab7695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 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.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2.1 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, 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.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..50b331b
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1132 @@
+2007-03-08 Robert McQueen <robert.mcqueen@collabora.collabora
+
+ * client/, generate/: Delete defunct directories.
+ * configure.ac, Makefile.am: Update in light of deleted directories.
+ * data/Makefile.am: Remove reference to obsolete siptest.manager.
+ * src/Makefile.am: Add sofiasip.manager to CLEANFILES.
+
+2007-03-08 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-helpers.c: (sip_conn_update_nua_outbound),
+ (sip_conn_update_nua_keepalive_interval),
+ (sip_conn_update_nua_contact_features):
+ * src/sip-connection-helpers.h:
+ * src/sip-connection.c: (sip_connection_start_connecting):
+ Support REGISTER keepalives through use of "expires" parameter
+ in NUTAG_M_FEATURES.
+
+2007-03-07 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-helpers.c: (sip_conn_update_nua_outbound):
+ Don't explicitly disable "natify" outbound option, necessary to
+ retain keepalives. Effectively disable keepalives for the _NONE setting.
+ * src/sip-connection-manager.c:
+ (sip_connection_manager_new_connection):
+ * src/sip-connection-private.h:
+ * src/sip-connection.c: (sip_connection_set_property),
+ (sip_connection_get_property), (sip_connection_class_init):
+ Renamed "natify" connection property to "discover-binding" for clarity.
+
+2007-03-06 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * Changed over to the branch using telepathy-glib
+ developed by Simon McVittie.
+
+2007-03-06 Simon McVittie <simon.mcvittie@collabora.co.uk>
+
+ * Use connection manager life-cycle code from telepathy-glib >= 0.5.5
+ * Generate sofiasip.manager from the source, to ensure they're in sync
+ * Remove obsolete siptest.manager
+ * Remove obsolete support for x-jingle-candidate attributes
+
+2007-02-28 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * "discover-binding" property also enables "use-rport" outbound option.
+ * Emit StreamStateChanged properly including the channel ID.
+
+2007-02-26 Robert McQueen <robert.mcqueen@collabora.co.uk>
+
+ * src/sip-media-channel.c: Add Properties interface to the
+ GetInterfaces method.
+
+2007-02-23 Robert McQueen <robert.mcqueen@collabora.co.uk>
+
+ * src/sip-media-channel.c, src/sip-media-channel.h: Use the properties
+ mixin to expose a "nat-traversal" property with the value "none".
+
+2007-02-23 Robert McQueen <robert.mcqueen@collabora.co.uk>
+
+ * generate/sip.def, generate/xml-pristine/sip-media-channel.xml,
+ generate/xml-modified/sip-media-channel.xml,
+ generate/src/sip-media-channel.c, generate/src/sip-media-channel.h:
+ Add the Properties interface to the media channel and regenerate.
+
+2007-02-16 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-manager.c: (priv_compose_proxy_uri),
+ (priv_compose_default_proxy_uri),
+ (sip_connection_manager_request_connection):
+ Compose the default proxy URI with regard to transport and port
+ parameters passed to SIPConnectionManager.
+ * src/sip-connection.c: (sip_connection_connect):
+ Don't initialize default proxy inside SIPConnection.
+
+2007-02-15 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-sofia.c: (priv_emit_remote_error),
+ (priv_r_invite): Don't assert on error responses received after
+ the media channel has been destroyed, only print a log message
+ if it's not a request termination.
+
+2007-02-14 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-helpers.c: (sip_conn_update_nua_outbound),
+ (sip_conn_update_nua_keepalive_interval):
+ * src/sip-connection-helpers.h:
+ * src/sip-connection-manager.c:
+ (sip_connection_manager_request_connection):
+ * src/sip-connection-private.h:
+ * src/sip-connection.c: (sip_connection_constructor),
+ (sip_connection_set_property), (sip_connection_get_property),
+ (sip_connection_class_init):
+ Implemented discovery of the public address option
+ (AKA "natify" in NUTAG_OUTBOUND).
+ Refactored outbound/keepalive parameter management.
+
+2007-02-09 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/telepathy-constants.h: Updated TpChannelGroupChangeReason enum
+ with new values added to the spec.
+ * src/sip-media-channel.c: (sip_media_channel_peer_error):
+ Select peer removal reason based on response status code.
+
+2007-02-08 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/Makefile.am: Added enumtypes files generated for
+ SIPConnectionKeepaliveMechanism.
+ * src/sip-connection-helpers.c:
+ (sip_conn_update_nua_keepalive_mechanism),
+ (sip_conn_update_nua_keepalive_interval):
+ * src/sip-connection-helpers.h:
+ * src/sip-connection-manager.c:
+ (sip_connection_manager_request_connection):
+ * src/sip-connection-private.h:
+ * src/sip-connection.c: (sip_connection_constructor),
+ (sip_connection_set_property), (sip_connection_get_property),
+ (sip_connection_class_init):
+ * src/sip-connection.h:
+ Initial support for the "keepalive-mechanism" connection property.
+ * src/telepathy-helpers.h: Added a missing dbus include.
+
+2007-02-05 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * data/sofiasip.manager: Added default values.
+
+2007-02-02 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection.c: (sip_connection_request_channel):
+ Allow creation of media channels with a zero creator handle.
+ * src/sip-media-channel.c: (sip_media_channel_dispose),
+ (sip_media_channel_respond_to_invite),
+ (priv_session_state_changed_cb), (priv_release_session),
+ (priv_create_session), (priv_media_channel_add_member):
+ Small code cleanups.
+
+2007-01-30 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-media-stream.c:
+ (priv_update_local_sdp): Quick fix for Farsight's new ability to
+ specify multiple transports per candidate.
+ Don't send x-jingle-candidate: lines in SDP.
+
+2007-01-29 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-sofia.c: (priv_emit_remote_error),
+ (priv_r_invite):
+ * src/sip-media-channel.h:
+ * src/sip-media-channel.c: (sip_media_channel_peer_error):
+ Emit a MembersChanged signal with the reason of error
+ when the peer responds with a non-200 final status to an INVITE.
+ * src/sip-media-channel.c: (priv_make_stream_list),
+ (sip_media_channel_stream_state), (priv_session_stream_added_cb),
+ (priv_create_session), (priv_media_channel_add_member):
+ * src/sip-media-session.c: (sip_media_session_get_peer):
+ * src/sip-media-session.h:
+ Refactored peer handle management for simplicity.
+ * src/sip-connection.c: (sip_connection_request_channel):
+ Code beautification.
+ * src/sip-text-channel.c: (sip_text_channel_message_emit):
+ * src/sip-text-channel.h: Changed a method's name to reflect its
+ public usage.
+ * src/sip-connection-sofia.c: (priv_r_message): Update to the method
+ name change for SIPTextChannel.
+
+2007-01-19 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * TODO: updated with recent developments.
+
+2007-01-19 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * data/sofiasip.manager, src/sip-connection-manager.c,
+ src/sip-connection.c, src/sip-connection-private.h:
+ Resurrected the registrar URI parameter for GConf tweaking.
+ Reverted to the single internal proxy property for a Connection.
+
+2007-01-18 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * src/sip-connection-manager.c, src/sip-connection.c: Componentized
+ proxy URI into separate parameters for host, port, and transport
+ (work in progress). Retired the registrar parameter.
+ * src/sip-connection.c, src/sip-connection-private.h,
+ src/sip-connection-helpers.h:
+ Utilize the url module from Sofia-SIP to work with SIP URI components.
+ * data/sofiasip.manager: Added proxy-host, port, transport parameters
+ instead of the single proxy URI.
+
+2007-01-16 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * data/sofiasip.manager: Added connection parameters to match
+ the UI draft requirements and the TODO listing.
+ * src/sip-connection-manager.c: Removed "http-proxy" as a
+ client-settable connection parameter.
+
+2007-01-03 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * sip-connection.c, sip-connection-manager.c: All connection instances
+ use the same Sofia root.
+
+2007-01-03 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * sip-connection.c: Fixed up destruction of NUA stack instance,
+ making the GLib source recursible for the occasion.
+
+2006-12-21 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-connection.c: Sanitize user-supplied SIP URI properties
+ so that no invalid SIP URIs are passed to the stack.
+ * sip-connection.c: Initialize the default outbound proxy at
+ connection initialization time.
+
+2006-12-19 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-connection-sofia.c: Added support for authenticating outbound
+ INVITE requests. Required for instance by some PSTN gateways.
+ * Release of 0.3.6.
+
+2006-12-18 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-media-stream.c: Added logic to detect mangled SDP fields when
+ calling through transparent media relays.
+ * tests/tp_caller.c: Do not directly use dbus_g_proxy_calls() anymore,
+ improve the logic for handling inbound calls.
+ * sip-media-session.c: Added the missing logic for handling local
+ acceptance of media sessions in the offer/answer logic.
+
+2006-12-15 Martti Mela <first.surname@nokia.com>
+
+ * sip-connection-sofia.c: only messages with Content-Type
+ "text/plain" are shown for the user.
+
+2006-12-15 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-media-channel.c: Added the missing NewSessionHandler signal.
+ * sip-media-stream.c: Fixed warnings caused by reinitializing the
+ gvalues.
+ * Release of 0.3.5.
+
+2006-12-14 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-media-channel.c, sip-media-stream.c, sip-media-session.c: Refactored
+ the offer/answer logic to allow sessions with multiple media.
+
+2006-12-13 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * sip-media-channel.c, sip-connection-helpers.c: Allow creation of
+ a media channel and emission of NewSignal with the null creator handle.
+ Assign the creator from the handle passed to
+ sip_media_channel_respond_to_invite().
+ * sip-connection-sofia.c: Emit NewSignal with the null creator handle
+ in case of an incoming call, as per an undocumented agreement.
+
+2006-12-13 Kai Vehmanen <first.surname@nokia.com>
+
+ * sip-connection.c: Modified the approach used for setting
+ key stack parameters.
+ * Both outbound and inbound audio calls now work.
+ * Release of 0.3.4.
+
+2006-12-13 Martti Mela <first.surname@nokia.com>
+
+ * configure.ac: --enable-debug now turns debugging on
+ * sip-connection-sofia.c: unsuccessful connection attempt now
+ produces error on UI.
+ * telepathy-sofiasip.c: connecting to signal "no-more-connections"
+ and handling clean exit. If GABBLE_PERSIST env flag is set, no
+ exit is done.
+
+2006-12-11 Mikhail Zabaluev <first.surname@nokia.com>
+
+ * sip-connection-sofia.c: New file.
+ * sip-connection-sofia.h: Moved code to sip-connection-sofia.c,
+ exposing the sole callback method.
+ * sip-connection.h, sip-connection.c: Exposed former
+ priv_status_changed() as sip_connection_status_changed().
+ * sip-connection-private.h: Fixed header inclusion.
+
+2006-12-07 Martti Mela <first.surname@nokia.com>
+
+ * sip-text-channel.c: Fixed a "Permission denied" after 2nd
+ message: referencing handle for an incoming message.
+
+2006-12-05 Martti Mela <first.surname@nokia.com>
+
+ * TODO: updated
+
+2006-12-04 Kai Vehmanen <first.surname@nokia.com>
+
+ * Release of 0.3.3.
+ * Fixed various bugs with communication towards the
+ stream-engine.
+ * Essential functionality for creating media channels
+ with the 0.13 telepathy API is now complete.
+
+2006-12-04 Martti Mela <first.surname@nokia.com>
+
+ * Fixed 'sent' signal: now successfully emitted after 200 OK
+ * Implemented functions for pending and acknowledging messages
+ * Added 'send-error' signal for unsuccessful msg delivery
+
+2006-11-10 Martti Mela <first.surname@nokia.com>
+
+ * incoming msg now supports 0.13 telepathy API
+
+2006-10-09 Kai Vehmanen <first.surname@nokia.com>
+
+ * Release of 0.3.2.
+
+2006-09-28 Kai Vehmanen <first.surname@nokia.com>
+
+ * Implemented the Group interface to sip-media-channel.
+
+2006-09-12 Kai Vehmanen <first.surname@nokia.com>
+
+ * Limit implementation to one media session at a time.
+ * Finished the jingle based media implementation. This is not compliant
+ with the IETF-ICE specs nor does it work against non-ICE SIP clients.
+ These limitations will be addressed in the future. See README for
+ more information on this topic.
+ * Updated TODO.
+
+2006-06-15 Kai Vehmanen <first.surname@nokia.com>
+
+ * Fixed bugs mapping SIP URIs to handles (non-static strings
+ as inputs to GQuarks).
+ * Release of 0.3.1.
+
+2006-06-14 Kai Vehmanen <first.surname@nokia.com>
+
+ * Release of 0.3.0.
+
+2006-06-08 Kai Vehmanen <first.surname@nokia.com>
+
+ * Various build fixes needed to get distcheck through.
+ * Modify main binary name to telepathy-sip.
+ * Rename top-level server dir to src.
+
+2006-06-07 Kai Vehmanen <first.surname@nokia.com>
+
+ * Add proper copyright statement to gintset.[ch].
+
+2006-06-04 Kai Vehmanen <first.surname@nokia.com>
+
+ * Updated the AUTHORS file.
+ * Added functions for stream SDP generation.
+ * Updated TODO, added section discussing differences between IETF and
+ Jabber/jingle ICE.
+
+2006-06-03 Kai Vehmanen <first.surname@nokia.com>
+
+ * Implement sip-media-session::stream_error.
+
+2006-06-02 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added new methods to sip-media-session and sip-media-channel.
+ * Fixed bugs in RequestHandle code.
+ * Implement more missing methods for sip-media-stream.
+ * Refactor channel creation in tp_caller.
+ * Updated tp_caller to request a handle with ReqeustHandle, and
+ then requesting for channel to the created handle.
+ * Fixed errors in handling connection state information.
+ * Updated hold methods to new async prototypes.
+ * Added RequestHandle implementation to sip-connection.
+ * Added SIP functions to the handles API. Keep the rest of the handles.h API
+ intact to ease merging with changes from tp-gabble.
+ * Modify handle functions to be async. Matches with current telepathy-gabble
+ implementation.
+ * Lots of updates to tp_caller. Now succesfully creates a connection, one media
+ channel, and cleans up after receiving a SIGINT.
+ * Implement get_group_flags for sip-media-channel i/f.
+ * Remove unused gabble code: group-mixins and handles-set.
+
+2006-05-31 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added tp_caller to the distribution.
+
+2006-05-30 Kai Vehmanen <first.surname@nokia.com>
+
+ * Enable to set the password as a cmdline parameter.
+
+2006-05-30 Kai Vehmanen <first.surname@nokia.com>
+
+ * Updated siptest.manager template.
+ * Updated to use the new nua_glib auth-api pushed to sofia-sip darcs
+ on 2006-05-30.
+
+2006-05-29 Kai Vehmanen <first.surname@nokia.com>
+
+ * Implemented connection:GetStatus.
+ * Updated the manager files.
+ * Implemented new_native_candidate method. Close media channels upon
+ connection disconnect.
+ * Fixed typos in not-implemented errors.
+ * Modified the media-stream::Ready signature to match tp interfaces 0.12 and 0.13.1.
+ * Fixed handling of the nua-op property.
+ * Add not-implemented warnigs to sip-connection methods.
+ * Added lots of support code to sip-media channel, session and stream codebases.
+ * Added nua-op property to sip-media-channel.
+
+2006-05-29 Martti Mela <first.surname@nokia.com>
+
+ * starting presence (SUB+NOTIFY) support
+
+2006-05-24 Kai Vehmanen <first.surname@nokia.com>
+
+ * Modify sip-connection properties to match those of nua_glib.h.
+ * Added tp_test to tests/.
+ * Modified manager name to sofiasip.
+ * Added tp-0.12 compatibility interfaces.
+ * Add compatibility methods for tp-0.12 interface.
+ * Added data subdirectory file, installation of sofiasip.service file and the necessary configure.ac additions.
+
+2006-05-23 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added kv-voiptest.py to the repo.
+ * Added not-implemented responses to sip-media-stream.
+
+2006-05-19 Martti Mela <first.surname@nokia.com>
+
+ * Fixed compiler warnings in sip-connection-manager.c.
+ * Renamed sipserver.c to sip-server.c to be consistent with other
+ sources and binary.
+
+2006-05-18 Kai Vehmanen <first.surname@nokia.com>
+
+ * Incoming and outgoing SIP MESSAGEs supported (initial)
+
+2006-05-17 Martti Mela <first.surname@nokia.com>
+
+ * Incoming SIP MESSAGEs now partly handled.
+
+2006-05-16 Martti Mela <first.surname@nokia.com>
+
+ * test-client.py: still updated message send()ing
+ * enhanced test-client.py KB exception handling
+ * test-client supports now sending SIP MESSAGEs
+ * sip-server now supports sending SIP MESSAGEs
+
+2006-05-12 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added ListChannels implementation.
+
+2006-05-15 Kai Vehmanen <first.surname@nokia.com>
+
+ * Changed name from SIPIMChannel to SIPTextChannel.
+ * Added sip-im-channel skeleton.
+
+2006-05-12 Martti Mela <first.surname@nokia.com>
+
+ * Started test program for sending IMs.
+ * sip-server: started IM integration
+ * Added bind URL and STUN server parameters for NUA gobj creation.
+
+2006-05-04 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added skeleton code for sip-media-stream.
+ * Separate derived and implementation specific declarations in
+ the public headers.
+
+2006-05-03 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added media-stream creation to media-channel and media-session.
+ * Added sip-media-stream to builds.
+
+2006-05-02 Kai Vehmanen <first.surname@nokia.com>
+
+ * Avoid using __func__.
+ * Added not-implemented stubs to sip-media-channel methods.
+ * Implemented get_interfaces for sip-media-channel.c
+ * Updated telepathy-sip sources to telepathy iface specs 0.13.1-20060502.
+ * Updated to telepathy iface specs 0.13.1-20060502.
+
+2006-04-27 Martti Mela <first.surname@nokia.com>
+
+ * Fixed build errors (time.h).
+
+2006-04-26 Kai Vehmanen <first.surname@nokia.com>
+
+ * Added sip-media-session files modified from generated templates.
+ * Modified sofia_glib to sofia_nua.
+ * Added basic code for session creation.
+ * Added sip-media-session files modified from generated templates.
+ * Do not compile handles.c. Fixed make rules.
+ * Added test client modified for Telepathy-SIP.
+
+2006-05-24 Kai Vehmanen <first.surname@nokia.com>
+
+ * Removed unused code.
+ * Added base properties to SIP channel.
+ * Moved sip-connection object registration to sip-connect code.
+
+2006-04-12 Kai Vehmanen <first.surname@nokia.com>
+
+ * Updated actual server code to telepathy interface spec 0.13.1.
+ * Updated to telepathy interface spec 0.13.1.
+ * Update to xml iface files to telepathy spec 0.13.1.
+
+2006-04-10 Kai Vehmanen <first.surname@nokia.com>
+
+ * Updated to 2006-04-10 telepathy-python specs, regenerated xml and src.
+ * Added debugging, modified to use a fixed handle, added more code to stubs.
+
+2006-04-04 Kai Vehmanen <first.surname@nokia.com>
+
+ * Modified sip-connection to use the gabble handle management functions.
+ * Added shared code from telepathy-gabble.
+ * Fixed the get_interfaces method.
+ * Fixed compilation errors with sip_connection.
+ * Backported code from original telepathy-sip. Compiles but will crash at startup.
+ * Added sip-media-channel template.
+ * Added interface marshal list files.
+
+2006-04-03 Kai Vehmanen <first.surname@nokia.com>
+
+ * Updated to the xml-modified src.
+ * Make manual modifications to Telepathy XML interface specs.
+ * Modified from SipFoobar to SIPFoobar in all interfaces.
+ * Added generated src files.
+ * Fixed client bindings to new prototypes.
+ * Added generate/ scripts, adapted from telepathy-gabble, and updated
+ the prototypes to the latest telepathy-python interfaces.
+ * Removed obsolete generation scripts. Will be replaced by generate/ scripts.
+ * Require dbus 0.60 or newer.
+ * Modified to use the correct DBUS flags for queing policy.
+ * Updated to use the new NuaGlib interface.
+
+2006-04-03 Kai Vehmanen <first.surname@nokia.com>
+
+ * Branch from telepathy-sip (Nov 29 2005) released by Rob
+ Taylor <robtaylor@floopily.org>.
+ * The branched code implements the Telepathy framework
+ interfaces as of v0.2 (2005/Nov). See the file AUTHORS for full
+ list of contributors to original telepathy-sip package.
+
+Tue Nov 29 15:42:23 EET 2005 robtaylor@floopily.org
+ * added some build notes to README
+
+Fri Nov 18 12:00:07 EET 2005 robtaylor@floopily.org
+ tagged Release 0.2.1 - snafu fix
+
+Fri Nov 18 11:18:54 EET 2005 robtaylor@floopily.org
+ * added missing files from last release
+
+Fri Nov 18 11:09:36 EET 2005 robtaylor@floopily.org
+ tagged Version 0.2
+
+Fri Nov 18 11:08:33 EET 2005 robtaylor@floopily.org
+ * cleaned up documentation for SofiaGlib
+
+Fri Nov 18 11:07:46 EET 2005 robtaylor@floopily.org
+ * cleaned up documentation for GIntSet
+
+Fri Nov 18 11:07:24 EET 2005 robtaylor@floopily.org
+ * added gtkdoc infrastructure
+
+Fri Nov 18 10:09:30 EET 2005 robtaylor@floopily.org
+ tagged Version 0.1
+
+Fri Nov 18 10:02:49 EET 2005 robtaylor@floopily.org
+ * send DTMF
+
+Fri Nov 18 10:02:10 EET 2005 robtaylor@floopily.org
+ * make contact go on hold when you leave the channel
+
+Fri Nov 18 09:56:39 EET 2005 robtaylor@floopily.org
+ * added some basic stuff for new media params
+
+Fri Nov 18 08:10:51 EET 2005 robtaylor@floopily.org
+ * made making calls, receiving call, call transfer and call forwarding work
+ this involved:
+ Solidifying the object ownership structure
+ spinning out SIPConnection's private structures into a header, to allow
+ SIPChannel to access them
+ adding functionality to sofia-glib to allow call tranfer and forwarding
+ adding a set_size funcion to GIntSet
+ adding dbus decoration for telepathy errors
+ a multitude of bug fixes
+ implementing a lot of missing interfaces
+
+Fri Nov 18 08:09:56 EET 2005 robtaylor@floopily.org
+ * added missing test-intsets.c
+
+Fri Nov 18 04:31:32 EET 2005 robtaylor@floopily.org
+ * regenerate from annotation of GetCapabilites
+
+Fri Nov 18 04:31:09 EET 2005 robtaylor@floopily.org
+ * annotated GetCapabilites so i can use the sidedoor
+
+Wed Nov 16 21:09:11 EET 2005 robtaylor@floopily.org
+ * various GIntSet fixes
+
+Wed Nov 16 21:08:44 EET 2005 robtaylor@floopily.org
+ * added unit test for GIntSet
+
+Wed Nov 16 18:58:41 EET 2005 robtaylor@floopily.org
+ * corrected malformed signal constructions
+
+Tue Nov 15 20:36:01 EET 2005 robtaylor@floopily.org
+ * code regneration for latest spec
+
+Tue Nov 15 20:34:18 EET 2005 rob.taylor@collabora.co.uk
+ * regeneration for latest spec
+
+Tue Nov 15 20:33:45 EET 2005 rob.taylor@collabora.co.uk
+ * update genxml to latest python, fix generated name
+
+Tue Nov 15 20:20:52 EET 2005 robtaylor@floopily.org
+ * error if sendmediaparams is called, we wont implement this for now
+
+Tue Nov 15 20:20:36 EET 2005 robtaylor@floopily.org
+ * add channel group flag reporting
+
+Tue Nov 15 20:20:08 EET 2005 robtaylor@floopily.org
+ * use sets for channel memebers
+
+Tue Nov 15 20:19:30 EET 2005 robtaylor@floopily.org
+ * use new type macro name SIP_TYPE_CONNECTION_MANAGER
+
+Tue Nov 15 20:17:18 EET 2005 robtaylor@floopily.org
+ * add SIPConnection parameter to SIPChannel
+
+Tue Nov 15 20:14:47 EET 2005 robtaylor@floopily.org
+ * changed GIntSet to use guint, add a foreach and to_array, fix a stupid bug
+
+Tue Nov 15 20:12:20 EET 2005 robtaylor@floopily.org
+ * fix stupid error in GIntSet
+
+Tue Nov 15 15:25:34 EET 2005 robtaylor@floopily.org
+ * regenerate from removal of generated _new methods
+
+Tue Nov 15 15:25:00 EET 2005 robtaylor@floopily.org
+ * remove generates of a _new method, its not appropriate for generation, and it was just plain wrong anyway...
+
+Tue Nov 15 14:09:22 EET 2005 robtaylor@floopily.org
+ * fixed stupid errors that stop building, sill lots of rework to do on SIPChannel
+
+Tue Nov 15 14:00:19 EET 2005 robtaylor@floopily.org
+ * convert to new api, add a handle reposiroty and a set of 'held' handles, add storage of users SDP
+
+Tue Nov 15 13:59:40 EET 2005 robtaylor@floopily.org
+ * emit correct interfaces list
+
+Tue Nov 15 13:56:12 EET 2005 robtaylor@floopily.org
+ * correct SIPConnectionManager for new api, change to store a set of SIPConnection objects rather than a hash of accountname to SIPConnection objects
+
+Tue Nov 15 13:55:11 EET 2005 robtaylor@floopily.org
+ * add mnual annotation of c name for SIPChannel:SendDTMF to work around bug in dbus-binding-tool
+
+Tue Nov 15 13:54:01 EET 2005 robtaylor@floopily.org
+ * fixed typo in makefile that caused telepathy-errors-enumtypes.h to be be misgnerated
+
+Tue Nov 15 13:53:05 EET 2005 robtaylor@floopily.org
+ * added an implementation of a set of integers, optimised for low-order integers
+
+Tue Nov 15 13:52:18 EET 2005 robtaylor@floopily.org
+ * renames generated client bindings to *-bindings.h
+
+Tue Nov 15 13:51:47 EET 2005 robtaylor@floopily.org
+ * added generation of tests/Makefile.am to configure.ac
+
+Tue Nov 15 13:50:30 EET 2005 robtaylor@floopily.org
+ * added a tests diretctory for unit tests, added a unittest for TelepathyHandleRepo
+
+Tue Nov 15 13:49:22 EET 2005 robtaylor@floopily.org
+ * added a telepathy handle repository implementation
+
+Tue Nov 15 13:48:44 EET 2005 robtaylor@floopily.org
+ * made a lot of things boring so darcs whatsnew -l is usable
+
+Tue Nov 15 13:42:44 EET 2005 robtaylor@floopily.org
+ * added TODO
+
+Mon Nov 14 21:39:12 EET 2005 robtaylor@floopily.org
+ * clarified a line
+
+Mon Nov 14 12:04:26 EET 2005 robtaylor@floopily.org
+ * regenerate code fro spec 0.10.1
+
+Mon Nov 14 12:03:56 EET 2005 robtaylor@floopily.org
+ * resolve conflict from 0.10.1 xmlregen
+
+Mon Nov 14 12:02:58 EET 2005 rob.taylor@collabora.co.uk
+ * updated xml from latest spec
+
+Mon Nov 14 02:07:59 EET 2005 robtaylor@floopily.org
+ * regenerate from removal of old stuff from sip-channel.xml
+
+Mon Nov 14 02:07:37 EET 2005 robtaylor@floopily.org
+ * removed old crap that was still in sip-channel.xml
+
+Mon Nov 14 02:00:29 EET 2005 robtaylor@floopily.org
+ * regenerate to remove gerror quarks and make a{s* params GHashTables
+
+Mon Nov 14 01:59:27 EET 2005 robtaylor@floopily.org
+ * remove the gquark error stuff, it's usually not right to have a seperate error
+ domain per dbus object
+
+Mon Nov 14 01:58:35 EET 2005 robtaylor@floopily.org
+ * all a{s* types should encode as a GHashTable, i think
+
+Mon Nov 14 01:48:59 EET 2005 robtaylor@floopily.org
+ * moved function bodites for functions that moved in the xml :/
+
+Mon Nov 14 01:19:33 EET 2005 robtaylor@floopily.org
+ * resolve conflicts
+
+Mon Nov 14 01:18:24 EET 2005 robtaylor@floopily.org
+ * regenerate code from new xml due to spec change
+
+Mon Nov 14 01:17:46 EET 2005 robtaylor@floopily.org
+ * resolved conflicts from xml regen of massive spec change
+
+Mon Nov 14 01:12:19 EET 2005 rob.taylor@collabora.co.uk
+ * regenerate xml from massive spec change
+
+Mon Nov 14 01:11:48 EET 2005 rob.taylor@collabora.co.uk
+ * modify genxml to add missing and new interfaces
+
+Mon Nov 14 01:16:07 EET 2005 robtaylor@floopily.org
+ * emit MEMBERS_CHANGED signals
+
+Mon Nov 14 01:13:53 EET 2005 robtaylor@floopily.org
+ * implement callstate change for new interface design
+
+Sun Nov 13 20:05:39 EET 2005 robtaylor@floopily.org
+ * fixups in real code for correcy prefix spotting in the gobject generation code
+
+Sun Nov 13 20:04:02 EET 2005 robtaylor@floopily.org
+ * regeneration from fixing our conception of prefix
+
+Sun Nov 13 20:03:20 EET 2005 robtaylor@floopily.org
+ * prefix should be just the 1st section of a name
+
+Sun Nov 13 20:01:29 EET 2005 robtaylor@floopily.org
+ * prefix is only to 1st underscore, fix enumtypes rules appropriately
+
+Sun Nov 13 19:50:44 EET 2005 robtaylor@floopily.org
+ * inclyde telepathy-errors.h
+
+Sun Nov 13 19:50:18 EET 2005 robtaylor@floopily.org
+ * remove all member state stuff as no longer necessary thanks to spec rewrite...
+
+Sun Nov 13 19:49:35 EET 2005 robtaylor@floopily.org
+ * fix enumtypes generation to have correct guardnames and type macro names
+
+Sun Nov 13 18:45:59 EET 2005 robtaylor@floopily.org
+ * regeneration from new xml
+
+Sun Nov 13 18:45:22 EET 2005 robtaylor@floopily.org
+ * removed pointless PYTHONPATH stuff from do_gen
+
+Sun Nov 13 18:27:34 EET 2005 robtaylor@floopily.org
+ * resolved clash in sip-channel.xml
+
+Sun Nov 13 18:25:58 EET 2005 robtaylor@floopily.org
+ * cleaned up do_gen a bit
+
+Sun Nov 13 18:11:53 EET 2005 rob.taylor@collabora.co.uk
+ * regenerated xml from latest spec changes
+
+Sun Nov 13 18:02:33 EET 2005 robtaylor@floopily.org
+ * 1st steps for handling incoming calls
+
+Sun Nov 13 18:01:59 EET 2005 robtaylor@floopily.org
+ * abortive remove_members impl. commented out for now, waiting for handle rework of spec
+
+Sun Nov 13 18:01:38 EET 2005 robtaylor@floopily.org
+ * added handling of bye
+
+Sun Nov 13 18:01:02 EET 2005 robtaylor@floopily.org
+ * renamed l_/r_sdp to local_/remote_sdp
+
+Sun Nov 13 17:57:38 EET 2005 robtaylor@floopily.org
+ * added ringing state for channel members
+
+Sun Nov 13 17:55:39 EET 2005 robtaylor@floopily.org
+ * add sofia-glib.c to sip_server_SOURCES
+
+Sun Nov 13 17:28:37 EET 2005 robtaylor@floopily.org
+ * resolved conflict in server/Makefile.am
+
+Sun Nov 13 17:12:30 EET 2005 robtaylor@floopily.org
+ * added generation of glib enum types for telepathy errors and made some rules nicer to reduce duplciation
+
+Sun Nov 13 17:11:36 EET 2005 robtaylor@floopily.org
+ * added gnerated telepathy-errors.h
+
+Sun Nov 13 17:10:26 EET 2005 robtaylor@floopily.org
+ * made a lot of file generated during make boring
+
+Sat Nov 12 23:08:53 EET 2005 robtaylor@floopily.org
+ * added code to generate an errors enum
+
+Sat Nov 12 23:08:28 EET 2005 robtaylor@floopily.org
+ * made gengobject importable
+
+Sat Nov 12 22:16:58 EET 2005 robtaylor@floopily.org
+ * regeneration from adding _H_ in generation of header guard names
+
+Sat Nov 12 22:16:33 EET 2005 robtaylor@floopily.org
+ * added an _H_ in generation of header guard names
+
+Sat Nov 12 22:13:23 EET 2005 robtaylor@floopily.org
+ * regeneration for fix to description generation
+
+Sat Nov 12 22:12:57 EET 2005 robtaylor@floopily.org
+ * fixed file description generation
+
+Sat Nov 12 22:08:45 EET 2005 robtaylor@floopily.org
+ * renamed python scripts so they can be imported
+
+Sat Nov 12 21:22:19 EET 2005 robtaylor@floopily.org
+ * regeneration from removing async annotation from RequestChannel
+
+Sat Nov 12 21:21:46 EET 2005 robtaylor@floopily.org
+ * RequestChannel doesnt need to be async, removing annotation
+
+Fri Nov 11 18:46:39 EET 2005 robtaylor@floopily.org
+ * regnerate to fix typos and make SIPChannel:RemoveMembers non-asyc
+
+Fri Nov 11 18:46:17 EET 2005 robtaylor@floopily.org
+ * fix typo
+
+Fri Nov 11 18:45:00 EET 2005 robtaylor@floopily.org
+ * RequestMember doesn't need to be async
+
+Fri Nov 11 18:54:17 EET 2005 robtaylor@floopily.org
+ * implement all remaining telepathy interface, create and destroy SIPChannels
+
+Fri Nov 11 18:51:50 EET 2005 robtaylor@floopily.org
+ * SIPConnectionManager now removes channels that are closed
+
+Fri Nov 11 18:51:28 EET 2005 robtaylor@floopily.org
+ * free private data for SIPConnectionManager
+
+Fri Nov 11 18:49:23 EET 2005 robtaylor@floopily.org
+ * initial implementation of sip-channel with support for making calls
+
+Fri Nov 11 16:57:32 EET 2005 robtaylor@floopily.org
+ * whitespace changes
+
+Fri Nov 11 16:56:08 EET 2005 robtaylor@floopily.org
+ * added missing sofia_glib_op_get/set_data
+
+Thu Nov 10 16:03:09 EET 2005 robtaylor@floopily.org
+ * regenerated from latest xml change
+
+Thu Nov 10 16:02:27 EET 2005 robtaylor@floopily.org
+ * resolved conflict in ./sip-connection-manager.xml
+
+Thu Nov 10 16:01:15 EET 2005 rob.taylor@collabora.co.uk
+ * regenerated xml from latest spec change
+
+Thu Nov 10 14:06:30 EET 2005 robtaylor@floopily.org
+ * regenerate
+
+Thu Nov 10 14:06:00 EET 2005 robtaylor@floopily.org
+ * add code to autogen some basic dbus method documentation
+
+Thu Nov 10 14:05:27 EET 2005 robtaylor@floopily.org
+ * add code to generate a finalize method in our classes
+
+Thu Nov 10 03:50:34 EET 2005 robtaylor@floopily.org
+ * regenerate
+
+Thu Nov 10 03:50:03 EET 2005 robtaylor@floopily.org
+ * remove *pointless* signal emit methods
+
+Thu Nov 10 03:30:40 EET 2005 robtaylor@floopily.org
+ * regenerate
+
+Thu Nov 10 03:30:12 EET 2005 robtaylor@floopily.org
+ * fix to gen-gobject.py to remove whitespace at end of generated files
+
+Thu Nov 10 03:12:32 EET 2005 robtaylor@floopily.org
+ * regenerated after last gen-gobject change
+
+Thu Nov 10 03:08:50 EET 2005 robtaylor@floopily.org
+ * added code to generate signal emitter bodies, and fix delcarations in headers to match
+
+Thu Nov 10 03:27:06 EET 2005 robtaylor@floopily.org
+ * set dispose and finalize in class init
+
+Thu Nov 10 03:26:19 EET 2005 robtaylor@floopily.org
+ * sofia_glib_authorize was static by accident, this exposes it
+
+Thu Nov 10 03:25:51 EET 2005 robtaylor@floopily.org
+ * whitespace
+
+Thu Nov 10 03:20:38 EET 2005 robtaylor@floopily.org
+ * move su_init into object init, so we can allocate strings in su in CONSTRUCT params
+
+Thu Nov 10 03:18:45 EET 2005 robtaylor@floopily.org
+ * pass in account info into SIPConnection construction
+
+Thu Nov 10 03:16:01 EET 2005 robtaylor@floopily.org
+ * cleanups due to actually spotting warnings...
+
+Thu Nov 10 03:13:44 EET 2005 robtaylor@floopily.org
+ * use -Wall -Werror
+
+Wed Nov 9 21:53:33 EET 2005 robtaylor@floopily.org
+ * fixed up register answered callback
+
+Wed Nov 9 21:52:33 EET 2005 robtaylor@floopily.org
+ * added parameters and constructor to SIPConnection
+
+Wed Nov 9 21:50:29 EET 2005 robtaylor@floopily.org
+ * added state and param enums,also added address to SIPConnectionPrivate
+
+Wed Nov 9 21:49:51 EET 2005 robtaylor@floopily.org
+ * added error for when we have two connection requests to the same name
+
+Wed Nov 9 21:49:36 EET 2005 robtaylor@floopily.org
+ * re-added sofia_glib_op_method_type
+
+Wed Nov 9 21:49:07 EET 2005 robtaylor@floopily.org
+ * add documenation to sofia-sip
+
+Wed Nov 9 20:22:55 EET 2005 robtaylor@floopily.org
+ * added the constuctor back in, we need this to access the construction params
+
+Wed Nov 9 19:53:36 EET 2005 robtaylor@floopily.org
+ * added properties for address,proxy and registrar, removed constructor brain damage
+
+Wed Nov 9 19:52:04 EET 2005 robtaylor@floopily.org
+ * moved creation of SofiaGlib into the connaction object, so we have one per connection
+
+Wed Nov 9 01:39:52 EET 2005 robtaylor@floopily.org
+ * regenerated
+
+Wed Nov 9 01:38:43 EET 2005 robtaylor@floopily.org
+ * made ListChannels async so we can use low-level bindings
+
+Wed Nov 9 01:32:04 EET 2005 robtaylor@floopily.org
+ * removed bogus annotate in sip-connection.xml
+
+Wed Nov 9 01:30:18 EET 2005 robtaylor@floopily.org
+ * regenerated from new xml
+
+Wed Nov 9 01:29:39 EET 2005 robtaylor@floopily.org
+ * removed various printf's left over from nua_cli
+
+Wed Nov 9 01:29:29 EET 2005 robtaylor@floopily.org
+ * whitespace change
+
+Wed Nov 9 01:28:54 EET 2005 robtaylor@floopily.org
+ * various cleanup in sofia-glib
+
+Wed Nov 9 01:27:26 EET 2005 robtaylor@floopily.org
+ * whitespace changes
+
+Wed Nov 9 01:26:23 EET 2005 robtaylor@floopily.org
+ * added code to start using sofia-glib
+
+Wed Nov 9 01:24:35 EET 2005 robtaylor@floopily.org
+ * changed occurrences of 'g' in signatures to 's', as dbus glib bindings don't support this yet
+
+Wed Nov 9 01:15:00 EET 2005 robtaylor@floopily.org
+ * annotated xml for async methods
+
+Wed Nov 9 01:08:22 EET 2005 robtaylor@floopily.org
+ * regenerated xml
+
+Wed Nov 9 01:07:51 EET 2005 robtaylor@floopily.org
+ * updated gen-xml.py to new telepathy api
+
+Tue Nov 8 14:52:56 EET 2005 robtaylor@floopily.org
+ * add an app-specific data member to SofiaGlibOp
+
+Tue Nov 8 14:52:03 EET 2005 robtaylor@floopily.org
+ * stop warnings being fatal
+
+Tue Nov 8 14:50:15 EET 2005 robtaylor@floopily.org
+ * SIPConnectionManager now uses sofia-glib to register with a service
+
+Tue Nov 8 14:19:57 EET 2005 robtaylor@floopily.org
+ * bug fixes to sofia-glib, add a function to get the owner of an operation
+
+Mon Nov 7 22:08:34 EET 2005 robtaylor@floopily.org
+ * added code to test dbus-glib-lowlevel changes
+
+Mon Nov 7 22:07:00 EET 2005 robtaylor@floopily.org
+ * added sofia-glib, a glib wrapper for libsofia-sip-ua
+
+Mon Nov 7 22:06:15 EET 2005 robtaylor@floopily.org
+ * updated configure.ac to check for sofia and dbus
+
+Tue Nov 1 04:45:47 EET 2005 robtaylor@floopily.org
+ * regeneration from change to sip-connection.xml
+
+Tue Nov 1 04:45:20 EET 2005 robtaylor@floopily.org
+ * make ListChannels async so we can use the low-level bindings
+
+Tue Nov 1 04:42:00 EET 2005 robtaylor@floopily.org
+ * return the right paths
+
+Fri Oct 28 18:24:22 EEST 2005 robtaylor@floopily.org
+ * oops.had forgotten to add sip-server.c a long time ago
+
+Fri Oct 28 18:19:02 EEST 2005 robtaylor@floopily.org
+ * regenerate
+
+Fri Oct 28 18:18:09 EEST 2005 robtaylor@floopily.org
+ * last change to gen-gobject was nonsense, this fixes it
+
+Fri Oct 28 18:12:33 EEST 2005 robtaylor@floopily.org
+ * re generate
+
+Fri Oct 28 18:10:56 EEST 2005 robtaylor@floopily.org
+ * modify gen-gobject to put the signal enum and signals array in the body
+
+Fri Oct 28 18:13:05 EEST 2005 robtaylor@floopily.org
+ * initial try at creating the Connection object on a call of sip_connection_manager_connect
+
+Fri Oct 28 15:14:27 EEST 2005 robtaylor@floopily.org
+ * regen from last change to gen-gobject
+
+Fri Oct 28 15:13:55 EEST 2005 robtaylor@floopily.org
+ * dont put quark definition in the header, silly
+
+Fri Oct 28 15:12:32 EEST 2005 robtaylor@floopily.org
+ * added sipserver core to do start-of-day
+
+Thu Oct 27 22:10:27 EEST 2005 robtaylor@floopily.org
+ * newly generated SIPChannel, SIPConnection and SIPConnectionManager
+
+Thu Oct 27 22:08:52 EEST 2005 robtaylor@floopily.org
+ * added a little helper
+
+Thu Oct 27 22:07:58 EEST 2005 robtaylor@floopily.org
+ * add numbering for multiple unnamed ret values
+
+Thu Oct 27 22:05:43 EEST 2005 robtaylor@floopily.org
+ * fix where gen-gobject produces syntax errors =)
+
+Thu Oct 27 22:04:46 EEST 2005 robtaylor@floopily.org
+ * DBusGValue doesn't exist yet
+
+Thu Oct 27 22:03:58 EEST 2005 robtaylor@floopily.org
+ * quick hack to avoid name clash in intro xml.
+
+Thu Oct 27 22:02:49 EEST 2005 robtaylor@floopily.org
+ * added marshalling for other signals, niceties
+
+Thu Oct 27 22:01:59 EEST 2005 robtaylor@floopily.org
+ * added pkg-config checks for glib and dbus
+
+Thu Oct 27 19:31:54 EEST 2005 robtaylor@floopily.org
+ * add stub methods, dispose and new. clean up classnaming.
+
+Thu Oct 27 19:30:58 EEST 2005 robtaylor@floopily.org
+ * add an error quark for async errors
+
+Thu Oct 27 19:30:16 EEST 2005 robtaylor@floopily.org
+ * fix copyright header writing to be accurate ;)
+
+Thu Oct 27 18:00:19 EEST 2005 robtaylor@floopily.org
+ * remove debug
+
+Thu Oct 27 17:59:50 EEST 2005 robtaylor@floopily.org
+ * add understanding of async methods
+
+Thu Oct 27 17:59:10 EEST 2005 robtaylor@floopily.org
+ * cosmetic fix for include guards
+
+Thu Oct 27 17:58:54 EEST 2005 robtaylor@floopily.org
+ * clean up generated license
+
+Thu Oct 27 17:57:05 EEST 2005 robtaylor@floopily.org
+ * added g_signal_new genertion in class init
+
+Thu Oct 27 17:56:26 EEST 2005 robtaylor@floopily.org
+ * added test async method call to sip-channel introspection xml
+
+Thu Oct 27 05:04:32 EEST 2005 robtaylor@floopily.org
+ * some gen-gobject output tidying
+
+Thu Oct 27 04:58:50 EEST 2005 robtaylor@floopily.org
+ * added 1st cut gobject stub generation from introspection xml
+
+Wed Oct 26 17:33:01 EEST 2005 robtaylor@floopily.org
+ * add gobject definition for SIPChannel
+
+Wed Oct 26 17:32:29 EEST 2005 robtaylor@floopily.org
+ * add glib-genmarshal stuff to generate marchalling for our dbus signals
+
+Wed Oct 26 17:31:09 EEST 2005 robtaylor@floopily.org
+ * rename dbus generated serverside includes to *-glue.h
+
+Wed Oct 26 17:30:39 EEST 2005 robtaylor@floopily.org
+ * add autoconf to check for glib and glib-genmarshal
+
+Tue Oct 25 13:21:01 EEST 2005 robtaylor@floopily.org
+ * remove python-specific type identifier
+
+Tue Oct 25 13:01:57 EEST 2005 robtaylor@floopily.org
+ * fixed thinkos in server automake
+
+Tue Oct 25 13:01:22 EEST 2005 robtaylor@floopily.org
+ * added missing client directory
+
+Tue Oct 25 12:32:55 EEST 2005 robtaylor@floopily.org
+ * split codebase into client and server
+
+Tue Oct 25 04:25:43 EEST 2005 robtaylor@floopily.org
+ * add all the autotool generated stuff to _boring
+
+Tue Oct 25 04:20:23 EEST 2005 robtaylor@floopily.org
+ * create a _boring file so we can ignore all the autotool generated stuff
+
+Tue Oct 25 04:19:53 EEST 2005 robtaylor@floopily.org
+ * add in missing GNU files
+
+Tue Oct 25 04:19:09 EEST 2005 robtaylor@floopily.org
+ * add in initial autotooling
+
+Tue Oct 25 04:18:30 EEST 2005 robtaylor@floopily.org
+ * added autofoo to generate service and client headers form the xml binding information
+
+Tue Oct 25 03:57:33 EEST 2005 robtaylor@floopily.org
+ * add some generated xml
+
+Tue Oct 25 03:56:51 EEST 2005 robtaylor@floopily.org
+ * change output to cwd for gen-xml.py
+
+Tue Oct 25 03:55:05 EEST 2005 robtaylor@floopily.org
+ * renamed output files from the xml generator
+
+Tue Oct 25 03:52:42 EEST 2005 robtaylor@floopily.org
+ * created a gen-xml.py that will use the telepathy python bindings to generate xml for the telepathy objects we use for the sip service
+
+Tue Oct 25 03:51:29 EEST 2005 robtaylor@floopily.org
+ * add in m4 defn of AS_AC_EXPAND from Rapha[_\c3_][_\ab_]l Slinckx's dbus binding tutorial
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..5c7dc72
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,22 @@
+#
+# Makefile.am for telepathy-sofiasip/src
+#
+# Copyright (C) 2006 Nokia Corporation
+# Contact: Kai Vehmanen <first.surname@nokia.com>
+# Licensed under LGPL. See file COPYING.
+#
+
+# ----------------------------------------------------------------------
+# Automake options
+
+ACLOCAL_AMFLAGS = -I m4
+
+# ----------------------------------------------------------------------
+# Build targets
+
+SUBDIRS = data \
+ docs \
+ m4 \
+ src \
+ tests
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..07dcbad
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,12 @@
+NEWS for telepathy-sofiasip
+===========================
+
+List of changes in each public release of telepathy-sofiasip (most
+recent at the top).
+
+See also ChangeLog.
+
+Changes in release: 0.x.y (2006-xx-yy)
+--------------------------------------
+
+- ...
diff --git a/README b/README
new file mode 100644
index 0000000..9844b9b
--- /dev/null
+++ b/README
@@ -0,0 +1,56 @@
+===============================================================
+README / telepathy-sofiasip
+===============================================================
+
+Building
+--------
+
+When building from the devel tree (you need automake-1.9):
+
+sh> gtkdocize # (optional)
+sh> autoreconf -i
+
+And then continue with the normal:
+
+sh> ./configure
+sh> make
+
+If using gtkdoc, a second 'make' is needed as the 1st will fail due
+to a bug in gtkdoc.
+
+Running
+-------
+
+Set the "SOFIASIP_PERSIST" environment variable to prevent the connection
+manager from exiting after all connections have been disconnected. This
+option is only available if telepathy-sofiasip was configured with
+the "--enable-debug" option.
+
+Comments on IETF ICE compliance
+------------------------------
+
+Telepathy frameworks streamed media support was first implemented for
+Jabber/Jingle ICE-RTP profile. Using the same interface for SIP based
+media session setup creates some challenges (most urgent first):
+
+Spec issues
+~~~~~~~~~~~
+
+- some differences in terms: IETF-ICE has component (candidate-id) and transport
+ identifiers (tid) and a session-level password (ice-pwd), which are similar but
+ not equivalent to Telepathy transport properties 'user' and 'pass'
+- the candidate-id values should be random with length of at least
+ four characters
+- libjingle does not support multiple components (like one for RTP and one
+ for RTCP) per candidate
+
+Implementation issues
+~~~~~~~~~~~~~~~~~~~~~
+
+- open: to talk with non-ICE compliant SIP endpoints, libjingle has to
+ support bypassing connectivity checks for the default candidate - can
+ this be done?
+- currently libjingle does not emit NativeCandidatePrepared, making it
+ more difficult to decide when to send all local candidates (whether to
+ wait until one of each type of candidates is generated, or some fixed
+ time interval?)
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..e4984cb
--- /dev/null
+++ b/TODO
@@ -0,0 +1,163 @@
+===============================================================
+TODO / telepathy-sofiasip
+===============================================================
+
+Feature Roadmap
+---------------
+
+- re-registration on network change detection
+- re-offer media streams on network change detection (handover)
+
+Critical todo items
+-------------------
+
+- when making outbound sessions with multiple media, only
+ first media is succesfully set to playing state
+ - signals are emitted correctly, but they do not seem to have
+ the correct effect
+- DONE: (works for me) segfault handling an offer that has fewer media
+ than locally available (audio offer, when audio+video locally available)
+
+Account settings
+----------------
+
+- note: requires modifications to data/sofiasip.manager, sip-connection.c
+ as well as to sip-connection-manager.c
+- ability to toggle whether to modify local contact (discover binding)
+ - whether to use rport and/or STUN and re-register with the updated
+ contact
+- additional set of username, realm and password for authentication
+ - to authenticate to PSTN gateways, etc where registrations credentials
+ are not sufficient
+ - also needed if the service provider uses a separate username
+ for authentication (different from user part of the public SIP address)
+- ability to disable known difficult-to-implement features
+ - early media with PRACK
+- ability to disable use of outbound proxy
+ - any use cases for this?
+ - see sip-connection.c:sip_connection_connect()
+
+Connection management
+---------------------
+
+- implement Connection.AdvertiseCapabilities()
+- check if we already have a connection to a requested account
+
+Media sessions
+--------------
+
+- if multiple network interfaces are present, the wrong IP address
+ may be offered in a c= SDP media line.
+- prevent an endless authentication loop when the server responds
+ with 401 or 407 repeatedly.
+- verify correct operation with 100rel/PRACK
+- verify correct operation with a remote node utilizing
+ early media
+- implement code for removing group members
+ - see sip-media-channel.c:priv_media_channel_remove_member()
+- implement StreamHandler.CodecChoice()
+
+Presence and messaging
+-----------------------
+
+- check that content type of receivng messages is "text/plain" before
+ forwarding; or parse the MIME payload and try to find a "text/plain" part
+- send back an error code for unsupported message body types
+- make sure that body character sets other than ASCII and UTF-8 are supported
+ or at least detected
+- implement Connection.Interface.Presense.AddStatus()
+- implement Connection.Interface.Presense.SetStatus()
+- implement Connection.Interface.Presense.RemoveStatus()
+- implement Connection.Interface.Presense.ClearStatus()
+- implement Connection.Interface.Presense.GetStatuses()
+- implement Connection.Interface.Presense.RequestContactInfo()
+- implement Connection.Interface.Presense.SetLastActivityTime()
+- ConnectionInterfacePresence; RequestPresence:
+ - Response to SUBSCRIBE initiated by nua_glib_subscribe() emits a signal
+ subscribe-answered from nua_glib but there is no signal for
+ telepathy-sofiasip client, i.e. client cannot be informed if subscribe was
+ successful.
+
+Test programs
+-------------
+
+- tp_caller is an ugly hack, major refactoring needed
+- unit tests for basic functionality (creation and removal of
+ conn.mgr etc objects, registration, calling to itself, etc)
+
+Plugin interface
+----------------
+
+- mechanism to dynamically load handlers for new types of
+ channels, and/or new types of connections
+
+General
+-------
+
+- various XXX items in the source codes (a generic todo item)
+ - status 2006-11-26: 37 XXXs
+ - status 2006-12-04: 36 XXXs
+ - status 2006-12-05: 24 XXXs
+ - status 2006-12-15: 20 XXXs
+ - status 2006-12-18: 19 XXXs
+
+Past todo items
+---------------
+
+- DONE: un-REGISTER does not exit
+- DONE: unsuccessful REGISTER not handled correctly
+- DONE: 3rd message (sent or recvd) causes "Permission denied" because of bad handle code
+- DONE: 'message-sent' emitted after 200 OK
+- DONE: 'send-error' emitted if message delivery failed
+- DONE: "Permission denied" shown when starting a chat by updating tp-sofiasip dbus API
+- DONE: various XXX-SIPify items (code copied from telepathy-gabble but not yet
+ converted to SIP) items around the codebase
+- DONE: all places marked with "#if 0" should be resolved
+- DONE: upgrade to tp-0.13 interfaces
+ - VoipEngine -> StreamEngine
+ - remaining FooHandle -> Foohandles changes
+- DONE: move from nua_glib to nua
+ - better API for extensions (new methods, custom headers, nua
+ identity, different presence usage scenarios, etc, etc)
+ - less maintenance (nua_glib+nua vs nua)...?
+- DONE: BYE is not correcly sent when Dbus connection dies
+ - it tries to send it, but process exits before BYE is completed
+ - segfault due to invalid handle
+ - 0x0804b8ab in cb_status_changed (conn=0x8082c38, data=0x2) at sip-connection-manager.c:70
+ - 70 g_hash_table_remove (connman->priv->connections, conn);
+- DONE: update handles code to use gheap.h (and not use quarks)
+- DONE: verify session cleanup (make a test case that repeatedly
+ creates and destroys media sessions)
+- DONE: test that signaling for local alerts works
+- DONE: the conn.mgr should parse the SDP and create a matching number
+ of sip-media-stream instance (otherwise we get an assert
+ from sip-media-session:sip_media_session_set_remote_info())
+- DONE: currently in auto-answer mode, should wait until client
+ modifies the local_pending_members set
+- DONE: correct handling incomning call hold
+ - verified with 0.3.5 (remote client N80ie)
+- DONE: call that fails with a 404 response is not properly handled
+ - channel disconnected but no proper error given to the UI
+- DONE: add an option to stream-engine interface to select non-jingle mode
+ of operation
+- DONE: specifying keepalive method
+- DONE: setting to override first-hop transport selection
+ - "transport", with possible settings of "udp", "tcp", "tcp/tls", "auto"
+ - "proxy" setting has been replaced by "transport", "proxy-host", "port"
+- DONE: specifying keepalive method frequency
+- DONE: rename "contact" to "bind-url"
+ -> removed contact altogether, instead use "address", "proxy" and
+ "registrar" to determine the set of required transports and
+ local sockets to activate
+- READY: account settings
+ - ability to set all key connection parameters
+- READY: solid registrations
+ - login and logout initiated by TP UIs
+ - ability to support multiple accounts
+- READY: inbound and outbound audio calls
+ - interoperability with PSTN gateways and SIP compliant clients
+- READY: sending and receiving instant messages (SIP MESSAGE)
+- READY: outbound and inbound audio/video calls
+- READY: basic SIP presence (avail/not-avail)
+ - not real presence
+ \ No newline at end of file
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..36d8c13
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,93 @@
+AC_PREREQ([2.59])
+
+AC_INIT
+AC_CONFIG_MACRO_DIR([m4])
+
+AS_VERSION(telepathy-sofiasip, TELEPATHY_SIP_VERSION, 0, 3, 11, 1, WERROR="no", WERROR="yes")
+
+AM_INIT_AUTOMAKE($PACKAGE, $VERSION)
+
+AM_PROG_LIBTOOL
+AM_CONFIG_HEADER(config.h)
+
+dnl check for tools
+AC_PROG_CC
+AC_PROG_CC_STDC
+AM_PROG_AS
+
+dnl decide on error flags
+AS_COMPILER_FLAG(-Wall, WALL="yes", WALL="no")
+
+if test "x$WALL" = "xyes"; then
+ ERROR_CFLAGS="-Wall"
+
+ if test "x$WERROR" = "xyes"; then
+ AS_COMPILER_FLAG(-Werror,ERROR_CFLAGS="$ERROR_CFLAGS -Werror",ERROR_CFLAGS="$ERROR_CFLAGS")
+ fi
+fi
+
+AC_SUBST(ERROR_CFLAGS)
+
+AC_ARG_ENABLE(debug,
+ AC_HELP_STRING([--enable-debug],[compile with debug code]),
+ enable_debug=$enableval, enable_debug=no )
+
+if test x$enable_debug = xyes; then
+ AC_DEFINE(ENABLE_DEBUG, [], [Enable debug code])
+fi
+
+AC_HEADER_STDC([])
+AC_C_INLINE
+
+dnl GTK docs
+GTK_DOC_CHECK
+
+dnl Check for Glib
+PKG_CHECK_MODULES(GLIB, gobject-2.0 >= 2.4, have_glib=yes, have_glib=no)
+
+if test x$have_glib = xno ; then
+ AC_MSG_ERROR([GLib development libraries not found])
+fi
+
+GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+AC_SUBST(GLIB_GENMARSHAL)
+
+dnl Check for DBus
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.60, dbus-glib-1 >= 0.60], have_dbus=yes, have_dbus=no)
+
+AC_SUBST(DBUS_CFLAGS)
+AC_SUBST(DBUS_LIBS)
+
+AS_AC_EXPAND(DATADIR, $datadir)
+DBUS_SERVICES_DIR="$DATADIR/dbus-1/services"
+AC_SUBST(DBUS_SERVICES_DIR)
+AC_DEFINE_UNQUOTED(DBUS_SERVICES_DIR, "$DBUS_SERVICES_DIR", [DBus services directory])
+
+dnl Check for sofia
+PKG_CHECK_MODULES(SOFIA_SIP_UA, sofia-sip-ua-glib >= 1.12.3)
+AC_SUBST(SOFIA_SIP_UA_LIBS)
+AC_SUBST(SOFIA_SIP_UA_CFLAGS)
+AC_SUBST(SOFIA_SIP_UA_VERSION)
+
+dnl Check for telepathy-glib
+PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.5.5])
+
+AC_SUBST(TELEPATHY_GLIB_CFLAGS)
+AC_SUBST(TELEPATHY_GLIB_LIBS)
+
+dnl Check for Telepathy client library (needed for some test apps)
+PKG_CHECK_MODULES(LIBTELEPATHY, [libtelepathy], have_libtelepathy=yes, have_libtelepathy=no)
+AC_SUBST(LIBTELEPATHY_CFLAGS)
+AC_SUBST(LIBTELEPATHY_LIBS)
+AM_CONDITIONAL(HAVE_LIBTELEPATHY, test x$have_libtelepathy = xyes)
+
+AC_OUTPUT( Makefile \
+src/Makefile \
+m4/Makefile \
+data/Makefile \
+tests/Makefile \
+docs/Makefile
+)
+
diff --git a/data/.git-darcs-dir b/data/.git-darcs-dir
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/data/.git-darcs-dir
diff --git a/data/Makefile.am b/data/Makefile.am
new file mode 100644
index 0000000..cf02bfe
--- /dev/null
+++ b/data/Makefile.am
@@ -0,0 +1,13 @@
+# Dbus service file
+BUILT_FILES = $(service_in_files:.service.in=.service)
+
+servicedir = $(DBUS_SERVICES_DIR)
+service_in_files = org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in
+service_DATA = $(BUILT_FILES)
+CLEANFILES = $(BUILT_FILES)
+
+EXTRA_DIST = $(service_in_files)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+ @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
diff --git a/data/org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in b/data/org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in
new file mode 100644
index 0000000..eea7bc7
--- /dev/null
+++ b/data/org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.ConnectionManager.sofiasip
+Exec=@bindir@/telepathy-sofiasip
+
diff --git a/docs/.git-darcs-dir b/docs/.git-darcs-dir
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/.git-darcs-dir
diff --git a/docs/Makefile.am b/docs/Makefile.am
new file mode 100644
index 0000000..2d3ef61
--- /dev/null
+++ b/docs/Makefile.am
@@ -0,0 +1,79 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.6 at least.
+AUTOMAKE_OPTIONS = 1.6
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=telepathy-sofiasip
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../server
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=
+CFILE_GLOB=
+
+# Header files to ignore when scanning.
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files=
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. INCLUDES=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+INCLUDES=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+# temporarily disabled (2006/04)
+#include $(top_srcdir)/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST =
diff --git a/docs/design.txt b/docs/design.txt
new file mode 100644
index 0000000..9eebb14
--- /dev/null
+++ b/docs/design.txt
@@ -0,0 +1,83 @@
+title: Design notes for Telepathy-SIP
+version: 20061215-6
+
+General
+-------
+
+Telepathy-sofiasip is a SIP connection manager for the Telepathy framework.
+The code is based on the Jabber/XMPP connection manager
+implementation (telepathy-gabble).
+
+Testing
+-------
+
+For testing basic functionality, use the 'tests/tp_caller' app that
+comes with the package.
+
+Use of Sofia-SIP
+----------------
+
+Telepathy-sofiasip originally used the Sofia-SIP "NuaGlib" API, but
+was ported to directly use the "NUA" API to get more direct access
+to the low-level interfaces provided by Sofia-SIP.
+
+There are two different architectural approaches how Sofia-SIP
+could be used:
+
+ 1) One NUA instance, with one set of operating system level
+ sockets for SIP signaling, is created by the connection
+ manager object.
+ 2) A separate NUA instance is created for each connection
+ to the network.
+
+The current implementation follows (2), but switching to (1) should
+be considered once the multiple identities support in sofia-sip
+matures.
+
+Outbound calls
+--------------
+
+- client requests a channel with type of "StreamedMedia" with remote URI XXX
+ using the RequestChannel method (if:org.freedesktop.Telepathy.Connection)
+ - SIPMediaChannel (sipchan) and SIPMediaSession (sipsess) objects are created
+ - 'sipsess' emits NewSessionHandler
+ - SIPConnection emits NewChannel
+- application requests media streams with "RequestStreams"
+ - SIPMediaStream (sipstream) objects are created
+ - emit sipsess.NewStreamHandler() signal for each stream
+ - emit sipsess.StreamAdded() signal for each stream
+- client creates a StreamEngine (e) and passes the new channel object (c)
+ to it with e.HandleChannel(c)
+ - stream engine connects to the SIPMediaChannel and issues c.GetSessionHandlers()
+ - StreamEngine will connect to signals emitted by the channel object
+ - StreamEngine will call SipMediaSession.Ready() to complete the setup
+- StreamEngine launches local candidate/codec discovery
+- once the local candidate/codecs are found
+ - note: unlike in Jingle, the candidates are sent in one go,
+ not one candidate at a time
+ - thus in the SIP implementation, we have to have a separate logic
+ to decide when to send our offer/answer
+ - the offer/answer logic depends on the status of all the streams,
+ and only once valid SDP description is available for all of them,
+ an offer/answer can be sent (see sip_media_session.c:priv_offer_answer_step())
+
+Incoming calls
+--------------
+
+- the SIP stack delivers the 'nua_i_invite' signal which is handled in
+ sip-connection-sofia.c:priv_i_invite()
+- SIPConnection will create the media channel objects and start setting
+ up them for a session as done for outbound calls (see above)
+
+Mapping sessions and streams to SIP
+-----------------------------------
+
+- basis for handling sessions and streams is defined in RFC3264,
+ with examples given in RFC4317
+- telepathy-sofiasip has to keep track of all stream added to
+ a session (removed streams have to be kept around in the signaling,
+ marked as not-used with zero as the port number)
+- the SIPMediaSession object maintains an array of SIPMediaStream
+ objects
+ - note that disabled, or streams with unknown media type,
+ are stored as NULL pointers to the array
diff --git a/m4/.git-darcs-dir b/m4/.git-darcs-dir
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/m4/.git-darcs-dir
diff --git a/m4/Makefile.am b/m4/Makefile.am
new file mode 100644
index 0000000..cfec8cf
--- /dev/null
+++ b/m4/Makefile.am
@@ -0,0 +1,3 @@
+EXTRA_DIST = \
+as-compiler-flag.m4 \
+as-version.m4
diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4
new file mode 100644
index 0000000..605708a
--- /dev/null
+++ b/m4/as-compiler-flag.m4
@@ -0,0 +1,33 @@
+dnl as-compiler-flag.m4 0.1.0
+
+dnl autostars m4 macro for detection of compiler flags
+
+dnl David Schleef <ds@schleef.org>
+
+dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS.
+dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags,
+dnl and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([AS_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $1"
+
+ AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ $2
+ true
+ else
+ $3
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
+
diff --git a/m4/as-version.m4 b/m4/as-version.m4
new file mode 100644
index 0000000..defc887
--- /dev/null
+++ b/m4/as-version.m4
@@ -0,0 +1,66 @@
+dnl as-version.m4 0.1.0
+
+dnl autostars m4 macro for versioning
+
+dnl Thomas Vander Stichele <thomas at apestaart dot org>
+
+dnl $Id: as-version.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $
+
+dnl AS_VERSION(PACKAGE, PREFIX, MAJOR, MINOR, MICRO, NANO,
+dnl ACTION-IF-NO-NANO, [ACTION-IF-NANO])
+
+dnl example
+dnl AS_VERSION(gstreamer, GST_VERSION, 0, 3, 2,)
+dnl for a 0.3.2 release version
+
+dnl this macro
+dnl - defines [$PREFIX]_MAJOR, MINOR and MICRO
+dnl - if NANO is empty, then we're in release mode, else in cvs/dev mode
+dnl - defines [$PREFIX], VERSION, and [$PREFIX]_RELEASE
+dnl - executes the relevant action
+dnl - AC_SUBST's PACKAGE, VERSION, [$PREFIX] and [$PREFIX]_RELEASE
+dnl as well as the little ones
+dnl - doesn't call AM_INIT_AUTOMAKE anymore because it prevents
+dnl maintainer mode from running ok
+dnl
+dnl don't forget to put #undef [$2] and [$2]_RELEASE in acconfig.h
+dnl if you use acconfig.h
+
+AC_DEFUN([AS_VERSION],
+[
+ PACKAGE=[$1]
+ [$2]_MAJOR=[$3]
+ [$2]_MINOR=[$4]
+ [$2]_MICRO=[$5]
+ NANO=[$6]
+ [$2]_NANO=$NANO
+ if test "x$NANO" = "x" || test "x$NANO" = "x0";
+ then
+ AC_MSG_NOTICE(configuring [$1] for release)
+ VERSION=[$3].[$4].[$5]
+ [$2]_RELEASE=1
+ dnl execute action
+ ifelse([$7], , :, [$7])
+ else
+ AC_MSG_NOTICE(configuring [$1] for development with nano $NANO)
+ VERSION=[$3].[$4].[$5].$NANO
+ [$2]_RELEASE=0.`date +%Y%m%d.%H%M%S`
+ dnl execute action
+ ifelse([$8], , :, [$8])
+ fi
+
+ [$2]=$VERSION
+ AC_DEFINE_UNQUOTED([$2], "$[$2]", [Define the version])
+ AC_SUBST([$2])
+ AC_DEFINE_UNQUOTED([$2]_RELEASE, "$[$2]_RELEASE", [Define the release version])
+ AC_SUBST([$2]_RELEASE)
+
+ AC_SUBST([$2]_MAJOR)
+ AC_SUBST([$2]_MINOR)
+ AC_SUBST([$2]_MICRO)
+ AC_SUBST([$2]_NANO)
+ AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Define the package name])
+ AC_SUBST(PACKAGE)
+ AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Define the version])
+ AC_SUBST(VERSION)
+])
diff --git a/m4/as_ac_expand.m4 b/m4/as_ac_expand.m4
new file mode 100644
index 0000000..5fc5c4e
--- /dev/null
+++ b/m4/as_ac_expand.m4
@@ -0,0 +1,41 @@
+dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR)
+dnl
+dnl example
+dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
+dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local
+
+AC_DEFUN([AS_AC_EXPAND],
+[
+ EXP_VAR=[$1]
+ FROM_VAR=[$2]
+
+ dnl first expand prefix and exec_prefix if necessary
+ prefix_save=$prefix
+ exec_prefix_save=$exec_prefix
+
+ dnl if no prefix given, then use /usr/local, the default prefix
+ if test "x$prefix" = "xNONE"; then
+ prefix=$ac_default_prefix
+ fi
+ dnl if no exec_prefix given, then use prefix
+ if test "x$exec_prefix" = "xNONE"; then
+ exec_prefix=$prefix
+ fi
+
+ full_var="$FROM_VAR"
+ dnl loop until it doesn't change anymore
+ while true; do
+ new_full_var="`eval echo $full_var`"
+ if test "x$new_full_var"="x$full_var"; then break; fi
+ full_var=$new_full_var
+ done
+
+ dnl clean up
+ full_var=$new_full_var
+ AC_SUBST([$1], "$full_var")
+
+ dnl restore prefix and exec_prefix
+ prefix=$prefix_save
+ exec_prefix=$exec_prefix_save
+])
+
diff --git a/src/.git-darcs-dir b/src/.git-darcs-dir
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/.git-darcs-dir
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..ae49462
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,118 @@
+#
+# Makefile.am for telepathy-sofiasip/src
+#
+# Copyright (C) 2006 Nokia Corporation
+# Contact: Kai Vehmanen <first.surname@nokia.com>
+# Licensed under LGPL. See file COPYING.
+#
+
+# ----------------------------------------------------------------------
+# Automake options
+
+managerdir = $(datadir)/telepathy/managers
+
+# ----------------------------------------------------------------------
+# Headers and libraries
+
+AM_CFLAGS = $(ERROR_CFLAGS) @DBUS_CFLAGS@ @GLIB_CFLAGS@ @SOFIA_SIP_UA_CFLAGS@ \
+ @TELEPATHY_GLIB_CFLAGS@
+AM_LDFLAGS = @DBUS_LIBS@ @GLIB_LIBS@ @SOFIA_SIP_UA_LIBS@ @TELEPATHY_GLIB_LIBS@
+
+# ----------------------------------------------------------------------
+# Build targets
+
+bin_PROGRAMS = telepathy-sofiasip
+manager_DATA = sofiasip.manager
+noinst_PROGRAMS = write-mgr-file
+noinst_LTLIBRARIES = libtpsip-convenience.la
+
+# ----------------------------------------------------------------------
+# Tests
+
+# ----------------------------------------------------------------------
+# Rules for building the targets
+
+signals-marshal.c: ${srcdir}/signals-marshal.list
+ glib-genmarshal --body --prefix=_tpsip_marshal $< >$@
+
+signals-marshal.h: ${srcdir}/signals-marshal.list
+ glib-genmarshal --header --prefix=_tpsip_marshal $< >$@
+
+# rules for makeing the glib enum objects
+%-enumtypes.h: %.h
+ glib-mkenums \
+ --fhead "#ifndef __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n#define __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename@\" */\n" \
+ --vhead "GType @enum_name@_get_type (void);\n#define $(shell echo $* | tr [:lower:]- [:upper:]_ | sed 's/_.*//')_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__ */" \
+ $< > $@
+
+%-enumtypes.c: %.h
+ glib-mkenums \
+ --fhead "#include <$*.h>" \
+ --fprod "\n/* enumerations from \"@filename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \
+ $< > $@
+
+sofiasip.manager: write-mgr-file
+ if ./write-mgr-file > $@.tmp; then \
+ mv -f $@.tmp $@;\
+ else \
+ rm -f $@.tmp; \
+ fi
+
+BUILT_SOURCES = \
+ signals-marshal.h \
+ signals-marshal.c \
+ sip-connection-enumtypes.h \
+ sip-connection-enumtypes.c
+
+libtpsip_convenience_la_SOURCES = \
+ sip-text-channel.h \
+ sip-text-channel.c \
+ sip-media-channel.h \
+ sip-media-channel.c \
+ sip-media-session.h \
+ sip-media-session.c \
+ sip-media-stream.h \
+ sip-media-stream.c \
+ sip-connection.h \
+ sip-connection.c \
+ sip-connection-manager.h \
+ sip-connection-manager.c \
+ debug.h \
+ debug.c \
+ media-factory.h \
+ media-factory.c \
+ text-factory.h \
+ text-factory.c \
+ sip-connection-helpers.h \
+ sip-connection-helpers.c \
+ sip-connection-private.h \
+ sip-connection-sofia.h \
+ sip-connection-sofia.c \
+ telepathy-helpers.h \
+ telepathy-helpers.c
+
+nodist_libtpsip_convenience_la_SOURCES = \
+ $(BUILT_SOURCES)
+
+telepathy_sofiasip_SOURCES = \
+ telepathy-sofiasip.c
+
+telepathy_sofiasip_LDADD = libtpsip-convenience.la -ltelepathy-glib
+
+write_mgr_file_SOURCES = \
+ write-mgr-file.c
+
+write_mgr_file_LDADD = libtpsip-convenience.la -ltelepathy-glib
+
+# ----------------------------------------------------------------------
+# Install and distribution rules
+
+EXTRA_DIST = signals-marshal.list
+
+# Correctly clean the generated headers, but keep the xml description
+CLEANFILES = $(BUILT_SOURCES) $(manager_DATA)
diff --git a/src/debug.c b/src/debug.c
new file mode 100644
index 0000000..d38c446
--- /dev/null
+++ b/src/debug.c
@@ -0,0 +1,62 @@
+#include "config.h"
+
+#ifdef ENABLE_DEBUG
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <telepathy-glib/debug.h>
+
+#include "debug.h"
+
+static SIPDebugFlags flags = 0;
+
+static GDebugKey keys[] = {
+ { "persist", SIP_DEBUG_PERSIST },
+ { "media-channel", SIP_DEBUG_MEDIA },
+ { "connection", SIP_DEBUG_CONNECTION },
+ { "im", SIP_DEBUG_IM },
+ { 0, },
+};
+
+void sip_debug_set_flags_from_env ()
+{
+ guint nkeys;
+ const gchar *flags_string;
+
+ for (nkeys = 0; keys[nkeys].value; nkeys++);
+
+ flags_string = g_getenv ("SOFIASIP_DEBUG");
+
+ if (flags_string)
+ {
+ tp_debug_set_flags_from_env ("SOFIASIP_DEBUG");
+ sip_debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys));
+ }
+}
+
+void sip_debug_set_flags (SIPDebugFlags new_flags)
+{
+ flags |= new_flags;
+}
+
+gboolean sip_debug_flag_is_set (SIPDebugFlags flag)
+{
+ return flag & flags;
+}
+
+void sip_debug (SIPDebugFlags flag,
+ const gchar *format,
+ ...)
+{
+ if (flag & flags)
+ {
+ va_list args;
+ va_start (args, format);
+ g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args);
+ va_end (args);
+ }
+}
+
+#endif /* ENABLE_DEBUG */
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..1c43cf5
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,52 @@
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H_
+
+#include "config.h"
+
+#ifdef ENABLE_DEBUG
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ SIP_DEBUG_PERSIST = 1 << 0,
+ SIP_DEBUG_MEDIA = 1 << 1,
+ SIP_DEBUG_CONNECTION = 1 << 2,
+ SIP_DEBUG_IM = 1 << 3,
+} SIPDebugFlags;
+
+void sip_debug_set_flags_from_env ();
+void sip_debug_set_flags (SIPDebugFlags flags);
+gboolean sip_debug_flag_is_set (SIPDebugFlags flag);
+void sip_debug (SIPDebugFlags flag, const gchar *format, ...)
+ G_GNUC_PRINTF (2, 3);
+
+G_END_DECLS
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ sip_debug(DEBUG_FLAG, "%s: " format, G_STRFUNC, ##__VA_ARGS__)
+
+#define DEBUGGING sip_debug_flag_is_set(DEBUG_FLAG)
+
+#endif /* DEBUG_FLAG */
+
+#else /* ENABLE_DEBUG */
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...)
+
+#define DEBUGGING 0
+
+#define NODE_DEBUG(n, s)
+
+#endif /* DEBUG_FLAG */
+
+#endif /* ENABLE_DEBUG */
+
+#endif /* __DEBUG_H__ */
diff --git a/src/media-factory.c b/src/media-factory.c
new file mode 100644
index 0000000..2f22651
--- /dev/null
+++ b/src/media-factory.c
@@ -0,0 +1,472 @@
+/*
+ * media-factory.c - Media channel factory for SIP connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <telepathy-glib/svc-connection.h>
+#include <telepathy-glib/interfaces.h>
+#include "media-factory.h"
+
+#undef MULTIPLE_MEDIA_CHANNELS
+
+static void factory_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SIPMediaFactory, sip_media_factory,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE,
+ factory_iface_init))
+
+enum
+{
+ PROP_CONNECTION = 1,
+ LAST_PROPERTY
+};
+
+typedef struct _SIPMediaFactoryPrivate SIPMediaFactoryPrivate;
+struct _SIPMediaFactoryPrivate
+{
+ /* unreferenced (since it owns this factory) */
+ SIPConnection *conn;
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ /* array of referenced (SIPMediaChannel *) */
+ GPtrArray *channels;
+ /* for unique channel object paths, currently always increments */
+ guint channel_index;
+#else
+ /* referenced singleton, or NULL */
+ SIPMediaChannel *channel;
+#endif
+ /* g_strdup'd gchar *sessionid => unowned SIPMediaChannel *chan */
+ GHashTable *session_chans;
+
+ gboolean dispose_has_run;
+};
+
+#define SIP_MEDIA_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_MEDIA_FACTORY, SIPMediaFactoryPrivate))
+
+static void
+sip_media_factory_init (SIPMediaFactory *fac)
+{
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ priv->conn = NULL;
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ priv->channels = g_ptr_array_sized_new (1);
+ priv->channel_index = 0;
+#else
+ priv->channel = NULL;
+#endif
+ priv->session_chans = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+sip_media_factory_dispose (GObject *object)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (object);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ tp_channel_factory_iface_close_all (TP_CHANNEL_FACTORY_IFACE (object));
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ g_assert (priv->channels == NULL);
+#else
+ g_assert (priv->channel == NULL);
+#endif
+ g_assert (priv->session_chans == NULL);
+
+ if (G_OBJECT_CLASS (sip_media_factory_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_media_factory_parent_class)->dispose (object);
+}
+
+static void
+sip_media_factory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (object);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_media_factory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (object);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_media_factory_class_init (SIPMediaFactoryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (SIPMediaFactoryPrivate));
+
+ object_class->get_property = sip_media_factory_get_property;
+ object_class->set_property = sip_media_factory_set_property;
+ object_class->dispose = sip_media_factory_dispose;
+
+ param_spec = g_param_spec_object ("connection", "SIPConnection object",
+ "SIP connection that owns this media "
+ "channel factory",
+ SIP_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+}
+
+static void
+sip_media_factory_close_all (TpChannelFactoryIface *iface)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (iface);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+ GHashTable *session_chans;
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ GPtrArray *channels;
+#else
+ SIPMediaChannel *chan;
+#endif
+
+ session_chans = priv->session_chans;
+ priv->session_chans = NULL;
+ if (session_chans)
+ g_hash_table_destroy (session_chans);
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ channels = priv->channels;
+ priv->channels = NULL;
+ if (channels)
+ g_ptr_array_free (channels, TRUE);
+#else
+ chan = priv->channel;
+ priv->channel = NULL;
+ if (chan)
+ g_object_unref (chan);
+#endif
+}
+
+static void
+sip_media_factory_connecting (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+sip_media_factory_connected (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+sip_media_factory_disconnected (TpChannelFactoryIface *iface)
+{
+}
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+struct _ForeachData
+{
+ TpChannelFunc foreach;
+ gpointer user_data;
+};
+
+static void
+_foreach_slave (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _ForeachData *data = (struct _ForeachData *)user_data;
+ TpChannelIface *chan = TP_CHANNEL_IFACE (value);
+
+ data->foreach (chan, data->user_data);
+}
+#endif
+
+static void
+sip_media_factory_foreach (TpChannelFactoryIface *iface,
+ TpChannelFunc foreach,
+ gpointer user_data)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (iface);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ struct _ForeachData data = { foreach, user_data };
+
+ g_hash_table_foreach (priv->channels, _foreach_slave, &data);
+#else
+ if (priv->channel)
+ foreach ((TpChannelIface *)priv->channel, user_data);
+#endif
+}
+
+static gboolean
+hash_is_same_channel (gpointer key, gpointer value, gpointer user_data)
+{
+ return (value == user_data);
+}
+
+/**
+ * channel_closed:
+ *
+ * Signal callback for when a media channel is closed. Removes the references
+ * that #SIPMediaFactory holds to them.
+ */
+static void
+channel_closed (SIPMediaChannel *chan, gpointer user_data)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (user_data);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ if (priv->session_chans)
+ {
+ g_hash_table_foreach_remove (priv->session_chans, hash_is_same_channel,
+ chan);
+ }
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ if (priv->channels)
+ {
+ g_ptr_array_remove (priv->channels, chan);
+ g_object_unref (chan);
+ }
+#else
+ if (priv->channel)
+ {
+ SIPMediaChannel *our_chan = priv->channel;
+
+ g_assert (chan == our_chan);
+ priv->channel = NULL;
+ g_object_unref (chan);
+ }
+#endif
+}
+
+/**
+ * new_media_channel
+ *
+ * Creates a new empty SIPMediaChannel.
+ */
+SIPMediaChannel *
+sip_media_factory_new_channel (SIPMediaFactory *fac, TpHandle creator,
+ nua_handle_t *nh, gpointer request)
+{
+ TpBaseConnection *conn;
+ SIPMediaFactoryPrivate *priv;
+ SIPMediaChannel *chan;
+ gchar *object_path;
+
+ g_assert (SIP_IS_MEDIA_FACTORY (fac));
+
+ priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+ conn = (TpBaseConnection *)priv->conn;
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ object_path = g_strdup_printf ("%s/MediaChannel%u", conn->object_path,
+ priv->channel_index++);
+#else
+ object_path = g_strdup_printf ("%s/MediaChannel", conn->object_path);
+#endif
+
+ g_debug ("%s: object path %s (created by #%d, NUA handle %p", G_STRFUNC,
+ object_path, creator, nh);
+
+ chan = g_object_new (SIP_TYPE_MEDIA_CHANNEL,
+ "connection", priv->conn,
+ "factory", fac,
+ "object-path", object_path,
+ "creator", creator,
+ "nua-handle", nh,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed, fac);
+
+#ifdef MULTIPLE_MEDIA_CHANNELS
+ g_ptr_array_add (priv->channels, chan);
+#else
+ priv->channel = chan;
+#endif
+
+ tp_channel_factory_iface_emit_new_channel (fac, (TpChannelIface *)chan,
+ request);
+
+ return chan;
+}
+
+static TpChannelFactoryRequestStatus
+sip_media_factory_request (TpChannelFactoryIface *iface,
+ const gchar *chan_type,
+ TpHandleType handle_type,
+ guint handle,
+ gpointer request,
+ TpChannelIface **ret,
+ GError **error)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (iface);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+ TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
+ TpChannelIface *chan;
+
+ if (strcmp (chan_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /* we support either empty calls (add the remote user later) or, as a
+ * shortcut, adding the remote user immediately. In the latter case
+ * you can't call yourself, though
+ */
+ if (handle_type != TP_HANDLE_TYPE_NONE
+ && (handle_type != TP_HANDLE_TYPE_CONTACT
+ || handle == conn->self_handle))
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
+ }
+
+#ifndef MULTIPLE_MEDIA_CHANNELS
+ if (priv->channel)
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ }
+#endif
+
+ chan = (TpChannelIface *)sip_media_factory_new_channel (fac,
+ conn->self_handle, NULL, request);
+
+ if (handle_type == TP_HANDLE_TYPE_CONTACT)
+ {
+ if (!sip_media_channel_add_member ((TpSvcChannelInterfaceGroup *)chan,
+ handle, "", error))
+ {
+ sip_media_channel_close (SIP_MEDIA_CHANNEL (chan));
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_ERROR;
+ }
+ }
+
+ *ret = chan;
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED;
+}
+
+const gchar *
+sip_media_factory_session_id_allocate (SIPMediaFactory *fac)
+{
+ SIPMediaFactoryPrivate *priv;
+ guint32 val;
+ gchar *sid = NULL;
+ gboolean unique = FALSE;
+
+ g_assert (SIP_IS_MEDIA_FACTORY (fac));
+ priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ while (!unique)
+ {
+ gpointer k, v;
+
+ val = g_random_int_range (1000000, G_MAXINT);
+
+ g_free (sid);
+ sid = g_strdup_printf ("%u", val);
+
+ unique = !g_hash_table_lookup_extended (priv->session_chans,
+ sid, &k, &v);
+ }
+
+ g_hash_table_insert (priv->session_chans, sid, NULL);
+
+ return (const gchar *) sid;
+}
+
+void
+sip_media_factory_session_id_register (SIPMediaFactory *fac,
+ const gchar *sid,
+ gpointer channel)
+{
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ g_debug ("%s: binding sid %s to %p", G_STRFUNC, sid, channel);
+
+ g_hash_table_insert (priv->session_chans, g_strdup (sid), channel);
+}
+
+void
+sip_media_factory_session_id_unregister (SIPMediaFactory *fac,
+ const gchar *sid)
+{
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ g_debug ("%s: unbinding sid %s", G_STRFUNC, sid);
+
+ /* FIXME: this leaks the strings, as a way of marking that a SID has been
+ * used in this process' lifetime. Surely there's something better
+ * we can do?
+ */
+ g_hash_table_insert (priv->session_chans, g_strdup (sid), NULL);
+}
+
+#ifndef MULTIPLE_MEDIA_CHANNELS
+SIPMediaChannel *
+sip_media_factory_get_only_channel (TpChannelFactoryIface *iface)
+{
+ SIPMediaFactory *fac = SIP_MEDIA_FACTORY (iface);
+ SIPMediaFactoryPrivate *priv = SIP_MEDIA_FACTORY_GET_PRIVATE (fac);
+
+ return priv->channel;
+}
+#endif
+
+static void
+factory_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *) g_iface;
+
+#define IMPLEMENT(x) klass->x = sip_media_factory_##x
+ IMPLEMENT(close_all);
+ IMPLEMENT(foreach);
+ IMPLEMENT(request);
+ IMPLEMENT(connecting);
+ IMPLEMENT(connected);
+ IMPLEMENT(disconnected);
+#undef IMPLEMENT
+}
diff --git a/src/media-factory.h b/src/media-factory.h
new file mode 100644
index 0000000..e614e4f
--- /dev/null
+++ b/src/media-factory.h
@@ -0,0 +1,80 @@
+/*
+ * media-factory.h - Media channel factory for SIP connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SIP_MEDIA_FACTORY_H__
+#define __SIP_MEDIA_FACTORY_H__
+
+#include <telepathy-glib/channel-factory-iface.h>
+
+#include "sip-connection-sofia.h"
+#include "sip-media-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SIPMediaFactory SIPMediaFactory;
+typedef struct _SIPMediaFactoryClass SIPMediaFactoryClass;
+
+struct _SIPMediaFactoryClass {
+ GObjectClass parent_class;
+};
+
+struct _SIPMediaFactory {
+ GObject parent;
+};
+
+GType sip_media_factory_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_MEDIA_FACTORY \
+ (sip_media_factory_get_type())
+#define SIP_MEDIA_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_MEDIA_FACTORY, SIPMediaFactory))
+#define SIP_MEDIA_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_MEDIA_FACTORY, SIPMediaFactoryClass))
+#define SIP_IS_MEDIA_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_MEDIA_FACTORY))
+#define SIP_IS_MEDIA_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_MEDIA_FACTORY))
+#define SIP_MEDIA_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_MEDIA_FACTORY, SIPMediaFactoryClass))
+
+/***********************************************************************
+ * Functions for managing media session IDs (sids)
+ ***********************************************************************/
+
+const gchar *sip_media_factory_session_id_allocate (SIPMediaFactory *fac);
+void sip_media_factory_session_id_register (SIPMediaFactory *fac,
+ const gchar *sid, gpointer channel);
+void sip_media_factory_session_id_unregister (SIPMediaFactory *fac,
+ const gchar *sid);
+
+/* temporary compatibility with code assuming we will never have multiple
+ * media channels
+ */
+SIPMediaChannel *sip_media_factory_get_only_channel (
+ TpChannelFactoryIface *iface);
+
+SIPMediaChannel *sip_media_factory_new_channel (
+ SIPMediaFactory *fac, TpHandle creator, nua_handle_t *nh,
+ gpointer request);
+
+G_END_DECLS
+
+#endif
diff --git a/src/signals-marshal.list b/src/signals-marshal.list
new file mode 100644
index 0000000..32aaa1d
--- /dev/null
+++ b/src/signals-marshal.list
@@ -0,0 +1,4 @@
+VOID:INT,STRING,STRING
+VOID:STRING,BOXED
+VOID:STRING,STRING
+VOID:STRING,STRING,STRING
diff --git a/src/sip-connection-helpers.c b/src/sip-connection-helpers.c
new file mode 100644
index 0000000..aa138b5
--- /dev/null
+++ b/src/sip-connection-helpers.c
@@ -0,0 +1,304 @@
+/*
+ * sip-connection-helpers.c - Helper routines used by SIPConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE 1
+#include <dbus/dbus-glib.h>
+
+#include <sofia-sip/sip.h>
+#include <sofia-sip/sip_header.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-connection.h>
+
+#include "sip-media-channel.h"
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "sip-connection-enumtypes.h"
+#include "sip-connection-private.h"
+
+#define DEBUG_FLAG SIP_DEBUG_CONNECTION
+#include "debug.h"
+
+
+/* Default keepalive timeout in seconds,
+ * a value obtained from Sofia-SIP documentation */
+#define SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL 120
+
+
+nua_t *sip_conn_sofia_nua(SIPConnection *obj)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (obj);
+
+ return priv->sofia_nua;
+}
+
+su_home_t *sip_conn_sofia_home (SIPConnection *conn)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
+
+ return priv->sofia_home;
+}
+
+static sip_to_t *priv_sip_to_url_make (su_home_t *home, const char *address)
+{
+ sip_to_t *to = sip_to_make (home, address);
+
+ if (to &&
+ url_sanitize(to->a_url) == 0)
+ return to;
+
+ return NULL;
+}
+
+nua_handle_t *sip_conn_create_register_handle (nua_t *nua, su_home_t *home, const char *address, TpHandle handle)
+{
+ sip_to_t *to;
+ nua_handle_t *result = NULL;
+ gpointer ptr = GUINT_TO_POINTER(handle);
+
+ to = priv_sip_to_url_make (home, address);
+
+ if (to)
+ result = nua_handle(nua, ptr, SIPTAG_TO(to), TAG_END());
+ else
+ g_warning("nua: Unable to create register handle for %u.\n", handle);
+
+ return result;
+}
+
+nua_handle_t *sip_conn_create_request_handle (nua_t *nua, su_home_t *home, const char *address, TpHandle handle)
+{
+ sip_to_t *to;
+ nua_handle_t *result = NULL;
+ gpointer ptr = GUINT_TO_POINTER(handle);
+
+ to = priv_sip_to_url_make (home, address);
+
+ if (to)
+ result = nua_handle(nua, ptr, NUTAG_URL(to->a_url), SIPTAG_TO(to), TAG_END());
+ else
+ g_warning("nua: Unable to create SIP request handle for %u.\n", handle);
+
+ return result;
+}
+
+static GHashTable*
+priv_nua_get_outbound_options (nua_t* nua)
+{
+ const char* outbound = NULL;
+ GHashTable* option_table;
+ gchar** options;
+ gchar* token;
+ gboolean value;
+ int i;
+
+ option_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ nua_get_params (nua, NUTAG_OUTBOUND_REF(outbound), TAG_END());
+ if (outbound == NULL)
+ return option_table;
+
+ g_debug ("%s: got outbound options %s", G_STRFUNC, outbound);
+
+ options = g_strsplit_set (outbound, " ", 0);
+
+ for (i = 0; (token = options[i]) != NULL; i++)
+ {
+ value = TRUE;
+ /* Look for the negation prefixes */
+ if (g_ascii_strncasecmp (token, "no", 2) == 0)
+ switch (token[2])
+ {
+ case '-':
+ case '_':
+ token += 3;
+ value = FALSE;
+ break;
+ case 'n':
+ case 'N':
+ switch (token[3])
+ {
+ case '-':
+ case '_':
+ token += 4;
+ value = FALSE;
+ break;
+ }
+ break;
+ }
+
+ g_hash_table_insert (option_table,
+ g_strdup (token),
+ GINT_TO_POINTER(value));
+ }
+
+ g_strfreev (options);
+
+ return option_table;
+}
+
+static void
+priv_nua_outbound_vectorize_walk (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ gchar ***pstrv = (gchar ***)user_data;
+ const gchar *option = (const gchar *)key;
+ *(*pstrv)++ = (value)? g_strdup (option) : g_strdup_printf ("no-%s", option);
+}
+
+static void
+priv_nua_set_outbound_options (nua_t* nua, GHashTable* option_table)
+{
+ gchar* outbound;
+ gchar** options;
+ gchar** walker;
+
+ /* construct the option string array */
+ options = g_new(gchar*, g_hash_table_size (option_table) + 1);
+
+ /* fill the array with option tokens and terminate with NULL */
+ walker = options;
+ g_hash_table_foreach (option_table, priv_nua_outbound_vectorize_walk, &walker);
+ *walker = NULL;
+
+ /* concatenate all tokens into a string */
+ outbound = g_strjoinv (" ", options);
+
+ g_strfreev (options);
+
+ g_assert (outbound != NULL);
+
+ /* deliver the option string to the stack */
+ g_debug ("%s: setting outbound options %s", G_STRFUNC, outbound);
+ nua_set_params (nua, NUTAG_OUTBOUND(outbound), TAG_NULL());
+
+ g_free (outbound);
+}
+
+void
+sip_conn_update_nua_outbound (SIPConnection *conn)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
+ GHashTable *option_table;
+
+ g_return_if_fail (priv->sofia_nua != NULL);
+
+ option_table = priv_nua_get_outbound_options (priv->sofia_nua);
+
+ /* Purge existing occurrences of the affected options */
+ g_hash_table_remove (option_table, "options-keepalive");
+ g_hash_table_remove (option_table, "use-stun");
+
+ /* Set options that affect keepalive behavior */
+ switch (priv->keepalive_mechanism)
+ {
+ case SIP_CONNECTION_KEEPALIVE_NONE:
+ case SIP_CONNECTION_KEEPALIVE_REGISTER:
+ /* For REGISTER keepalives, we use NUTAG_M_FEATURES */
+ g_hash_table_insert (option_table,
+ g_strdup ("options-keepalive"),
+ GINT_TO_POINTER(FALSE));
+ g_hash_table_insert (option_table,
+ g_strdup ("use-stun"),
+ GINT_TO_POINTER(FALSE));
+ break;
+ case SIP_CONNECTION_KEEPALIVE_OPTIONS:
+ g_hash_table_insert (option_table,
+ g_strdup ("options-keepalive"),
+ GINT_TO_POINTER(TRUE));
+ /*
+ g_hash_table_insert (option_table,
+ g_strdup ("use-stun"),
+ GINT_TO_POINTER(FALSE));
+ */
+ break;
+ case SIP_CONNECTION_KEEPALIVE_STUN:
+ g_hash_table_insert (option_table,
+ g_strdup ("use-stun"),
+ GINT_TO_POINTER(TRUE));
+ break;
+ case SIP_CONNECTION_KEEPALIVE_AUTO:
+ default:
+ break;
+ }
+
+ g_hash_table_insert (option_table,
+ g_strdup ("use-rport"),
+ GINT_TO_POINTER(priv->discover_binding));
+
+ /* Hand options back to the NUA */
+
+ priv_nua_set_outbound_options (priv->sofia_nua, option_table);
+
+ g_hash_table_destroy (option_table);
+}
+
+void
+sip_conn_update_nua_keepalive_interval (SIPConnection *conn)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
+ long keepalive_interval;
+
+ if (priv->keepalive_mechanism == SIP_CONNECTION_KEEPALIVE_NONE)
+ keepalive_interval = 0;
+ else if (priv->keepalive_interval == 0)
+ /* XXX: figure out proper default timeouts depending on transport */
+ keepalive_interval = SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL;
+ else
+ keepalive_interval = (long)priv->keepalive_interval;
+ keepalive_interval *= 1000;
+
+ g_debug ("%s: setting keepalive interval to %ld msec", G_STRFUNC, keepalive_interval);
+
+ nua_set_params (priv->sofia_nua,
+ NUTAG_KEEPALIVE(keepalive_interval),
+ TAG_NULL());
+}
+
+void
+sip_conn_update_nua_contact_features (SIPConnection *conn)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (conn);
+ char *contact_features;
+ guint timeout;
+
+ if (priv->keepalive_mechanism != SIP_CONNECTION_KEEPALIVE_REGISTER)
+ return;
+
+ timeout = (priv->keepalive_interval > 0)
+ ? priv->keepalive_interval
+ : SIP_CONNECTION_DEFAULT_KEEPALIVE_INTERVAL;
+ contact_features = g_strdup_printf ("expires=%u", timeout);
+ nua_set_params(priv->sofia_nua,
+ NUTAG_M_FEATURES(contact_features),
+ TAG_NULL());
+ g_free (contact_features);
+}
diff --git a/src/sip-connection-helpers.h b/src/sip-connection-helpers.h
new file mode 100644
index 0000000..1684f5b
--- /dev/null
+++ b/src/sip-connection-helpers.h
@@ -0,0 +1,77 @@
+/*
+ * sip-connection-helpers.h - Helper routines used by SIPConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_CONNECTION_HELPERS_H__
+#define __SIP_CONNECTION_HELPERS_H__
+
+#include <glib-object.h>
+
+/* note: As one Sofia-SIP NUA instance is created per SIP connection,
+ * SIPConnection is used as the primary context pointer. See
+ * {top}/docs/design.txt for further information.
+ *
+ * Each NUA handle is mapped 1:1 to a Telepathy handle (guint).
+ * The handles are stored as pointer values and have to be
+ * properly casted before use with GPOINTER_TO_UINT() and
+ * GUINT_TO_POINTER().
+ */
+
+struct SIPConnection;
+struct SIPConnectionManager;
+
+#define NUA_MAGIC_T SIPConnection
+#define SU_ROOT_MAGIC_T SIPConnectionManager
+#define SU_TIMER_ARG_T SIPConnection
+#define NUA_HMAGIC_T gpointer
+
+#include <sofia-sip/nua.h>
+#include <sofia-sip/su.h>
+#include <sofia-sip/sl_utils.h>
+#include <sofia-sip/su_glib.h>
+#include <sofia-sip/tport_tag.h>
+
+#include "sip-media-channel.h"
+#include "sip-text-channel.h"
+#include "sip-connection.h"
+#include <telepathy-glib/handle.h>
+
+
+G_BEGIN_DECLS
+
+/***********************************************************************
+ * Functions for accessing Sofia-SIP interface handles
+ ***********************************************************************/
+
+nua_t *sip_conn_sofia_nua (SIPConnection *conn);
+su_home_t *sip_conn_sofia_home (SIPConnection *conn);
+
+nua_handle_t *sip_conn_create_register_handle (nua_t *nua, su_home_t *home, const char *address, TpHandle handle);
+nua_handle_t *sip_conn_create_request_handle (nua_t *nua, su_home_t *home, const char *address, TpHandle handle);
+
+/***********************************************************************
+ * Functions for managing NUA outbound/keepalive parameters
+ ***********************************************************************/
+
+void sip_conn_update_nua_outbound (SIPConnection *conn);
+void sip_conn_update_nua_keepalive_interval (SIPConnection *conn);
+void sip_conn_update_nua_contact_features (SIPConnection *conn);
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_CONNECTION_HELPERS_H__*/
diff --git a/src/sip-connection-manager.c b/src/sip-connection-manager.c
new file mode 100644
index 0000000..00b6aee
--- /dev/null
+++ b/src/sip-connection-manager.c
@@ -0,0 +1,475 @@
+/*
+ * sip-connection-manager.c - Source for SIPConnectionManager
+ * Copyright (C) 2005-2007 Collabora Ltd.
+ * Copyright (C) 2005-2007 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Martti Mela <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-connection-manager).
+ * @author See gabble-connection-manager.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-protocol.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "sip-connection-manager.h"
+#include "signals-marshal.h"
+
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/svc-connection-manager.h>
+
+#define DEBUG_FLAG SIP_DEBUG_CONNECTION
+#include "debug.h"
+
+static void cm_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(SIPConnectionManager, sip_connection_manager,
+ TP_TYPE_BASE_CONNECTION_MANAGER,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_MANAGER,
+ cm_iface_init))
+
+/* private structure *//* typedef struct _SIPConnectionManagerPrivate SIPConnectionManagerPrivate; */
+
+typedef struct {
+ gchar *account;
+ gchar *password;
+ gchar *registrar;
+ gchar *proxy_host;
+ guint port;
+ gchar *transport;
+ gboolean discover_binding;
+ gboolean use_http_proxy;
+ gchar *keepalive_mechanism;
+ gint keepalive_interval;
+ gchar *stun_server;
+ guint stun_port;
+ gchar *extra_auth_user;
+ gchar *extra_auth_password;
+ gboolean disable_difficult;
+} SIPConnParams;
+
+static void *
+alloc_params (void)
+{
+ return g_slice_new0 (SIPConnParams);
+}
+
+static void
+free_params (void *p)
+{
+ SIPConnParams *params = (SIPConnParams *)p;
+
+ g_free (params->account);
+ g_free (params->password);
+ g_free (params->registrar);
+ g_free (params->proxy_host);
+ g_free (params->transport);
+ g_free (params->keepalive_mechanism);
+ g_free (params->stun_server);
+ g_free (params->extra_auth_user);
+ g_free (params->extra_auth_password);
+
+ g_slice_free (SIPConnParams, params);
+}
+
+enum {
+ SIP_CONN_PARAM_ACCOUNT = 0,
+ SIP_CONN_PARAM_PASSWORD,
+ SIP_CONN_PARAM_REGISTRAR,
+ SIP_CONN_PARAM_PROXY_HOST,
+ SIP_CONN_PARAM_PORT,
+ SIP_CONN_PARAM_TRANSPORT,
+ SIP_CONN_PARAM_DISCOVER_BINDING,
+ SIP_CONN_PARAM_USE_HTTP_PROXY,
+ SIP_CONN_PARAM_KEEPALIVE_MECHANISM,
+ SIP_CONN_PARAM_KEEPALIVE_INTERVAL,
+ SIP_CONN_PARAM_STUN_SERVER,
+ SIP_CONN_PARAM_STUN_PORT,
+ SIP_CONN_PARAM_EXTRA_AUTH_USER,
+ SIP_CONN_PARAM_EXTRA_AUTH_PASSWORD,
+ SIP_CONN_PARAM_DISABLE_DIFFICULT,
+ N_SIP_CONN_PARAMS
+};
+
+static const TpCMParamSpec sip_params[] = {
+ /* Account (a sip: URI) */
+ { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
+ NULL, G_STRUCT_OFFSET (SIPConnParams, account) },
+ /* Password */
+ { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, /* according to the .manager file this is
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
+ but in the code this is not the case */
+ NULL, G_STRUCT_OFFSET (SIPConnParams, password) },
+ /* Registrar */
+ { "registrar", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, registrar) },
+ /* Used to compose proxy URI */
+ { "proxy-host", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, proxy_host) },
+ /* Used to compose proxy URI */
+ { "port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(SIP_DEFAULT_PORT),
+ G_STRUCT_OFFSET (SIPConnParams, port) },
+ /* Used to compose proxy URI */
+ { "transport", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto",
+ G_STRUCT_OFFSET (SIPConnParams, transport) },
+ /* Not used */
+ { "discover-binding", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE),
+ G_STRUCT_OFFSET (SIPConnParams, discover_binding) },
+ /* Not used */
+ { "use-http-proxy", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
+ G_STRUCT_OFFSET (SIPConnParams, use_http_proxy) },
+ /* Not used */
+ { "keepalive-mechanism", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, keepalive_mechanism) },
+ /* KA interval */
+ { "keepalive-interval", DBUS_TYPE_INT32_AS_STRING, G_TYPE_INT,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, keepalive_interval) },
+ /* STUN server */
+ { "stun-server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, stun_server) },
+ /* Not used, presumably the normal STUN port is hardcoded elsewhere */
+ { "stun-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(3478),
+ G_STRUCT_OFFSET (SIPConnParams, stun_port) },
+ /* Not used */
+ { "extra-auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, extra_auth_user) },
+ /* Not used */
+ { "extra-auth-password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, G_STRUCT_OFFSET (SIPConnParams, extra_auth_password) },
+ /* Not used */
+ { "disable-difficult", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
+ G_STRUCT_OFFSET (SIPConnParams, disable_difficult) },
+ { NULL, NULL, 0, 0, NULL, 0 }
+};
+
+const TpCMProtocolSpec sofiasip_protocols[] = {
+ { "sip", sip_params, alloc_params, free_params },
+ { NULL, NULL }
+};
+
+struct _SIPConnectionManagerPrivate
+{
+ su_root_t *sofia_root;
+};
+
+#define SIP_CONNECTION_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_CONNECTION_MANAGER, SIPConnectionManagerPrivate))
+
+static void
+sip_connection_manager_init (SIPConnectionManager *obj)
+{
+ SIPConnectionManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (obj,
+ SIP_TYPE_CONNECTION_MANAGER, SIPConnectionManagerPrivate);
+ GSource *source;
+
+ obj->priv = priv;
+
+ priv->sofia_root = su_glib_root_create(obj);
+ su_root_threading(priv->sofia_root, 0);
+ source = su_root_gsource(priv->sofia_root);
+ g_source_attach(source, NULL);
+}
+
+static void sip_connection_manager_finalize (GObject *object);
+static TpBaseConnection *sip_connection_manager_new_connection (
+ TpBaseConnectionManager *base, const gchar *proto,
+ TpIntSet *params_present, void *parsed_params, GError **error);
+
+static void
+sip_connection_manager_class_init (SIPConnectionManagerClass *sip_connection_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_connection_manager_class);
+ TpBaseConnectionManagerClass *base_class =
+ (TpBaseConnectionManagerClass *)sip_connection_manager_class;
+
+ g_type_class_add_private (sip_connection_manager_class, sizeof (SIPConnectionManagerPrivate));
+
+ object_class->finalize = sip_connection_manager_finalize;
+
+ base_class->new_connection = sip_connection_manager_new_connection;
+ base_class->cm_dbus_name = "sofiasip";
+ base_class->protocol_params = sofiasip_protocols;
+}
+
+void
+sip_connection_manager_finalize (GObject *object)
+{
+ SIPConnectionManager *self = SIP_CONNECTION_MANAGER (object);
+ SIPConnectionManagerPrivate *priv = SIP_CONNECTION_MANAGER_GET_PRIVATE (self);
+ GSource *source;
+
+ source = su_root_gsource(priv->sofia_root);
+ g_source_destroy(source);
+ su_root_destroy(priv->sofia_root);
+
+ G_OBJECT_CLASS (sip_connection_manager_parent_class)->finalize (object);
+}
+
+/**
+ * sip_connection_manager_get_parameters
+ *
+ * Implements DBus method GetParameters
+ * on interface org.freedesktop.Telepathy.ConnectionManager
+ */
+static void
+sip_connection_manager_get_parameters (TpSvcConnectionManager *iface,
+ const gchar *proto,
+ DBusGMethodInvocation *context)
+{
+ GPtrArray *ret = g_ptr_array_new ();
+
+ /* FIXME: there are actually lots of parameters... */
+ tp_svc_connection_manager_return_from_get_parameters (context, ret);
+ g_ptr_array_free (ret, TRUE);
+}
+
+/**
+ * sip_connection_manager_list_protocols
+ *
+ * Implements DBus method ListProtocols
+ * on interface org.freedesktop.Telepathy.ConnectionManager
+ */
+static void
+sip_connection_manager_list_protocols (TpSvcConnectionManager *iface,
+ DBusGMethodInvocation *context)
+{
+ const char *protocols[] = { "sip", NULL };
+
+ tp_svc_connection_manager_return_from_list_protocols (
+ context, protocols);
+}
+
+static gchar *
+priv_compose_proxy_uri (const gchar *host,
+ const gchar *transport,
+ guint port)
+{
+ const gchar *scheme = "sip";
+ const gchar *params = "";
+ gboolean is_secure = FALSE;
+
+ if (host == NULL)
+ return NULL;
+
+ /* Encode transport */
+
+ if (transport == NULL || !strcmp (transport, "auto")) {
+ /*no mention of transport in the URI*/
+ } else if (!strcmp (transport, "tcp")) {
+ params = ";transport=tcp";
+ } else if (!strcmp (transport, "udp")) {
+ params = ";transport=udp";
+ } else if (!strcmp (transport, "tls")) {
+ scheme = "sips";
+ is_secure = TRUE;
+ } else {
+ g_warning ("transport %s not recognized", transport);
+ }
+
+ /* Skip default port */
+
+ if (!is_secure) {
+ if (port == SIP_DEFAULT_PORT) {
+ port = 0;
+ }
+ } else {
+ if (port == SIPS_DEFAULT_PORT) {
+ port = 0;
+ }
+ }
+
+ /* Format the resulting URI */
+
+ if (port) {
+ return g_strdup_printf ("%s:%s:%u%s", scheme, host, port, params);
+ } else {
+ return g_strdup_printf ("%s:%s%s", scheme, host, params);
+ }
+}
+
+/**
+ * Returns a default SIP proxy address based on the public
+ * SIP address 'sip_address' and . For instance
+ * "sip:first.surname@company.com" would result in "sip:company.com".
+ * The SIP stack will further utilize DNS lookups to find the IP address
+ * for the SIP server responsible for the domain "company.com".
+ */
+static gchar *
+priv_compose_default_proxy_uri (const gchar *sip_address,
+ const gchar *transport,
+ guint port)
+{
+ char *result = NULL;
+ char *host;
+ char *found;
+
+ g_return_val_if_fail (sip_address != NULL, NULL);
+
+ /* skip sip and sips prefixes, updating transport if necessary */
+ found = strchr (sip_address, ':');
+ if (found != NULL) {
+ if (strncmp("sip:", sip_address, 4) == 0) {
+ ;
+ } else if (strncmp("sips:", sip_address, 5) == 0) {
+ if (transport == NULL || strcmp (transport, "auto") == 0)
+ transport = "tls";
+ } else {
+ /* error, unknown URI prefix */
+ return NULL;
+ }
+
+ sip_address = found + 1;
+ }
+
+ /* skip userinfo */
+ found = strchr (sip_address, '@');
+ if (found != NULL)
+ sip_address = found + 1;
+
+ /* copy rest of the string */
+ host = g_strdup (sip_address);
+
+ /* mark end (uri-parameters defs) */
+ found = strchr (host, ';');
+ if (found != NULL)
+ *found = '\0';
+
+ result = priv_compose_proxy_uri (host, transport, port);
+
+ g_free (host);
+
+ return result;
+}
+
+static SIPConnectionKeepaliveMechanism
+priv_parse_keepalive (const gchar *str)
+{
+ if (str == NULL || strcmp (str, "auto") == 0)
+ return SIP_CONNECTION_KEEPALIVE_AUTO;
+ if (strcmp (str, "register") == 0)
+ return SIP_CONNECTION_KEEPALIVE_REGISTER;
+ if (strcmp (str, "options") == 0)
+ return SIP_CONNECTION_KEEPALIVE_OPTIONS;
+ if (strcmp (str, "stun") == 0)
+ return SIP_CONNECTION_KEEPALIVE_STUN;
+ if (strcmp (str, "off") == 0)
+ return SIP_CONNECTION_KEEPALIVE_NONE;
+
+ g_warning ("unsupported keepalive-method value \"%s\", falling back to auto", str);
+ return SIP_CONNECTION_KEEPALIVE_AUTO;
+}
+
+#define SET_PROPERTY_IF_PARAM_SET(prop, param, member) \
+ if (tp_intset_is_member (params_present, param)) \
+ { \
+ g_object_set (connection, prop, member, NULL); \
+ }
+
+/**
+ * sip_connection_manager_request_connection
+ *
+ * Implements DBus method RequestConnection
+ * on interface org.freedesktop.Telepathy.ConnectionManager
+ */
+static TpBaseConnection *
+sip_connection_manager_new_connection (TpBaseConnectionManager *base,
+ const gchar *proto,
+ TpIntSet *params_present,
+ void *parsed_params,
+ GError **error)
+{
+ SIPConnectionManager *obj = SIP_CONNECTION_MANAGER (base);
+ TpBaseConnection *connection = NULL;
+ SIPConnParams *params = (SIPConnParams *)parsed_params;
+ gchar *proxy = NULL;
+ SIPConnectionKeepaliveMechanism keepalive_mechanism;
+
+ if (strcmp (proto, "sip")) {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "This connection manager only implements protocol 'sip', not '%s'",
+ proto);
+ return NULL;
+ }
+
+ /* TODO: retrieve global HTTP proxy settings if
+ * "use-http-proxy" parameter is enabled */
+
+ /* TpBaseConnectionManager code has already checked that required params
+ * are present
+ */
+ g_assert (params->account);
+
+ DEBUG("New SIP connection to %s", params->account);
+
+ connection = (TpBaseConnection *)g_object_new(SIP_TYPE_CONNECTION,
+ "protocol", "sip",
+ "sofia-root", obj->priv->sofia_root,
+ "address", params->account,
+ NULL);
+
+ if (params->proxy_host == NULL) {
+ proxy = priv_compose_default_proxy_uri (params->account,
+ params->transport,
+ params->port);
+ g_debug ("Set outbound proxy address to <%s>, based on <%s>", proxy, params->account);
+ } else
+ proxy = priv_compose_proxy_uri (params->proxy_host,
+ params->transport,
+ params->port);
+
+ g_object_set (connection, "proxy", proxy, NULL);
+ g_free (proxy);
+
+ SET_PROPERTY_IF_PARAM_SET ("password", SIP_CONN_PARAM_PASSWORD,
+ params->password);
+
+ SET_PROPERTY_IF_PARAM_SET ("registrar", SIP_CONN_PARAM_REGISTRAR,
+ params->registrar);
+
+ SET_PROPERTY_IF_PARAM_SET ("discover-binding", SIP_CONN_PARAM_DISCOVER_BINDING,
+ params->discover_binding);
+
+ SET_PROPERTY_IF_PARAM_SET ("stun-server", SIP_CONN_PARAM_STUN_SERVER,
+ params->stun_server);
+
+ SET_PROPERTY_IF_PARAM_SET ("keepalive-interval",
+ SIP_CONN_PARAM_KEEPALIVE_INTERVAL, params->keepalive_interval);
+
+ keepalive_mechanism = priv_parse_keepalive (params->keepalive_mechanism);
+ g_object_set (connection, "keepalive-mechanism", keepalive_mechanism, NULL);
+
+ return connection;
+}
+
+static void
+cm_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionManagerClass *klass = (TpSvcConnectionManagerClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_manager_implement_##x (klass, \
+ sip_connection_manager_##x)
+ IMPLEMENT(get_parameters);
+ IMPLEMENT(list_protocols);
+#undef IMPLEMENT
+}
diff --git a/src/sip-connection-manager.h b/src/sip-connection-manager.h
new file mode 100644
index 0000000..76cad60
--- /dev/null
+++ b/src/sip-connection-manager.h
@@ -0,0 +1,62 @@
+/*
+ * sip-connection-manager.h - Header for SIPConnectionManager
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005,2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_CONNECTION_MANAGER_H__
+#define __SIP_CONNECTION_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SIPConnectionManager SIPConnectionManager;
+typedef struct _SIPConnectionManagerClass SIPConnectionManagerClass;
+typedef struct _SIPConnectionManagerPrivate SIPConnectionManagerPrivate;
+
+struct _SIPConnectionManagerClass {
+ TpBaseConnectionManagerClass parent_class;
+};
+
+struct _SIPConnectionManager {
+ TpBaseConnectionManager parent;
+ SIPConnectionManagerPrivate *priv;
+};
+
+GType sip_connection_manager_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_CONNECTION_MANAGER \
+ (sip_connection_manager_get_type())
+#define SIP_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_CONNECTION_MANAGER, SIPConnectionManager))
+#define SIP_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_CONNECTION_MANAGER, SIPConnectionManagerClass))
+#define SIP_IS_CONNECTION_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_CONNECTION_MANAGER))
+#define SIP_IS_CONNECTION_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_CONNECTION_MANAGER))
+#define SIP_CONNECTION_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_CONNECTION_MANAGER, SIPConnectionManagerClass))
+
+extern const TpCMProtocolSpec sofiasip_protocols[];
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_CONNECTION_MANAGER_H__*/
diff --git a/src/sip-connection-private.h b/src/sip-connection-private.h
new file mode 100644
index 0000000..8fe0fd1
--- /dev/null
+++ b/src/sip-connection-private.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2007 Collabora Ltd. and Nokia Corporation
+ *
+ * sip-connection-private.h- Private structues for SIPConnection
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SIP_CONNECTION_PRIVATE_H__
+#define __SIP_CONNECTION_PRIVATE_H__
+
+#include <telepathy-glib/channel-factory-iface.h>
+
+#include "sip-media-channel.h"
+
+enum {
+ SIP_NUA_SHUTDOWN_NOT_STARTED = 0,
+ SIP_NUA_SHUTDOWN_STARTED,
+ SIP_NUA_SHUTDOWN_DONE
+};
+
+struct _SIPConnectionPrivate
+{
+ gchar *requested_address;
+ gboolean dispose_has_run;
+
+ nua_t *sofia_nua;
+ su_root_t *sofia_root;
+ su_home_t *sofia_home;
+ nua_handle_t *register_op;
+
+ gint sofia_shutdown;
+
+ /* channels */
+ TpChannelFactoryIface *text_factory;
+ TpChannelFactoryIface *media_factory;
+
+ gchar *address;
+ gchar *password;
+ gchar *proxy;
+ gchar *registrar;
+ SIPConnectionKeepaliveMechanism keepalive_mechanism;
+ gint keepalive_interval;
+ gchar *http_proxy;
+ gchar *stun_server;
+ gboolean discover_binding;
+};
+
+#define SIP_PROTOCOL_STRING "sip"
+
+#define SIP_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_CONNECTION, SIPConnectionPrivate))
+
+/* signal enum */
+enum
+{
+ DISCONNECTED,
+ LAST_SIGNAL
+};
+
+extern guint sip_conn_signals[LAST_SIGNAL];
+
+#endif /*__SIP_CONNECTION_PRIVATE_H__*/
diff --git a/src/sip-connection-sofia.c b/src/sip-connection-sofia.c
new file mode 100644
index 0000000..7adec63
--- /dev/null
+++ b/src/sip-connection-sofia.c
@@ -0,0 +1,743 @@
+/*
+ * sip-connection-sofia.c - Source for SIPConnection Sofia event handling
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2007 Collabora Ltd.
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/interfaces.h>
+
+#include "sip-connection-sofia.h"
+#include "sip-connection-private.h"
+#include "sip-connection-helpers.h"
+#include "media-factory.h"
+#include "text-factory.h"
+
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/sip_parser.h>
+#include <sofia-sip/sip_status.h>
+#include <sofia-sip/msg_parser.h>
+#include <sofia-sip/msg_types.h>
+#include <sofia-sip/su_tag_io.h> /* for tl_print() */
+
+#define DEBUG_FLAG SIP_DEBUG_CONNECTION
+#include "debug.h"
+
+/**
+ * Handles authentication challenge for operation 'op'.
+ */
+static void priv_handle_auth (SIPConnection *self, nua_handle_t *nh, sip_t const *sip, tagi_t *tags)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ sip_www_authenticate_t const *wa = sip->sip_www_authenticate;
+ sip_proxy_authenticate_t const *pa = sip->sip_proxy_authenticate;
+ sip_from_t const *sipfrom = sip->sip_from, *sipto = sip->sip_to;
+ const char *realm = NULL;
+ const char *method = NULL;
+
+ /* use the userpart in "From" header */
+ const char *user = (sipfrom && sipfrom->a_url && sipfrom->a_url->url_user) ? sipfrom->a_url->url_user : NULL;
+
+ /* alternatively use the userpart in "To" header */
+ if (user == NULL)
+ user = (sipto && sipto->a_url && sipto->a_url->url_user) ? sipto->a_url->url_user : NULL;
+
+ DEBUG("enter");
+
+ tl_gets(tags,
+ SIPTAG_WWW_AUTHENTICATE_REF(wa),
+ SIPTAG_PROXY_AUTHENTICATE_REF(pa),
+ TAG_NULL());
+
+ /* step: figure out the realm of the challenge */
+ if (wa) {
+ realm = msg_params_find(wa->au_params, "realm=");
+ method = wa->au_scheme;
+ }
+ else if (pa) {
+ realm = msg_params_find(pa->au_params, "realm=");
+ method = pa->au_scheme;
+ }
+
+ /* step: if all info is available, add an authorization response */
+ if (user && realm && method) {
+ GString *tmp = g_string_new(NULL);
+
+ if (realm[0] == '"')
+ g_string_printf(tmp, "%s:%s:%s:%s",
+ method, realm, user, priv->password);
+ else
+ g_string_printf(tmp, "%s:\"%s\":%s:%s",
+ method, realm, user, priv->password);
+
+
+ g_message ("sofiasip: %s authenticating user='%s' realm='%s' nh='%p'",
+ wa ? "server" : "proxy", user, realm, nh);
+
+ nua_authenticate(nh, NUTAG_AUTH(tmp->str), TAG_END());
+
+ }
+}
+
+static void
+priv_emit_remote_error (SIPConnection *self,
+ nua_handle_t *nh,
+ TpHandle handle,
+ int status,
+ char const *phrase)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ SIPMediaChannel *channel = sip_media_factory_get_only_channel (
+ priv->media_factory);
+
+ if (channel == NULL)
+ {
+ if (status != 487)
+ g_message ("error response %03d received for a destroyed media channel", status);
+ return;
+ }
+
+ /* XXX: more checks on the handle belonging to channel membership */
+ g_return_if_fail (handle > 0);
+
+ sip_media_channel_peer_error (channel, status, phrase);
+}
+
+static void priv_r_invite(int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip,
+ tagi_t tags[])
+{
+ DEBUG("enter");
+
+ g_message ("sofiasip: outbound INVITE: %03d %s", status, phrase);
+
+ if (status >= 300) {
+ if (status == 401 || status == 407) {
+ priv_handle_auth (self, nh, sip, tags);
+ } else {
+ /* redirects (3xx responses) are not handled properly */
+ /* smcv-FIXME: need to work out which channel we're dealing with here */
+ priv_emit_remote_error (self, nh, GPOINTER_TO_UINT(hmagic), status, phrase);
+ }
+ }
+}
+
+static void
+priv_r_register (int status,
+ char const *phrase,
+ nua_t *nua,
+ SIPConnection *self,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ DEBUG("enter");
+
+ g_message ("sofiasip: REGISTER: %03d %s", status, phrase);
+
+ if (status < 200) {
+ return;
+ }
+
+ if (status == 401 || status == 407) {
+ priv_handle_auth (self, nh, sip, tags);
+ }
+ else if (status == 403) {
+ g_message ("sofiasip: REGISTER failed, wrong credentials, disconnecting.");
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED);
+ nua_handle_destroy (priv->register_op);
+ priv->register_op = NULL;
+ }
+ else if (status >= 300) {
+ g_message ("sofiasip: REGISTER failed, disconnecting.");
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ nua_handle_destroy (priv->register_op);
+ priv->register_op = NULL;
+ }
+ else if (status == 200) {
+ g_message ("sofiasip: succesfully registered %s to network", priv->address);
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+ }
+}
+
+static void
+priv_r_unregister (int status,
+ char const *phrase,
+ nua_t *nua,
+ SIPConnection *self,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ nua_handle_t *register_op;
+
+ g_message ("sofiasip: un-REGISTER: %03d %s", status, phrase);
+
+ if (status < 200)
+ return;
+
+ if (status == 401 || status == 407)
+ {
+ /* In SIP, de-registration can fail! However, there's not a lot we can
+ * do about this in the Telepathy model - once you've gone DISCONNECTED
+ * you're really not meant to go "oops, I'm still CONNECTED after all".
+ * So we ignore it and hope it goes away. */
+ g_warning ("Registrar won't let me unregister: %d %s", status, phrase);
+ }
+
+ register_op = priv->register_op;
+ priv->register_op = NULL;
+ if (register_op)
+ nua_handle_destroy (register_op);
+}
+
+static void priv_r_shutdown(int status,
+ char const *phrase,
+ nua_t *nua,
+ SIPConnection *self,
+ nua_handle_t *nh,
+ nua_hmagic_t *op,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ gint old_state;
+
+ g_message("sofiasip: nua_shutdown: %03d %s", status, phrase);
+
+ if (status < 200)
+ return;
+
+ old_state = priv->sofia_shutdown;
+ priv->sofia_shutdown = SIP_NUA_SHUTDOWN_DONE;
+
+ if (old_state == SIP_NUA_SHUTDOWN_STARTED)
+ tp_base_connection_finish_shutdown ((TpBaseConnection *)self);
+}
+
+static void priv_r_get_params(int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ sip_from_t const *from = NULL;
+
+ g_message("sofiasip: nua_r_get_params: %03d %s", status, phrase);
+
+ if (status < 200)
+ return;
+
+ /* note: print contents of all tags to stdout */
+ tl_print(stdout, "tp-sofiasip stack parameters:\n", tags);
+
+ tl_gets(tags, SIPTAG_FROM_REF(from), TAG_END());
+
+ if (from) {
+ char const *new_address =
+ sip_header_as_string(priv->sofia_home, (sip_header_t *)from);
+ if (new_address) {
+ g_message ("Updating the public SIP address to %s.\n", new_address);
+ g_free (priv->address);
+ priv->address = g_strdup(new_address);
+ }
+ }
+}
+
+static gboolean priv_parse_sip_to(sip_t const *sip, su_home_t *home,
+ const gchar **to_str,
+ gchar **to_url_str)
+{
+ if (sip && sip->sip_to) {
+ *to_str = sip->sip_to->a_display;
+ *to_url_str = url_as_string(home, sip->sip_to->a_url);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean priv_parse_sip_from (sip_t const *sip, su_home_t *home, const gchar **from_str, gchar **from_url_str, const gchar **subject_str)
+{
+ if (sip && sip->sip_from) {
+ *from_str = sip->sip_from->a_display;
+ *from_url_str = url_as_string(home, sip->sip_from->a_url);
+ *subject_str = sip->sip_subject ? sip->sip_subject->g_string : "";
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static TpHandle
+priv_parse_handle (SIPConnection *conn,
+ nua_handle_t *nh,
+ TpHandle ihandle,
+ const gchar *from_url_str)
+{
+ TpBaseConnection *base = (TpBaseConnection *)conn;
+ TpHandle ohandle = ihandle;
+ const gchar *handle_identity = NULL;
+
+ /* step: check whether this is a known identity */
+ if (ihandle > 0)
+ handle_identity = tp_handle_inspect (
+ base->handles[TP_HANDLE_TYPE_CONTACT], ihandle);
+
+ if (handle_identity == NULL) {
+ ohandle = tp_handle_request (
+ base->handles[TP_HANDLE_TYPE_CONTACT], from_url_str, TRUE);
+ }
+
+ nua_handle_bind (nh, GUINT_TO_POINTER (ohandle));
+
+ g_assert (ohandle > 0);
+
+ return ohandle;
+}
+
+
+static void priv_r_message(int status, char const *phrase, nua_t *nua,
+ SIPConnection *self, nua_handle_t *nh,
+ nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ SIPTextChannel *channel;
+ TpHandle handle = GPOINTER_TO_UINT (op);
+ const gchar *to_str;
+ gchar *to_url_str;
+ su_home_t *home = sip_conn_sofia_home (self);
+
+ g_message("sofiasip: nua_r_message: %03d %s", status, phrase);
+
+ if (!priv_parse_sip_to(sip, home, &to_str, &to_url_str))
+ return;
+
+ if (status == 200)
+ g_message("Message delivered for %s <%s>",
+ to_str, to_url_str);
+
+ handle = priv_parse_handle (self, nh, handle, to_url_str);
+ channel = sip_text_factory_lookup_channel (priv->text_factory, handle);
+
+ if (!channel || (status < 200))
+ return;
+ else {
+ sip_text_channel_message_emit(nh, channel, status);
+ }
+}
+
+
+static void priv_i_invite(int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ TpHandle handle = GPOINTER_TO_UINT (op);
+ SIPMediaChannel *channel = sip_media_factory_get_only_channel (
+ priv->media_factory);
+ su_home_t *home = sip_conn_sofia_home (self);
+ const gchar *from_str, *subject_str;
+ gchar *from_url_str = NULL;
+
+ if (priv_parse_sip_from (sip, home, &from_str, &from_url_str, &subject_str)) {
+
+ g_message("Got incoming invite from %s <%s> on topic '%s'",
+ from_str, from_url_str, subject_str);
+
+ if (handle == 0) {
+
+ /* case: a new handle */
+
+ if (channel == NULL) {
+
+ /* case 1: ready to establish a media session */
+
+ /* Accordingly to lassis, NewChannel has to be emitted
+ * with the null handle for incoming calls */
+ channel = sip_media_factory_new_channel (
+ SIP_MEDIA_FACTORY (priv->media_factory), 0, nh, NULL);
+ if (channel) {
+ /* figure out a new handle for the identity */
+ handle = priv_parse_handle (self, nh, handle, from_url_str);
+
+ sip_media_channel_respond_to_invite(channel,
+ handle,
+ subject_str,
+ from_url_str);
+ }
+ else
+ g_message ("Creation of SIP media channel failed");
+ }
+ else {
+ /* case 2: already have a media channel, report we are
+ busy */
+ nua_respond (nh, 480, sip_480_Temporarily_unavailable, TAG_END());
+ }
+ }
+ else {
+ /* note: re-INVITEs are handled in priv_i_state() */
+ g_warning ("Got a re-INVITE for handle %u", handle);
+ }
+ }
+ else
+ g_warning ("Unable to parse headers in incoming invite");
+
+ su_free (home, from_url_str);
+}
+
+static void priv_i_message(int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[])
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ TpChannelIface *channel;
+ TpHandle handle = GPOINTER_TO_UINT (op);
+ GString *message;
+ const gchar *from_str, *subject_str;
+ gchar *from_url_str;
+ su_home_t *home = sip_conn_sofia_home (self);
+
+ /* Block anything else except text/plain messages (like isComposings) */
+ if (sip->sip_content_type && (strcmp("text/plain", sip->sip_content_type->c_type)))
+ return;
+
+ if (priv_parse_sip_from (sip, home, &from_str, &from_url_str, &subject_str)) {
+
+ g_message("Got incoming message from %s <%s> on topic '%s'",
+ from_str, from_url_str, subject_str);
+
+ handle = priv_parse_handle (self, nh, handle, from_url_str);
+
+ channel = (TpChannelIface *)sip_text_factory_lookup_channel (
+ priv->text_factory, handle);
+
+ if (!channel)
+ {
+ channel = (TpChannelIface *)sip_text_factory_new_channel (
+ priv->text_factory, handle, NULL);
+ g_assert (channel != NULL);
+ }
+
+ if (sip->sip_payload && sip->sip_payload->pl_len > 0)
+ message = g_string_new_len(sip->sip_payload->pl_data, sip->sip_payload->pl_len);
+ else
+ message = g_string_new ("");
+
+ sip_text_channel_receive(SIP_TEXT_CHANNEL (channel), handle, from_str,
+ from_url_str, subject_str, message->str);
+
+ g_string_free (message, TRUE);
+ su_free (home, from_url_str);
+ }
+ else
+ g_warning ("Unable to parse headers in incoming message.");
+}
+
+static void priv_i_state(int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[])
+{
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ TpHandle handle = GPOINTER_TO_UINT (op);
+ char const *l_sdp = NULL, *r_sdp = NULL;
+ int offer_recv = 0, answer_recv = 0, offer_sent = 0, answer_sent = 0;
+ int ss_state = nua_callstate_init;
+ SIPMediaChannel *channel = sip_media_factory_get_only_channel (
+ priv->media_factory);
+
+ tl_gets(tags,
+ NUTAG_CALLSTATE_REF(ss_state),
+ NUTAG_OFFER_RECV_REF(offer_recv),
+ NUTAG_ANSWER_RECV_REF(answer_recv),
+ NUTAG_OFFER_SENT_REF(offer_sent),
+ NUTAG_ANSWER_SENT_REF(answer_sent),
+ SOATAG_LOCAL_SDP_STR_REF(l_sdp),
+ SOATAG_REMOTE_SDP_STR_REF(r_sdp),
+ TAG_END());
+
+ g_message("sofiasip: nua_state_changed: %03d %s", status, phrase);
+
+ if (l_sdp) {
+ g_return_if_fail(answer_sent || offer_sent);
+ }
+
+ if (r_sdp) {
+ g_return_if_fail(answer_recv || offer_recv);
+ if (channel && r_sdp) {
+ int res = sip_media_channel_set_remote_info (channel, r_sdp);
+ if (res < 0)
+ {
+ sip_media_channel_close (channel);
+ }
+ }
+ }
+
+ switch ((enum nua_callstate)ss_state) {
+ case nua_callstate_received:
+ /* In auto-alert mode, we don't need to call nua_respond(), see NUTAG_AUTOALERT() */
+ nua_respond(nh, SIP_180_RINGING, TAG_END());
+ break;
+
+ case nua_callstate_early:
+ /* nua_respond(nh, SIP_200_OK, TAG_END()); */
+
+ case nua_callstate_completing:
+ /* In auto-ack mode, we don't need to call nua_ack(), see NUTAG_AUTOACK() */
+ break;
+
+ case nua_callstate_ready:
+ /* XXX -> note: only print if state has changed */
+ g_message ("sofiasip: call to %s is active => '%s'",
+ tp_handle_inspect (base->handles[TP_HANDLE_TYPE_CONTACT],
+ handle),
+ nua_callstate_name (ss_state));
+ break;
+
+ case nua_callstate_terminated:
+ if (nh) {
+ /* smcv-FIXME: need to work out which channel we're dealing with here */
+ SIPMediaChannel *chan = sip_media_factory_get_only_channel (
+ priv->media_factory);
+ g_message ("sofiasip: call to %s is terminated",
+ handle > 0 ?
+ tp_handle_inspect (base->handles[TP_HANDLE_TYPE_CONTACT],
+ handle)
+ : "<unknown>");
+ if (chan)
+ sip_media_channel_close (chan);
+ nua_handle_destroy (nh);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Callback for events delivered by the SIP stack.
+ *
+ * See libsofia-sip-ua/nua/nua.h documentation.
+ */
+void
+sip_connection_sofia_callback(nua_event_t event,
+ int status,
+ char const *phrase,
+ nua_t *nua,
+ SIPConnection *self,
+ nua_handle_t *nh,
+ nua_hmagic_t *op,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ TpHandle handle = GPOINTER_TO_UINT (op);
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ SIPConnectionPrivate *priv;
+
+ DEBUG("enter: NUA at %p (conn %p), event #%d %s, %d %s", nua, self, event,
+ nua_event_name (event), status, phrase);
+ DEBUG ("Connection refcount is %d", ((GObject *)self)->ref_count);
+
+ g_return_if_fail (self);
+ priv = SIP_CONNECTION_GET_PRIVATE (self);
+ g_return_if_fail (priv);
+
+ switch (event) {
+
+ /* incoming requests
+ * ------------------------- */
+
+ case nua_i_fork:
+ /* self_i_fork(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_i_invite:
+ priv_i_invite (status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_i_state:
+ priv_i_state (status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_i_bye:
+ /* self_i_bye(nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_i_message:
+ priv_i_message(status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_i_refer:
+ /* self_i_refer(nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_i_notify:
+ /* self_i_notify(nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_i_cancel:
+ /* self_i_cancel(nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_i_error:
+ /* self_i_error(nua, self, nh, op, status, phrase, tags); */
+ break;
+
+ case nua_i_active:
+ case nua_i_ack:
+ case nua_i_terminated:
+ /* ignore these */
+ break;
+
+ /* responses to our requests
+ * ------------------------- */
+
+ case nua_r_shutdown:
+ priv_r_shutdown (status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_r_register:
+ priv_r_register (status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_r_unregister:
+ priv_r_unregister (status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_r_invite:
+ priv_r_invite(status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_r_bye:
+ /* self_r_bye(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_message:
+ priv_r_message(status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ case nua_r_refer:
+ /* self_r_refer(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_subscribe:
+ /* self_r_subscribe(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_unsubscribe:
+ /* self_r_unsubscribe(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_publish:
+ /* self_r_publish(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_notify:
+ /* self_r_notify(status, phrase, nua, self, nh, op, sip, tags); */
+ break;
+
+ case nua_r_get_params:
+ priv_r_get_params(status, phrase, nua, self, nh, op, sip, tags);
+ break;
+
+ default:
+ if (status > 100)
+ g_message ("sip-connection: unknown event '%s' (%d): %03d %s handle=%u",
+ nua_event_name(event), event, status, phrase, handle);
+ else
+ g_message ("sip-connection: unknown event %d handle=%u", event, handle);
+
+ if (handle > 0 &&
+ !tp_handle_is_valid (base->handles[TP_HANDLE_TYPE_CONTACT],
+ handle, NULL)) {
+ /* note: unknown handle, not associated to any existing
+ * call, message, registration, etc, so it can
+ * be safely destroyed */
+ g_message ("NOTE: destroying handle %p (%u).", nh, handle);
+ nua_handle_destroy(nh);
+ }
+
+ break;
+ }
+
+ DEBUG ("exit");
+}
+
+
+#if 0
+/* XXX: these methods have not yet been ported to the new NUA API */
+
+static void
+cb_subscribe_answered(NuaGlib* obj, NuaGlibOp *op, int status, const char*message, gpointer data)
+{
+ SIPConnection *sipconn = (SIPConnection *)data;
+
+ if (sipconn);
+
+ g_message ("Subscribe answered: %d with status %d with message %s",
+ nua_glib_op_method_type(op),
+ status, message);
+
+ /* XXX -- mela: emit a signal to our client */
+}
+
+static void
+cb_incoming_notify(NuaGlib *sofia_nua_glib, NuaGlibOp *op, const char *event,
+ const char *content_type, const char *message, gpointer data)
+{
+ SIPConnection *self = (SIPConnection *) data;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ SIPTextChannel *channel;
+ TpHandle handle;
+
+ handle = 1;
+ channel = NULL;
+ if (priv);
+
+}
+
+static void cb_call_terminated(NuaGlib *sofia_nua_glib, NuaGlibOp *op, int status, gpointer data)
+{
+ SIPConnection *self = (SIPConnection *) data;
+
+ DEBUG("enter");
+
+ /* as we only support one media channel at a time, terminate all */
+ sip_conn_close_media_channels (self);
+}
+
+#endif
diff --git a/src/sip-connection-sofia.h b/src/sip-connection-sofia.h
new file mode 100644
index 0000000..43f8564
--- /dev/null
+++ b/src/sip-connection-sofia.h
@@ -0,0 +1,41 @@
+/*
+ * sip-connection-sofia.h - Source for SIPConnection Sofia event handling
+ * Copyright (C) 2006 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_CONNECTION_SOFIA_H__
+#define __SIP_CONNECTION_SOFIA_H__
+
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+
+G_BEGIN_DECLS
+
+/**
+ * Callback for events delivered by the SIP stack.
+ *
+ * See libsofia-sip-ua/nua/nua.h documentation.
+ */
+void sip_connection_sofia_callback(nua_event_t event,
+ int status, char const *phrase,
+ nua_t *nua, SIPConnection *self,
+ nua_handle_t *nh, nua_hmagic_t *op, sip_t const *sip,
+ tagi_t tags[]);
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_CONNECTION_SOFIA_H__*/
diff --git a/src/sip-connection.c b/src/sip-connection.c
new file mode 100644
index 0000000..1790148
--- /dev/null
+++ b/src/sip-connection.c
@@ -0,0 +1,901 @@
+/*
+ * sip-connection.c - Source for SIPConnection
+ * Copyright (C) 2005-2007 Collabora Ltd.
+ * Copyright (C) 2005-2007 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Martti Mela <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-connection).
+ * @author See gabble-connection.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE 1
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo-static.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/intset.h>
+#include <telepathy-glib/svc-connection.h>
+
+#include "media-factory.h"
+#include "text-factory.h"
+#include "sip-connection.h"
+
+#define DEBUG_FLAG SIP_DEBUG_CONNECTION
+#include "debug.h"
+
+static void conn_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(SIPConnection, sip_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION,
+ conn_iface_init))
+
+#include "sip-connection-enumtypes.h"
+#include "sip-connection-helpers.h"
+#include "sip-connection-private.h"
+#include "sip-connection-sofia.h"
+#include <sofia-sip/stun_tag.h>
+
+#define ERROR_IF_NOT_CONNECTED_ASYNC(BASE, CONTEXT) \
+ if ((BASE)->status != TP_CONNECTION_STATUS_CONNECTED) \
+ { \
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, \
+ "Connection is disconnected" }; \
+ DEBUG ("rejected request as disconnected"); \
+ dbus_g_method_return_error ((CONTEXT), &e); \
+ return; \
+ }
+
+static GObjectClass *parent_class=NULL;
+
+
+/* properties */
+enum
+{
+ PROP_ADDRESS = 1, /**< public SIP address (SIP URI) */
+ PROP_PASSWORD, /**< account password (for registration) */
+
+ PROP_PROXY, /**< outbound SIP proxy (SIP URI) */
+ PROP_REGISTRAR, /**< SIP registrar (SIP URI) */
+
+ PROP_KEEPALIVE_MECHANISM, /**< keepalive mechanism as defined by SIPConnectionKeepaliveMechanism */
+ PROP_KEEPALIVE_INTERVAL, /**< keepalive interval in seconds */
+ PROP_HTTP_PROXY, /**< HTTP proxy URI; use HTTP-CONNECT to reach SIP servers */
+ PROP_DISCOVER_BINDING, /**< enable discovery of public binding */
+ PROP_STUN_SERVER, /**< STUN server address (if not set, derived
+ from public SIP address */
+ PROP_SOFIA_ROOT, /**< Event root pointer from the Sofia-SIP stack */
+ LAST_PROPERTY
+};
+
+guint sip_conn_signals[LAST_SIGNAL] = {0};
+
+/**
+ * Returns a duplicated char array of a *user-provided* SIP
+ * URI, which might be missing the URI prefix, or some other
+ * required components.
+ *
+ * @return NULL if a valid URI cannot be created from 'input'
+ */
+static gchar *priv_sip_strdup(const gchar *input)
+{
+ if (input == NULL)
+ return NULL;
+
+ if (strncmp("sip:", input, 4) == 0 ||
+ strncmp("sips:", input, 5) == 0)
+ return g_strdup (input);
+
+ return g_strdup_printf ("sip:%s", input);
+}
+
+static GObject *
+sip_connection_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *obj;
+
+ {
+ /* Invoke parent constructor.
+ * this calls our init, and then set_property with any
+ * CONSTRUCT params
+ */
+ SIPConnectionClass *klass;
+ klass = SIP_CONNECTION_CLASS (g_type_class_peek (SIP_TYPE_CONNECTION));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type,
+ n_construct_properties,
+ construct_properties);
+ }
+
+ SIPConnection *self = SIP_CONNECTION (obj);
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ /* step: create home objects */
+ priv->sofia_home = su_home_new(sizeof (su_home_t));
+
+ /* the non-construct parameters will be empty */
+ g_assert (priv->registrar == NULL);
+ g_assert (priv->proxy == NULL);
+ g_assert (priv->http_proxy == NULL);
+ g_assert (priv->stun_server == NULL);
+
+ g_assert (priv->sofia_nua == NULL);
+
+ g_message ("SIPConnection constructed at %p", obj);
+ return obj;
+}
+
+/* keep these two in sync */
+enum
+{
+ LIST_HANDLE_PUBLISH = 1,
+ LIST_HANDLE_SUBSCRIBE,
+ LIST_HANDLE_KNOWN,
+};
+static const char *list_handle_strings[] =
+{
+ "publish", /* LIST_HANDLE_PUBLISH */
+ "subscribe", /* LIST_HANDLE_SUBSCRIBE */
+ "known", /* LIST_HANDLE_KNOWN */
+ NULL
+};
+
+static void
+sip_handle_repos_init (TpHandleRepoIface *repos[LAST_TP_HANDLE_TYPE+1])
+{
+ repos[TP_HANDLE_TYPE_CONTACT] =
+ (TpHandleRepoIface *)g_object_new (TP_TYPE_DYNAMIC_HANDLE_REPO,
+ "handle-type", TP_HANDLE_TYPE_CONTACT, NULL);
+ repos[TP_HANDLE_TYPE_LIST] =
+ (TpHandleRepoIface *)g_object_new (TP_TYPE_STATIC_HANDLE_REPO,
+ "handle-type", TP_HANDLE_TYPE_LIST,
+ "handle-names", list_handle_strings, NULL);
+}
+
+static GPtrArray *
+sip_connection_create_channel_factories (TpBaseConnection *base)
+{
+ SIPConnection *self = SIP_CONNECTION (base);
+ SIPConnectionPrivate *priv;
+ GPtrArray *factories = g_ptr_array_sized_new (2);
+
+ g_assert (SIP_IS_CONNECTION (self));
+ priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ priv->text_factory = (TpChannelFactoryIface *)g_object_new (
+ SIP_TYPE_TEXT_FACTORY, "connection", self, NULL);
+ g_ptr_array_add (factories, priv->text_factory);
+
+ priv->media_factory = (TpChannelFactoryIface *)g_object_new (
+ SIP_TYPE_MEDIA_FACTORY, "connection", self, NULL);
+ g_ptr_array_add (factories, priv->media_factory);
+
+ return factories;
+}
+
+static void
+sip_connection_init (SIPConnection *obj)
+{
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (obj);
+
+ priv->keepalive_interval = -1;
+}
+
+static void
+sip_connection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPConnection *self = (SIPConnection*) object;
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ switch (property_id) {
+ case PROP_ADDRESS: {
+ /* check address has been set*/
+ g_assert (strcmp (g_value_get_string (value), "no-address-set") != 0);
+ /* check this is 1st call, cannot be changed afterwords */
+ g_assert (base->self_handle == 0);
+
+ /* just store the address, self_handle set in constructor */
+ priv->requested_address = priv_sip_strdup (g_value_get_string (value));
+ break;
+ }
+ case PROP_PASSWORD: {
+ g_free(priv->password);
+ priv->password = g_value_dup_string (value);
+ break;
+ }
+ case PROP_PROXY: {
+ g_free(priv->proxy);
+ priv->proxy = priv_sip_strdup (g_value_get_string (value));
+ if (priv->sofia_nua)
+ nua_set_params(priv->sofia_nua, NUTAG_PROXY(priv->proxy), TAG_END());
+ break;
+ }
+ case PROP_REGISTRAR: {
+ g_free((gpointer)priv->registrar);
+ priv->registrar = priv_sip_strdup(g_value_get_string (value));
+ if (priv->sofia_nua)
+ nua_set_params(priv->sofia_nua, NUTAG_REGISTRAR(priv->registrar), TAG_END());
+ break;
+ }
+ case PROP_KEEPALIVE_MECHANISM: {
+ priv->keepalive_mechanism = g_value_get_enum (value);
+ if (priv->sofia_nua) {
+ sip_conn_update_nua_outbound (self);
+ sip_conn_update_nua_keepalive_interval (self);
+ }
+ break;
+ }
+ case PROP_KEEPALIVE_INTERVAL: {
+ priv->keepalive_interval = g_value_get_int (value);
+ if (priv->sofia_nua
+ && priv->keepalive_mechanism != SIP_CONNECTION_KEEPALIVE_NONE) {
+ sip_conn_update_nua_keepalive_interval(self);
+ }
+ break;
+ }
+ case PROP_HTTP_PROXY: {
+ g_free((gpointer)priv->http_proxy);
+ priv->http_proxy = g_value_dup_string (value);
+ if (priv->sofia_nua)
+ nua_set_params(priv->sofia_nua, TPTAG_HTTP_CONNECT(priv->http_proxy), TAG_END());
+ break;
+ }
+ case PROP_DISCOVER_BINDING: {
+ priv->discover_binding = g_value_get_boolean (value);
+ if (priv->sofia_nua)
+ sip_conn_update_nua_outbound (self);
+ break;
+ }
+ case PROP_STUN_SERVER: {
+ g_free((gpointer)priv->stun_server);
+ priv->stun_server = g_value_dup_string (value);
+ if (priv->sofia_nua)
+ nua_set_params(priv->sofia_nua, STUNTAG_SERVER(priv->stun_server), TAG_END());
+ break;
+ }
+ case PROP_SOFIA_ROOT: {
+ priv->sofia_root = g_value_get_pointer (value);
+ break;
+ }
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
+ break;
+ }
+}
+
+static void
+sip_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPConnection *self = (SIPConnection *) object;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ switch (property_id) {
+ case PROP_ADDRESS: {
+ if (priv->address)
+ g_value_set_string (value, priv->address);
+ else
+ g_value_set_string (value, priv->requested_address);
+ break;
+ }
+ case PROP_PASSWORD: {
+ g_value_set_string (value, priv->password);
+ break;
+ }
+ case PROP_PROXY: {
+ g_value_set_string (value, priv->proxy);
+ break;
+ }
+ case PROP_REGISTRAR: {
+ g_value_set_string (value, priv->registrar);
+ break;
+ }
+ case PROP_KEEPALIVE_MECHANISM: {
+ g_value_set_enum (value, priv->keepalive_mechanism);
+ break;
+ }
+ case PROP_KEEPALIVE_INTERVAL: {
+ g_value_set_int (value, priv->keepalive_interval);
+ break;
+ }
+ case PROP_HTTP_PROXY: {
+ g_value_set_string (value, priv->http_proxy);
+ break;
+ }
+ case PROP_DISCOVER_BINDING: {
+ g_value_set_boolean (value, priv->discover_binding);
+ break;
+ }
+ case PROP_STUN_SERVER: {
+ g_value_set_string (value, priv->stun_server);
+ break;
+ }
+ case PROP_SOFIA_ROOT: {
+ g_value_set_pointer (value, priv->sofia_root);
+ break;
+ }
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
+ break;
+ }
+}
+
+static void sip_connection_dispose (GObject *object);
+static void sip_connection_finalize (GObject *object);
+
+static gchar *
+sip_connection_unique_name (TpBaseConnection *base)
+{
+ SIPConnection *conn = SIP_CONNECTION (base);
+ SIPConnectionPrivate *priv;
+
+ g_assert (SIP_IS_CONNECTION (conn));
+ priv = SIP_CONNECTION_GET_PRIVATE (conn);
+ return g_strdup (priv->requested_address);
+}
+
+static void sip_connection_disconnected (TpBaseConnection *base);
+static void sip_connection_shut_down (TpBaseConnection *base);
+static gboolean sip_connection_start_connecting (TpBaseConnection *base,
+ GError **error);
+
+static void
+sip_connection_class_init (SIPConnectionClass *sip_connection_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_connection_class);
+ TpBaseConnectionClass *base_class =
+ (TpBaseConnectionClass *)sip_connection_class;
+ GParamSpec *param_spec;
+
+#define INST_PROP(x) \
+ g_object_class_install_property (object_class, x, param_spec)
+
+ /* Implement pure-virtual methods */
+ base_class->init_handle_repos = sip_handle_repos_init;
+ base_class->get_unique_connection_name = sip_connection_unique_name;
+ base_class->create_channel_factories =
+ sip_connection_create_channel_factories;
+ base_class->disconnected = sip_connection_disconnected;
+ base_class->start_connecting = sip_connection_start_connecting;
+ base_class->shut_down = sip_connection_shut_down;
+
+ g_type_class_add_private (sip_connection_class, sizeof (SIPConnectionPrivate));
+
+ object_class->constructor = sip_connection_constructor;
+
+ object_class->dispose = sip_connection_dispose;
+ object_class->finalize = sip_connection_finalize;
+
+ object_class->set_property = sip_connection_set_property;
+ object_class->get_property = sip_connection_get_property;
+
+ param_spec = g_param_spec_pointer("sofia-root",
+ "Sofia root",
+ "Event root from Sofia-SIP stack",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ INST_PROP(PROP_SOFIA_ROOT);
+
+ param_spec = g_param_spec_string("address",
+ "SIPConnection construction property",
+ "Public SIP address",
+ "no-address-set", /*default value*/
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ INST_PROP(PROP_ADDRESS);
+
+ param_spec = g_param_spec_string("password",
+ "SIP account password",
+ "Password for SIP registration",
+ "", /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_PASSWORD);
+
+ param_spec = g_param_spec_string("proxy",
+ "Outbound proxy",
+ "SIP URI for outbound proxy (e.g. 'sip:sipproxy.myprovider.com') [optional]",
+ NULL, /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_PROXY);
+
+ param_spec = g_param_spec_string("registrar",
+ "Registrar",
+ "SIP URI for registrar (e.g. 'sip:sip.myprovider.com') [optional]",
+ NULL, /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_REGISTRAR);
+
+ param_spec = g_param_spec_enum ("keepalive-mechanism",
+ "Keepalive mechanism",
+ "SIP registration keepalive mechanism",
+ sip_connection_keepalive_mechanism_get_type (),
+ SIP_CONNECTION_KEEPALIVE_AUTO,
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_KEEPALIVE_MECHANISM);
+
+ param_spec = g_param_spec_int("keepalive-interval",
+ "Keepalive interval",
+ "Interval between keepalives in seconds (0 = disable, -1 = let stack decide.",
+ -1, G_MAXINT32, -1,
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_KEEPALIVE_INTERVAL);
+
+ param_spec = g_param_spec_string("http-proxy",
+ "HTTP proxy URI",
+ "Use HTTP-CONNECT to reach the SIP servers, empty to disable [optional]",
+ NULL, /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_HTTP_PROXY);
+
+ param_spec = g_param_spec_boolean("discover-binding",
+ "Discover public address",
+ "Enable discovery of public address beyond NAT",
+ TRUE, /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_DISCOVER_BINDING);
+
+ param_spec = g_param_spec_string("stun-server",
+ "STUN server address",
+ "STUN server address (FQDN or IP address) and optionally the port (e.g. 'stun.myprovider.com:3478') [optional]",
+ NULL, /*default value*/
+ G_PARAM_READWRITE);
+ INST_PROP(PROP_STUN_SERVER);
+
+ sip_conn_signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (sip_connection_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+sip_connection_shut_down (TpBaseConnection *base)
+{
+ SIPConnection *self = SIP_CONNECTION (base);
+ SIPConnectionPrivate *priv;
+
+ g_assert (SIP_IS_CONNECTION (self));
+ priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ /* this shouldn't happen - shut_down is called when we change
+ * state to DISCONNECTED, and that should never happen unless we've
+ * started connecting
+ */
+ g_return_if_fail (priv->sofia_nua != NULL);
+
+ if (priv->sofia_shutdown == SIP_NUA_SHUTDOWN_NOT_STARTED)
+ {
+ /* we cannot release resources until the SIP stack has properly
+ * exited */
+ priv->sofia_shutdown = SIP_NUA_SHUTDOWN_STARTED;
+ nua_shutdown (priv->sofia_nua);
+ }
+ else if (priv->sofia_shutdown == SIP_NUA_SHUTDOWN_DONE)
+ {
+ /* If SofiaSIP can be made to notify us of network errors, shut_down
+ * might be called in response to a disconnect event caused by
+ * a SofiaSIP disconnection. (This doesn't yet happen.)
+ */
+ tp_base_connection_finish_shutdown (base);
+ }
+}
+
+void
+sip_connection_dispose (GObject *object)
+{
+ SIPConnection *self = SIP_CONNECTION (object);
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ DEBUG ("Disposing of SIPConnection %p", self);
+
+ /* these are borrowed refs, the real ones are owned by the superclass */
+ priv->media_factory = NULL;
+ priv->text_factory = NULL;
+
+ tp_handle_unref (base->handles[TP_HANDLE_TYPE_CONTACT],
+ base->self_handle);
+ base->self_handle = 0;
+
+ if (G_OBJECT_CLASS (sip_connection_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_connection_parent_class)->dispose (object);
+}
+
+static void
+priv_free_nua (SIPConnection *self, SIPConnectionPrivate *priv)
+{
+ GSource *source;
+ gboolean source_recursive;
+
+ if (priv->register_op)
+ {
+ nua_handle_t *register_op = priv->register_op;
+ priv->register_op = NULL;
+ nua_handle_destroy (register_op);
+ }
+
+ source = su_root_gsource(priv->sofia_root);
+
+ /* XXX: temporarily allow recursion in the Sofia source to work around
+ * nua_destroy() requiring nested mainloop iterations to complete
+ * (Sofia-SIP bug #1624446). Actual recursion safety of the source is to be
+ * examined. */
+ source_recursive = g_source_get_can_recurse (source);
+ if (!source_recursive)
+ {
+ g_debug ("forcing Sofia root GSource to be recursive");
+ g_source_set_can_recurse (source, TRUE);
+ }
+
+ if (priv->sofia_nua)
+ {
+ nua_t *nua = priv->sofia_nua;
+
+ /* if we've created the NUA (which happens when we start connecting),
+ * we should be referenced by the CM until its shutdown process
+ * has finished */
+ g_assert (priv->sofia_shutdown == SIP_NUA_SHUTDOWN_DONE);
+
+ priv->sofia_nua = NULL;
+ nua_destroy (nua);
+ }
+
+ if (!source_recursive)
+ g_source_set_can_recurse (source, FALSE);
+}
+
+void
+sip_connection_finalize (GObject *obj)
+{
+ SIPConnection *self = SIP_CONNECTION (obj);
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+
+ DEBUG("enter");
+
+ priv_free_nua (self, priv);
+
+ su_home_unref (priv->sofia_home);
+
+ g_free (priv->address);
+ g_free (priv->proxy);
+ g_free (priv->registrar);
+ g_free (priv->http_proxy);
+ g_free (priv->stun_server);
+
+ DEBUG ("exit");
+
+ G_OBJECT_CLASS (sip_connection_parent_class)->finalize (obj);
+}
+
+
+/**
+ * sip_connection_connect
+ *
+ * Implements DBus method Connect
+ * on interface org.freedesktop.Telepathy.Connection
+ */
+static gboolean
+sip_connection_start_connecting (TpBaseConnection *base,
+ GError **error)
+{
+ SIPConnection *self = SIP_CONNECTION (base);
+ SIPConnectionPrivate *priv = SIP_CONNECTION_GET_PRIVATE (self);
+ gchar *sip_address;
+
+ g_message("%s: Connection %p ref-count=%u (obj)", G_STRFUNC, self,
+ G_OBJECT(self)->ref_count);
+
+ g_assert (base->status == TP_INTERNAL_CONNECTION_STATUS_NEW);
+
+ /* the construct parameters will be non-empty */
+ g_assert (priv->sofia_root != NULL);
+ g_assert (priv->requested_address != NULL);
+
+ /* step: create stack instance */
+ priv->sofia_nua = nua_create(priv->sofia_root,
+ sip_connection_sofia_callback, self,
+ SOATAG_AF (SOA_AF_IP4_IP6),
+ TAG_IF(priv->requested_address,
+ SIPTAG_FROM_STR(priv->requested_address)),
+ TAG_IF(priv->proxy,
+ NUTAG_PROXY(priv->proxy)),
+ TAG_NULL ());
+ if (priv->sofia_nua == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Unable to create SIP stack");
+ return FALSE;
+ }
+
+ nua_set_params(priv->sofia_nua,
+ TAG_IF (priv->keepalive_interval > 0,
+ NUTAG_KEEPALIVE((glong)priv->keepalive_interval * 1000)),
+ NUTAG_ENABLEMESSAGE(1),
+ NUTAG_ENABLEINVITE(1),
+ NUTAG_AUTOALERT(0),
+ NUTAG_AUTOANSWER(0),
+ TAG_NULL());
+
+ sip_conn_update_nua_outbound (self);
+ sip_conn_update_nua_contact_features (self);
+
+ g_message ("Sofia-SIP NUA at address %p (SIP URI: %s)",
+ priv->sofia_nua, priv->requested_address);
+
+ base->self_handle = tp_handle_request (
+ base->handles[TP_HANDLE_TYPE_CONTACT], priv->requested_address,
+ TRUE);
+ tp_handle_ref (base->handles[TP_HANDLE_TYPE_CONTACT],
+ base->self_handle);
+
+ g_message ("self_handle = %d", base->self_handle);
+
+ /* XXX: should there be configuration option to disable use
+ * of outbound proxy, any use-cases? */
+
+ sip_address = priv->address ? priv->address : priv->requested_address;
+
+ /* for debugging purposes, request a dump of stack configuration
+ * at registration time */
+ nua_get_params(priv->sofia_nua, TAG_ANY(), TAG_NULL());
+
+ priv->register_op = sip_conn_create_register_handle(priv->sofia_nua,
+ priv->sofia_home, sip_address, base->self_handle);
+ nua_register(priv->register_op, TAG_NULL());
+
+ return TRUE;
+}
+
+
+/**
+ * sip_connection_disconnected
+ *
+ * Called after the connection becomes disconnected.
+ */
+static void
+sip_connection_disconnected (TpBaseConnection *base)
+{
+ SIPConnection *obj = SIP_CONNECTION (base);
+ SIPConnectionPrivate *priv;
+
+ g_assert (SIP_IS_CONNECTION (obj));
+ priv = SIP_CONNECTION_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ if (priv->sofia_nua && priv->register_op)
+ nua_unregister(priv->register_op, TAG_NULL());
+}
+
+/**
+ * sip_connection_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Connection
+ */
+static void
+sip_connection_get_interfaces (TpSvcConnection *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPConnection *self = SIP_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *)self;
+ const char *interfaces[] = {
+ TP_IFACE_PROPERTIES_INTERFACE,
+ NULL };
+
+ DEBUG ("called");
+
+ ERROR_IF_NOT_CONNECTED_ASYNC (base, context)
+ tp_svc_connection_return_from_get_interfaces (context, interfaces);
+}
+
+
+static gboolean
+sipuri_is_valid (const gchar *sipuri, GError **error)
+{
+ if (strncmp (sipuri, "sip", 3) &&
+ strncmp (sipuri, "tel", 3))
+ {
+ g_debug ("%s: not a valid sip/sips/tel URI (%s)", G_STRFUNC, sipuri);
+
+ if (error)
+ *error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "invalid SIP URI");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+priv_sip_connection_request_handle (SIPConnection *obj,
+ guint handle_type,
+ const gchar *name,
+ TpHandle *handle,
+ GError **error,
+ const gchar* clientname)
+{
+ TpBaseConnection *base = (TpBaseConnection *)obj;
+ SIPConnectionPrivate *priv;
+
+ priv = SIP_CONNECTION_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ if (!tp_handle_type_is_valid (handle_type, error))
+ {
+ return FALSE;
+ }
+
+ switch (handle_type)
+ {
+ case TP_HANDLE_TYPE_CONTACT:
+ if (!sipuri_is_valid (name, error))
+ {
+ return FALSE;
+ }
+
+ *handle = tp_handle_request (
+ base->handles[TP_HANDLE_TYPE_CONTACT], name, TRUE);
+
+ g_debug("%s:\n\tverify handle '%s' => %u (%s)", G_STRFUNC, name, *handle,
+ tp_handle_inspect(base->handles[TP_HANDLE_TYPE_CONTACT],
+ *handle));
+
+ if (*handle == 0)
+ {
+ g_debug ("%s: requested handle %s was invalid", G_STRFUNC, name);
+
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "requested handle %s was invalid", name);
+ return FALSE;
+ }
+ break;
+
+ case TP_HANDLE_TYPE_LIST:
+ if (!strcmp (name, "publish"))
+ {
+ *handle = LIST_HANDLE_PUBLISH;
+ }
+ else if (!strcmp (name, "subscribe"))
+ {
+ *handle = LIST_HANDLE_SUBSCRIBE;
+ }
+ else
+ {
+ g_debug ("%s: requested list channel %s not available", G_STRFUNC, name);
+
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "requested list channel %s not available",
+ name);
+
+ return FALSE;
+ }
+ break;
+
+ default:
+ g_debug ("%s: unimplemented handle type %u", G_STRFUNC, handle_type);
+ tp_g_set_error_unsupported_handle_type (handle_type, error);
+ return FALSE;
+ }
+
+ if (!tp_handle_client_hold (base->handles[handle_type], clientname,
+ *handle, error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ * sip_connection_request_handles
+ *
+ * Implements DBus method RequestHandles
+ * on interface org.freedesktop.Telepathy.Connection
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occured, DBus will throw the error only if this
+ * function returns false.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+sip_connection_request_handles (TpSvcConnection *iface,
+ guint handle_type,
+ const gchar **names,
+ DBusGMethodInvocation *context)
+{
+ SIPConnection *obj = SIP_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *)obj;
+ gint count = 0, i;
+ const gchar **h;
+ GArray *handles;
+ GError *error = NULL;
+ SIPConnectionPrivate *priv;
+
+ priv = SIP_CONNECTION_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ ERROR_IF_NOT_CONNECTED_ASYNC (base, context)
+
+ for (h = names; *h != NULL; h++)
+ ++count;
+
+ handles = g_array_sized_new(FALSE, FALSE, sizeof(TpHandle), count);
+
+ for (i = 0; i < count; i++) {
+ TpHandle handle;
+
+ priv_sip_connection_request_handle(obj, handle_type, names[i], &handle,
+ &error, dbus_g_method_get_sender (context));
+ if (error != NULL) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ g_array_free (handles, TRUE);
+ return;
+ }
+
+ g_array_append_val(handles, handle);
+ }
+
+ tp_svc_connection_return_from_request_handles (context, handles);
+ g_array_free (handles, TRUE);
+}
+
+static void
+conn_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionClass *klass = (TpSvcConnectionClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_implement_##x (klass,\
+ sip_connection_##x)
+ IMPLEMENT(get_interfaces);
+ IMPLEMENT(request_handles);
+#undef IMPLEMENT
+}
diff --git a/src/sip-connection.h b/src/sip-connection.h
new file mode 100644
index 0000000..8310b08
--- /dev/null
+++ b/src/sip-connection.h
@@ -0,0 +1,77 @@
+/*
+ * sip-connection.h - Header for SIPConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005,2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_CONNECTION_H__
+#define __SIP_CONNECTION_H__
+
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/properties-mixin.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/handle-repo.h>
+
+#include "sip-connection-manager.h"
+
+
+G_BEGIN_DECLS
+
+typedef struct _SIPConnection SIPConnection;
+typedef struct _SIPConnectionClass SIPConnectionClass;
+typedef struct _SIPConnectionPrivate SIPConnectionPrivate;
+
+
+typedef enum /*< lowercase_name=sip_connection_keepalive_mechanism >*/
+{
+ SIP_CONNECTION_KEEPALIVE_AUTO = 0, /** Keepalive management is up to the implementation */
+ SIP_CONNECTION_KEEPALIVE_NONE, /** Disable keepalive management */
+ SIP_CONNECTION_KEEPALIVE_REGISTER, /** Maintain registration with REGISTER requests */
+ SIP_CONNECTION_KEEPALIVE_OPTIONS, /** Maintain registration with OPTIONS requests */
+ SIP_CONNECTION_KEEPALIVE_STUN, /** Maintain registration with STUN as described in IETF draft-sip-outbound */
+} SIPConnectionKeepaliveMechanism;
+
+
+struct _SIPConnectionClass {
+ TpBaseConnectionClass parent_class;
+};
+
+struct _SIPConnection {
+ TpBaseConnection parent;
+};
+
+GType sip_connection_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_CONNECTION \
+ (sip_connection_get_type())
+#define SIP_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_CONNECTION, SIPConnection))
+#define SIP_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_CONNECTION, SIPConnectionClass))
+#define SIP_IS_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_CONNECTION))
+#define SIP_IS_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_CONNECTION))
+#define SIP_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_CONNECTION, SipConnectionClass))
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_CONNECTION_H__*/
diff --git a/src/sip-media-channel.c b/src/sip-media-channel.c
new file mode 100644
index 0000000..07e895e
--- /dev/null
+++ b/src/sip-media-channel.c
@@ -0,0 +1,1254 @@
+/*
+ * sip-media-channel.c - Source for SIPMediaChannel
+ * Copyright (C) 2005-2007 Collabora Ltd.
+ * Copyright (C) 2005-2007 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-media-channel).
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define DBUS_API_SUBJECT_TO_CHANGE 1
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/intset.h>
+#include <telepathy-glib/svc-channel.h>
+
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "sip-media-session.h"
+#include "sip-media-stream.h"
+
+#include "sip-media-channel.h"
+#include "media-factory.h"
+#include "signals-marshal.h"
+
+#define DEBUG_FLAG SIP_DEBUG_MEDIA
+#include "debug.h"
+
+static void channel_iface_init (gpointer, gpointer);
+static void media_signalling_iface_init (gpointer, gpointer);
+static void streamed_media_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SIPMediaChannel, sip_media_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
+ media_signalling_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
+ streamed_media_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
+ tp_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+#define TP_SESSION_HANDLER_SET_TYPE (dbus_g_type_get_struct ("GValueArray", \
+ DBUS_TYPE_G_OBJECT_PATH, \
+ G_TYPE_STRING, \
+ G_TYPE_INVALID))
+
+#define TP_CHANNEL_STREAM_TYPE (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_INVALID))
+
+/* signal enum */
+enum
+{
+ SIG_NEW_MEDIA_SESSION_HANDLER = 1,
+ SIG_PROPERTIES_CHANGED,
+ SIG_PROPERTY_FLAGS_CHANGED,
+ SIG_LAST
+};
+
+static guint signals[SIG_LAST] = {0};
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_FACTORY,
+ PROP_OBJECT_PATH,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_CREATOR,
+ PROP_NUA_OP,
+ PROP_NAT_TRAVERSAL,
+ LAST_PROPERTY
+};
+
+/* TP channel properties */
+enum
+{
+ TP_PROP_NAT_TRAVERSAL = 0,
+ NUM_TP_PROPS
+};
+
+const TpPropertySignature media_channel_property_signatures[NUM_TP_PROPS] =
+{
+ { "nat-traversal", G_TYPE_STRING }
+};
+
+/* private structure */
+typedef struct _SIPMediaChannelPrivate SIPMediaChannelPrivate;
+
+struct _SIPMediaChannelPrivate
+{
+ gboolean dispose_has_run;
+ gboolean closed;
+ SIPConnection *conn;
+ SIPMediaFactory *factory;
+ SIPMediaSession *session;
+ gchar *object_path;
+ TpHandle creator;
+ gpointer *nua_op;
+};
+
+#define SIP_MEDIA_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_MEDIA_CHANNEL, SIPMediaChannelPrivate))
+
+static GPtrArray *priv_make_stream_list (SIPMediaChannel *self, GPtrArray *streams);
+
+/***********************************************************************
+ * Set: Gobject interface
+ ***********************************************************************/
+
+static void
+sip_media_channel_init (SIPMediaChannel *obj)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ /* allocate any data required by the object here */
+
+ /* initialise the properties mixin *before* GObject
+ * sets the construct-time properties */
+ tp_properties_mixin_init (G_OBJECT (obj),
+ G_STRUCT_OFFSET (SIPMediaChannel, properties));
+
+ /* to keep the compiler happy */
+ priv = NULL;
+}
+
+static GObject *
+sip_media_channel_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SIPMediaChannelPrivate *priv;
+ DBusGConnection *bus;
+ TpBaseConnection *conn;
+
+ DEBUG("enter");
+
+ obj = G_OBJECT_CLASS (sip_media_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (SIP_MEDIA_CHANNEL (obj));
+ conn = (TpBaseConnection *)(priv->conn);
+
+ /* register object on the bus */
+ bus = tp_get_bus ();
+
+ g_debug("registering object to dbus path=%s.\n", priv->object_path);
+ dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+
+ tp_group_mixin_init ((TpSvcChannelInterfaceGroup *)obj,
+ G_STRUCT_OFFSET (SIPMediaChannel, group),
+ conn->handles[TP_HANDLE_TYPE_CONTACT],
+ conn->self_handle);
+
+ /* automatically add creator to channel, if defined */
+ if (priv->creator) {
+ TpIntSet *set = tp_intset_new ();
+ tp_intset_add (set, priv->creator);
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)obj,
+ "", set, NULL, NULL, NULL, 0, 0);
+ tp_intset_destroy (set);
+ }
+
+ /* allow member adding */
+ tp_group_mixin_change_flags ((TpSvcChannelInterfaceGroup *)obj,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD, 0);
+
+ return obj;
+}
+
+static void sip_media_channel_dispose (GObject *object);
+static void sip_media_channel_finalize (GObject *object);
+static void sip_media_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void sip_media_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void priv_create_session (SIPMediaChannel *channel,
+ TpHandle peer,
+ const gchar *sid);
+static void priv_release_session(SIPMediaChannel *channel);
+gboolean sip_media_channel_add_member (TpSvcChannelInterfaceGroup *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error);
+static gboolean priv_media_channel_remove_member (TpSvcChannelInterfaceGroup *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error);
+
+static void
+sip_media_channel_class_init (SIPMediaChannelClass *sip_media_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_media_channel_class);
+ GParamSpec *param_spec;
+
+ DEBUG("enter");
+
+ g_type_class_add_private (sip_media_channel_class, sizeof (SIPMediaChannelPrivate));
+
+ object_class->constructor = sip_media_channel_constructor;
+
+ object_class->get_property = sip_media_channel_get_property;
+ object_class->set_property = sip_media_channel_set_property;
+
+ object_class->dispose = sip_media_channel_dispose;
+ object_class->finalize = sip_media_channel_finalize;
+
+ tp_group_mixin_class_init ((TpSvcChannelInterfaceGroupClass *)object_class,
+ G_STRUCT_OFFSET (SIPMediaChannelClass, group_class),
+ sip_media_channel_add_member,
+ priv_media_channel_remove_member);
+
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+ tp_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SIPMediaChannelClass, properties_class),
+ media_channel_property_signatures, NUM_TP_PROPS, NULL);
+
+ param_spec = g_param_spec_object ("connection", "SIPConnection object",
+ "SIP connection object that owns this "
+ "SIP media channel object.",
+ SIP_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_object ("factory", "SIPMediaFactory object",
+ "Channel factory object that owns this "
+ "SIP media channel object.",
+ SIP_TYPE_MEDIA_FACTORY,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_FACTORY, param_spec);
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_string ("channel-type", "Telepathy channel type",
+ "The D-Bus interface representing the "
+ "type of this channel.",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CHANNEL_TYPE, param_spec);
+
+ param_spec = g_param_spec_uint ("creator", "Channel creator",
+ "The TpHandle representing the contact "
+ "who created the channel.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CREATOR, param_spec);
+
+ param_spec = g_param_spec_pointer("nua-handle", "Sofia-SIP NUA operator handle",
+ "Handle associated with this media channel.",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_NUA_OP, param_spec);
+
+ param_spec = g_param_spec_string ("nat-traversal", "NAT traversal mechanism",
+ "A string representing the type of NAT "
+ "traversal that should be performed for "
+ "streams on this channel.",
+ "none",
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL, param_spec);
+
+ signals[SIG_NEW_MEDIA_SESSION_HANDLER] =
+ g_signal_new ("new-media-session-handler",
+ G_OBJECT_CLASS_TYPE (sip_media_channel_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tpsip_marshal_VOID__INT_STRING_STRING,
+ G_TYPE_NONE, 3, G_TYPE_UINT, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING);
+}
+
+static void
+sip_media_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaChannel *chan = SIP_MEDIA_CHANNEL (object);
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ case PROP_FACTORY:
+ g_value_set_object (value, priv->factory);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, 0);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_NONE);
+ break;
+ case PROP_CREATOR:
+ g_value_set_uint (value, priv->creator);
+ break;
+ case PROP_NUA_OP:
+ g_value_set_pointer (value, priv->nua_op);
+ break;
+ default:
+ /* the NAT_TRAVERSAL property lives in the mixin */
+ {
+ const gchar *param_name = g_param_spec_get_name (pspec);
+ guint tp_property_id;
+
+ if (tp_properties_mixin_has_property (object, param_name,
+ &tp_property_id))
+ {
+ GValue *tp_property_value =
+ chan->properties.properties[tp_property_id].value;
+
+ if (tp_property_value)
+ {
+ g_value_copy (tp_property_value, value);
+ return;
+ }
+ }
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_media_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaChannel *chan = SIP_MEDIA_CHANNEL (object);
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id) {
+ case PROP_HANDLE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_HANDLE_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_FACTORY:
+ priv->factory = g_value_get_object (value);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_CREATOR:
+ priv->creator = g_value_get_uint (value);
+ break;
+ case PROP_NUA_OP:
+ priv->nua_op = g_value_get_pointer (value);
+ break;
+ default:
+ /* the NAT_TRAVERSAL property lives in the mixin */
+ {
+ const gchar *param_name = g_param_spec_get_name (pspec);
+ guint tp_property_id;
+
+ if (tp_properties_mixin_has_property (object, param_name,
+ &tp_property_id))
+ {
+ tp_properties_mixin_change_value (object, tp_property_id,
+ value, NULL);
+ tp_properties_mixin_change_flags (object, tp_property_id,
+ TP_PROPERTY_FLAG_READ, 0, NULL);
+ return;
+ }
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+void
+sip_media_channel_dispose (GObject *object)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (object);
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ DEBUG("enter");
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ priv_release_session(self);
+
+ if (!priv->closed)
+ sip_media_channel_close (self);
+
+ if (G_OBJECT_CLASS (sip_media_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_media_channel_parent_class)->dispose (object);
+
+ DEBUG ("exit");
+}
+
+void
+sip_media_channel_finalize (GObject *object)
+{
+ /* free any data held directly by the object here */
+ tp_group_mixin_finalize (TP_SVC_CHANNEL_INTERFACE_GROUP (object));
+
+ tp_properties_mixin_finalize (object);
+
+ G_OBJECT_CLASS (sip_media_channel_parent_class)->finalize (object);
+
+ DEBUG ("exit");
+}
+
+/***********************************************************************
+ * Set: Channel interface implementation (same for 0.12/0.13)
+ ***********************************************************************/
+
+/**
+ * sip_media_channel_close_async
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_media_channel_close_async (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (iface);
+
+ sip_media_channel_close (self);
+ tp_svc_channel_return_from_close (context);
+}
+
+void
+sip_media_channel_close (SIPMediaChannel *obj)
+{
+ SIPMediaChannelPrivate *priv;
+
+ DEBUG("enter");
+
+ g_assert (SIP_IS_MEDIA_CHANNEL (obj));
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (obj);
+
+ if (priv->closed)
+ return;
+
+ priv->closed = TRUE;
+
+ if (priv->session &&
+ SIP_IS_MEDIA_SESSION (priv->session)) {
+ sip_media_session_terminate (priv->session);
+ }
+
+ tp_svc_channel_emit_closed ((TpSvcChannel *)obj);
+
+ return;
+}
+
+
+/**
+ * sip_media_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_media_channel_get_channel_type (TpSvcChannel *obj,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
+}
+
+
+/**
+ * sip_media_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_media_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_handle (context, 0, 0);
+}
+
+/**
+ * sip_media_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_media_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ const gchar *interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
+ TP_IFACE_PROPERTIES_INTERFACE,
+ NULL
+ };
+
+ tp_svc_channel_return_from_get_interfaces (context, interfaces);
+}
+
+/***********************************************************************
+ * Set: Channel.Interface.MediaSignalling Telepathy-0.13 interface
+ ***********************************************************************/
+
+/**
+ * sip_media_channel_get_session_handlers
+ *
+ * Implements DBus method GetSessionHandlers
+ * on interface org.freedesktop.Telepathy.Channel.Interface.MediaSignalling
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occured, DBus will throw the error only if this
+ * function returns false.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+sip_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (iface);
+ SIPMediaChannelPrivate *priv;
+ GPtrArray *ret;
+
+ DEBUG("enter");
+
+ g_assert (SIP_IS_MEDIA_CHANNEL (self));
+
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->session) {
+ GValue handler = { 0, };
+ TpHandle member;
+ gchar *path;
+
+ g_value_init (&handler, TP_SESSION_HANDLER_SET_TYPE);
+ g_value_set_static_boxed (&handler,
+ dbus_g_type_specialized_construct (TP_SESSION_HANDLER_SET_TYPE));
+
+ g_object_get (priv->session,
+ "peer", &member,
+ "object-path", &path,
+ NULL);
+
+ dbus_g_type_struct_set (&handler,
+ 0, path,
+ 1, "rtp",
+ G_MAXUINT);
+
+ g_free (path);
+
+ ret = g_ptr_array_sized_new (1);
+ g_ptr_array_add (ret, g_value_get_boxed (&handler));
+ }
+ else {
+ ret = g_ptr_array_sized_new (0);
+ }
+
+ tp_svc_channel_interface_media_signalling_return_from_get_session_handlers (
+ context, ret);
+ g_ptr_array_free (ret, TRUE);
+}
+
+
+/***********************************************************************
+ * Set: Channel.Type.StreamedMedia Telepathy-0.13 interface
+ ***********************************************************************/
+
+/**
+ * sip_media_channel_list_streams
+ *
+ * Implements D-Bus method ListStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+sip_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (iface);
+ SIPMediaChannelPrivate *priv;
+ GPtrArray *streams = NULL;
+ GPtrArray *ret;
+
+ g_assert (SIP_IS_MEDIA_CHANNEL (self));
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ /* FIXME: I suspect this leaks memory */
+ if (sip_media_session_list_streams (priv->session, &streams)) {
+ ret = priv_make_stream_list (self, streams);
+ }
+ else {
+ ret = g_ptr_array_new ();
+ }
+
+ tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret);
+ g_ptr_array_free (ret, TRUE);
+}
+
+/**
+ * sip_media_channel_remove_streams
+ *
+ * Implements D-Bus method RemoveStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+sip_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *self,
+ const GArray *streams,
+ DBusGMethodInvocation *context)
+{
+ /* FIXME: stub */
+ tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
+}
+
+/**
+ * sip_media_channel_request_stream_direction
+ *
+ * Implements D-Bus method RequestStreamDirection
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+sip_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface,
+ guint stream_id,
+ guint stream_direction,
+ DBusGMethodInvocation *context)
+{
+ /* XXX: requires performing a re-INVITE either disabling a
+ * a media, or putting it to hold */
+ GError error = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "RequestStreamDirection not implemented" };
+ g_debug ("%s: not implemented", G_STRFUNC);
+ dbus_g_method_return_error (context, &error);
+}
+
+
+/**
+ * sip_media_channel_request_streams
+ *
+ * Implements D-Bus method RequestStreams
+ * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
+ */
+static void
+sip_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
+ guint contact_handle,
+ const GArray *types,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (iface);
+ GError *error = NULL;
+ GPtrArray *ret;
+ SIPMediaChannelPrivate *priv;
+ TpBaseConnection *conn;
+
+ GPtrArray *streams;
+
+ DEBUG("enter");
+
+ g_assert (SIP_IS_MEDIA_CHANNEL (self));
+
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ conn = (TpBaseConnection *)(priv->conn);
+
+ if (!tp_handle_is_valid (conn->handles[TP_HANDLE_TYPE_CONTACT],
+ contact_handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ if (!tp_handle_set_is_member (self->group.members, contact_handle) &&
+ !tp_handle_set_is_member (self->group.remote_pending, contact_handle))
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "given handle "
+ "%u is not a member of the channel", contact_handle);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* if the person is a channel member, we should have a session */
+ g_assert (priv->session != NULL);
+
+ if (!sip_media_session_request_streams (priv->session, types, &streams, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ ret = priv_make_stream_list (self, streams);
+
+ g_assert(types->len == (ret)->len);
+
+ g_ptr_array_free (streams, TRUE);
+
+ tp_svc_channel_type_streamed_media_return_from_request_streams (context, ret);
+ g_ptr_array_free (ret, TRUE);
+ DEBUG ("exit");
+}
+
+static GPtrArray *priv_make_stream_list (SIPMediaChannel *self, GPtrArray *streams)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ GPtrArray *ret;
+ guint i;
+
+ DEBUG("enter");
+
+ ret = g_ptr_array_sized_new (streams->len);
+
+ for (i = 0; i < streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index (streams, i);
+ GValue entry = { 0, };
+ guint id;
+ TpHandle peer;
+ TpMediaStreamType type = TP_MEDIA_STREAM_TYPE_AUDIO;
+ TpMediaStreamState connection_state = TP_MEDIA_STREAM_STATE_CONNECTED;
+ /* CombinedStreamDirection combined_direction; */
+
+ /* note: removed streams are kept in the ptr-array as NULL
+ * items (one cannot remove m-lines in SDP negotiation)
+ */
+
+ if (stream == NULL)
+ continue;
+
+ g_object_get (stream,
+ "id", &id,
+ "media-type", &type,
+ /* XXX: add to sip-stream -> "connection-state", &connection_state, */
+ /* "combined-direction", &combined_direction,*/
+ NULL);
+
+ if (id != i)
+ g_warning("%s: strange stream id %d, should be %d", G_STRFUNC, id, i);
+
+ g_assert (priv->session);
+ peer = sip_media_session_get_peer (priv->session);
+
+ g_value_init (&entry, TP_CHANNEL_STREAM_TYPE);
+ g_value_take_boxed (&entry,
+ dbus_g_type_specialized_construct (TP_CHANNEL_STREAM_TYPE));
+
+ dbus_g_type_struct_set (&entry,
+ 0, id,
+ 1, peer,
+ 2, type,
+ 3, connection_state,
+ 4, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
+ 5, 0, /* no pending send */
+ G_MAXUINT);
+
+ g_ptr_array_add (ret, g_value_get_boxed (&entry));
+ }
+
+ DEBUG ("exit");
+
+ return ret;
+}
+
+/***********************************************************************
+ * Set: sip-media-channel API towards sip-connection
+ ***********************************************************************/
+
+/**
+ * Invite the given handle to this channel
+ */
+void sip_media_channel_respond_to_invite (SIPMediaChannel *self,
+ TpHandle handle,
+ const char *subject,
+ const char *remoteurl)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ GObject *obj = G_OBJECT (self);
+ TpIntSet *set;
+
+ DEBUG("enter");
+
+ priv->creator = handle;
+
+ g_message ("%s: adding handle %d (%s)",
+ G_STRFUNC,
+ handle,
+ tp_handle_inspect (
+ conn->handles[TP_HANDLE_TYPE_CONTACT], handle));
+
+ set = tp_intset_new ();
+ tp_intset_add (set, handle);
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)obj,
+ "", set, NULL, NULL, NULL, 0, 0);
+ tp_intset_destroy (set);
+
+ if (priv->session == NULL) {
+ priv_create_session(self, handle, remoteurl);
+
+ /* note: start the local stream-engine; once the local
+ * candidate are ready, reply with nua_respond()
+ *
+ * with the tp-0.13 API, the streams need to be created
+ * based on remote SDP (see sip_media_session_set_remote_info()) */
+ }
+
+ /* XXX: should be attached more data than just the handle?
+ * - yes, we need to be able to access all the <op,handle> pairs */
+
+ /* add self_handle to local pending */
+ set = tp_intset_new ();
+ tp_intset_add (set, mixin->self_handle);
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)obj,
+ "", NULL, NULL, set, NULL, 0, 0);
+ tp_intset_destroy (set);
+}
+
+int sip_media_channel_set_remote_info (SIPMediaChannel *chan, const char* r_sdp)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (chan);
+ int res = -1;
+
+ DEBUG("enter");
+
+ if (priv->session) {
+ res = sip_media_session_set_remote_info (priv->session, r_sdp);
+ }
+
+ DEBUG ("exit");
+
+ return res;
+}
+
+void sip_media_channel_stream_state (SIPMediaChannel *chan,
+ guint id,
+ guint state)
+{
+ tp_svc_channel_type_streamed_media_emit_stream_state_changed(
+ (TpSvcChannelTypeStreamedMedia *)chan, id, state);
+}
+
+void
+sip_media_channel_peer_error (SIPMediaChannel *self,
+ guint status,
+ const char* message)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpIntSet *set;
+ TpHandle peer;
+ guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_ERROR;
+
+ DEBUG("enter");
+
+ g_message ("%s: peer responded with error %u %s",
+ G_STRFUNC,
+ status,
+ message);
+
+ g_return_if_fail (priv->session != NULL);
+
+ switch (status)
+ {
+ case 404:
+ case 410:
+ case 604:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT;
+ break;
+ case 486:
+ case 600:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_BUSY;
+ break;
+ case 408:
+ case 480:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER;
+ break;
+ case 603:
+ /* No reason means roughly "rejected" */
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
+ break;
+ /*
+ case 403:
+ reason = TP_CHANNEL_GROUP_CHANGE_REASON_?;
+ break;
+ */
+ }
+
+ peer = sip_media_session_get_peer (priv->session);
+
+ set = tp_intset_new ();
+ tp_intset_add (set, peer);
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)self, message,
+ NULL, set, NULL, NULL, 0, reason);
+ tp_intset_destroy (set);
+}
+
+/***********************************************************************
+ * Set: Helper functions follow (not based on generated templates)
+ ***********************************************************************/
+
+static void priv_session_state_changed_cb (SIPMediaSession *session,
+ GParamSpec *arg1,
+ SIPMediaChannel *channel)
+{
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
+ JingleSessionState state;
+ TpHandle peer;
+ TpIntSet *set;
+
+ DEBUG("enter");
+
+ g_object_get (session,
+ "state", &state,
+ "peer", &peer,
+ NULL);
+
+ set = tp_intset_new ();
+
+ if (state == JS_STATE_ACTIVE) {
+ /* add the peer to the member list */
+ tp_intset_add (set, peer);
+
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)channel,
+ "", set, NULL, NULL, NULL, 0, 0);
+
+ /* update flags accordingly -- allow removal, deny adding and rescinding */
+ tp_group_mixin_change_flags ((TpSvcChannelInterfaceGroup *)channel,
+ TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
+ }
+ else if (state == JS_STATE_ENDED) {
+ /* remove us and the peer from the member list */
+ tp_intset_add (set, mixin->self_handle);
+ tp_intset_add (set, peer);
+ tp_group_mixin_change_members ((TpSvcChannelInterfaceGroup *)channel,
+ "", NULL, set, NULL, NULL, 0, 0);
+
+ /* update flags accordingly -- allow adding, deny removal */
+ tp_group_mixin_change_flags ((TpSvcChannelInterfaceGroup *)channel,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD, TP_CHANNEL_GROUP_FLAG_CAN_REMOVE);
+
+ priv_release_session(channel);
+ }
+
+ tp_intset_destroy (set);
+
+ DEBUG ("exit");
+}
+
+static void priv_session_stream_added_cb (SIPMediaSession *session,
+ SIPMediaStream *stream,
+ SIPMediaChannel *chan)
+{
+ guint id, handle, type;
+
+ DEBUG("enter");
+
+ /* emit StreamAdded */
+ handle = sip_media_session_get_peer (session);
+ g_object_get (stream, "id", &id, "media-type", &type, NULL);
+
+ tp_svc_channel_type_streamed_media_emit_stream_added (
+ (TpSvcChannelTypeStreamedMedia *)chan, id, handle, type);
+}
+
+static void priv_session_terminated_cb (SIPMediaSession *session,
+ gpointer user_data)
+{
+ SIPMediaChannel *channel = SIP_MEDIA_CHANNEL (user_data);
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ gchar *sid;
+
+ DEBUG("enter");
+ g_object_get (session, "session-id", &sid, NULL);
+ sip_media_factory_session_id_unregister(priv->factory, sid);
+ g_free (sid);
+}
+
+static void
+priv_release_session(SIPMediaChannel *channel)
+{
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+
+ DEBUG("enter");
+
+ /* close the channel */
+ sip_media_channel_close (channel);
+
+ /* remove the session */
+ if (priv->session)
+ {
+ SIPMediaSession *session = priv->session;
+
+ priv->session = NULL;
+ g_object_unref (session);
+ }
+}
+
+/**
+ * create_session
+ *
+ * Creates a SIPMediaSession object for given peer.
+ *
+ * If "remoteurl" is set to NULL, a unique session identifier is
+ * generated and the "initiator" property of the newly created
+ * SIPMediaSession is set to our own handle.
+ */
+static void
+priv_create_session (SIPMediaChannel *channel,
+ TpHandle peer,
+ const gchar *remote_url)
+{
+ SIPMediaChannelPrivate *priv;
+ TpBaseConnection *conn;
+ SIPMediaSession *session;
+ gchar *object_path;
+ const gchar *sid = NULL;
+ TpHandle initiator;
+
+ DEBUG("enter");
+
+ g_assert (SIP_IS_MEDIA_CHANNEL (channel));
+
+ priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (channel);
+ conn = (TpBaseConnection *)(priv->conn);
+ g_assert (priv->session == NULL);
+
+ object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);
+
+ if (remote_url == NULL) {
+ initiator = conn->self_handle;
+ /* allocate a hash-entry for the new jingle session */
+ sid = sip_media_factory_session_id_allocate (priv->factory);
+ }
+ else {
+ initiator = peer;
+ sid = remote_url;
+ }
+
+ g_debug("%s: allocating session, initiator=%u, peer=%u.", G_STRFUNC, initiator, peer);
+
+ session = g_object_new (SIP_TYPE_MEDIA_SESSION,
+ "media-channel", channel,
+ "object-path", object_path,
+ "session-id", sid,
+ "initiator", initiator,
+ "peer", peer,
+ NULL);
+
+ g_signal_connect (session, "notify::state",
+ (GCallback) priv_session_state_changed_cb, channel);
+ g_signal_connect (session, "stream-added",
+ (GCallback) priv_session_stream_added_cb, channel);
+ g_signal_connect (session, "terminated",
+ (GCallback) priv_session_terminated_cb, channel);
+
+ priv->session = session;
+
+ /* keep a list of media session ids */
+ sip_media_factory_session_id_register (priv->factory, sid, channel);
+
+ tp_svc_channel_interface_media_signalling_emit_new_session_handler (
+ (TpSvcChannelInterfaceMediaSignalling *)channel, object_path, "rtp");
+
+ g_free (object_path);
+
+ DEBUG ("exit");
+}
+
+gboolean
+sip_media_channel_add_member (TpSvcChannelInterfaceGroup *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ SIPMediaChannel *self = SIP_MEDIA_CHANNEL (iface);
+ SIPMediaChannelPrivate *priv = SIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (iface);
+
+ DEBUG("enter");
+
+ g_debug ("mixin->self_handle=%d, priv->creator=%d, handle=%d",
+ mixin->self_handle, priv->creator, handle);
+
+ /* case a: outgoing call (we are the initiator, a new handle added) */
+ if (mixin->self_handle == priv->creator &&
+ mixin->self_handle != handle) {
+
+ TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+ TpIntSet *lset, *rset;
+
+ g_debug("making outbound call - setting peer handle to %u.\n", handle);
+
+ priv_create_session(self, handle, NULL);
+
+ /* make remote pending */
+ rset = tp_intset_new ();
+ lset = tp_intset_new ();
+ tp_intset_add (lset, mixin->self_handle);
+ tp_intset_add (rset, handle);
+ tp_group_mixin_change_members (iface, "", lset, NULL, NULL, rset, 0, 0);
+ tp_intset_destroy (lset);
+ tp_intset_destroy (rset);
+
+ /* and update flags accordingly */
+ tp_group_mixin_change_flags (iface,
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
+ TP_CHANNEL_GROUP_FLAG_CAN_RESCIND,
+ 0);
+ }
+ /* case b: an incoming invite */
+ else {
+ if (priv->session &&
+ handle == mixin->self_handle &&
+ tp_handle_set_is_member (mixin->local_pending, handle)) {
+
+ TpIntSet *set;
+
+ g_debug ("%s - accepting an incoming invite", G_STRFUNC);
+
+ set = tp_intset_new ();
+ tp_intset_add (set, mixin->self_handle);
+ tp_group_mixin_change_members (iface, "", set, NULL, NULL, NULL, 0, 0);
+ tp_intset_destroy (set);
+
+ sip_media_session_accept (priv->session, TRUE);
+
+ }
+ }
+ return TRUE;
+}
+
+static gboolean priv_media_channel_remove_member (TpSvcChannelInterfaceGroup *obj, TpHandle handle, const gchar *message, GError **error)
+{
+ /* XXX: no implemented */
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+static void
+channel_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_implement_##x (\
+ klass, sip_media_channel_##x##suffix)
+ IMPLEMENT(close,_async);
+ IMPLEMENT(get_channel_type,);
+ IMPLEMENT(get_handle,);
+ IMPLEMENT(get_interfaces,);
+#undef IMPLEMENT
+}
+
+static void
+streamed_media_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelTypeStreamedMediaClass *klass = (TpSvcChannelTypeStreamedMediaClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_streamed_media_implement_##x (\
+ klass, sip_media_channel_##x)
+ IMPLEMENT(list_streams);
+ IMPLEMENT(remove_streams);
+ IMPLEMENT(request_stream_direction);
+ IMPLEMENT(request_streams);
+#undef IMPLEMENT
+}
+
+static void
+media_signalling_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelInterfaceMediaSignallingClass *klass = (TpSvcChannelInterfaceMediaSignallingClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_media_signalling_implement_##x (\
+ klass, sip_media_channel_##x)
+ IMPLEMENT(get_session_handlers);
+#undef IMPLEMENT
+}
diff --git a/src/sip-media-channel.h b/src/sip-media-channel.h
new file mode 100644
index 0000000..f2ec36a
--- /dev/null
+++ b/src/sip-media-channel.h
@@ -0,0 +1,85 @@
+/*
+ * sip-media-channel.h - Header for SIPMediaChannel
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_MEDIA_CHANNEL_H__
+#define __SIP_MEDIA_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/group-mixin.h>
+#include <telepathy-glib/properties-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SIPMediaChannel SIPMediaChannel;
+typedef struct _SIPMediaChannelClass SIPMediaChannelClass;
+
+struct _SIPMediaChannelClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpPropertiesMixinClass properties_class;
+};
+
+struct _SIPMediaChannel {
+ GObject parent;
+ TpGroupMixin group;
+ TpPropertiesMixin properties;
+};
+
+GType sip_media_channel_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_MEDIA_CHANNEL \
+ (sip_media_channel_get_type())
+#define SIP_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_MEDIA_CHANNEL, SIPMediaChannel))
+#define SIP_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_MEDIA_CHANNEL, SIPMediaChannelClass))
+#define SIP_IS_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_MEDIA_CHANNEL))
+#define SIP_IS_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_MEDIA_CHANNEL))
+#define SIP_MEDIA_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_MEDIA_CHANNEL, SIPMediaChannelClass))
+
+
+gboolean sip_media_channel_add_member (TpSvcChannelInterfaceGroup *,
+ TpHandle, const gchar *, GError **);
+
+void sip_media_channel_close (SIPMediaChannel *self);
+
+/***********************************************************************
+ * Additional declarations (not based on generated templates)
+ ***********************************************************************/
+
+void sip_media_channel_respond_to_invite (SIPMediaChannel *self,
+ TpHandle handle,
+ const char *subject,
+ const char *remoteurl);
+int sip_media_channel_set_remote_info (SIPMediaChannel *chan, const char* r_sdp);
+void sip_media_channel_stream_state (SIPMediaChannel *chan,
+ guint stream_id,
+ guint state);
+void sip_media_channel_peer_error (SIPMediaChannel *self,
+ guint status,
+ const char* message);
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_MEDIA_CHANNEL_H__*/
diff --git a/src/sip-media-session.c b/src/sip-media-session.c
new file mode 100644
index 0000000..ed33a67
--- /dev/null
+++ b/src/sip-media-session.c
@@ -0,0 +1,1049 @@
+/*
+ * sip-media-session.c - Source for SIPMediaSession
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005,2006 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-media-session).
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+
+#include <sofia-sip/sdp.h>
+#include <sofia-sip/sip_status.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/svc-media-interfaces.h>
+
+#include "sip-media-channel.h"
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "sip-media-session.h"
+#include "sip-media-stream.h"
+
+#define DEBUG_FLAG SIP_DEBUG_MEDIA
+#include "debug.h"
+
+static void session_handler_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(SIPMediaSession,
+ sip_media_session,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER,
+ session_handler_iface_init)
+ )
+
+#define DEFAULT_SESSION_TIMEOUT 50000
+
+/* signal enum */
+enum
+{
+ SIG_STREAM_ADDED,
+ SIG_TERMINATED,
+ SIG_LAST_SIGNAL
+};
+
+/* properties */
+enum
+{
+ PROP_MEDIA_CHANNEL = 1,
+ PROP_OBJECT_PATH,
+ PROP_SESSION_ID,
+ PROP_INITIATOR,
+ PROP_PEER,
+ PROP_STATE,
+ LAST_PROPERTY
+};
+
+static guint signals[SIG_LAST_SIGNAL] = {0};
+
+typedef struct {
+ gchar *name;
+ gchar *attributes;
+} SessionStateDescription;
+
+/**
+ * StreamEngine session states:
+ * - pending-created, objects created, local cand/codec query ongoing
+ * - pending-initiated, 'Ready' signal received
+ * - active, remote codecs delivered to StreamEngine (note,
+ * SteamEngine might still fail to verify connectivity and report
+ * an error)
+ * - ended, session has ended
+ */
+static const SessionStateDescription session_states[] =
+{
+ { "JS_STATE_PENDING_CREATED", ANSI_BOLD_ON ANSI_FG_BLACK ANSI_BG_WHITE },
+ { "JS_STATE_PENDING_INITIATED", ANSI_BOLD_ON ANSI_BG_MAGENTA },
+ { "JS_STATE_ACTIVE", ANSI_BOLD_ON ANSI_BG_BLUE },
+ { "JS_STATE_ENDED", ANSI_BG_RED }
+};
+
+/* private structure */
+typedef struct _SIPMediaSessionPrivate SIPMediaSessionPrivate;
+
+struct _SIPMediaSessionPrivate
+{
+ SIPConnection *conn;
+ SIPMediaChannel *channel; /** see gobj. prop. 'media-channel' */
+ gchar *object_path; /** see gobj. prop. 'object-path' */
+ gchar *id; /** see gobj. prop. 'session-id' */
+ TpHandle initiator; /** see gobj. prop. 'initator' */
+ TpHandle peer; /** see gobj. prop. 'peer' */
+ JingleSessionState state; /** see gobj. prop. 'state' */
+ guint timer_id;
+ gboolean accepted; /**< session has been locally accepted for use */
+ gboolean oa_pending; /**< offer/answer waiting to be sent */
+ gboolean se_ready; /**< connection established with stream-engine */
+ gboolean dispose_has_run;
+ GPtrArray *streams;
+};
+
+#define SIP_MEDIA_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_MEDIA_SESSION, SIPMediaSessionPrivate))
+
+static void sip_media_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void sip_media_session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static gboolean priv_timeout_session (gpointer data);
+static SIPMediaStream* priv_create_media_stream (SIPMediaSession *session, guint media_type);
+
+static nua_handle_t *priv_get_nua_handle_for_session (SIPMediaSession *session);
+static void priv_offer_answer_step (SIPMediaSession *session);
+
+static void sip_media_session_init (SIPMediaSession *obj)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (obj);
+
+ g_debug ("%s called", G_STRFUNC);
+
+ /* allocate any data required by the object here */
+
+ priv->streams = g_ptr_array_sized_new (0);
+}
+
+static GObject *
+sip_media_session_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SIPMediaSessionPrivate *priv;
+ DBusGConnection *bus;
+
+ obj = G_OBJECT_CLASS (sip_media_session_parent_class)->
+ constructor (type, n_props, props);
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (SIP_MEDIA_SESSION (obj));
+
+ g_object_get (priv->channel, "connection", &priv->conn, NULL);
+
+ priv->state = JS_STATE_PENDING_CREATED;
+
+ /* note: session is always created to either create a new outbound
+ * request for a media channel, or to respond to an incoming
+ * request ... thus oa_pending is TRUE at start */
+ priv->oa_pending = TRUE;
+
+ bus = tp_get_bus ();
+ dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+
+ return obj;
+}
+
+static void sip_media_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaSession *session = SIP_MEDIA_SESSION (object);
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ switch (property_id) {
+ case PROP_MEDIA_CHANNEL:
+ g_value_set_object (value, priv->channel);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_SESSION_ID:
+ g_value_set_string (value, priv->id);
+ break;
+ case PROP_INITIATOR:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_PEER:
+ g_value_set_uint (value, priv->peer);
+ break;
+ case PROP_STATE:
+ g_value_set_uint (value, priv->state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void priv_session_state_changed (SIPMediaSession *session,
+ JingleSessionState prev_state,
+ JingleSessionState new_state);
+
+static void sip_media_session_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaSession *session = SIP_MEDIA_SESSION (object);
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ JingleSessionState prev_state;
+
+ switch (property_id) {
+ case PROP_MEDIA_CHANNEL:
+ priv->channel = g_value_get_object (value);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_SESSION_ID:
+ g_free (priv->id);
+ priv->id = g_value_dup_string (value);
+ break;
+ case PROP_INITIATOR:
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_PEER:
+ priv->peer = g_value_get_uint (value);
+ break;
+ case PROP_STATE:
+ prev_state = priv->state;
+ priv->state = g_value_get_uint (value);
+
+ if (priv->state != prev_state)
+ priv_session_state_changed (session, prev_state, priv->state);
+
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void sip_media_session_dispose (GObject *object);
+static void sip_media_session_finalize (GObject *object);
+
+static void
+sip_media_session_class_init (SIPMediaSessionClass *sip_media_session_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_media_session_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (sip_media_session_class, sizeof (SIPMediaSessionPrivate));
+
+ object_class->constructor = sip_media_session_constructor;
+
+ object_class->get_property = sip_media_session_get_property;
+ object_class->set_property = sip_media_session_set_property;
+
+ object_class->dispose = sip_media_session_dispose;
+ object_class->finalize = sip_media_session_finalize;
+
+ param_spec = g_param_spec_object ("media-channel", "SIPMediaChannel object",
+ "SIP media channel object that owns this "
+ "media session object.",
+ SIP_TYPE_MEDIA_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_MEDIA_CHANNEL, param_spec);
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_string ("session-id", "Session ID",
+ "A unique session identifier used "
+ "throughout all communication.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_SESSION_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator", "Session initiator",
+ "The TpHandle representing the contact "
+ "who initiated the session.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_INITIATOR, param_spec);
+
+ param_spec = g_param_spec_uint ("peer", "Session peer",
+ "The TpHandle representing the contact "
+ "with whom this session communicates.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_PEER, param_spec);
+
+ param_spec = g_param_spec_uint ("state", "Session state",
+ "The current state that the session is in.",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_STATE, param_spec);
+
+ signals[SIG_STREAM_ADDED] =
+ g_signal_new ("stream-added",
+ G_OBJECT_CLASS_TYPE (sip_media_session_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ signals[SIG_TERMINATED] =
+ g_signal_new ("terminated",
+ G_OBJECT_CLASS_TYPE (sip_media_session_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+sip_media_session_dispose (GObject *object)
+{
+ SIPMediaSession *self = SIP_MEDIA_SESSION (object);
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_signal_emit (self, signals[SIG_TERMINATED], 0);
+
+ if (priv->conn)
+ {
+ g_object_unref(priv->conn);
+ priv->conn = NULL;
+ }
+
+ if (priv->timer_id) {
+ g_source_remove (priv->timer_id);
+ priv->timer_id = 0;
+ }
+
+ if (G_OBJECT_CLASS (sip_media_session_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_media_session_parent_class)->dispose (object);
+
+ DEBUG ("exit");
+}
+
+void
+sip_media_session_finalize (GObject *object)
+{
+ SIPMediaSession *self = SIP_MEDIA_SESSION (object);
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (self);
+ guint i;
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (sip_media_session_parent_class)->finalize (object);
+
+ for (i = 0; i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index (priv->streams, i);
+ if (stream)
+ g_object_unref (stream);
+ }
+
+ g_ptr_array_free(priv->streams, FALSE);
+
+ DEBUG ("exit");
+}
+
+
+
+/**
+ * sip_media_session_error
+ *
+ * Implements DBus method Error
+ * on interface org.freedesktop.Telepathy.Media.SessionHandler
+ */
+static void
+sip_media_session_error (TpSvcMediaSessionHandler *iface,
+ guint errno,
+ const gchar *message,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaSession *obj = SIP_MEDIA_SESSION (iface);
+
+ GMS_DEBUG_INFO (obj, "Media.SessionHandler::Error called (%s) terminating session", message);
+
+ sip_media_session_terminate (obj);
+
+ tp_svc_media_session_handler_return_from_error (context);
+}
+
+static void priv_emit_new_stream (SIPMediaSession *self,
+ SIPMediaStream *stream)
+{
+ gchar *object_path;
+ guint id, media_type;
+
+ g_object_get (stream,
+ "object-path", &object_path,
+ "id", &id,
+ "media-type", &media_type,
+ NULL);
+
+ /* note: all of the streams are bidirectional from farsight's point of view, it's
+ * just in the signalling they change */
+
+ tp_svc_media_session_handler_emit_new_stream_handler (
+ (TpSvcMediaSessionHandler *)self, object_path, id, media_type,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+ g_free (object_path);
+}
+
+
+/**
+ * sip_media_session_ready
+ *
+ * Implements DBus method Ready
+ * on interface org.freedesktop.Telepathy.Media.SessionHandler
+ */
+static void
+sip_media_session_ready (TpSvcMediaSessionHandler *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaSession *obj = SIP_MEDIA_SESSION (iface);
+ SIPMediaSessionPrivate *priv;
+ guint i;
+
+ DEBUG ("enter");
+
+ g_assert (SIP_IS_MEDIA_SESSION (obj));
+
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (obj);
+
+ priv->se_ready = TRUE;
+
+#ifdef TP_API_0_12
+ {
+ gchar *object_path;
+ guint id;
+
+ g_object_get (priv->stream,
+ "object-path", &object_path,
+ "id", &id,
+ NULL);
+
+ tp_svc_media_session_handler_emit_new_media_stream_handler (
+ (TpSvcMediaSessionHandler *)self, object_path, TP_MEDIA_STREAM_TYPE_AUDIO,
+ TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+ g_free (object_path);
+ }
+#else
+ /* note: streams are generated in priv_create_media_stream() */
+
+ for (i = 0; i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index (priv->streams, i);
+ if (stream)
+ priv_emit_new_stream (obj, stream);
+ }
+
+#endif
+
+ DEBUG ("exit");
+
+ tp_svc_media_session_handler_return_from_ready (context);
+}
+
+/***********************************************************************
+ * Helper functions follow (not based on generated templates)
+ ***********************************************************************/
+
+TpHandle
+sip_media_session_get_peer (SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ return priv->peer;
+}
+
+static void priv_session_state_changed (SIPMediaSession *session,
+ JingleSessionState prev_state,
+ JingleSessionState new_state)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ GMS_DEBUG_EVENT (session, "state changed from %s to %s",
+ session_states[prev_state].name,
+ session_states[new_state].name);
+
+ if (new_state == JS_STATE_PENDING_INITIATED)
+ {
+ priv->timer_id =
+ g_timeout_add (DEFAULT_SESSION_TIMEOUT, priv_timeout_session, session);
+ }
+ else if (new_state == JS_STATE_ACTIVE)
+ {
+ if (priv->timer_id) {
+ g_source_remove (priv->timer_id);
+ priv->timer_id = 0;
+ }
+ }
+}
+
+#if _GMS_DEBUG_LEVEL
+void
+sip_media_session_debug (SIPMediaSession *session,
+ DebugMessageType type,
+ const gchar *format, ...)
+{
+ va_list list;
+ gchar buf[512];
+ SIPMediaSessionPrivate *priv;
+ time_t curtime;
+ struct tm *loctime;
+ gchar stamp[10];
+ const gchar *type_str;
+
+ g_assert (SIP_IS_MEDIA_SESSION (session));
+
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ curtime = time (NULL);
+ loctime = localtime (&curtime);
+
+ strftime (stamp, sizeof (stamp), "%T", loctime);
+
+ va_start (list, format);
+
+ vsnprintf (buf, sizeof (buf), format, list);
+
+ va_end (list);
+
+ switch (type) {
+ case DEBUG_MSG_INFO:
+ type_str = ANSI_BOLD_ON ANSI_FG_WHITE;
+ break;
+ case DEBUG_MSG_DUMP:
+ type_str = ANSI_BOLD_ON ANSI_FG_GREEN;
+ break;
+ case DEBUG_MSG_WARNING:
+ type_str = ANSI_BOLD_ON ANSI_FG_YELLOW;
+ break;
+ case DEBUG_MSG_ERROR:
+ type_str = ANSI_BOLD_ON ANSI_FG_WHITE ANSI_BG_RED;
+ break;
+ case DEBUG_MSG_EVENT:
+ type_str = ANSI_BOLD_ON ANSI_FG_CYAN;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ printf ("[%s%s%s] %s%-26s%s %s%s%s\n",
+ ANSI_BOLD_ON ANSI_FG_WHITE,
+ stamp,
+ ANSI_RESET,
+ session_states[priv->state].attributes,
+ session_states[priv->state].name,
+ ANSI_RESET,
+ type_str,
+ buf,
+ ANSI_RESET);
+
+ fflush (stdout);
+}
+#endif /* _GMS_DEBUG_LEVEL */
+
+static gboolean priv_timeout_session (gpointer data)
+{
+ SIPMediaSession *session = data;
+
+ g_debug ("%s: session timed out", G_STRFUNC);
+ if (session)
+ sip_media_session_terminate (session);
+
+ return FALSE;
+}
+
+void sip_media_session_terminate (SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ nua_t *nua = sip_conn_sofia_nua(priv->conn);
+
+ DEBUG ("enter");
+
+ if (priv->state == JS_STATE_ENDED)
+ return;
+
+ if (priv->state == JS_STATE_PENDING_INITIATED ||
+ priv->state == JS_STATE_ACTIVE) {
+ g_message("%s: sending SIP BYE (%p).", G_STRFUNC, nua);
+ nua_handle_t *nh = priv_get_nua_handle_for_session(session);
+ if (nh)
+ nua_bye (nh, TAG_END());
+ else
+ g_warning ("Unable to send BYE, channel handle not available.");
+ }
+
+ g_object_set (session, "state", JS_STATE_ENDED, NULL);
+}
+
+/**
+ * Converts a sofia-sip media type enum to Telepathy media type.
+ * See <sofia-sip/sdp.h> and <telepathy-constants.h>.
+ *
+ * @return G_MAXUINT if the media type cannot be mapped
+ */
+static guint priv_tp_media_type (sdp_media_e sip_mtype)
+{
+ switch (sip_mtype)
+ {
+ case sdp_media_audio: return TP_MEDIA_STREAM_TYPE_AUDIO;
+ case sdp_media_video: return TP_MEDIA_STREAM_TYPE_VIDEO;
+ default: return G_MAXUINT;
+ }
+
+ g_assert_not_reached();
+}
+
+int sip_media_session_set_remote_info (SIPMediaSession *session, const char* r_sdp)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ su_home_t temphome[1] = { SU_HOME_INIT(temphome) };
+ sdp_parser_t *parser;
+ const char *pa_error;
+ int res = 0;
+
+ DEBUG ("enter");
+
+ parser = sdp_parse(temphome, r_sdp, strlen(r_sdp), sdp_f_insane);
+ pa_error = sdp_parsing_error(parser);
+ if (pa_error) {
+ g_warning("%s: error parsing SDP: %s\n", __func__, pa_error);
+ res = -1;
+ }
+ else {
+ sdp_session_t *parsed_sdp = sdp_session(parser);
+ sdp_media_t *media = parsed_sdp->sdp_media;
+ guint i, supported_media_cnt = 0;
+
+ g_debug("Succesfully parsed remote SDP.");
+
+ /* note: for each session, we maintain an ordered list of
+ * streams (SDP m-lines) which are matched 1:1 to
+ * the streams of the remote SDP */
+
+ for (i = 0; media; i++) {
+ SIPMediaStream *stream = NULL;
+
+ if (i >= priv->streams->len)
+ stream = priv_create_media_stream (session, priv_tp_media_type (media->m_type));
+ else
+ stream = g_ptr_array_index(priv->streams, i);
+
+ if (media->m_type == sdp_media_audio ||
+ media->m_type == sdp_media_video)
+ ++supported_media_cnt;
+
+ g_debug ("Setting remote SDP for stream (%u:%p).", i, stream);
+
+ /* note: it is ok for the stream to be NULL (unsupported media type) */
+ if (stream)
+ res = sip_media_stream_set_remote_info (stream, media, parsed_sdp);
+
+ media = media->m_next;
+ }
+
+ if (supported_media_cnt == 0) {
+ g_warning ("No supported media in the session, aborting.");
+ res = -1;
+ }
+
+ g_assert(media == NULL);
+ g_assert(i == priv->streams->len);
+
+ /* XXX: hmm, this is not the correct place really */
+ g_object_set (session, "state", JS_STATE_ACTIVE, NULL);
+ }
+
+ sdp_parser_free(parser);
+ su_home_deinit(temphome);
+
+ DEBUG ("exit");
+
+ return res;
+}
+
+void sip_media_session_stream_state (SIPMediaSession *sess,
+ guint stream_id,
+ guint state)
+{
+ SIPMediaSessionPrivate *priv;
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (sess);
+ g_assert (priv);
+ sip_media_channel_stream_state (priv->channel, stream_id, state);
+}
+
+gboolean sip_media_session_request_streams (SIPMediaSession *session,
+ const GArray *media_types,
+ GPtrArray **ret,
+ GError **error)
+{
+ guint i;
+
+ DEBUG ("enter");
+
+ *ret = g_ptr_array_sized_new (media_types->len);
+
+ for (i = 0; i < media_types->len; i++) {
+ guint media_type = g_array_index (media_types, guint, i);
+ SIPMediaStream *stream;
+
+ g_debug("%s: len of %d, i = %d\n", G_STRFUNC, media_types->len, i);
+
+ stream = priv_create_media_stream (session, media_type);
+
+ g_ptr_array_add (*ret, stream);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Returns a list of pointers to SIPMediaStream objects
+ * associated with this session.
+ */
+/* FIXME: error handling is a bit weird, and might be assuming ret is
+ * initially NULL */
+gboolean sip_media_session_list_streams (SIPMediaSession *session,
+ GPtrArray **ret)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ guint i;
+
+ if (priv->streams && priv->streams->len > 0) {
+ *ret = g_ptr_array_sized_new (priv->streams->len);
+
+ for (i = 0; *ret && i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index(priv->streams, i);
+
+ if (stream)
+ g_ptr_array_add (*ret, stream);
+ }
+ }
+
+ return *ret != NULL;
+}
+
+void sip_media_session_accept (SIPMediaSession *self, gboolean accept)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (self);
+ gboolean p = priv->accepted;
+
+ g_debug ("%s: accepting session: %d", G_STRFUNC, accept);
+
+ priv->accepted = accept;
+
+ if (accept != p)
+ priv_offer_answer_step (self);
+}
+
+/***********************************************************************
+ * Helper functions follow (not based on generated templates)
+ ***********************************************************************/
+
+static nua_handle_t *priv_get_nua_handle_for_session (SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ gpointer *tmp = NULL;
+
+ if (priv->channel)
+ g_object_get (priv->channel, "nua-handle", &tmp, NULL);
+
+ return (nua_handle_t*)tmp;
+}
+
+static void priv_stream_new_active_candidate_pair_cb (SIPMediaStream *stream,
+ const gchar *native_candidate_id,
+ const gchar *remote_candidate_id,
+ SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_SESSION (session));
+
+ DEBUG ("enter");
+
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ /* g_assert (priv->state < JS_STATE_ACTIVE); */
+
+ GMS_DEBUG_INFO (session, "stream-engine reported a new active candidate pair [\"%s\" - \"%s\"]",
+ native_candidate_id, remote_candidate_id);
+
+ /* XXX: active candidate pair, requires signaling action,
+ * but currently done in priv_stream_ready_cb() */
+}
+
+static void priv_stream_new_native_candidate_cb (SIPMediaStream *stream,
+ const gchar *candidate_id,
+ const GPtrArray *transports,
+ SIPMediaSession *session)
+{
+}
+
+static void priv_session_media_state (SIPMediaSession *session, gboolean playing)
+{
+ guint i;
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ for (i = 0; i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index(priv->streams, i);
+ if (stream)
+ sip_media_stream_set_playing (stream, playing);
+ }
+}
+
+/**
+ * Sends an outbound offer/answer if all streams of the session
+ * are prepared.
+ *
+ * Following inputs are considered in decision making:
+ * - status of local streams (set up with stream-engine)
+ * - whether session is locally accepted
+ * - whether we are the initiator or not
+ * - whether an offer/answer step is pending (either initial,
+ * or a requested update to the session state)
+ */
+static void priv_offer_answer_step (SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+ TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
+ guint i;
+ gint non_ready_streams = 0;
+
+ DEBUG ("enter");
+
+ /* step: check status of streams */
+ for (i = 0; i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index(priv->streams, i);
+ if (stream &&
+ sip_media_stream_is_ready (stream) != TRUE)
+ ++non_ready_streams;
+ }
+
+ /* step: if all stream are ready, send an offer/answer */
+ if (non_ready_streams == 0 &&
+ priv->oa_pending) {
+ nua_t *sofia_nua = sip_conn_sofia_nua (priv->conn);
+ GString *user_sdp = g_string_sized_new (0);
+
+ for (i = 0; i < priv->streams->len; i++) {
+ SIPMediaStream *stream = g_ptr_array_index(priv->streams, i);
+ if (stream)
+ user_sdp = g_string_append (user_sdp, sip_media_stream_local_sdp(stream));
+ else
+ user_sdp = g_string_append (user_sdp, "m=unknown 0 -/-");
+ }
+
+ /* send an offer if the session was initiated by us */
+ if (priv->initiator != priv->peer) {
+ nua_handle_t *nh;
+ su_home_t *sofia_home = sip_conn_sofia_home (priv->conn);
+ const char *dest_uri;
+
+ dest_uri = tp_handle_inspect (
+ conn->handles[TP_HANDLE_TYPE_CONTACT], priv->peer);
+
+ g_debug ("mapped handle %u to uri %s.", priv->peer, dest_uri);
+
+ if (dest_uri) {
+ nh = sip_conn_create_request_handle (sofia_nua, sofia_home, dest_uri,
+ priv->peer);
+
+ /* note: we need to be prepared to receive media right after the
+ * offer is sent, so we must set state to playing */
+ priv_session_media_state (session, 1);
+
+ nua_invite (nh,
+ SOATAG_USER_SDP_STR(user_sdp->str),
+ SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
+ SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
+ TAG_END());
+
+ priv->oa_pending = FALSE;
+
+ g_object_set (priv->channel, "nua-handle", (gpointer)nh, NULL);
+ }
+ else
+ g_warning ("Unable to send offer due to invalid destination SIP URI.");
+ }
+ else {
+ /* note: only send a reply if session is locally accepted */
+ if (priv->accepted == TRUE) {
+ nua_handle_t *handle = priv_get_nua_handle_for_session(session);
+ g_debug("Answering with SDP: <<<%s>>>.", user_sdp->str);
+ if (handle) {
+ nua_respond (handle, 200, sip_200_OK,
+ SOATAG_USER_SDP_STR (user_sdp->str),
+ SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
+ SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
+ TAG_END());
+
+ priv->oa_pending = FALSE;
+
+ /* note: we have accepted the call, set state to playing */
+ priv_session_media_state (session, 1);
+ }
+ else
+ g_warning ("Unable to answer to the incoming INVITE, channel handle not available.");
+ }
+ }
+ }
+}
+
+static void priv_stream_ready_cb (SIPMediaStream *stream,
+ const GPtrArray *codecs,
+ SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_SESSION (session));
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ DEBUG ("enter");
+
+ if (priv->state < JS_STATE_PENDING_INITIATED)
+ g_object_set (session, "state", JS_STATE_PENDING_INITIATED, NULL);
+
+ priv_offer_answer_step (session);
+}
+
+static void priv_stream_supported_codecs_cb (SIPMediaStream *stream,
+ const GPtrArray *codecs,
+ SIPMediaSession *session)
+{
+ SIPMediaSessionPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_SESSION (session));
+
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ if (priv->initiator != priv->peer)
+ {
+ GMS_DEBUG_INFO (session, "%s: session not initiated by peer so we're "
+ "not preparing an accept message",
+ G_STRFUNC);
+ return;
+ }
+}
+
+static SIPMediaStream* priv_create_media_stream (SIPMediaSession *self, guint media_type)
+{
+ SIPMediaSessionPrivate *priv;
+ gchar *object_path;
+ SIPMediaStream *stream = NULL;
+
+ g_assert (SIP_IS_MEDIA_SESSION (self));
+
+ DEBUG ("enter");
+
+ priv = SIP_MEDIA_SESSION_GET_PRIVATE (self);
+
+ if (media_type == TP_MEDIA_STREAM_TYPE_AUDIO ||
+ media_type == TP_MEDIA_STREAM_TYPE_VIDEO) {
+
+ object_path = g_strdup_printf ("%s/MediaStream%d", priv->object_path, priv->streams->len);
+
+ stream = g_object_new (SIP_TYPE_MEDIA_STREAM,
+ "media-session", self,
+ "media-type", media_type,
+ "object-path", object_path,
+ "id", priv->streams->len,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (stream, "new-active-candidate-pair",
+ (GCallback) priv_stream_new_active_candidate_pair_cb,
+ self);
+ g_signal_connect (stream, "new-native-candidate",
+ (GCallback) priv_stream_new_native_candidate_cb,
+ self);
+ g_signal_connect (stream, "ready",
+ (GCallback) priv_stream_ready_cb,
+ self);
+ g_signal_connect (stream, "supported-codecs",
+ (GCallback) priv_stream_supported_codecs_cb,
+ self);
+
+ if (priv->se_ready == TRUE) {
+ priv_emit_new_stream (self, stream);
+ }
+
+ g_signal_emit (self, signals[SIG_STREAM_ADDED], 0, stream);
+ }
+
+ /* note: we add an entry even for unsupported media types */
+ g_ptr_array_add (priv->streams, stream);
+
+ DEBUG ("exit");
+
+ return stream;
+}
+
+static void
+session_handler_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcMediaSessionHandlerClass *klass = (TpSvcMediaSessionHandlerClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\
+ klass, (tp_svc_media_session_handler_##x##_impl) sip_media_session_##x)
+ IMPLEMENT(error);
+ IMPLEMENT(ready);
+#undef IMPLEMENT
+}
diff --git a/src/sip-media-session.h b/src/sip-media-session.h
new file mode 100644
index 0000000..0bcdadf
--- /dev/null
+++ b/src/sip-media-session.h
@@ -0,0 +1,141 @@
+/*
+ * sip-media-session.h - Header for SIPMediaSession
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_MEDIA_SESSION_H__
+#define __SIP_MEDIA_SESSION_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ JS_STATE_PENDING_CREATED = 0,
+ JS_STATE_PENDING_INITIATED,
+ JS_STATE_ACTIVE,
+ JS_STATE_ENDED
+} JingleSessionState;
+
+typedef struct _SIPMediaSession SIPMediaSession;
+typedef struct _SIPMediaSessionClass SIPMediaSessionClass;
+
+struct _SIPMediaSessionClass {
+ GObjectClass parent_class;
+};
+
+struct _SIPMediaSession {
+ GObject parent;
+};
+
+GType sip_media_session_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_MEDIA_SESSION \
+ (sip_media_session_get_type())
+#define SIP_MEDIA_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_MEDIA_SESSION, SIPMediaSession))
+#define SIP_MEDIA_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_MEDIA_SESSION, SIPMediaSessionClass))
+#define SIP_IS_MEDIA_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_MEDIA_SESSION))
+#define SIP_IS_MEDIA_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_MEDIA_SESSION))
+#define SIP_MEDIA_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_MEDIA_SESSION, SIPMediaSessionClass))
+
+/***********************************************************************
+ * Additional declarations (not based on generated templates)
+ ***********************************************************************/
+
+TpHandle sip_media_session_get_peer (SIPMediaSession *session);
+void sip_media_session_terminate (SIPMediaSession *session);
+int sip_media_session_set_remote_info (SIPMediaSession *chan, const char* r_sdp);
+void sip_media_session_stream_state (SIPMediaSession *sess,
+ guint stream_id,
+ guint state);
+gboolean sip_media_session_request_streams (SIPMediaSession *session,
+ const GArray *media_types,
+ GPtrArray **ret,
+ GError **error);
+gboolean sip_media_session_list_streams (SIPMediaSession *session,
+ GPtrArray **ret);
+void sip_media_session_accept (SIPMediaSession *self, gboolean accept);
+
+typedef enum {
+ DEBUG_MSG_INFO = 0,
+ DEBUG_MSG_DUMP,
+ DEBUG_MSG_WARNING,
+ DEBUG_MSG_ERROR,
+ DEBUG_MSG_EVENT
+} DebugMessageType;
+
+#ifndef _GMS_DEBUG_LEVEL
+#define _GMS_DEBUG_LEVEL 2
+#endif
+
+#if _GMS_DEBUG_LEVEL
+
+#define ANSI_RESET "\x1b[0m"
+#define ANSI_BOLD_ON "\x1b[1m"
+#define ANSI_BOLD_OFF "\x1b[22m"
+#define ANSI_INVERSE_ON "\x1b[7m"
+
+#define ANSI_BG_RED "\x1b[41m"
+#define ANSI_BG_GREEN "\x1b[42m"
+#define ANSI_BG_YELLOW "\x1b[43m"
+#define ANSI_BG_BLUE "\x1b[44m"
+#define ANSI_BG_MAGENTA "\x1b[45m"
+#define ANSI_BG_CYAN "\x1b[46m"
+#define ANSI_BG_WHITE "\x1b[47m"
+
+#define ANSI_FG_BLACK "\x1b[30m"
+#define ANSI_FG_RED "\x1b[31m"
+#define ANSI_FG_GREEN "\x1b[32m"
+#define ANSI_FG_YELLOW "\x1b[33m"
+#define ANSI_FG_BLUE "\x1b[34m"
+#define ANSI_FG_MAGENTA "\x1b[35m"
+#define ANSI_FG_CYAN "\x1b[36m"
+#define ANSI_FG_WHITE "\x1b[37m"
+
+#define GMS_DEBUG_INFO(s, ...) sip_media_session_debug (s, DEBUG_MSG_INFO, __VA_ARGS__)
+#if _GMS_DEBUG_LEVEL > 1
+#define GMS_DEBUG_DUMP(s, ...) sip_media_session_debug (s, DEBUG_MSG_DUMP, __VA_ARGS__)
+#else
+#define GMS_DEBUG_DUMP(s, ...)
+#endif
+#define GMS_DEBUG_WARNING(s, ...) sip_media_session_debug (s, DEBUG_MSG_WARNING, __VA_ARGS__)
+#define GMS_DEBUG_ERROR(s, ...) sip_media_session_debug (s, DEBUG_MSG_ERROR, __VA_ARGS__)
+#define GMS_DEBUG_EVENT(s, ...) sip_media_session_debug (s, DEBUG_MSG_EVENT, __VA_ARGS__)
+
+void sip_media_session_debug (SIPMediaSession *session,
+ DebugMessageType type,
+ const gchar *format, ...);
+
+#else
+
+#define GMS_DEBUG_INFO(s, ...)
+#define GMS_DEBUG_DUMP(s, ...)
+#define GMS_DEBUG_WARNING(s, ...)
+#define GMS_DEBUG_ERROR(s, ...)
+#define GMS_DEBUG_EVENT(s, ...)
+
+#endif
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_MEDIA_SESSION_H__*/
diff --git a/src/sip-media-stream.c b/src/sip-media-stream.c
new file mode 100644
index 0000000..8a9c40f
--- /dev/null
+++ b/src/sip-media-stream.c
@@ -0,0 +1,1266 @@
+/*
+ * sip-media-stream.c - Source for SIPMediaStream
+ * Copyright (C) 2006 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-media-stream).
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/svc-media-interfaces.h>
+
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "sip-media-channel.h"
+#include "sip-media-session.h"
+
+#include "sip-media-stream.h"
+#include "signals-marshal.h"
+
+#define DEBUG_FLAG SIP_DEBUG_MEDIA
+#include "debug.h"
+
+static void stream_handler_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(SIPMediaStream,
+ sip_media_stream,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_STREAM_HANDLER,
+ stream_handler_iface_init)
+ )
+
+/* signal enum */
+enum
+{
+ SIG_NEW_ACTIVE_CANDIDATE_PAIR,
+ SIG_NEW_NATIVE_CANDIDATE,
+ SIG_READY,
+ SIG_SUPPORTED_CODECS,
+
+ SIG_LAST_SIGNAL
+};
+
+static guint signals[SIG_LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_MEDIA_SESSION = 1,
+ PROP_OBJECT_PATH,
+ PROP_ID,
+ PROP_MEDIA_TYPE,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SIPMediaStreamPrivate SIPMediaStreamPrivate;
+
+struct _SIPMediaStreamPrivate
+{
+ SIPConnection *conn;
+ SIPMediaSession *session; /** see gobj. prop. 'media-session' */
+ gchar *object_path; /** see gobj. prop. 'object-path' */
+ guint id; /** see gobj. prop. 'id' */
+ guint media_type; /** see gobj. prop. 'media-type' */
+
+ gchar *stream_sdp; /** SDP description of the stream */
+
+ gboolean sdp_generated;
+ gboolean ready_received; /** our ready method has been called */
+ gboolean native_cands_prepared; /** all candidates discovered */
+ gboolean native_codecs_prepared; /** all codecs discovered */
+ gboolean playing; /** stream set to playing */
+
+ GValue native_codecs; /** intersected codec list */
+ GValue native_candidates;
+
+ GValue remote_codecs;
+ GValue remote_candidates;
+ gboolean push_remote_requested;
+ gboolean remote_cands_sent;
+
+ gboolean dispose_has_run;
+};
+
+#define SIP_MEDIA_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_MEDIA_STREAM, SIPMediaStreamPrivate))
+
+static void priv_session_stream_state_changed_cb (SIPMediaSession *session,
+ GParamSpec *arg1,
+ SIPMediaStream *stream);
+static gboolean priv_set_remote_codecs(SIPMediaStream *stream, sdp_media_t *sdpmedia);
+static void push_remote_codecs (SIPMediaStream *stream);
+static void push_remote_candidates (SIPMediaStream *stream);
+static int priv_update_local_sdp(SIPMediaStream *stream);
+static void priv_generate_sdp (SIPMediaStream *stream);
+
+#if _GMS_DEBUG_LEVEL > 1
+static const char *gms_tp_protocols[] = {
+ "TP_MEDIA_STREAM_PROTO_UDP (0)",
+ "TP_MEDIA_STREAM_PROTO_TCP (1)"
+};
+
+static const char *gms_tp_transports[] = {
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL (0)",
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED (1)",
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY (2)"
+};
+#endif
+
+/***********************************************************************
+ * Set: Gobject interface
+ ***********************************************************************/
+
+static void
+sip_media_stream_init (SIPMediaStream *obj)
+{
+ SIPMediaStreamPrivate *priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ /* allocate any data required by the object here */
+
+ priv = NULL;
+}
+
+static GObject *
+sip_media_stream_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SIPMediaStreamPrivate *priv;
+ SIPMediaChannel *chan;
+ DBusGConnection *bus;
+
+ /* call base class constructor */
+ obj = G_OBJECT_CLASS (sip_media_stream_parent_class)->
+ constructor (type, n_props, props);
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (SIP_MEDIA_STREAM (obj));
+
+ g_signal_connect (priv->session, "notify::state",
+ (GCallback) priv_session_stream_state_changed_cb, obj);
+
+ /* get the connection handle once */
+ g_object_get (priv->session, "media-channel", &chan, NULL);
+ g_object_get (chan, "connection", &priv->conn, NULL);
+ g_object_unref (chan);
+
+ priv->playing = FALSE;
+
+ g_value_init (&priv->native_codecs, TP_TYPE_CODEC_LIST);
+ g_value_take_boxed (&priv->native_codecs,
+ dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));
+
+ g_value_init (&priv->native_candidates, TP_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&priv->native_candidates,
+ dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
+
+ g_value_init (&priv->remote_codecs, TP_TYPE_CODEC_LIST);
+ g_value_take_boxed (&priv->remote_codecs,
+ dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));
+
+ g_value_init (&priv->remote_candidates, TP_TYPE_CANDIDATE_LIST);
+ g_value_take_boxed (&priv->remote_candidates,
+ dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
+
+ priv->native_cands_prepared = FALSE;
+ priv->native_codecs_prepared = FALSE;
+
+ priv->push_remote_requested = FALSE;
+
+ /* go for the bus */
+ bus = tp_get_bus ();
+ dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+
+ return obj;
+}
+
+static void
+sip_media_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaStream *stream = SIP_MEDIA_STREAM (object);
+ SIPMediaStreamPrivate *priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ switch (property_id) {
+ case PROP_MEDIA_SESSION:
+ g_value_set_object (value, priv->session);
+ break;
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_ID:
+ g_value_set_uint (value, priv->id);
+ break;
+ case PROP_MEDIA_TYPE:
+ g_value_set_uint (value, priv->media_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_media_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPMediaStream *stream = SIP_MEDIA_STREAM (object);
+ SIPMediaStreamPrivate *priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ switch (property_id) {
+ case PROP_MEDIA_SESSION:
+ priv->session = g_value_get_object (value);
+ break;
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_ID:
+ priv->id = g_value_get_uint (value);
+ break;
+ case PROP_MEDIA_TYPE:
+ priv->media_type = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void sip_media_stream_dispose (GObject *object);
+static void sip_media_stream_finalize (GObject *object);
+
+static void
+sip_media_stream_class_init (SIPMediaStreamClass *sip_media_stream_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_media_stream_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (sip_media_stream_class, sizeof (SIPMediaStreamPrivate));
+
+ object_class->constructor = sip_media_stream_constructor;
+
+ object_class->get_property = sip_media_stream_get_property;
+ object_class->set_property = sip_media_stream_set_property;
+
+ object_class->dispose = sip_media_stream_dispose;
+ object_class->finalize = sip_media_stream_finalize;
+
+ param_spec = g_param_spec_object ("media-session", "GabbleMediaSession object",
+ "SIP media session object that owns this "
+ "media stream object.",
+ SIP_TYPE_MEDIA_SESSION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_MEDIA_SESSION, param_spec);
+
+ param_spec = g_param_spec_string ("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_uint ("id", "Stream ID",
+ "A stream number for the stream used in the "
+ "D-Bus API.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_ID, param_spec);
+
+ param_spec = g_param_spec_uint ("media-type", "Stream media type",
+ "A constant indicating which media type the "
+ "stream carries.",
+ TP_MEDIA_STREAM_TYPE_AUDIO,
+ TP_MEDIA_STREAM_TYPE_VIDEO,
+ TP_MEDIA_STREAM_TYPE_AUDIO,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);
+
+ /* signals not exported by DBus interface */
+ signals[SIG_NEW_ACTIVE_CANDIDATE_PAIR] =
+ g_signal_new ("new-active-candidate-pair",
+ G_OBJECT_CLASS_TYPE (sip_media_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tpsip_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+
+ signals[SIG_NEW_NATIVE_CANDIDATE] =
+ g_signal_new ("new-native-candidate",
+ G_OBJECT_CLASS_TYPE (sip_media_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ _tpsip_marshal_VOID__STRING_BOXED,
+ G_TYPE_NONE, 2, G_TYPE_STRING, TP_TYPE_TRANSPORT_LIST);
+
+ signals[SIG_READY] =
+ g_signal_new ("ready",
+ G_OBJECT_CLASS_TYPE (sip_media_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST);
+
+ signals[SIG_SUPPORTED_CODECS] =
+ g_signal_new ("supported-codecs",
+ G_OBJECT_CLASS_TYPE (sip_media_stream_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, TP_TYPE_CODEC_LIST);
+}
+
+void
+sip_media_stream_dispose (GObject *object)
+{
+ SIPMediaStream *self = SIP_MEDIA_STREAM (object);
+ SIPMediaStreamPrivate *priv = SIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release ref taken in constructor */
+ g_object_unref (priv->conn);
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (sip_media_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_media_stream_parent_class)->dispose (object);
+
+ DEBUG ("exit");
+}
+
+void
+sip_media_stream_finalize (GObject *object)
+{
+ SIPMediaStream *self = SIP_MEDIA_STREAM (object);
+ SIPMediaStreamPrivate *priv = SIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_free (priv->stream_sdp);
+
+ g_value_unset (&priv->native_codecs);
+ g_value_unset (&priv->native_candidates);
+
+ g_value_unset (&priv->remote_codecs);
+ g_value_unset (&priv->remote_candidates);
+
+ G_OBJECT_CLASS (sip_media_stream_parent_class)->finalize (object);
+
+ DEBUG ("exit");
+}
+
+/***********************************************************************
+ * Set: Media.StreamHandler interface implementation (same for 0.12/0.13???)
+ ***********************************************************************/
+
+/**
+ * sip_media_stream_codec_choice
+ *
+ * Implements DBus method CodecChoice
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_codec_choice (TpSvcMediaStreamHandler *iface,
+ guint codec_id,
+ DBusGMethodInvocation *context)
+{
+ /* Inform the connection manager of the current codec choice.
+ * -> note: not implemented by tp-gabble either (2006/May) */
+
+ g_debug ("%s: not implemented (ignoring)", G_STRFUNC);
+
+ tp_svc_media_stream_handler_return_from_codec_choice (context);
+}
+
+/**
+ * sip_media_stream_error
+ *
+ * Implements DBus method Error
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_error (TpSvcMediaStreamHandler *iface,
+ guint errno,
+ const gchar *message,
+ DBusGMethodInvocation *context)
+{
+ /* Note: Inform the connection manager that an error occured in this stream. */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ GMS_DEBUG_WARNING (priv->session, "Media.StreamHandler::Error called -- terminating session");
+
+ sip_media_session_terminate (priv->session);
+
+ tp_svc_media_stream_handler_return_from_error (context);
+}
+
+
+/**
+ * sip_media_stream_native_candidates_prepared
+ *
+ * Implements DBus method NativeCandidatesPrepared
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_native_candidates_prepared (TpSvcMediaStreamHandler *iface,
+ DBusGMethodInvocation *context)
+{
+ /* purpose: "Informs the connection manager that all possible native candisates
+ * have been discovered for the moment."
+ *
+ * note: only emitted by the stream-engine when built without
+ * libjingle (tested with s-e 0.3.11, 2006/Dec)
+ */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+
+ g_debug ("%s: unexpected - stream-engine called NativeCandidatesPrepared, possibly in non-libjingle mode", G_STRFUNC);
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ priv->native_cands_prepared = TRUE;
+ if (priv->native_codecs_prepared == TRUE) {
+ priv_generate_sdp(obj);
+ }
+
+ tp_svc_media_stream_handler_return_from_native_candidates_prepared (context);
+}
+
+
+/**
+ * sip_media_stream_new_active_candidate_pair
+ *
+ * Implements DBus method NewActiveCandidatePair
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_new_active_candidate_pair (TpSvcMediaStreamHandler *iface,
+ const gchar *native_candidate_id,
+ const gchar *remote_candidate_id,
+ DBusGMethodInvocation *context)
+{
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ g_signal_emit (obj, signals[SIG_NEW_ACTIVE_CANDIDATE_PAIR], 0,
+ native_candidate_id, remote_candidate_id);
+
+ tp_svc_media_stream_handler_return_from_new_active_candidate_pair (context);
+}
+
+
+/**
+ * sip_media_stream_new_native_candidate
+ *
+ * Implements DBus method NewNativeCandidate
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
+ const gchar *candidate_id,
+ const GPtrArray *transports,
+ DBusGMethodInvocation *context)
+{
+ /* purpose: "Inform this MediaStreamHandler that a new native transport candidate
+ *
+ * - decide whether it's time generate an offer/answer (based on gathered
+ * candidates); this should be done after candidates_prepared(),
+ * but current stream-engine never emits this
+ * - create SDP segment for this stream (the m-line and associated
+ * c-line and attributes)
+ * - mark that we've created SDP (so that additional new candidates
+ * can be processed correced
+ * - emit 'Ready' when ready to send offer/answer
+ */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+ JingleSessionState state;
+ GPtrArray *candidates;
+ GValue candidate = { 0, };
+ GValueArray *transport;
+ const gchar *addr;
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ g_object_get (priv->session, "state", &state, NULL);
+
+ /* FIXME: maybe this should be an assertion in case the channel
+ * isn't closed early enough right now? */
+ if (state > JS_STATE_ACTIVE) {
+ g_debug ("%s: state > JS_STATE_ACTIVE, doing nothing", G_STRFUNC);
+ tp_svc_media_stream_handler_return_from_new_native_candidate (context);
+ return;
+ }
+
+ if (priv->sdp_generated == TRUE) {
+ g_debug ("%s: SDP for stream already generated, ignoring candidate '%s'", G_STRFUNC, candidate_id);
+ tp_svc_media_stream_handler_return_from_new_native_candidate (context);
+ return;
+ }
+
+ candidates = g_value_get_boxed (&priv->native_candidates);
+
+ g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT);
+ g_value_set_static_boxed (&candidate,
+ dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT));
+
+ dbus_g_type_struct_set (&candidate,
+ 0, candidate_id,
+ 1, transports,
+ G_MAXUINT);
+
+ transport = g_ptr_array_index (transports, 0);
+ addr = g_value_get_string (g_value_array_get_nth (transport, 1));
+ if (!strcmp (addr, "127.0.0.1"))
+ {
+ GMS_DEBUG_WARNING (priv->session, "%s: ignoring native localhost candidate",
+ G_STRFUNC);
+ tp_svc_media_stream_handler_return_from_new_native_candidate (context);
+ return;
+ }
+
+ g_ptr_array_add (candidates, g_value_get_boxed (&candidate));
+
+ GMS_DEBUG_INFO (priv->session, "put 1 native candidate from stream-engine into cache");
+
+ if (candidates->len > 1 &&
+ priv->native_codecs_prepared == TRUE &&
+ priv->sdp_generated != TRUE) {
+ priv_generate_sdp(obj);
+ }
+
+ g_signal_emit (obj, signals[SIG_NEW_NATIVE_CANDIDATE], 0,
+ candidate_id, transports);
+
+ tp_svc_media_stream_handler_return_from_new_native_candidate (context);
+}
+
+
+/**
+ * sip_media_stream_ready
+ *
+ * Implements DBus method Ready
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_ready (TpSvcMediaStreamHandler *iface,
+ const GPtrArray *codecs,
+ DBusGMethodInvocation *context)
+{
+ /* purpose: "Inform the connection manager that a client is ready to handle
+ * this StreamHandler. Also provide it with info about all supported
+ * codecs."
+ *
+ * - note, with SIP we don't send the invite just yet (we need
+ * candidates first
+ */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+ GValue val = { 0, };
+
+ DEBUG ("enter");
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ priv->ready_received = TRUE;
+
+ GMS_DEBUG_INFO (priv->session, "putting list of all %d locally supported "
+ "codecs from stream-engine into cache", codecs->len);
+ g_value_init (&val, TP_TYPE_CODEC_LIST);
+ g_value_set_static_boxed (&val, codecs);
+ g_value_copy (&val, &priv->native_codecs);
+
+ priv->native_codecs_prepared = TRUE;
+ if (priv->native_cands_prepared == TRUE &&
+ priv->sdp_generated != TRUE) {
+ priv_generate_sdp(obj);
+ }
+
+ if (priv->push_remote_requested) {
+ push_remote_candidates (obj);
+ push_remote_codecs (obj);
+ }
+
+ /* note: for inbound sessions, emit active candidate pair once
+ remote info is set */
+ if (priv->remote_cands_sent == TRUE) {
+ /* XXX: hack, find out the correct candidate ids from somewhere */
+ g_debug ("emitting SetActiveCandidatePair for L1-L1 (2)");
+ tp_svc_media_stream_handler_emit_set_active_candidate_pair (
+ iface, "L1", "L1");
+ }
+
+ tp_svc_media_stream_handler_return_from_ready (context);
+}
+
+/* FIXME: set_local_codecs not implemented */
+
+/**
+ * sip_media_stream_stream_state
+ *
+ * Implements DBus method StreamState
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_stream_state (TpSvcMediaStreamHandler *iface,
+ guint state,
+ DBusGMethodInvocation *context)
+{
+ /* purpose: "Informs the connection manager of the stream's current state
+ * State is as specified in *ChannelTypeStreamedMedia::GetStreams."
+ *
+ * - set the stream state for session
+ */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ sip_media_session_stream_state (priv->session, priv->id, state);
+
+ tp_svc_media_stream_handler_return_from_stream_state (context);
+}
+
+/**
+ * sip_media_stream_supported_codecs
+ *
+ * Implements DBus method SupportedCodecs
+ * on interface org.freedesktop.Telepathy.Media.StreamHandler
+ */
+static void
+sip_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
+ const GPtrArray *codecs,
+ DBusGMethodInvocation *context)
+{
+ /* purpose: "Inform the connection manager of the supported codecs for this session.
+ * This is called after the connection manager has emitted SetRemoteCodecs
+ * to notify what codecs are supported by the peer, and will thus be an
+ * intersection of all locally supported codecs (passed to Ready)
+ * and those supported by the peer."
+ *
+ * - emit SupportedCodecs
+ */
+
+ SIPMediaStream *obj = SIP_MEDIA_STREAM (iface);
+ SIPMediaStreamPrivate *priv;
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ if (SIP_IS_MEDIA_SESSION (priv->session))
+ GMS_DEBUG_INFO (priv->session, "got codec intersection containing %d "
+ "codecs from stream-engine", codecs->len);
+
+ /* store the intersection for later on */
+ g_value_set_boxed (&priv->native_codecs, codecs);
+
+ g_signal_emit (obj, signals[SIG_SUPPORTED_CODECS], 0, codecs);
+
+ tp_svc_media_stream_handler_return_from_supported_codecs (context);
+}
+
+/***********************************************************************
+ * Helper functions follow (not based on generated templates)
+ ***********************************************************************/
+
+/**
+ * Described the local stream configuration in SDP (RFC2327),
+ * or NULL if stream not configured yet.
+ */
+const char *sip_media_stream_local_sdp (SIPMediaStream *obj)
+{
+ SIPMediaStreamPrivate *priv;
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ if (priv->sdp_generated != TRUE) {
+ g_warning ("Stream not in ready state, cannot describe SDP.");
+ return NULL;
+ }
+
+ return priv->stream_sdp;
+}
+
+
+/**
+ * Sets the remote candidates and codecs for this stream, as
+ * received via signaling.
+ *
+ * Parses the SDP information, generated TP candidates and
+ * stores the information to 'priv->remote_candidates'.
+ */
+gboolean sip_media_stream_set_remote_info (SIPMediaStream *stream, sdp_media_t *media, sdp_session_t *session)
+{
+ SIPMediaStreamPrivate *priv;
+ gboolean res = TRUE;
+ GPtrArray *tp_candidates;
+ GValue tp_candidate = { 0, };
+ GPtrArray *tp_transports;
+ GValue tp_transport = { 0, };
+ sdp_attribute_t *attr;
+ int cands_in_sdp = 0;
+ unsigned long r_port = media->m_port;
+ sdp_connection_t *sdp_conns = sdp_media_connections(media);
+
+ DEBUG ("enter");
+
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ tp_candidates = g_value_get_boxed (&priv->remote_candidates);
+
+ g_value_init (&tp_candidate, TP_TYPE_CANDIDATE_STRUCT);
+ g_value_init (&tp_transport, TP_TYPE_TRANSPORT_STRUCT);
+
+ for(attr = media->m_attributes; attr; attr = attr->a_next) {
+ if (strcmp(attr->a_name,"x-jingle-candidate") == 0)
+ ++cands_in_sdp;
+ }
+
+ /* note: As the c-line addresses might get modified by transparent
+ * media relays, make sure some of the remote candidates match
+ * the address seen on SDP c-line.
+ *
+ * If no match is found, there is a media relay on path,
+ * and it is safest to use its address for media, and
+ * ignore the jingle-candidates.
+ */
+
+ /* step 1 : use the jingle candidates (unmodified c-line)
+ * ... no longer done, we're not trying to use Jingle any more
+ */
+
+ /* step 2: no remote jingle-candidates, or modified c-line address
+ * (see notes above), so use the address from SDP c-line
+ * as the only remote candidate */
+ g_debug ("%s: using SDP c-line for sending media", G_STRFUNC);
+
+ if (sdp_conns && r_port > 0)
+ {
+ /* remote side does not support ICE/jingle */
+ g_value_take_boxed (&tp_transport,
+ dbus_g_type_specialized_construct (TP_TYPE_TRANSPORT_STRUCT));
+
+ dbus_g_type_struct_set (&tp_transport,
+ 0, "0", /* component number */
+ 1, sdp_conns->c_address,
+ 2, r_port,
+ 3, "UDP",
+ 4, "RTP",
+ 5, "AVP",
+ 6, 0.0f, /* qvalue */
+ 7, "local",
+ 8, "",
+ 9, "",
+ G_MAXUINT);
+
+ printf("c_address=<%s>, c_port=<%lu>\n", sdp_conns->c_address, r_port);
+
+ tp_transports = g_ptr_array_sized_new (1);
+ g_ptr_array_add (tp_transports, g_value_get_boxed (&tp_transport));
+
+ g_value_take_boxed (&tp_candidate,
+ dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_STRUCT));
+
+ dbus_g_type_struct_set (&tp_candidate,
+ 0, "L1", /* candidate id */
+ 1, tp_transports,
+ G_MAXUINT);
+
+ g_ptr_array_add (tp_candidates, g_value_get_boxed (&tp_candidate));
+ }
+ else
+ {
+ g_warning ("No valid remote candidates, unable to configure stream engine for sending.");
+ res = FALSE;
+ }
+
+ if (res == TRUE) {
+ /* note: convert from sdp to priv->remote_codecs */
+ res = priv_set_remote_codecs (stream, media);
+
+ if (priv->ready_received) {
+ push_remote_candidates (stream);
+ push_remote_codecs (stream);
+ }
+ else {
+ /* cannot push until the local candidates are available */
+ priv->push_remote_requested = TRUE;
+ }
+ }
+
+ return res;
+}
+
+static gboolean priv_set_remote_codecs(SIPMediaStream *stream, sdp_media_t *sdpmedia)
+{
+ SIPMediaStreamPrivate *priv;
+ gboolean res = TRUE;
+ GPtrArray *codecs;
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ DEBUG ("enter");
+
+ codecs = g_value_get_boxed (&priv->remote_codecs);
+
+ while (sdpmedia) {
+ if (sdpmedia->m_type == sdp_media_audio ||
+ sdpmedia->m_type == sdp_media_video) {
+ sdp_rtpmap_t *rtpmap = sdpmedia->m_rtpmaps;
+ while (rtpmap) {
+ GValue codec = { 0, };
+
+ g_value_init (&codec, TP_TYPE_CODEC_STRUCT);
+ g_value_take_boxed (&codec,
+ dbus_g_type_specialized_construct (TP_TYPE_CODEC_STRUCT));
+
+ /* RFC2327: see "m=" line definition
+ * - note, 'encoding_params' is assumed to be channel
+ * count (i.e. channels in farsight) */
+
+ dbus_g_type_struct_set (&codec,
+ /* payload type: */
+ 0, rtpmap->rm_pt,
+ /* encoding name: */
+ 1, rtpmap->rm_encoding,
+ /* media type */
+ 2, (sdpmedia->m_type == sdp_media_audio ?
+ TP_MEDIA_STREAM_TYPE_AUDIO : TP_MEDIA_STREAM_TYPE_VIDEO),
+ /* clock-rate */
+ 3, rtpmap->rm_rate,
+ /* number of supported channels: */
+ 4, rtpmap->rm_params ? atoi(rtpmap->rm_params) : 0,
+ /* optional params: */
+ 5, g_hash_table_new (g_str_hash, g_str_equal),
+ G_MAXUINT);
+
+ g_ptr_array_add (codecs, g_value_get_boxed (&codec));
+
+ rtpmap = rtpmap->rm_next;
+ }
+
+ /* note: only describes the first matching audio/video media */
+ break;
+ }
+
+ sdpmedia = sdpmedia->m_next;
+ }
+
+ return res;
+}
+
+/**
+ * Sets the media state to playing or disabled. When not playing,
+ * received RTP packets are not played locally, nor recorded audio
+ * is sent to the network.
+ */
+void sip_media_stream_set_playing (SIPMediaStream *stream, gboolean playing)
+{
+ SIPMediaStreamPrivate *priv;
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ g_debug ("emitting SetStreamPlaying signal with %d", playing);
+ priv->playing = playing;
+
+ if (priv->ready_received) {
+ g_debug ("%s: emitting SetStreamPlaying TRUE", G_STRFUNC);
+ tp_svc_media_stream_handler_emit_set_stream_playing (
+ (TpSvcMediaStreamHandler *)stream, playing);
+ }
+}
+
+/**
+ * Returns true if the stream has a valid SDP description and
+ * connection has been established with the stream engine.
+ */
+gboolean sip_media_stream_is_ready (SIPMediaStream *self)
+{
+ SIPMediaStreamPrivate *priv;
+ g_assert (SIP_IS_MEDIA_STREAM (self));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ return priv->sdp_generated && priv->ready_received;
+}
+
+static void priv_session_stream_state_changed_cb (SIPMediaSession *session,
+ GParamSpec *arg1,
+ SIPMediaStream *stream)
+{
+ JingleSessionState state;
+
+ g_object_get (session, "state", &state, NULL);
+ g_debug ("stream state cb: session js-state to %d.", state);
+}
+
+static void priv_generate_sdp (SIPMediaStream *obj)
+{
+ SIPMediaStreamPrivate *priv;
+ GPtrArray *candidates, *codecs;
+
+ g_assert (SIP_IS_MEDIA_STREAM (obj));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ candidates = g_value_get_boxed (&priv->native_candidates);
+ codecs = g_value_get_boxed (&priv->native_codecs);
+ priv_update_local_sdp (obj);
+
+ priv->sdp_generated = TRUE;
+
+ g_signal_emit (obj, signals[SIG_READY], 0, codecs);
+}
+
+/**
+ * Notify StreamEngine of remote codecs.
+ *
+ * @pre Ready signal must be receiveid (priv->ready_received)
+ */
+static void push_remote_codecs (SIPMediaStream *stream)
+{
+ SIPMediaStreamPrivate *priv;
+ JingleSessionState state;
+ GPtrArray *codecs;
+
+ DEBUG ("enter");
+
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ g_assert (priv);
+ g_assert (priv->ready_received);
+
+ g_object_get (priv->session, "state", &state, NULL);
+
+ codecs = g_value_get_boxed (&priv->remote_codecs);
+
+ GMS_DEBUG_EVENT (priv->session, "passing %d remote codecs to stream-engine",
+ codecs->len);
+
+ if (codecs->len > 0) {
+ tp_svc_media_stream_handler_emit_set_remote_codecs (
+ (TpSvcMediaStreamHandler *)stream, codecs);
+
+ g_value_take_boxed (&priv->remote_codecs,
+ dbus_g_type_specialized_construct (TP_TYPE_CODEC_LIST));
+ }
+}
+
+static void push_remote_candidates (SIPMediaStream *stream)
+{
+ SIPMediaStreamPrivate *priv;
+ JingleSessionState state;
+ GPtrArray *candidates;
+ guint i;
+
+ DEBUG ("enter");
+
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ g_assert (priv);
+ g_assert (priv->ready_received);
+
+ candidates = g_value_get_boxed (&priv->remote_candidates);
+
+ g_object_get (priv->session, "state", &state, NULL);
+ g_assert (state < JS_STATE_ENDED);
+
+ g_debug ("%s: number of candidates to push %d.", G_STRFUNC, candidates->len);
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ GValueArray *candidate = g_ptr_array_index (candidates, i);
+ const gchar *candidate_id;
+ const GPtrArray *transports;
+
+ candidate_id = g_value_get_string (g_value_array_get_nth (candidate, 0));
+ transports = g_value_get_boxed (g_value_array_get_nth (candidate, 1));
+
+ GMS_DEBUG_EVENT (priv->session, "passing 1 remote candidate "
+ "to stream-engine");
+
+ tp_svc_media_stream_handler_emit_add_remote_candidate (
+ (TpSvcMediaStreamHandler *)stream, candidate_id, transports);
+ }
+
+ g_value_take_boxed (&priv->remote_candidates,
+ dbus_g_type_specialized_construct (TP_TYPE_CANDIDATE_LIST));
+
+ priv->remote_cands_sent = TRUE;
+
+ /* note: for outbound sessions (for which remote cands become
+ * available at a later stage), emit active candidate pair
+ * and playing status once remote info set */
+
+ if (priv->ready_received) {
+ /* XXX: hack, find out the correct candidate ids from somewhere */
+ g_debug ("%s: emitting SetActiveCandidatePair for L1-L1", G_STRFUNC);
+ tp_svc_media_stream_handler_emit_set_active_candidate_pair (
+ (TpSvcMediaStreamHandler *)stream, "L1", "L1");
+ }
+
+ if (priv->playing) {
+ g_debug ("%s: emitting SetStreamPlaying TRUE", G_STRFUNC);
+ tp_svc_media_stream_handler_emit_set_stream_playing (
+ (TpSvcMediaStreamHandler *)stream, TRUE);
+}
+}
+
+static const char* priv_media_type_to_str(guint media_type)
+{
+switch (media_type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO: return "audio";
+ case TP_MEDIA_STREAM_TYPE_VIDEO: return "video";
+ default: g_assert_not_reached ();
+ ;
+ }
+return "-";
+}
+
+/**
+* Refreshes the local SDP based on Farsight stream, and current
+* object, state.
+*/
+static int priv_update_local_sdp(SIPMediaStream *stream)
+{
+ SIPMediaStreamPrivate *priv;
+ const char *c_sdp_version = "v=0\r\n";
+ const char *c_crlf = "\r\n";
+ gchar *tmpa_str = NULL, *tmpb_str;
+ gchar *aline_str = NULL, *cline_str = NULL, *mline_str = NULL, *malines_str = NULL;
+ GValue transport = { 0 };
+ GValue codec = { 0, };
+ GValue candidate = { 0 };
+ const GPtrArray *codecs, *candidates;
+ const gchar *ca_id;
+ const GPtrArray *ca_tports;
+ gchar *tr_addr;
+ gchar *tr_user = NULL;
+ gchar *tr_pass = NULL;
+ gchar *tr_subtype, *tr_profile;
+ gulong tr_port, tr_component;
+ gdouble tr_pref;
+ TpMediaStreamBaseProto tr_proto;
+ TpMediaStreamTransportType tr_type;
+ int i;
+ /* int result = -1; */
+
+ /* Note: impl limits ...
+ * - no multi-stream support
+ * - no IPv6 support (missing from the Farsight API?)
+ */
+
+ g_assert (SIP_IS_MEDIA_STREAM (stream));
+ priv = SIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ candidates = g_value_get_boxed (&priv->native_candidates);
+ codecs = g_value_get_boxed (&priv->native_codecs);
+
+ g_value_init (&candidate, TP_TYPE_CANDIDATE_STRUCT);
+ g_value_init (&transport, TP_TYPE_TRANSPORT_STRUCT);
+ g_value_init (&codec, TP_TYPE_CODEC_STRUCT);
+
+ for (i = 0; i < candidates->len; i++) {
+ g_value_set_static_boxed (&candidate, g_ptr_array_index (candidates, i));
+
+ dbus_g_type_struct_get (&candidate,
+ 0, &ca_id,
+ 1, &ca_tports,
+ G_MAXUINT);
+
+ g_assert (ca_tports->len >= 1);
+
+ /* XXX: should select the most preferable transport
+ * or use some other criteria */
+ g_value_set_static_boxed (&transport, g_ptr_array_index (ca_tports, 0));
+
+ dbus_g_type_struct_get (&transport,
+ 0, &tr_component,
+ 1, &tr_addr,
+ 2, &tr_port,
+ 3, &tr_proto,
+ 4, &tr_subtype,
+ 5, &tr_profile,
+ 6, &tr_pref,
+ 7, &tr_type,
+ 8, &tr_user,
+ 9, &tr_pass,
+ G_MAXUINT);
+
+ if (i == candidates->len - 1) {
+ /* generate the c= line based on last candidate */
+ if (tr_proto == TP_MEDIA_STREAM_BASE_PROTO_UDP) {
+ cline_str = g_strdup_printf("c=IN IP4 %s%s", tr_addr, c_crlf);
+ /* leave end of 'mline_str' for PT ids */
+ mline_str = g_strdup_printf("m=%s %lu %s/%s",
+ priv_media_type_to_str (priv->media_type),
+ tr_port, tr_subtype, tr_profile);
+ }
+ else {
+ cline_str = g_strdup("c=IN IP4 0.0.0.0\r\n");
+ /* no transport, so need to check codecs */
+ }
+ }
+
+ /* for ice-09, the "typ" attribute needs to be added for srflx and
+ * relay attributes */
+ }
+
+ malines_str = g_strdup ("");
+
+ for (i = 0; i < codecs->len; i++) {
+ guint co_id, co_type, co_clockrate, co_channels;
+ gchar *co_name;
+
+ g_value_set_static_boxed (&codec, g_ptr_array_index (codecs, i));
+
+ dbus_g_type_struct_get (&codec,
+ 0, &co_id,
+ 1, &co_name,
+ 2, &co_type,
+ 3, &co_clockrate,
+ 4, &co_channels,
+ G_MAXUINT);
+
+ /* step: add entry to media a-lines */
+ tmpa_str = g_strdup_printf("%sa=rtpmap:%u %s/%u",
+ malines_str,
+ co_id,
+ co_name,
+ co_clockrate);
+ if (co_channels > 1) {
+ tmpb_str = g_strdup_printf("%s/%u%s",
+ tmpa_str,
+ co_channels,
+ c_crlf);
+ }
+ else {
+ tmpb_str = g_strdup_printf("%s%s",
+ tmpa_str,
+ c_crlf);
+ }
+ g_free (malines_str);
+ g_free (tmpa_str);
+ malines_str = tmpb_str;
+
+ /* step: add PT id to mline */
+ tmpa_str = mline_str;
+ tmpb_str = g_strdup_printf(" %u", co_id);
+ mline_str = g_strconcat(mline_str, tmpb_str, NULL);
+ g_free(tmpa_str), g_free(tmpb_str);
+ }
+
+
+ GMS_DEBUG_DUMP (priv->session,
+ " from Telepathy DBus struct: [%s\"%s\", %s[%s1, \"%s\", %d, %s, "
+ "\"%s\", \"%s\", %f, %s, \"%s\", \"%s\"%s]]",
+ ANSI_BOLD_OFF, ca_id, ANSI_BOLD_ON, ANSI_BOLD_OFF, tr_addr, tr_port,
+ gms_tp_protocols[tr_proto], "RTP", "AVP", tr_pref, gms_tp_transports[tr_type], tr_user, tr_pass,
+ ANSI_BOLD_ON);
+
+ tmpa_str = g_strconcat(c_sdp_version, mline_str, c_crlf, cline_str, malines_str, NULL);
+
+ g_free(priv->stream_sdp);
+ priv->stream_sdp = tmpa_str;
+ g_debug("Updated stream SDP:{\n%s}\n", priv->stream_sdp);
+
+ g_free(tr_addr);
+ g_free(tr_user);
+ g_free(tr_pass);
+ g_free(tr_profile);
+ g_free(tr_subtype);
+
+ g_free(aline_str);
+ g_free(cline_str);
+ g_free(mline_str);
+ g_free(malines_str);
+
+ return 0;
+}
+
+static void
+stream_handler_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcMediaStreamHandlerClass *klass = (TpSvcMediaStreamHandlerClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_media_stream_handler_implement_##x (\
+ klass, (tp_svc_media_stream_handler_##x##_impl) sip_media_stream_##x)
+ IMPLEMENT(codec_choice);
+ IMPLEMENT(error);
+ IMPLEMENT(native_candidates_prepared);
+ IMPLEMENT(new_active_candidate_pair);
+ IMPLEMENT(new_native_candidate);
+ IMPLEMENT(ready);
+ /* IMPLEMENT(set_local_codecs); */
+ IMPLEMENT(stream_state);
+ IMPLEMENT(supported_codecs);
+#undef IMPLEMENT
+}
diff --git a/src/sip-media-stream.h b/src/sip-media-stream.h
new file mode 100644
index 0000000..f605df5
--- /dev/null
+++ b/src/sip-media-stream.h
@@ -0,0 +1,98 @@
+/*
+ * sip-media-stream.h - Header for SIPMediaStream
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005,2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_MEDIA_STREAM_H__
+#define __SIP_MEDIA_STREAM_H__
+
+#include <glib-object.h>
+#include <sofia-sip/sdp.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SIPMediaStream SIPMediaStream;
+typedef struct _SIPMediaStreamClass SIPMediaStreamClass;
+
+struct _SIPMediaStreamClass {
+ GObjectClass parent_class;
+};
+
+struct _SIPMediaStream {
+ GObject parent;
+};
+
+GType sip_media_stream_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_MEDIA_STREAM \
+ (sip_media_stream_get_type())
+#define SIP_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_MEDIA_STREAM, SIPMediaStream))
+#define SIP_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_MEDIA_STREAM, SIPMediaStreamClass))
+#define SIP_IS_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_MEDIA_STREAM))
+#define SIP_IS_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_MEDIA_STREAM))
+#define SIP_MEDIA_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_MEDIA_STREAM, SIPMediaStreamClass))
+
+/***********************************************************************
+ * Additional declarations (not based on generated templates)
+ ***********************************************************************/
+
+const char *sip_media_stream_local_sdp (SIPMediaStream *self);
+gboolean sip_media_stream_set_remote_info (SIPMediaStream *self, sdp_media_t *media, sdp_session_t *session);
+void sip_media_stream_set_playing (SIPMediaStream *self, gboolean playing);
+gboolean sip_media_stream_is_ready (SIPMediaStream *self);
+
+#define TP_TYPE_TRANSPORT_STRUCT (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ G_TYPE_STRING, \
+ G_TYPE_DOUBLE, \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ G_TYPE_STRING, \
+ G_TYPE_INVALID))
+#define TP_TYPE_TRANSPORT_LIST (dbus_g_type_get_collection ("GPtrArray", \
+ TP_TYPE_TRANSPORT_STRUCT))
+#define TP_TYPE_CANDIDATE_STRUCT (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_STRING, \
+ TP_TYPE_TRANSPORT_LIST, \
+ G_TYPE_INVALID))
+#define TP_TYPE_CANDIDATE_LIST (dbus_g_type_get_collection ("GPtrArray", \
+ TP_TYPE_CANDIDATE_STRUCT))
+
+#define TP_TYPE_CODEC_STRUCT (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ DBUS_TYPE_G_STRING_STRING_HASHTABLE, \
+ G_TYPE_INVALID))
+#define TP_TYPE_CODEC_LIST (dbus_g_type_get_collection ("GPtrArray", \
+ TP_TYPE_CODEC_STRUCT))
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_MEDIA_STREAM_H__*/
diff --git a/src/sip-text-channel.c b/src/sip-text-channel.c
new file mode 100644
index 0000000..0c56ed2
--- /dev/null
+++ b/src/sip-text-channel.c
@@ -0,0 +1,787 @@
+/*
+ * sip-text-channel.c - Source for SIPTextChannel
+ * Copyright (C) 2005-2007 Collabora Ltd.
+ * Copyright (C) 2005-2007 Nokia Corporation
+ * @author Martti Mela <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-im-channel).
+ * @author See gabble-im-channel.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sofia-sip/sip.h>
+#include <sofia-sip/sip_header.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/svc-channel.h>
+
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+
+#include "sip-text-channel.h"
+
+static void channel_iface_init (gpointer, gpointer);
+static void text_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SIPTextChannel, sip_text_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_OBJECT_PATH,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ /* PROP_CREATOR, */
+ LAST_PROPERTY
+};
+
+
+
+#define TP_TYPE_PENDING_MESSAGE_STRUCT (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_UINT, \
+ G_TYPE_STRING, \
+ G_TYPE_INVALID))
+
+
+#define enter g_debug("%s: enter", G_STRFUNC)
+
+
+/* private structures */
+
+typedef struct _SIPTextPendingMessage SIPTextPendingMessage;
+
+struct _SIPTextPendingMessage
+{
+ guint id;
+ nua_handle_t *nh;
+
+
+ time_t timestamp;
+ TpHandle sender;
+
+ TpChannelTextMessageType type;
+
+ gchar *text;
+};
+
+typedef struct _SIPTextChannelPrivate SIPTextChannelPrivate;
+
+struct _SIPTextChannelPrivate
+{
+ SIPConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+
+ guint recv_id;
+ guint sent_id;
+ GQueue *pending_messages;
+ GQueue *messages_to_be_acknowledged;
+
+ gboolean closed;
+
+ gboolean dispose_has_run;
+};
+
+
+#define _sip_text_pending_new() \
+ (g_slice_new(SIPTextPendingMessage))
+#define _sip_text_pending_new0() \
+ (g_slice_new0(SIPTextPendingMessage))
+
+#define SIP_TEXT_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_TEXT_CHANNEL, SIPTextChannelPrivate))
+
+static void _sip_text_pending_free(SIPTextPendingMessage *msg)
+{
+ enter;
+
+ if (msg->text)
+ {
+ g_free(msg->text);
+ }
+ msg->nh = NULL;
+
+ g_slice_free(SIPTextPendingMessage, msg);
+}
+
+
+static void
+sip_text_channel_init (SIPTextChannel *obj)
+{
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (obj);
+
+ enter;
+
+ priv->pending_messages = g_queue_new ();
+ priv->messages_to_be_acknowledged = g_queue_new ();
+
+ /* XXX -- mela: priv->last_msg = _sip_text_pending_new0(); */
+
+}
+
+static
+GObject *sip_text_channel_constructor(GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SIPTextChannelPrivate *priv;
+ DBusGConnection *bus;
+
+ enter;
+
+ obj =
+ G_OBJECT_CLASS(sip_text_channel_parent_class)->constructor(type,
+ n_props,
+ props);
+ priv = SIP_TEXT_CHANNEL_GET_PRIVATE(SIP_TEXT_CHANNEL(obj));
+
+ bus = tp_get_bus();
+ dbus_g_connection_register_g_object(bus, priv->object_path, obj);
+
+ return obj;
+}
+
+
+static void sip_text_channel_get_property(GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void sip_text_channel_set_property(GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void sip_text_channel_dispose(GObject *object);
+static void sip_text_channel_finalize(GObject *object);
+
+static void
+sip_text_channel_class_init(SIPTextChannelClass *sip_text_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (sip_text_channel_class);
+ GParamSpec *param_spec;
+
+ enter;
+
+ g_type_class_add_private (sip_text_channel_class, sizeof (SIPTextChannelPrivate));
+
+ object_class->get_property = sip_text_channel_get_property;
+ object_class->set_property = sip_text_channel_set_property;
+
+ object_class->constructor = sip_text_channel_constructor;
+
+ object_class->dispose = sip_text_channel_dispose;
+ object_class->finalize = sip_text_channel_finalize;
+
+ param_spec = g_param_spec_object("connection", "SIPConnection object",
+ "SIP connection object that owns this "
+ "SIP media channel object.",
+ SIP_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property(object_class, PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_string("object-path", "D-Bus object path",
+ "The D-Bus object path used for this "
+ "object on the bus.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property(object_class, PROP_OBJECT_PATH, param_spec);
+
+ param_spec = g_param_spec_string("channel-type", "Telepathy channel type",
+ "The D-Bus interface representing the "
+ "type of this channel.",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property(object_class, PROP_CHANNEL_TYPE, param_spec);
+
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+}
+
+static
+void sip_text_channel_get_property(GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPTextChannel *chan = SIP_TEXT_CHANNEL(object);
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE(chan);
+
+ enter;
+
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object(value, priv->conn);
+ break;
+
+ case PROP_OBJECT_PATH:
+ g_value_set_string(value, priv->object_path);
+ break;
+
+ case PROP_CHANNEL_TYPE:
+ g_value_set_string(value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint(value, TP_HANDLE_TYPE_CONTACT);
+ break;
+
+ case PROP_HANDLE:
+ g_debug ("%s: PROP_HANDLE: %d", G_STRFUNC, priv->handle); /* XXX -- mela */
+ g_debug ("%s: priv->handle = %d", G_STRFUNC, priv->handle); /* XXX -- mela */
+ g_value_set_uint(value, priv->handle);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static
+void
+sip_text_channel_set_property(GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPTextChannel *chan = SIP_TEXT_CHANNEL (object);
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (chan);
+
+ enter;
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_debug ("%s: PROP_CONNECTION", G_STRFUNC);
+ priv->conn = g_value_get_object (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ g_debug ("%s: PROP_OBJECT_PATH", G_STRFUNC);
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint(value);
+ g_debug ("%s: priv->handle = %d", G_STRFUNC, priv->handle);
+ g_debug ("%s: PROP_HANDLE: %d", G_STRFUNC, priv->handle);
+ break;
+
+ default:
+ g_debug ("%s: default", G_STRFUNC);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+void
+sip_text_channel_dispose(GObject *object)
+{
+ SIPTextChannel *self = SIP_TEXT_CHANNEL (object);
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (self);
+
+ enter;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ /* release any references held by the object here */
+
+ if (G_OBJECT_CLASS (sip_text_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_text_channel_parent_class)->dispose (object);
+}
+
+void
+sip_text_channel_finalize(GObject *object)
+{
+ SIPTextChannel *self = SIP_TEXT_CHANNEL (object);
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (self);
+
+ enter;
+
+ if (!priv)
+ return;
+
+ /* free any data held directly by the object here */
+
+ G_OBJECT_CLASS (sip_text_channel_parent_class)->finalize (object);
+}
+
+
+
+static gint sip_pending_message_compare(gconstpointer msg, gconstpointer id)
+{
+ SIPTextPendingMessage *message = (SIPTextPendingMessage *)(msg);
+
+ enter;
+
+ return (message->id != GPOINTER_TO_INT(id));
+}
+
+
+static gint sip_acknowledged_messages_compare(gconstpointer msg,
+ gconstpointer id)
+{
+ SIPTextPendingMessage *message = (SIPTextPendingMessage *)msg;
+ nua_handle_t *nh = (nua_handle_t *) id;
+ enter;
+
+ return (message->nh != nh);
+}
+
+/**
+ * sip_text_channel_acknowledge_pending_messages
+ *
+ * Implements DBus method AcknowledgePendingMessages
+ * on interface org.freedesktop.Telepathy.Channel.Type.Text
+ */
+static void
+sip_text_channel_acknowledge_pending_messages(TpSvcChannelTypeText *iface,
+ const GArray *ids,
+ DBusGMethodInvocation *context)
+{
+ SIPTextChannel *obj = SIP_TEXT_CHANNEL (iface);
+ SIPTextChannelPrivate *priv;
+ TpBaseConnection *conn;
+ GList **nodes;
+ SIPTextPendingMessage *msg;
+ guint i;
+
+ enter;
+
+ priv = SIP_TEXT_CHANNEL_GET_PRIVATE(obj);
+ conn = (TpBaseConnection *)(priv->conn);
+
+ nodes = g_new(GList *, ids->len);
+
+ for (i = 0; i < ids->len; i++)
+ {
+ guint id = g_array_index(ids, guint, i);
+
+ nodes[i] = g_queue_find_custom (priv->pending_messages,
+ GINT_TO_POINTER (id),
+ sip_pending_message_compare);
+
+ if (nodes[i] == NULL)
+ {
+ GError *error = NULL;
+
+ g_debug ("invalid message id %u", id);
+
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "invalid message id %u", id);
+
+ g_free(nodes);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ }
+
+ for (i = 0; i < ids->len; i++)
+ {
+ msg = (SIPTextPendingMessage *) nodes[i]->data;
+
+ g_debug ("acknowleding message id %u", msg->id);
+
+ g_queue_remove (priv->pending_messages, msg);
+
+ tp_handle_unref (conn->handles[TP_HANDLE_TYPE_CONTACT],
+ msg->sender);
+ _sip_text_pending_free (msg);
+ }
+
+ g_free(nodes);
+
+ tp_svc_channel_type_text_return_from_acknowledge_pending_messages (context);
+}
+
+
+/**
+ * sip_text_channel_close
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_text_channel_close (TpSvcChannel *iface, DBusGMethodInvocation *context)
+{
+ enter;
+
+ tp_svc_channel_return_from_close (context);
+}
+
+
+/**
+ * sip_text_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_text_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ enter;
+
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+
+/**
+ * sip_text_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_text_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SIPTextChannel *obj = SIP_TEXT_CHANNEL (iface);
+
+ SIPTextChannelPrivate *priv;
+
+ enter;
+
+ g_assert(obj != NULL);
+ g_assert(SIP_IS_TEXT_CHANNEL(obj));
+
+ priv = SIP_TEXT_CHANNEL_GET_PRIVATE(obj);
+
+ g_debug ("%s: priv->handle = %d", G_STRFUNC, priv->handle); /* XXX -- mela */
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ priv->handle);
+}
+
+
+/**
+ * sip_text_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+sip_text_channel_get_interfaces(TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ const char *interfaces[] = { NULL };
+
+ enter;
+ tp_svc_channel_return_from_get_interfaces (context, interfaces);
+}
+
+
+/**
+ * sip_text_channel_get_message_types
+ *
+ * Implements DBus method GetMessageTypes
+ * on interface org.freedesktop.Telepathy.Channel.Type.Text
+ */
+static void
+sip_text_channel_get_message_types(TpSvcChannelTypeText *iface,
+ DBusGMethodInvocation *context)
+{
+ GArray *ret = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ guint normal[1] = { TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL };
+
+ enter;
+ g_array_append_val (ret, normal);
+ tp_svc_channel_type_text_return_from_get_message_types (context, ret);
+ g_array_free (ret, TRUE);
+}
+
+
+/**
+ * sip_text_channel_list_pending_messages
+ *
+ * Implements DBus method ListPendingMessages
+ * on interface org.freedesktop.Telepathy.Channel.Type.Text
+ */
+static void
+sip_text_channel_list_pending_messages(TpSvcChannelTypeText *iface,
+ gboolean clear,
+ DBusGMethodInvocation *context)
+{
+ SIPTextChannel *self = SIP_TEXT_CHANNEL(iface);
+ SIPTextChannelPrivate *priv;
+ guint count;
+ GPtrArray *messages;
+ GList *cur;
+
+ priv = SIP_TEXT_CHANNEL_GET_PRIVATE (self);
+
+ count = g_queue_get_length (priv->pending_messages);
+ messages = g_ptr_array_sized_new (count);
+
+ for (cur = (clear ? g_queue_pop_head_link(priv->pending_messages)
+ : g_queue_peek_head_link(priv->pending_messages));
+ cur != NULL;
+ cur = (clear ? g_queue_pop_head_link(priv->pending_messages)
+ : cur->next))
+ {
+ SIPTextPendingMessage *msg = (SIPTextPendingMessage *) cur->data;
+ GValue val = { 0, };
+
+ g_value_init (&val, TP_TYPE_PENDING_MESSAGE_STRUCT);
+ g_value_take_boxed (&val,
+ dbus_g_type_specialized_construct (TP_TYPE_PENDING_MESSAGE_STRUCT));
+ dbus_g_type_struct_set (&val,
+ 0, msg->id,
+ 1, msg->timestamp,
+ 2, msg->sender,
+ 3, msg->type,
+ 4, 0 /* msg->flags */,
+ 5, msg->text,
+ G_MAXUINT);
+
+ g_ptr_array_add (messages, g_value_get_boxed (&val));
+ }
+
+ tp_svc_channel_type_text_return_from_list_pending_messages (context,
+ messages);
+}
+
+
+/**
+ * sip_text_channel_send
+ *
+ * Implements DBus method Send
+ * on interface org.freedesktop.Telepathy.Channel.Type.Text
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occured, DBus will throw the error only if this
+ * function returns false.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+sip_text_channel_send(TpSvcChannelTypeText *iface,
+ guint type,
+ const gchar *text,
+ DBusGMethodInvocation *context)
+{
+ SIPTextChannel *self = SIP_TEXT_CHANNEL(iface);
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ SIPConnection *sip_conn = SIP_CONNECTION (priv->conn);
+ TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
+ SIPTextPendingMessage *msg = NULL;
+ gchar *object_path;
+ nua_t *sofia_nua = sip_conn_sofia_nua (sip_conn);
+ su_home_t *sofia_home = sip_conn_sofia_home (sip_conn);
+ nua_handle_t *msg_nh = NULL;
+ const char *recipient;
+
+ enter;
+
+ g_assert(sofia_nua);
+ g_assert(sofia_home);
+
+ object_path = g_strdup_printf ("%s/TextChannel%u", priv->object_path, priv->handle);
+
+ g_debug ("%s: priv->handle = %d", G_STRFUNC, priv->handle);
+ recipient = tp_handle_inspect(
+ conn->handles[TP_HANDLE_TYPE_CONTACT],
+ priv->handle);
+
+ if ((recipient == NULL) || (strlen(recipient) == 0)) {
+ GError invalid = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "invalid recipient" };
+
+ g_debug("%s: invalid recipient %d", G_STRFUNC, priv->handle);
+ dbus_g_method_return_error (context, &invalid);
+ return;
+ }
+
+ /* XXX: do we have any need to track the handles at client side? */
+
+ msg_nh = sip_conn_create_request_handle (sofia_nua, sofia_home,
+ recipient, priv->handle);
+ nua_message(msg_nh,
+ SIPTAG_CONTENT_TYPE_STR("text/plain"),
+ SIPTAG_PAYLOAD_STR(text),
+ TAG_END());
+
+ msg = _sip_text_pending_new();
+ msg->nh = msg_nh;
+ msg->text = g_strdup(text);
+ msg->type = type;
+ msg->timestamp = time(NULL);
+
+ g_queue_push_tail(priv->messages_to_be_acknowledged, msg);
+
+ tp_svc_channel_type_text_return_from_send (context);
+}
+
+
+void sip_text_channel_message_emit(nua_handle_t *nh, SIPTextChannel *obj, int status)
+{
+ SIPTextChannelPrivate *priv = SIP_TEXT_CHANNEL_GET_PRIVATE (obj);
+ SIPTextPendingMessage *msg;
+ TpChannelTextSendError send_error;
+ GList *node;
+
+ enter;
+
+ node = g_queue_find_custom(priv->messages_to_be_acknowledged, nh,
+ sip_acknowledged_messages_compare);
+
+ /* Shouldn't happen... */
+ if (!node) {
+ g_debug("%s: message not found", G_STRFUNC);
+ return;
+ }
+
+ msg = (SIPTextPendingMessage *)node->data;
+
+ if (!msg) {
+ g_critical ("%s: message broken. Corrupted memory?", G_STRFUNC);
+ return;
+ }
+
+ if (status >= 200 && status < 300) {
+ tp_svc_channel_type_text_emit_sent ((TpSvcChannelTypeText *)obj,
+ msg->timestamp, msg->type, msg->text);
+ return;
+ }
+ else if (status >= 400 && status < 500) {
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT;
+ }
+ else {
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
+ }
+
+ tp_svc_channel_type_text_emit_send_error ((TpSvcChannelTypeText *)obj,
+ send_error, msg->timestamp, msg->type, msg->text);
+
+ g_queue_remove(priv->messages_to_be_acknowledged, msg);
+ _sip_text_pending_free(msg);
+}
+
+
+/**
+ * sip_text_channel_receive
+
+ <arg type="u" name="id" />
+ <arg type="u" name="timestamp" />
+ <arg type="u" name="sender" />
+ <arg type="u" name="type" />
+ <arg type="u" name="flags" />
+ <arg type="s" name="text" />
+
+ *
+ */
+void sip_text_channel_receive(SIPTextChannel *chan,
+ TpHandle sender,
+ const char *display_name,
+ const char *url,
+ const char *subject,
+ const char *message)
+{
+ SIPTextPendingMessage *msg;
+ SIPTextChannelPrivate *priv;
+ TpBaseConnection *conn;
+ GObject *obj;
+
+ enter;
+
+ g_assert(chan != NULL);
+ g_assert(SIP_IS_TEXT_CHANNEL(chan));
+ priv = SIP_TEXT_CHANNEL_GET_PRIVATE (chan);
+ conn = (TpBaseConnection *)(priv->conn);
+ obj = &chan->parent;
+
+ msg = _sip_text_pending_new();
+
+ msg->id = priv->recv_id++;
+ msg->timestamp = time(NULL);
+ msg->sender = sender;
+ msg->type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+ msg->text = g_strdup(message);
+
+ g_queue_push_tail(priv->pending_messages, msg);
+
+ tp_handle_ref (conn->handles[TP_HANDLE_TYPE_CONTACT], msg->sender);
+
+ g_debug ("%s: message = %s", G_STRFUNC, message);
+
+ tp_svc_channel_type_text_emit_received ((TpSvcChannelTypeText *)chan,
+ msg->id, msg->timestamp, msg->sender, msg->type,
+ 0 /* flags */, msg->text);
+}
+
+static void
+channel_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
+
+#define IMPLEMENT(x, suffix) tp_svc_channel_implement_##x (\
+ klass, sip_text_channel_##x##suffix)
+ IMPLEMENT(close,);
+ IMPLEMENT(get_channel_type,);
+ IMPLEMENT(get_handle,);
+ IMPLEMENT(get_interfaces,);
+#undef IMPLEMENT
+}
+
+static void
+text_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelTypeTextClass *klass = (TpSvcChannelTypeTextClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass,\
+ sip_text_channel_##x)
+ IMPLEMENT(acknowledge_pending_messages);
+ IMPLEMENT(get_message_types);
+ IMPLEMENT(list_pending_messages);
+ IMPLEMENT(send);
+#undef IMPLEMENT
+}
diff --git a/src/sip-text-channel.h b/src/sip-text-channel.h
new file mode 100644
index 0000000..8e7312b
--- /dev/null
+++ b/src/sip-text-channel.h
@@ -0,0 +1,66 @@
+/*
+ * sip-text-channel.h - Header for SIPTextChannel
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __SIP_TEXT_CHANNEL_H__
+#define __SIP_TEXT_CHANNEL_H__
+
+#include <glib-object.h>
+
+typedef struct _SipHandleStorage SipHandleStorage;
+
+G_BEGIN_DECLS
+
+typedef struct _SIPTextChannel SIPTextChannel;
+typedef struct _SIPTextChannelClass SIPTextChannelClass;
+
+struct _SIPTextChannelClass {
+ GObjectClass parent_class;
+};
+
+struct _SIPTextChannel {
+ GObject parent;
+};
+
+GType sip_text_channel_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_TEXT_CHANNEL \
+ (sip_text_channel_get_type())
+#define SIP_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_TEXT_CHANNEL, SIPTextChannel))
+#define SIP_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_TEXT_CHANNEL, SIPTextChannelClass))
+#define SIP_IS_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_TEXT_CHANNEL))
+#define SIP_IS_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_TEXT_CHANNEL))
+#define SIP_TEXT_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_TEXT_CHANNEL, SIPTextChannelClass))
+
+
+void sip_text_channel_message_emit(nua_handle_t *nh, SIPTextChannel *obj, int status);
+
+void sip_text_channel_receive(SIPTextChannel *obj, TpHandle sender,
+ const char *display_name, const char *url,
+ const char *subject, const char *message);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __SIP_TEXT_CHANNEL_H__*/
diff --git a/src/telepathy-helpers.c b/src/telepathy-helpers.c
new file mode 100644
index 0000000..f10d375
--- /dev/null
+++ b/src/telepathy-helpers.c
@@ -0,0 +1,60 @@
+/*
+ * telepathy-helpers.c - Source for some Telepathy D-Bus helper functions
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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 2.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <dbus/dbus-glib.h>
+#include "telepathy-helpers.h"
+
+static void _list_builder (gpointer key, gpointer value, gpointer data);
+
+GSList *
+tp_hash_to_key_value_list (GHashTable *hash)
+{
+ GSList *ret = NULL;
+
+ g_hash_table_foreach (hash, _list_builder, &ret);
+
+ return ret;
+}
+
+void
+tp_key_value_list_free (GSList *list)
+{
+ GSList *iter;
+
+ for (iter = list; iter; iter = g_slist_next(iter))
+ {
+ g_free (iter->data);
+ }
+
+ g_slist_free (list);
+}
+
+static void _list_builder (gpointer key, gpointer value, gpointer data)
+{
+ GSList **list = (GSList **) data;
+ TpKeyValue *kv = g_new0 (TpKeyValue, 1);
+
+ kv->key = key;
+ kv->value = value;
+
+ *list = g_slist_prepend (*list, kv);
+}
+
diff --git a/src/telepathy-helpers.h b/src/telepathy-helpers.h
new file mode 100644
index 0000000..7f8ce60
--- /dev/null
+++ b/src/telepathy-helpers.h
@@ -0,0 +1,42 @@
+/*
+ * telepathy-helpers.h - Header for various helper functions
+ * for telepathy implementation
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005 Nokia Corporation
+ *
+ * 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 2.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __TELEPATHY_HELPERS_H__
+#define __TELEPATHY_HELPERS_H__
+
+#include <glib.h>
+#include <dbus/dbus-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ gpointer key;
+ gpointer value;
+} TpKeyValue;
+
+GSList *tp_hash_to_key_value_list (GHashTable *hash);
+void tp_key_value_list_free (GSList *list);
+
+G_END_DECLS
+
+#endif /* __TELEPATHY_HELPERS_H__ */
+
diff --git a/src/telepathy-sofiasip.c b/src/telepathy-sofiasip.c
new file mode 100644
index 0000000..15956a3
--- /dev/null
+++ b/src/telepathy-sofiasip.c
@@ -0,0 +1,55 @@
+/*
+ * sip-connection.c - Source for SIPConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2005,2006 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Martti Mela <first.surname@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-connection).
+ * @author See gabble.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "debug.h"
+
+#include "sip-connection-manager.h"
+#include <telepathy-glib/run.h>
+#include <telepathy-glib/debug.h>
+
+#define DIE_TIME 5000
+
+static TpBaseConnectionManager *
+construct_cm (void)
+{
+ return (TpBaseConnectionManager *)g_object_new (
+ SIP_TYPE_CONNECTION_MANAGER, NULL);
+}
+
+int
+main (int argc, char** argv)
+{
+#ifdef ENABLE_DEBUG
+ sip_debug_set_flags_from_env ();
+
+ if (g_getenv ("SOFIASIP_PERSIST"))
+ {
+ sip_debug_set_flags (0xffff);
+ tp_debug_set_all_flags ();
+ }
+#endif
+
+ return tp_run_connection_manager ("telepathy-sofiasip", VERSION,
+ construct_cm, argc, argv);
+}
diff --git a/src/text-factory.c b/src/text-factory.c
new file mode 100644
index 0000000..ab008d1
--- /dev/null
+++ b/src/text-factory.c
@@ -0,0 +1,322 @@
+/*
+ * text-factory.c - Text channel factory for SIP connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <telepathy-glib/svc-connection.h>
+#include <telepathy-glib/interfaces.h>
+#include "text-factory.h"
+
+static void factory_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (SIPTextFactory, sip_text_factory,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE,
+ factory_iface_init))
+
+enum
+{
+ PROP_CONNECTION = 1,
+ LAST_PROPERTY
+};
+
+typedef struct _SIPTextFactoryPrivate SIPTextFactoryPrivate;
+struct _SIPTextFactoryPrivate
+{
+ SIPConnection *conn;
+ /* guint handle => SIPTextChannel *channel */
+ GHashTable *channels;
+
+ gboolean dispose_has_run;
+};
+
+#define SIP_TEXT_FACTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SIP_TYPE_TEXT_FACTORY, SIPTextFactoryPrivate))
+
+static void
+sip_text_factory_init (SIPTextFactory *fac)
+{
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ priv->conn = NULL;
+ priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+sip_text_factory_dispose (GObject *object)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (object);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ tp_channel_factory_iface_close_all (TP_CHANNEL_FACTORY_IFACE (object));
+
+ g_assert (priv->channels == NULL);
+
+ if (G_OBJECT_CLASS (sip_text_factory_parent_class)->dispose)
+ G_OBJECT_CLASS (sip_text_factory_parent_class)->dispose (object);
+}
+
+static void
+sip_text_factory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (object);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_text_factory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (object);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+sip_text_factory_class_init (SIPTextFactoryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (SIPTextFactoryPrivate));
+
+ object_class->get_property = sip_text_factory_get_property;
+ object_class->set_property = sip_text_factory_set_property;
+ object_class->dispose = sip_text_factory_dispose;
+
+ param_spec = g_param_spec_object ("connection", "SIPConnection object",
+ "SIP connection that owns this text "
+ "channel factory",
+ SIP_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+}
+
+static void
+sip_text_factory_close_all (TpChannelFactoryIface *iface)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (iface);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+ GHashTable *channels;
+
+ if (!priv->channels)
+ return;
+
+ channels = priv->channels;
+ priv->channels = NULL;
+ g_hash_table_destroy (channels);
+}
+
+static void
+sip_text_factory_connecting (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+sip_text_factory_connected (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+sip_text_factory_disconnected (TpChannelFactoryIface *iface)
+{
+}
+
+struct _ForeachData
+{
+ TpChannelFunc foreach;
+ gpointer user_data;
+};
+
+static void
+_foreach_slave (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _ForeachData *data = (struct _ForeachData *)user_data;
+ TpChannelIface *chan = TP_CHANNEL_IFACE (value);
+
+ data->foreach (chan, data->user_data);
+}
+
+static void
+sip_text_factory_foreach (TpChannelFactoryIface *iface,
+ TpChannelFunc foreach,
+ gpointer user_data)
+{
+ struct _ForeachData data = { foreach, user_data };
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (iface);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ g_hash_table_foreach (priv->channels, _foreach_slave, &data);
+}
+
+/**
+ * text_channel_closed_cb:
+ *
+ * Signal callback for when a text channel is closed. Removes the references
+ * that #SIPChannelFactory holds to them.
+ */
+static void
+channel_closed (SIPTextChannel *chan, gpointer user_data)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (user_data);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+ TpHandle contact_handle;
+
+ g_object_get (chan, "handle", &contact_handle, NULL);
+ g_debug ("%s: removing text channel with handle %d", G_STRFUNC, contact_handle);
+
+ g_hash_table_remove (priv->channels, GINT_TO_POINTER (contact_handle));
+}
+
+/**
+ * new_text_channel
+ *
+ * Creates a new empty SIPTextChannel.
+ */
+SIPTextChannel *
+sip_text_factory_new_channel (TpChannelFactoryIface *iface,
+ TpHandle handle,
+ gpointer request)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (iface);
+ SIPTextFactoryPrivate *priv;
+ SIPTextChannel *chan;
+ gchar *object_path;
+ TpBaseConnection *conn;
+
+ g_assert (SIP_IS_TEXT_FACTORY (fac));
+
+ priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+ conn = (TpBaseConnection *)(priv->conn);
+
+ object_path = g_strdup_printf ("%s/TextChannel%u",
+ conn->object_path, handle);
+
+ g_debug ("%s: object path %s", G_STRFUNC, object_path);
+
+ chan = g_object_new (SIP_TYPE_TEXT_CHANNEL,
+ "connection", priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed, priv->conn);
+
+ g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ tp_channel_factory_iface_emit_new_channel (fac, (TpChannelIface *)chan,
+ request);
+
+ return chan;
+}
+
+SIPTextChannel *
+sip_text_factory_lookup_channel (TpChannelFactoryIface *iface,
+ guint handle)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (iface);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+
+ return (SIPTextChannel *)g_hash_table_lookup (priv->channels,
+ GUINT_TO_POINTER(handle));
+}
+
+static TpChannelFactoryRequestStatus
+sip_text_factory_request (TpChannelFactoryIface *iface,
+ const gchar *chan_type,
+ TpHandleType handle_type,
+ guint handle,
+ gpointer request,
+ TpChannelIface **ret,
+ GError **error)
+{
+ SIPTextFactory *fac = SIP_TEXT_FACTORY (iface);
+ SIPTextFactoryPrivate *priv = SIP_TEXT_FACTORY_GET_PRIVATE (fac);
+ TpChannelIface *chan;
+ TpChannelFactoryRequestStatus status;
+
+ if (strcmp (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT))
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (handle_type != TP_HANDLE_TYPE_CONTACT)
+ {
+ return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+ }
+
+ status = TP_CHANNEL_FACTORY_REQUEST_STATUS_EXISTING;
+ chan = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle));
+ if (!chan)
+ {
+ chan = (TpChannelIface *)sip_text_factory_new_channel (iface, handle,
+ request);
+ status = TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED;
+ }
+ *ret = chan;
+ return status;
+}
+
+static void
+factory_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *) g_iface;
+
+#define IMPLEMENT(x) klass->x = sip_text_factory_##x
+ IMPLEMENT(close_all);
+ IMPLEMENT(foreach);
+ IMPLEMENT(request);
+ IMPLEMENT(connecting);
+ IMPLEMENT(connected);
+ IMPLEMENT(disconnected);
+#undef IMPLEMENT
+}
diff --git a/src/text-factory.h b/src/text-factory.h
new file mode 100644
index 0000000..fb167a4
--- /dev/null
+++ b/src/text-factory.h
@@ -0,0 +1,66 @@
+/*
+ * text-factory.h - Text channel factory for SIP connection manager
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SIP_TEXT_FACTORY_H__
+#define __SIP_TEXT_FACTORY_H__
+
+#include <telepathy-glib/channel-factory-iface.h>
+
+#include "sip-connection-sofia.h"
+#include "sip-text-channel.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SIPTextFactory SIPTextFactory;
+typedef struct _SIPTextFactoryClass SIPTextFactoryClass;
+
+struct _SIPTextFactoryClass {
+ GObjectClass parent_class;
+};
+
+struct _SIPTextFactory {
+ GObject parent;
+};
+
+GType sip_text_factory_get_type(void);
+
+/* TYPE MACROS */
+#define SIP_TYPE_TEXT_FACTORY \
+ (sip_text_factory_get_type())
+#define SIP_TEXT_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SIP_TYPE_TEXT_FACTORY, SIPTextFactory))
+#define SIP_TEXT_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SIP_TYPE_TEXT_FACTORY, SIPTextFactoryClass))
+#define SIP_IS_TEXT_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SIP_TYPE_TEXT_FACTORY))
+#define SIP_IS_TEXT_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SIP_TYPE_TEXT_FACTORY))
+#define SIP_TEXT_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SIP_TYPE_TEXT_FACTORY, SIPTextFactoryClass))
+
+SIPTextChannel *sip_text_factory_lookup_channel (TpChannelFactoryIface *iface,
+ guint handle);
+
+SIPTextChannel *sip_text_factory_new_channel (TpChannelFactoryIface *iface,
+ TpHandle handle, gpointer request);
+
+G_END_DECLS
+
+#endif
diff --git a/src/write-mgr-file.c b/src/write-mgr-file.c
new file mode 100644
index 0000000..958f13a
--- /dev/null
+++ b/src/write-mgr-file.c
@@ -0,0 +1,112 @@
+/*
+ * write_mgr_file.c - utility to produce .manager files.
+ * Copyright (C) 2006-2007 Collabora Ltd.
+ * Copyright (C) 2006-2007 Nokia Corporation
+ *
+ * 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 2.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-protocol.h>
+
+#include <telepathy-glib/enums.h>
+#include "sip-connection-manager.h"
+
+/* Cloned straight from Gabble - this might eventually go in a library */
+static gchar *
+mgr_file_contents (const char *busname,
+ const char *objpath,
+ const TpCMProtocolSpec protocols[],
+ GError **error)
+{
+ GKeyFile *f = g_key_file_new();
+ const TpCMProtocolSpec *protocol;
+ const TpCMParamSpec *row;
+
+ g_key_file_set_string(f, "ConnectionManager", "BusName", busname);
+ g_key_file_set_string(f, "ConnectionManager", "ObjectPath", objpath);
+
+ for (protocol = protocols; protocol->name; protocol++)
+ {
+ gchar *section_name = g_strdup_printf("Protocol %s", protocol->name);
+
+ for (row = protocol->parameters; row->name; row++)
+ {
+ gchar *param_name = g_strdup_printf("param-%s", row->name);
+ gchar *param_value = g_strdup_printf("%s%s%s", row->dtype,
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""),
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""));
+ g_key_file_set_string(f, section_name, param_name, param_value);
+ g_free(param_value);
+ g_free(param_name);
+ }
+
+ for (row = protocol->parameters; row->name; row++)
+ {
+ if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT)
+ {
+ gchar *default_name = g_strdup_printf("default-%s", row->name);
+
+ switch (row->gtype)
+ {
+ case G_TYPE_STRING:
+ g_key_file_set_string(f, section_name, default_name,
+ row->def);
+ break;
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ g_key_file_set_integer(f, section_name, default_name,
+ GPOINTER_TO_INT(row->def));
+ break;
+ case G_TYPE_BOOLEAN:
+ g_key_file_set_boolean(f, section_name, default_name,
+ GPOINTER_TO_INT(row->def) ? 1 : 0);
+ }
+ g_free(default_name);
+ }
+ }
+ g_free(section_name);
+ }
+ return g_key_file_to_data(f, NULL, error);
+}
+
+/* defined by telepathy-glib if your version is new enough - included here
+ * so telepathy-glib 0.5.4 and 0.5.5 will still work
+ */
+#ifndef TP_CM_BUS_NAME_BASE
+# define TP_CM_BUS_NAME_BASE "org.freedesktop.Telepathy.ConnectionManager."
+# define TP_CM_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/ConnectionManager/"
+#endif
+
+int
+main (void)
+{
+ GError *error = NULL;
+
+ gchar *s = mgr_file_contents(TP_CM_BUS_NAME_BASE "sofiasip",
+ TP_CM_OBJECT_PATH_BASE "sofiasip",
+ sofiasip_protocols, &error);
+ if (!s)
+ {
+ fprintf(stderr, error->message);
+ g_error_free(error);
+ return 1;
+ }
+ printf("%s", s);
+ g_free(s);
+ return 0;
+}
diff --git a/tests/.git-darcs-dir b/tests/.git-darcs-dir
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/.git-darcs-dir
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..f783e1d
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,17 @@
+if HAVE_LIBTELEPATHY
+tp_tests = tp_caller
+endif
+
+noinst_PROGRAMS = $(tp_tests)
+
+tp_caller_SOURCES = \
+ tp_caller.c
+tp_caller_LDADD = \
+ $(LIBTELEPATHY_LIBS)
+tp_caller_CFLAGS = \
+ $(LIBTELEPATHY_CFLAGS)
+
+AM_CFLAGS = $(ERROR_CFLAGS) @DBUS_CFLAGS@ @GLIB_CFLAGS@ @SOFIA_SIP_UA_CFLAGS@ \
+ -I $(top_srcdir)/src -I $(top_builddir)/src \
+ @TELEPATHY_GLIB_CFLAGS@
+AM_LDFLAGS = @DBUS_LIBS@ @GLIB_LIBS@ @SOFIA_SIP_UA_LIBS@
diff --git a/tests/tp_caller.c b/tests/tp_caller.c
new file mode 100644
index 0000000..f1a0a49
--- /dev/null
+++ b/tests/tp_caller.c
@@ -0,0 +1,564 @@
+/* tp_test.c - telepathy-sofiasip test utility (modified from
+ * libtelepathy's proto.c)
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#ifndef DBUS_API_SUBJECT_TO_CHANGE
+#define DBUS_API_SUBJECT_TO_CHANGE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-connmgr.h>
+#include <libtelepathy/tp-chan.h>
+#include <libtelepathy/tp-chan-gen.h>
+#include <libtelepathy/tp-ch-gen.h>
+#include <libtelepathy/tp-chan-iface-group-gen.h>
+#include <libtelepathy/tp-chan-type-text-gen.h>
+#include <libtelepathy/tp-chan-type-streamed-media-gen.h>
+#include <libtelepathy/tp-props-iface.h>
+#include <libtelepathy/tp-constants.h>
+#include <libtelepathy/tp-interfaces.h>
+
+/*
+ * Test connection manager and account
+ */
+#define CONNMGR_NAME "sofiasip"
+#define CONNMGR_BUS "org.freedesktop.Telepathy.ConnectionManager.sofiasip"
+#define CONNMGR_PATH "/org/freedesktop/Telepathy/ConnectionManager/sofiasip"
+#define PROTOCOL "sip"
+
+enum {
+ STATE_START = 0,
+ STATE_CHAN,
+ STATE_STREAMS,
+ STATE_RUNNING,
+ STATE_COMPLETED,
+};
+
+static gboolean global_connected = FALSE;
+static guint global_remote_handle = 0;
+static GMainLoop *global_mainloop = NULL;
+static DBusGProxy *global_streamengine = NULL;
+static DBusGConnection *global_dbus_connection = NULL;
+static gint global_exit_request = 0;
+static char* global_conn_path = NULL;
+static char* global_chan_path = NULL;
+static guint global_chan_handle = 0;
+static guint global_call_state = STATE_START;
+
+static void newconnection_handler(DBusGProxy *proxy, const char *s1,
+ const char *o, const char *s2,
+ gpointer user_data);
+
+static void new_channel_handler(DBusGProxy *proxy, const char *object_path,
+ const char *channel_type, guint handle_type,
+ guint handle, gboolean suppress_handle,
+ gpointer user_data);
+
+static gboolean status_changed_cb(DBusGProxy *proxy,
+ guint status, guint reason,
+ gpointer user_data);
+
+static void request_handles_reply_cb(DBusGProxy *proxy, GArray *OUT_arg2, GError *error, gpointer userdata);
+
+static void request_chan_cb(DBusGProxy *proxy,
+ gchar *channel_path,
+ GError *error,
+ gpointer data);
+
+static void request_streams_cb(DBusGProxy *proxy,
+ GPtrArray *streams,
+ GError *error,
+ gpointer data);
+
+static void tpcaller_signal_handler(int signo);
+
+static void check_conn_properties(TpConn *conn);
+
+static TpConn *action_login(const char* sip_address, const char* sip_password, const char* sip_proxy)
+{
+ DBusGConnection *connection;
+ TpConn *conn;
+ DBusGProxy *streamengine = NULL;
+ TpConnMgr *connmgr;
+ guint status = 1;
+ GError *error = NULL;
+ GValue val_acc, val_auth_usr, val_pwd, val_proxy;
+ GHashTable *connection_parameters = g_hash_table_new(g_str_hash, NULL);
+
+ connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+ if (connection == NULL) {
+ g_printerr("Failed to open connection to bus: %s\n",
+ error->message);
+ g_error_free(error);
+ exit(1);
+ }
+
+ printf("connected to DBus with connection %p\n", connection);
+ global_dbus_connection = connection;
+
+ /* Create a connection manager object */
+ g_print("Attempting to create a connection manager object.\n");
+ connmgr = tp_connmgr_new(connection, CONNMGR_BUS, CONNMGR_PATH,
+ TP_IFACE_CONN_MGR_INTERFACE);
+
+ if (connmgr == NULL) {
+ g_error("Failed to create a connection manager, skipping manager startup.");
+ }
+ else {
+ g_print("Creating a connection manager succeeded.\n");
+ }
+
+ g_print("Attempting to register a signal handler for NewConnection signal.\n");
+ dbus_g_proxy_connect_signal(DBUS_G_PROXY(connmgr), "NewConnection",
+ G_CALLBACK(newconnection_handler),
+ NULL, NULL);
+
+ /* Setting "g_type" is a hack since GValue is broken */
+ val_acc.g_type = 0;
+ val_auth_usr.g_type = 0;
+ val_proxy.g_type = 0;
+ val_pwd.g_type = 0;
+
+ g_value_init(&val_acc, G_TYPE_STRING);
+ g_value_init(&val_auth_usr, G_TYPE_STRING);
+ g_value_init(&val_proxy, G_TYPE_STRING);
+ g_value_init(&val_pwd, G_TYPE_STRING);
+
+ /* Connection parameters are dummy: fill in suitable replacements */
+
+ g_value_set_string(&val_acc, sip_address);
+ g_value_set_string(&val_pwd, sip_password);
+
+ g_hash_table_insert(connection_parameters, "account",
+ (gpointer) &val_acc);
+ g_hash_table_insert(connection_parameters, "password",
+ (gpointer) &val_pwd);
+ if (sip_proxy != NULL) {
+ g_value_set_string(&val_proxy, sip_proxy);
+ g_hash_table_insert(connection_parameters, "proxy",
+ (gpointer) &val_proxy);
+ }
+
+ /* Create a new actual connection with the connection manager */
+
+ g_print("Attempting to create a connection object.\n");
+
+ conn = tp_connmgr_new_connection(connmgr,
+ connection_parameters,
+ PROTOCOL);
+ g_assert(conn != NULL);
+
+ /* step: connection creation succesful */
+ dbus_g_proxy_connect_signal(DBUS_G_PROXY(conn), "NewChannel",
+ G_CALLBACK(new_channel_handler),
+ NULL, NULL);
+ g_print("Creation of a connection object succeeded.\n");
+
+ streamengine = dbus_g_proxy_new_for_name (connection,
+ "org.freedesktop.Telepathy.StreamEngine",
+ "/org/freedesktop/Telepathy/StreamEngine",
+ "org.freedesktop.Telepathy.ChannelHandler");
+
+ g_assert(streamengine != NULL);
+ global_streamengine = streamengine;
+
+ /* Check if connection is active; if not, add a callback
+ * for StatusChange signal */
+
+ if (!tp_conn_get_status(DBUS_G_PROXY(conn), &status, &error) || status != 0) {
+ g_print("GetStatus did not work synchronously, trying async version.\n");
+
+ dbus_g_proxy_connect_signal(DBUS_G_PROXY(conn), "StatusChanged",
+ G_CALLBACK(status_changed_cb),
+ NULL, NULL);
+ if (error)
+ g_error_free(error);
+
+ while (global_connected != TRUE && global_exit_request == 0) {
+ g_main_context_iteration(NULL, TRUE);
+ }
+ } else {
+ check_conn_properties(conn);
+ }
+
+ g_hash_table_destroy(connection_parameters);
+
+ return conn;
+}
+
+static int action_make_call(TpConn *conn, const char* to_uri)
+{
+ DBusGProxy *streamengine = global_streamengine;
+ GError *error = NULL;
+ TpChan *channel;
+ DBusGProxy *stream = NULL;
+ GArray *types = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ guint mediatype = TP_MEDIA_STREAM_TYPE_AUDIO;
+ const char *urilist[2] = { NULL, NULL };
+ int result = 0;
+ guint old_state = STATE_START;
+
+ urilist[0] = to_uri;
+
+ /* state machine for setting up a call */
+
+ while (global_call_state != STATE_COMPLETED) {
+
+ switch (global_call_state)
+ {
+ case STATE_START:
+ g_print("Requesting handle for SIP URI %s.\n", to_uri);
+ tp_conn_request_handles_async(DBUS_G_PROXY(conn), TP_CONN_HANDLE_TYPE_CONTACT, (const char**)urilist, request_handles_reply_cb, NULL);
+
+ break;
+
+ case STATE_CHAN:
+
+ /* request for a channel for outbound call */
+ g_print("Attempting to make an outbound call to %s (%d).\n", to_uri, global_remote_handle);
+ tp_conn_request_channel_async(DBUS_G_PROXY(conn),
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ TP_CONN_HANDLE_TYPE_CONTACT, global_remote_handle, TRUE,
+ request_chan_cb, NULL);
+
+ break;
+
+ case STATE_STREAMS:
+ g_debug("Calling HandleChannel on %p, connection='%s', chan='%s'.\n",
+ streamengine, global_conn_path, global_chan_path);
+
+ error = NULL;
+
+ tp_ch_handle_channel (streamengine,
+ CONNMGR_BUS,
+ global_conn_path,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ global_chan_path,
+ TP_CONN_HANDLE_TYPE_CONTACT,
+ global_remote_handle,
+ &error);
+
+ if (error) {
+ g_print ("ERROR: %s", error->message);
+ g_error_free (error);
+ g_object_unref (streamengine);
+ result = -1;
+
+ }
+ else
+ g_print ("Succesful HandleChannel with streamengine.\n");
+
+ channel = tp_chan_new (global_dbus_connection,
+ CONNMGR_BUS,
+ global_chan_path,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ TP_CONN_HANDLE_TYPE_CONTACT,
+ global_remote_handle);
+
+ g_assert (channel);
+
+ stream = tp_chan_get_interface (channel,
+ TELEPATHY_CHAN_IFACE_STREAMED_QUARK);
+
+ if (stream) {
+ g_array_append_val (types, mediatype);
+
+ g_debug("%s: calling RequestStream with types->len=%d", G_STRFUNC, types->len);
+
+ tp_chan_type_streamed_media_request_streams_async (stream,
+ global_chan_handle /*global_remote_handle*/,
+ types,
+ request_streams_cb,
+ NULL);
+ }
+ break;
+
+ case STATE_RUNNING:
+ /* stream ready, call setup completed */
+ global_call_state = STATE_COMPLETED;
+ break;
+ }
+
+ /* run the mainloop */
+ while (global_call_state == old_state &&
+ global_exit_request == 0) {
+ g_main_context_iteration(NULL, TRUE);
+ }
+
+ old_state = global_call_state;
+
+ if (global_exit_request)
+ global_call_state = STATE_COMPLETED;
+
+ }
+
+ return result;
+}
+
+int main(int argc, char **argv)
+{
+ TpConn *conn;
+ GError *error = NULL;
+ GMainLoop *mainloop;
+ char* sip_proxy = getenv("SIP_PROXY");
+
+#ifndef _WIN32
+ /* see: http://www.opengroup.org/onlinepubs/007908799/xsh/sigaction.html */
+ struct sigaction sigact;
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_handler = tpcaller_signal_handler;
+ sigaction(SIGINT, &sigact, NULL); /* ctrl-c */
+ sigaction(SIGABRT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+#endif
+
+ if (argc < 3) {
+ g_message("Usage: tp_caller <sip-aor> <password> [<sip-address-to-call> <sip-outbound-proxy>]");
+ exit(1);
+ }
+
+ g_type_init();
+ global_mainloop = mainloop = g_main_loop_new (NULL, FALSE);
+
+ if (sip_proxy == NULL)
+ sip_proxy = argc > 4 ? argv[4] : NULL;
+
+ conn = action_login(argv[1], argv[2], sip_proxy);
+
+ if (conn) {
+ if (argc > 3 &&
+ strncmp(argv[3], "sip:", 4) == 0) {
+ /* only call if 3rd param is a valid SIP URI */
+ action_make_call(conn, argv[3]);
+ };
+
+ g_print("Entering tp_caller mainloop.\n");
+ g_main_loop_run (mainloop);
+
+ g_print("Disconnecting from network.\n");
+
+ tp_conn_disconnect (DBUS_G_PROXY(conn), &error);
+ if (error)
+ g_error_free (error);
+
+ g_object_unref (conn);
+ dbus_g_connection_unref (global_dbus_connection);
+ }
+ else
+ g_warning("Unable to login with the requested account.");
+
+ g_print("Closing connection to SIP network.\n");
+
+ return 0;
+}
+
+static void newconnection_handler(DBusGProxy *proxy, const char *bus,
+ const char *path, const char *proto,
+ gpointer user_data)
+
+{
+ g_print("NewConnection callback:\n\tbus=%s\n\tpath=%s\n\tproto=%s\n", bus, path, proto);
+ global_conn_path = g_strdup(path);
+}
+
+static void handle_incoming_call (DBusGConnection *proxy, const char *chan_path, guint handle)
+{
+
+ GError *error = NULL;
+ GArray *array;
+ TpChan *channel;
+ DBusGProxy *chgroup = NULL;
+
+ channel = tp_chan_new (proxy,
+ CONNMGR_BUS,
+ chan_path,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ TP_CONN_HANDLE_TYPE_CONTACT,
+ handle);
+ g_assert (channel);
+
+ chgroup = tp_chan_get_interface (channel,
+ TELEPATHY_CHAN_IFACE_GROUP_QUARK);
+ g_assert (chgroup);
+
+ g_print("\tInbound call, passing the media channel to stream engine.\n");
+
+ /* step 1: pass the channel to the stream engine */
+
+ tp_ch_handle_channel (global_streamengine,
+ CONNMGR_BUS,
+ global_conn_path,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
+ chan_path,
+ TP_CONN_HANDLE_TYPE_CONTACT,
+ handle,
+ &error);
+
+ if (error) {
+ g_print ("ERROR: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* step 2: inform connection manager we accept the session */
+
+ tp_chan_iface_group_get_self_handle (chgroup, &global_chan_handle, &error);
+ if (error) {
+ g_warning ("cannot get self handle: %s", error->message);
+ g_error_free (error), error = NULL;
+ }
+
+ g_print("\tAccepting the call (self handle %d).\n", global_chan_handle);
+
+ array = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (array, global_chan_handle);
+
+ tp_chan_iface_group_add_members (chgroup, array, "", &error);
+ if (error) {
+ g_warning ("cannot add member %u to media channel: %s", handle, error->message);
+ g_error_free (error), error = NULL;
+ }
+
+ g_array_free(array, TRUE);
+}
+
+static void new_channel_handler(DBusGProxy *proxy, const char *object_path,
+ const char *channel_type, guint handle_type,
+ guint handle, gboolean suppress_handler,
+ gpointer user_data)
+{
+ g_print("NewChannel callback\n\tpath=%s\n\ttype=%s, handle=%u\n",
+ object_path, channel_type, handle);
+
+ if (strcmp(channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) == 0) {
+ global_chan_path = g_strdup(object_path);
+ /* note: handle == 0 for inbound calls */
+ global_chan_handle = handle;
+ if (suppress_handler == 0) {
+ /* an inbound session, let's answer it */
+ handle_incoming_call (global_dbus_connection, global_chan_path, handle);
+ }
+ }
+}
+
+static void request_chan_cb(DBusGProxy *proxy,
+ gchar *channel_path,
+ GError *error,
+ gpointer data)
+{
+
+ g_print("RequestChan callback:\n\tchanpath=%s\n", channel_path);
+
+ if (error != NULL) {
+ g_warning("%s: error in callback - %s\n", G_STRFUNC, error->message);
+ g_error_free(error);
+ global_exit_request = 1;
+ return;
+ }
+
+ if (channel_path == NULL) {
+ global_exit_request = 1;
+ return;
+ }
+
+ global_call_state = STATE_STREAMS;
+}
+
+static void request_streams_cb(DBusGProxy *proxy,
+ GPtrArray *streams,
+ GError *error,
+ gpointer data)
+{
+ g_print("RequestStreams callback with %d streams\n", streams ? streams->len : 0);
+ if (error != NULL) {
+ g_warning("%s: error in callback - %s\n", G_STRFUNC, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ global_call_state = STATE_RUNNING;
+}
+
+static gboolean status_changed_cb(DBusGProxy *proxy,
+ guint status, guint reason,
+ gpointer user_data)
+{
+ g_print("StatusChanged signal\n");
+ if (status == 0) {
+ g_print("\tConnected!\n");
+ global_connected = TRUE;
+ check_conn_properties(TELEPATHY_CONN(proxy));
+ }
+ else {
+ global_connected = FALSE;
+ }
+ return TRUE;
+}
+
+static void request_handles_reply_cb(DBusGProxy *proxy, GArray *handles, GError *error, gpointer userdata)
+{
+ guint i;
+
+ g_print("RequestHandles callback\n");
+
+ if (error) {
+ g_warning("%s: error in callback - %s\n", G_STRFUNC, error->message);
+ g_error_free(error);
+ global_exit_request = 1;
+ return;
+ }
+
+ for(i = 0; handles && i < handles->len; i++) {
+ guint ret_handle = g_array_index(handles, guint, i);
+
+ if (i == 0) {
+ global_remote_handle = ret_handle;
+ }
+
+ g_print("\tRequested handle received: %u (item %u)\n", ret_handle, i);
+ }
+
+ global_call_state = STATE_CHAN;
+}
+
+static void tpcaller_signal_handler(int signo)
+{
+ global_exit_request = 1;
+
+ if (global_mainloop)
+ g_main_loop_quit(global_mainloop);
+}
+
+static void check_conn_properties(TpConn *conn)
+{
+ TpPropsIface *conn_props;
+
+ conn_props = TELEPATHY_PROPS_IFACE (tp_conn_get_interface (
+ conn, TELEPATHY_PROPS_IFACE_QUARK));
+ if (conn_props == NULL) {
+ g_warning ("The connection object does not support " TP_IFACE_PROPERTIES);
+ }
+}