diff options
author | robot101 <> | 2007-03-08 17:28:17 +0000 |
---|---|---|
committer | robot101 <> | 2007-03-08 17:28:17 +0000 |
commit | 868dd2a46dd28b49eb2f8bfa521e5c615f594bc3 (patch) | |
tree | 0619bdef59c55ecb8a3414df610eed08b59d5e8d | |
parent | 36e7ba52d4397763aca609c368bc6f2472b644a1 (diff) |
[svn-to-darcs @ 3]
Initial import.
20070308172817-9b33b-67cd56c0fd9ed7be56f874da659e87cbac239c81.gz
52 files changed, 11839 insertions, 0 deletions
@@ -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> @@ -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 + @@ -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) +-------------------------------------- + +- ... @@ -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?) @@ -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); + } +} |