commit 8f920659d5aca6b6de1fc5632c72c146a81a7c91 Author: Andreas Eversberg Date: Sun Oct 2 08:16:23 2022 +0200 Initial GIT import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2359e46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +Makefile +Makefile.in +.deps +.libs +*.o +*.lo +*.la +*.pc +aclocal.m4 +acinclude.m4 +aminclude.am +m4/*.m4 +autom4te.cache +compile +config.h* +config.sub +config.log +config.status +config.guess +configure +depcomp +missing +ltmain.sh +install-sh +stamp-h1 +libtool + +.tarball-version +.version +.dirstamp + +Doxyfile + +.*.sw? + +src/libdebug/libdebug.a +src/libg711/libg711.a +src/libjitter/libjitter.a +src/libfm/libfm.a +src/libdtmf/libdtmf.a +src/libfsk/libfsk.a +src/liboptions/liboptions.a +src/libosmocc/libosmocc.a +src/libsample/libsample.a +src/libfilter/libfilter.a +src/libtimer/libtimer.a +src/libph_socket/libph_socket.a +src/pstn/osmo-cc-pstn-endpoint + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..689d568 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e166919 --- /dev/null +++ b/configure.ac @@ -0,0 +1,95 @@ +AC_INIT([osmo-cc-pstn-endpoint], + m4_esyscmd([./git-version-gen .tarball-version]), + [jolly@eversberg.eu]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([subdir-objects dist-bzip2]) +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_RANLIB + +AC_CONFIG_MACRO_DIR([m4]) + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +CFLAGS="$CFLAGS -Wall" +CPPFLAGS="$CPPFLAGS -Wall" + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h) + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +AC_CHECK_LIB([m], [main]) +AC_CHECK_LIB([pthread], [main]) + +AC_OUTPUT( + src/liboptions/Makefile + src/libdebug/Makefile + src/libsample/Makefile + src/libfsk/Makefile + src/libdtmf/Makefile + src/libfm/Makefile + src/libfilter/Makefile + src/libtimer/Makefile + src/libjitter/Makefile + src/libosmocc/Makefile + src/libg711/Makefile + src/libph_socket/Makefile + src/pstn/Makefile + src/Makefile + Makefile) diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/m4/.keep b/m4/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..37340b7 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,17 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = \ + liboptions \ + libdebug \ + libsample \ + libfilter \ + libfm \ + libdtmf \ + libfsk \ + libtimer \ + libjitter \ + libosmocc \ + libph_socket \ + libg711 \ + pstn + diff --git a/src/pstn/Makefile.am b/src/pstn/Makefile.am new file mode 100644 index 0000000..37973fd --- /dev/null +++ b/src/pstn/Makefile.am @@ -0,0 +1,28 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +bin_PROGRAMS = \ + osmo-cc-pstn-endpoint + +osmo_cc_pstn_endpoint_SOURCES = \ + tones.c \ + callerid.c \ + pstn.c \ + main.c + +osmo_cc_pstn_endpoint_LDADD = \ + $(COMMON_LA) \ + ../libdebug/libdebug.a \ + ../liboptions/liboptions.a \ + ../libsample/libsample.a \ + ../libfilter/libfilter.a \ + ../libdtmf/libdtmf.a \ + ../libfsk/libfsk.a \ + ../libfm/libfm.a \ + ../libfilter/libfilter.a \ + ../libtimer/libtimer.a \ + ../libjitter/libjitter.a \ + ../libosmocc/libosmocc.a \ + ../libg711/libg711.a \ + ../libph_socket/libph_socket.a \ + -lm + diff --git a/src/pstn/callerid.c b/src/pstn/callerid.c new file mode 100644 index 0000000..4258f88 --- /dev/null +++ b/src/pstn/callerid.c @@ -0,0 +1,400 @@ +/* caller ID transmission + * + * (C) 2022 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* see: + * ETSI EN 300 659-1 + * ETSI EN 300 659-2 + * ETSI EN 300 659-3 + */ + +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "../libosmocc/message.h" +#include "callerid.h" + +#define db2level(db) pow(10, (double)(db) / 20.0) + +/* Notes for dBV: + * See TR 101 182 for complex impedance, that is 1020 Ohms. + * This is close to 1mW. + */ + +#define DBV_TO_DBM -0.086 /* impedance 1020 Ohms */ + +#define DTAS_TX_DBV -16.0 /* relative to 1 Volt RMS */ +#define DTAS_DURATION 0.100 /* 100 ms */ +#define FSK_TX_DBV -15.5 /* relative to 1 Volt RMS */ +#define FSK_BAUD_RATE 1200 +#define FSK_BIT_ADJUST 0.5 /* must be 0.5 to completely sync each bit */ +#define DTMF_TONE_ON 0.070 /* 70 ms */ +#define DTMF_TONE_OFF 0.070 /* 70 ms */ + +#define TONE_DTAS_1 2130.0 +#define TONE_DTAS_2 2750.0 +#define TONE_V23_F0 2100.0 +#define TONE_V23_F1 1300.0 +#define TONE_BELL_F0 2200.0 +#define TONE_BELL_F1 1200.0 + +#define WAIT_RING_FSK 0.700 /* wait 500ms-2000ms after ring before sending FSK */ +#define WAIT_CW_DTAS 0.050 /* wait 50ms after CW before sending DT_AS */ +#define WAIT_DTAS_TEACK 0.210 /* wait 160ms + 50ms(latency) for TE-ACK after DT_AS */ +#define WAIT_TEACK_FSK 0.055 /* wait 55ms after recognition of TE-ACK before sending FSK */ +#define WAIT_RING_DTMF 0.500 /* wait after 1st ring before sending DTMF */ +#define WAIT_FSK_END 0.200 /* wait after FSK until restoring line condition */ +#define WAIT_DTMF_END 1.000 /* wait after FSK until restoring line condition */ + + +/* provide bit to FSK modulator */ +static int fsk_send_bit(void *inst) +{ + callerid_t *cid = (callerid_t *)inst; + + /* FSK transmission done */ + if (cid->pos >= cid->len + 3) + return -1; + + /* send 3 extra bit to make sure that CID passes all filers */ + if (cid->pos >= cid->len) { + cid->pos++; + return 1; + } + + /* channel seizure */ + if (cid->seize) + return (cid->seize-- & 1); + + /* mark */ + if (cid->mark) { + cid->mark--; + return 1; + } + + /* start bit */ + if (cid->bpos == 0) { + cid->bpos++; + return 0; + } + /* stop bit */ + if (cid->bpos > 8) { + cid->bpos = 0; + cid->pos++; + return 1; + } + /* data bit */ + cid->bpos++; + return (cid->data[cid->pos] >> (cid->bpos - 2)) & 1; +} + +/* init caller ID processor */ +int callerid_init(callerid_t *cid, int samplerate, int bell, char dtmf) +{ + double f0, f1; + int i; + int rc; + + memset(cid, 0, sizeof(*cid)); + cid->samplerate = samplerate; + cid->use_dtmf = dtmf; + + for (i = 0; i < 65536; i++) + cid->dtas_sine[i] = sin((double)i / 65536.0 * 2.0 * M_PI) * db2level(DTAS_TX_DBV + DBV_TO_DBM); + + if (!cid->use_dtmf) { + /* FSK */ + if (bell) { + /* BELL 202 */ + f0 = TONE_BELL_F0; + f1 = TONE_BELL_F1; + } else { + /* V.23 (used by Telekom) */ + f0 = TONE_V23_F0; + f1 = TONE_V23_F1; + } + rc = fsk_mod_init(&cid->fsk, cid, fsk_send_bit, samplerate, FSK_BAUD_RATE, f0, f1, db2level(FSK_TX_DBV + DBV_TO_DBM), 0, 1); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "FSK init failed!\n"); + return rc; + } + } else { + /* DTMF or DT_AS */ + /* because we render with 1 mW, we need to set level to 1.0 */ + dtmf_encode_init(&cid->dtmf, samplerate, 1.0); + } + + + return 0; +} + +/* exit caller ID processor */ +void callerid_exit(callerid_t *cid) +{ + if (!cid->use_dtmf) + fsk_mod_cleanup(&cid->fsk); + + // no samplerate cleanup + + // no DTMF cleanup +} + +/* subroutine to add information element (TLV) */ +static uint8_t *add_ie(uint8_t *p, uint8_t type, int len, const uint8_t *val) +{ + *p++ = type; + *p++ = len; + memcpy(p, val, len); + + return p + len; +} + +/* set caller ID and start transmission */ +int callerid_set(callerid_t *cid, int cw, int dt_as, const char *callerid, uint8_t caller_type, int use_date) +{ + int clen = strlen(callerid); + if (clen > 32) { + PDEBUG(DDSP, DEBUG_ERROR, "Callerid too long!\n"); + return -EINVAL; + } + + if (!cid->use_dtmf) { + uint8_t *p; + uint8_t par = 0; + int i; + time_t time_sec; + struct tm *tm; + uint8_t data[8]; + + if (caller_type == OSMO_CC_PRESENT_RESTRICTED) + PDEBUG(DDSP, DEBUG_INFO, "Sending restricted caller ID reason.\n"); + else if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0]) + PDEBUG(DDSP, DEBUG_INFO, "Sending caller ID '%s' via FSK.\n", callerid); + else + PDEBUG(DDSP, DEBUG_INFO, "Sending unavailable caller ID reason.\n"); + + p = cid->data + 2; + if (use_date) { + /* add IE (data) */ + time_sec = get_time(); + tm = localtime(&time_sec); + data[0] = '0' + (tm->tm_mon + 1) / 10; + data[1] = '0' + (tm->tm_mon + 1) % 10; + data[2] = '0' + (tm->tm_mday) / 10; + data[3] = '0' + (tm->tm_mday) % 10; + data[4] = '0' + (tm->tm_hour) / 10; + data[5] = '0' + (tm->tm_hour) % 10; + data[6] = '0' + (tm->tm_min) / 10; + data[7] = '0' + (tm->tm_min) % 10; + p = add_ie(p, 0x01, 8, data); + } + if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0]) { + /* add IE (cid) */ + p = add_ie(p, 0x02, strlen(callerid), (const uint8_t *)callerid); + } else { + /* add IE (unavailable reason) */ + if (caller_type == OSMO_CC_PRESENT_RESTRICTED) + data[0] = 0x50; /* restricted */ + else + data[0] = 0x4f; /* not available */ + p = add_ie(p, 0x04, 1, data); + } + /* preceed type+length */ + cid->len = p - cid->data - 2; + cid->data[0] = (cw) ? 0x82 : 0x80; + cid->data[1] = cid->len; + cid->len += 2; + /* append parity */ + p = cid->data; + for(i = 0; i < cid->len; i++) + par += *p++; + par = (~par) + 1; + *p++ = par; + cid->len++; + cid->pos = 0; + cid->bpos = 0; + cid->seize = (cw) ? 0 : 300; + cid->mark = 180; + if (dt_as) { + cid->state = CID_STATE_WAIT_DTAS; + cid->wait = (int)(WAIT_CW_DTAS * (double)cid->samplerate); + PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via FSK, waiting to start DT-AS transmission.\n", callerid); + } else { + cid->state = CID_STATE_WAIT_FSK; + cid->wait = (int)(WAIT_RING_FSK * (double)cid->samplerate); + PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via FSK, waiting to start FSK transmission.\n", callerid); + } + } else { + if (caller_type == OSMO_CC_PRESENT_RESTRICTED) { + PDEBUG(DDSP, DEBUG_INFO, "Not sending restricted caller ID.\n"); + return 0; + } else if (caller_type == OSMO_CC_PRESENT_ALLOWED && callerid[0]) { + PDEBUG(DDSP, DEBUG_INFO, "Sending caller ID '%s' via DTMF.\n", callerid); + } else { + PDEBUG(DDSP, DEBUG_INFO, "Not sending unavailable caller ID.\n"); + return 0; + } + cid->data[0] = cid->use_dtmf; + memcpy(cid->data, callerid, clen); + cid->data[clen + 1] = 'C'; + cid->pos = 0; + cid->wait = (int)(WAIT_RING_DTMF * (double)cid->samplerate); + cid->state = CID_STATE_WAIT_DTMF; + PDEBUG(DDSP, DEBUG_DEBUG, "Start sending callerid '%s' via DTMF, waiting to start DTMF transmission.\n", callerid); + } + + return 0; +} + +/* TE-ACK received, digit 'D' shall be recognised */ +void callerid_te_ack(callerid_t *cid, char digit) +{ + if (cid->state != CID_STATE_WAIT_TE_ACK) + return; + + if (digit < 'A' || digit > 'D') { + PDEBUG(DDSP, DEBUG_DEBUG, "Ignoring digit '%c', this is not a valid TE-ACK signal.\n", digit); + return; + } + + PDEBUG(DDSP, DEBUG_DEBUG, "Received valid TE-ACK digit '%c', wait to send FSK transmission.\n", digit); + /* wait for FSK */ + cid->state = CID_STATE_WAIT_FSK; + cid->wait = (int)(WAIT_TEACK_FSK * (double)cid->samplerate); +} + +/* send audio chunk. + * if return value is less than given length, the transmission is complete. + */ +int callerid_send(callerid_t *cid, sample_t *samples, int length) +{ + int count = 0, ret; + int i; + +again: + switch (cid->state) { + case CID_STATE_NULL: + break; + case CID_STATE_WAIT_DTAS: + case CID_STATE_WAIT_DTMF: + case CID_STATE_WAIT_FSK: + case CID_STATE_WAIT_TE_ACK: + case CID_STATE_WAIT_END: + if (length <= cid->wait) { + memset(samples, 0, sizeof(*samples) * length); + count += length; + cid->wait -= length; + break; + } + memset(samples, 0, sizeof(*samples) * cid->wait); + count += cid->wait; + samples += cid->wait; + length -= cid->wait; + cid->wait = 0; + switch (cid->state) { + case CID_STATE_WAIT_DTAS: + cid->state = CID_STATE_SEND_DTAS; + cid->dtas_phaseshift65536[0] = TONE_DTAS_1 / (double)cid->samplerate * 65536.0; + cid->dtas_phaseshift65536[1] = TONE_DTAS_2 / (double)cid->samplerate * 65536.0; + cid->dtas_phase65536[0] = 0.0; + cid->dtas_phase65536[1] = 0.0; + cid->dt_as_count = (int)(DTAS_DURATION * (double)cid->samplerate); + PDEBUG(DDSP, DEBUG_DEBUG, "Now start DT_AS transmission.\n"); + break; + case CID_STATE_WAIT_DTMF: + cid->state = CID_STATE_SEND_DTMF; + PDEBUG(DDSP, DEBUG_DEBUG, "Now start DTMF transmission.\n"); + dtmf_encode_set_tone(&cid->dtmf, cid->data[cid->pos++], DTMF_TONE_ON, DTMF_TONE_OFF); + break; + case CID_STATE_WAIT_FSK: + cid->state = CID_STATE_SEND_FSK; + PDEBUG(DDSP, DEBUG_DEBUG, "Now start FSK transmission.\n"); + break; + case CID_STATE_WAIT_TE_ACK: + PDEBUG(DDSP, DEBUG_INFO, "No TE-ACK received. Phone does not seem to support off-hook caller ID.\n"); + cid->state = CID_STATE_NULL; + break; + case CID_STATE_WAIT_END: + PDEBUG(DDSP, DEBUG_DEBUG, "Reestablish audio.\n"); + cid->state = CID_STATE_NULL; + break; + default: + ; /* should never happen */ + } + if (cid->state == CID_STATE_NULL) + break; + goto again; + case CID_STATE_SEND_DTAS: + for (i = 0; i < length; i++) { + if (cid->dt_as_count == 0) + break; + cid->dt_as_count--; + *samples++ = cid->dtas_sine[(uint16_t)cid->dtas_phase65536[0]] + cid->dtas_sine[(uint16_t)cid->dtas_phase65536[1]]; + cid->dtas_phase65536[0] += cid->dtas_phaseshift65536[0]; + if (cid->dtas_phase65536[0] >= 65536.0) + cid->dtas_phase65536[0] -= 65536.0; + cid->dtas_phase65536[1] += cid->dtas_phaseshift65536[1]; + if (cid->dtas_phase65536[1] >= 65536.0) + cid->dtas_phase65536[1] -= 65536.0; + } + length -= i; + count += i; + if (cid->dt_as_count == 0) { + /* wait for TE ACK */ + cid->state = CID_STATE_WAIT_TE_ACK; + cid->wait = (int)(WAIT_DTAS_TEACK * (double)cid->samplerate); + goto again; + } + break; + case CID_STATE_SEND_DTMF: + ret = dtmf_encode(&cid->dtmf, samples, length); + count += ret; + samples += ret; + length -= ret; + if (!length) + break; + if (!cid->data[cid->pos]) { + PDEBUG(DDSP, DEBUG_DEBUG, "DTMF transmission done, waiting to re-establish audio.\n"); + cid->wait = (int)(WAIT_DTMF_END * (double)cid->samplerate); + cid->state = CID_STATE_WAIT_END; + break; + } + dtmf_encode_set_tone(&cid->dtmf, cid->data[cid->pos++], DTMF_TONE_ON, DTMF_TONE_OFF); + goto again; + case CID_STATE_SEND_FSK: + ret = fsk_mod_send(&cid->fsk, samples, length, 0); + count += ret; + samples += ret; + length -= ret; + if (length) { + PDEBUG(DDSP, DEBUG_DEBUG, "FSK transmission done, waiting to re-establish audio.\n"); + cid->wait = (int)(WAIT_FSK_END * (double)cid->samplerate); + cid->state = CID_STATE_WAIT_END; + break; + } + break; + } + return count; +} + diff --git a/src/pstn/callerid.h b/src/pstn/callerid.h new file mode 100644 index 0000000..d116fc9 --- /dev/null +++ b/src/pstn/callerid.h @@ -0,0 +1,47 @@ + +#include "../libfsk/fsk.h" +#include "../libdtmf/dtmf_encode.h" + +enum cid_state { + CID_STATE_NULL = 0, + CID_STATE_WAIT_DTAS, + CID_STATE_SEND_DTAS, + CID_STATE_WAIT_TE_ACK, + CID_STATE_WAIT_FSK, + CID_STATE_SEND_FSK, + CID_STATE_WAIT_DTMF, + CID_STATE_SEND_DTMF, + CID_STATE_WAIT_END, +}; + +typedef struct callerid { + /* settings */ + int samplerate; /* sample rate to render output */ + char use_dtmf; /* if set, use DTMF instead of FSK, start digit given */ + + /* play states */ + enum cid_state state; /* current action to perform */ + int wait; /* time to send silence until next tone is sent */ + + /* frame */ + uint8_t data[1024]; /* buffer to hold frame data to be sent */ + int len; /* bytes in buffer */ + int pos; /* byte in buffer to play next */ + int bpos; /* bit to play next, in case of FSK */ + int seize, mark; /* bit counters for seizure and mark signal */ + + /* tone generation */ + double dtas_sine[65536]; /* sine wave for both DT_AS frequencies at correct level */ + double dtas_phaseshift65536[2]; /* frequency of DT_AS */ + double dtas_phase65536[2]; /* current phase of DT_AS */ + int dt_as_count; /* count samples of DT_AS */ + fsk_mod_t fsk; /* modulator for FSK */ + dtmf_enc_t dtmf; /* modulator for DTMF */ +} callerid_t; + +int callerid_init(callerid_t *cid, int samplerate, int bell, char dtmf); +void callerid_exit(callerid_t *cid); +int callerid_set(callerid_t *cid, int cw, int dt_as, const char *callerid, uint8_t caller_type, int use_date); +void callerid_te_ack(callerid_t *cid, char digit); +int callerid_send(callerid_t *cid, sample_t *samples, int length); + diff --git a/src/pstn/main.c b/src/pstn/main.c new file mode 100644 index 0000000..4b5b871 --- /dev/null +++ b/src/pstn/main.c @@ -0,0 +1,413 @@ +/* osmo-cc-pstn-endpoint main + * + * (C) 2020 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../liboptions/options.h" +#include "../libg711/g711.h" +#include "pstn.h" + +pstn_t *pstn_ep = NULL; +int num_kanal = 1; + +#define SUBSCRIBER_MAX 16 +static char law = 'a'; +static int serving_location = 1; /* private network serving local user */ +static const char *socketname = NULL; +static const char *name = "pstn"; +static const char *subscribers[SUBSCRIBER_MAX] = { "" }; +static int subscriber_num = 0; +static int tx_delay = 0; +static int clip = 0; +static int cid_bell = 0; +static int cid_dtmf = 0; +static int cid_date = 0; +static int enblock = 4; +static int recall = 0; +static int ringing_types_incoming[SUBSCRIBER_MAX] = { 0 }; +static int ringing_type_incoming_num = 0; +static int ringing_type_hold = 1; +static enum tones_type tones_type = TONES_TYPE_GERMAN; +static int rt_prio = 1; +#define MAX_CC_ARGS 1024 +static int cc_argc = 0; +static const char *cc_argv[MAX_CC_ARGS]; + +static void print_usage(const char *app) +{ + printf("Usage: %s -s []\n", app); +} + +static void print_help() +{ + /* - - */ + printf(" -h --help\n"); + printf(" This help\n"); + printf(" --config [~/]\n"); + printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n"); + printf(" Each line in config file is one option, '-' or '--' must not be given!\n"); + debug_print_help(); + printf(" -s --socket \n"); + printf(" Path to UNIX socket that provides layer 1 connection to an PSTN device\n"); + printf(" -n --name \n"); + printf(" Give name of this interface. It will be sent in each call towards\n"); + printf(" -I --subscriber \n"); + printf(" What caller ID to send on calls made from terminal. (default = '%s')\n", subscribers[0]); + printf(" -I -R -I -R ...\n"); + printf(" You may specify multiple subscriber IDs and as much ringing types.\n"); + printf(" Calls to a subscriber's ID will then ring as specified. Calls to an\n"); + printf(" unspecified subscriber ID will ring as specified for first subscriber.\n"); + printf(" --tx-delay \n"); + printf(" Give a delay in milliseconds. This is required for modem/fax. Audio\n"); + printf(" toward ISDN interface is buffered with the given delay.\n"); + printf(" This feature alters dejittering strategy.\n"); + printf(" --clip [--cid-date]\n"); + printf(" Enable caller ID, optionally with date info. (disabled by default)\n"); + printf(" --cid-bell\n"); + printf(" Use Bell 202 to modulate caller ID, rather than V.23 (default).\n"); + printf(" Note that both systems are so similar (and have equal center frequency),\n"); + printf(" so that it does not matter which one is used. (my oppinion!)\n"); + printf(" --cid-dtmf D\n"); + printf(" Use DTMF with given start digit 'D' for default, rather than FSK.\n"); + printf(" 'D' is used in Taiwan, 'A' in Brazil...\n"); + printf(" --cid-date\n"); + printf(" Send date+time with caller ID. (May cause errors on some phones.)\n"); + printf(" --enblock off | \n"); + printf(" Enable en-block dialing, to collect number before setup. The value\n"); + printf(" given is the number of seconds to wait for more digits to be dialed.\n"); + printf(" (default = %d)\n", enblock); + printf(" --recall\n"); + printf(" Enable recall / call waiting. (disabled by default)\n"); + printf(" -R --ringing-type-incoming \n"); + printf(" Cadenced ringing for incoming call. (default = %d)\n", ringing_types_incoming[0]); + printf(" --ringing-type-hold \n"); + printf(" Cadenced ringing for call on hold. (default = %d)\n", ringing_type_hold); + printf(" -T --local-tones german | oldgerman | american\n"); + printf(" Send locally generated tones, if not provided by remote interface.\n"); + printf(" DTMF may not work with old German tones, because they might interfer!\n"); + printf(" --ulaw\n"); + printf(" Use U-LAW for b-channel coding instead of alaw.\n"); + printf(" --serving-location (see Q.931)\n"); + printf(" 0 = user, 1 = private network serving local user (default=%d)\n", serving_location); + printf(" -r --realtime \n"); + printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio); + printf(" -C --cc \"\" [--cc ...]\n"); + printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n"); +} + +static void print_recall() +{ + /* - - */ + printf("\n"); + printf("Recall:\n"); + printf("To enable recall, use --recall option. Then you can place or receive a second\n"); + printf("call and switch between them.\n"); + printf("If you have an active call, press hook-flash to hold the active call and make\n"); + printf("a second call.\n"); + printf("If you have two connected calls, press hook-flash to switch between these\n"); + printf("calls.\n"); + printf("If an incoming call is waiting, you will hear CW singal. Press hook-flash to\n"); + printf("switch between these calls.\n"); + printf("If you want to release the active call and retrieve the call on hold, hang up\n"); + printf("and the phone will ring. Answer it.\n"); + printf("\n"); +} + +#define OPT_TX_DELAY 256 +#define OPT_TX_CLIP 257 +#define OPT_TX_CID_BELL 258 +#define OPT_TX_CID_DTMF 259 +#define OPT_TX_CID_DATE 260 +#define OPT_TX_ENBLOCK 261 +#define OPT_TX_RECALL 262 +#define OPT_TX_RING_I 'R' +#define OPT_TX_RING_H 264 +#define OPT_ULAW 265 +#define OPT_SERVING 266 + +static void add_options(void) +{ + option_add('h', "help", 0); + option_add('v', "verbose", 1); + option_add('s', "socket", 1); + option_add('n', "name", 1); + option_add('I', "subscriber", 1); + option_add(OPT_TX_DELAY, "tx-delay", 1); + option_add(OPT_TX_CLIP, "clip", 0); + option_add(OPT_TX_CID_BELL, "cid-bell", 0); + option_add(OPT_TX_CID_DTMF, "cid-dtmf", 1); + option_add(OPT_TX_CID_DATE, "cid-date", 0); + option_add(OPT_TX_ENBLOCK, "enblock", 1); + option_add(OPT_TX_RECALL, "recall", 0); + option_add(OPT_TX_RING_I, "ringing-type-incoming", 1); + option_add(OPT_TX_RING_H, "ringing-type-hold", 1); + option_add('T', "local-tones", 0); + option_add(OPT_ULAW, "ulaw", 0); + option_add(OPT_SERVING, "serving-location", 1); + option_add('r', "realtime", 1); + option_add('C', "cc", 1); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + + switch (short_option) { + case 'h': + print_usage(argv[0]); + print_help(); + return 0; + case 'v': + if (!strcasecmp(argv[argi], "list")) { + debug_list_cat(); + return 0; + } + rc = parse_debug_opt(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Failed to parse debug option, please use -h for help.\n"); + return rc; + } + break; + case 's': + socketname = options_strdup(argv[argi]); + break; + case 'n': + name = options_strdup(argv[argi]); + break; + case 'I': + if (subscriber_num == SUBSCRIBER_MAX) { + fprintf(stderr, "Cannot define more than %d subscriber Ids.\n", SUBSCRIBER_MAX); + return -EINVAL; + } + subscribers[subscriber_num++] = options_strdup(argv[argi]); + break; + case OPT_TX_DELAY: + tx_delay = atoi(argv[argi]); + break; + case OPT_TX_CLIP: + clip = 1; + break; + case OPT_TX_CID_BELL: + cid_bell = 1; + break; + case OPT_TX_CID_DTMF: + if (strlen(argv[argi]) != 1 || argv[argi][0] < 'A' || argv[argi][0] > 'D') { + fprintf(stderr, "Only DTMF start digits 'A', 'B', 'C', 'D' are allowed.\n"); + return -EINVAL; + } + cid_dtmf = argv[argi][0]; + break; + case OPT_TX_CID_DATE: + cid_date = 1; + break; + case OPT_TX_ENBLOCK: + enblock = atoi(argv[argi]); + break; + case OPT_TX_RECALL: + recall = 1; + break; + case OPT_TX_RING_I: + if (ringing_type_incoming_num == SUBSCRIBER_MAX) { + fprintf(stderr, "Cannot define more than %d ringing types.\n", SUBSCRIBER_MAX); + return -EINVAL; + } + ringing_types_incoming[ringing_type_incoming_num++] = atoi(argv[argi]); + break; + case OPT_TX_RING_H: + ringing_type_hold = atoi(argv[argi]); + break; + case 'T': + if (!strcasecmp(argv[argi], "american")) + tones_type = TONES_TYPE_AMERICAN; + else if (!strcasecmp(argv[argi], "german")) + tones_type = TONES_TYPE_GERMAN; + else if (!strcasecmp(argv[argi], "oldgerman")) + tones_type = TONES_TYPE_OLDGERMAN; + else { + fprintf(stderr, "Invalid tones type given!\n"); + return -EINVAL; + } + break; + case OPT_ULAW: + law = 'u'; + break; + case OPT_SERVING: + serving_location = atoi(argv[argi]);; + break; + case 'r': + rt_prio = atoi(argv[argi]); + break; + case 'C': + if (!strcasecmp(argv[argi], "help")) { + osmo_cc_help(); + return 0; + } + if (cc_argc == MAX_CC_ARGS) { + fprintf(stderr, "Too many osmo-cc args!\n"); + break; + } + cc_argv[cc_argc++] = options_strdup(argv[argi]); + break; + default: + return -EINVAL; + } + return 1; +} + +static int quit = 0; +void sighandler(int sigset) +{ + if (sigset == SIGHUP || sigset == SIGPIPE) + return; + + fprintf(stderr, "\nSignal %d received.\n", sigset); + + quit = 1; +} + +int main(int argc, char *argv[]) +{ + int argi, rc; + + /* init codecs */ + g711_init(); + + cc_argv[cc_argc++] = options_strdup("remote auto"); + + /* handle options / config file */ + add_options(); + rc = options_config_file(argc, argv, "~/.osmocom/pstn/pstn.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + /* init fm */ + fm_init(0); + + if (!socketname) { + fprintf(stderr, "No socket name given, use '-h' for help.\n"); + goto error; + } + + /* change tones to ulaw */ + if (law == 'u') + isdn_tone_generate_ulaw_samples(); + + /* check subscribers and their ringing types */ + if (subscriber_num == 0) + subscriber_num = 1; + if (ringing_type_incoming_num == 0) + ringing_type_incoming_num = 1; + if (subscriber_num != ringing_type_incoming_num) { + fprintf(stderr, "You need to specify as many ringing types as you specified subscriber IDs, use '-h' for help.\n"); + goto error; + } + + pstn_ep = pstn_create(); + if (!pstn_ep) + goto error; + + rc = pstn_init(pstn_ep, name, socketname, subscribers, subscriber_num, serving_location, tx_delay, clip, cid_bell, cid_dtmf, cid_date, enblock, recall, ringing_types_incoming, ringing_type_hold, tones_type, law); + if (rc) { + PDEBUG(DTEL, DEBUG_ERROR, "Endpoint initializing failed!\n"); + goto error; + } + + rc = osmo_cc_new(&pstn_ep->cc_ep, OSMO_CC_VERSION, name, OSMO_CC_LOCATION_USER, cc_message, NULL, pstn_ep, cc_argc, cc_argv); + if (rc < 0) + goto error; + + /* real time priority */ + if (rt_prio > 0) { + struct sched_param schedp; + int rc; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = rt_prio; + rc = sched_setscheduler(0, SCHED_RR, &schedp); + if (rc) + fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio); + } + + printf("PSTN endpoint ready, waiting for V5 application to connect...\n"); + + print_recall(); + + if (tones_type == TONES_TYPE_OLDGERMAN) { + printf("********************\n"); + printf("DTMF may not work with old German dial tone, because it disturbs the DTMF tones!\n"); + printf("********************\n"); + } + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + while (!quit) { + int w; + process_timer(); + pstn_work(pstn_ep); + rtp_work(pstn_ep); + do { + w = 0; + w |= osmo_cc_handle(); + } while (w); + usleep(1000); + } + + signal(SIGINT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + /* reset real time prio */ + if (rt_prio > 0) { + struct sched_param schedp; + + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &schedp); + } + +error: + if (pstn_ep) { + osmo_cc_delete(&pstn_ep->cc_ep); + pstn_destroy(pstn_ep); + } + + options_free(); + + /* exit fm */ + fm_exit(); + + return 0; +} + diff --git a/src/pstn/pstn.c b/src/pstn/pstn.c new file mode 100644 index 0000000..954468b --- /dev/null +++ b/src/pstn/pstn.c @@ -0,0 +1,1717 @@ +/* POTS/PSTN FXS implementation + * + * (C) 2022 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libsample/sample.h" +#include "../libg711/g711.h" +#include "pstn.h" +#include "../libosmocc/helper.h" + +#define db2level(db) pow(10, (double)(db) / 20.0) + +#define DIALTONE_TO 60.0 +#define DIALING_TO 20.0 +#define RELEASE_TO 60.0 +#define HOOKFLASH_TO 1.1 /* 1100 ms */ + +#define TONE_CW (pstn->tones_type != TONES_TYPE_AMERICAN) ? TONE_GERMAN_CW : TONE_AMERICAN_CW +#define TONE_DIALTONE (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_DIALTONE : TONE_GERMAN_OLDDIALTONE) : TONE_AMERICAN_DIALTONE +#define TONE_BUSY (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_BUSY : TONE_GERMAN_OLDBUSY) : TONE_AMERICAN_BUSY +#define TONE_HANGUP (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_HANGUP : TONE_GERMAN_OLDHANGUP) : TONE_AMERICAN_HANGUP +#define TONE_GASSENBESETZT (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_GASSENBESETZT : TONE_GERMAN_OLDBUSY) : TONE_AMERICAN_BUSY +#define TONE_RINGING (pstn->tones_type != TONES_TYPE_AMERICAN) ? ((pstn->tones_type != TONES_TYPE_OLDGERMAN) ? TONE_GERMAN_RINGING : TONE_GERMAN_OLDRINGING) : TONE_AMERICAN_RINGING + +/* Uncomment this, to hear caller ID even after answering: */ +//#define TEST_CALLERID + +static void v5_sig_ind(pstn_t *pstn, uint8_t *data, int len); +static void v5_est_ack_req(pstn_t *pstn); +static void v5_sig_req(pstn_t *pstn, uint8_t *ie, int length); +static void v5_disc_req_and_cleanup(pstn_t *pstn); + +void __attribute__((noinline)) safe_strcpy(char *dst, const char *src, size_t size) { strncpy(dst, src, size - 1); } + +static struct osmo_cc_helper_audio_codecs codecs_alaw_ulaw[] = { + { "PCMA", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, + { "PCMU", 8000, 1, g711_transcode_alaw_flipped_to_ulaw, g711_transcode_ulaw_to_alaw_flipped }, + { NULL, 0, 0, NULL, NULL}, +}; + +static struct osmo_cc_helper_audio_codecs codecs_ulaw_alaw[] = { + { "PCMU", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, + { "PCMA", 8000, 1, g711_transcode_ulaw_flipped_to_alaw, g711_transcode_alaw_to_ulaw_flipped }, + { NULL, 0, 0, NULL, NULL}, +}; + +static const char *pstn_state_name(enum pstn_state state) +{ + switch (state) { + case PSTN_STATE_OOS: + return "OUT-OF-SERVICE"; + case PSTN_STATE_NULL: + return "NULL"; + case PSTN_STATE_EST_LE: + return "ESTABLISH-LE"; + case PSTN_STATE_EST_AN: + return "ESTABLISH-AN"; + case PSTN_STATE_ACTIVE: + return "ACTIVE"; + case PSTN_STATE_DISC_REQ: + return "DISC-REQ"; + case PSTN_STATE_BLOCKED: + return "BLOCKED"; + default: + return ""; + } +}; + +static const char *pstn_event_name(enum pstn_event event) +{ + switch (event) { + case PSTN_EVENT_EST_REQ: + return "FE-establish_request"; + case PSTN_EVENT_EST_ACK_IND: + return "FE-establish_acknowlege_indication"; + case PSTN_EVENT_EST_IND: + return "FE-establish_indication"; + case PSTN_EVENT_EST_ACK_REQ: + return "FE-establish_scknowlege_request"; + case PSTN_EVENT_SIG_REQ: + return "FE-line_signal_request"; + case PSTN_EVENT_SIG_IND: + return "FE-line_signal_indication"; + case PSTN_EVENT_PARAM_REQ: + return "FE-protocol_parameter_request"; + case PSTN_EVENT_DISC_REQ: + return "FE-disconnect_request"; + case PSTN_EVENT_DISC_CPL_REQ: + return "FE-disconnect_complete_request"; + case PSTN_EVENT_DISC_CPL_IND: + return "FE-disconnect_complete_inidcation"; + default: + return ""; + } +}; + +static const char *pstn_call_state_name(enum pstn_call_state state) +{ + switch (state) { + case CALL_STATE_NULL: + return "NULL"; + case CALL_STATE_ENBLOCK: + return "ENBLOCK"; + case CALL_STATE_ALERTING_SUB: + return "ALERTING-SUB"; + case CALL_STATE_OVERLAP_NET: + return "OVERLAP-NET"; + case CALL_STATE_PROCEEDING_NET: + return "PROCEEDING-NET"; + case CALL_STATE_ALERTING_NET: + return "ALERTING-NET"; + case CALL_STATE_ACTIVE: + return "ACTIVE"; + case CALL_STATE_HOLD: + return "HOLD"; + case CALL_STATE_DISCONNECT_NET: + return "DISCONNECT-NET"; + default: + return ""; + } +}; + +static const char *timer_ident_name(enum timer_ident ident) +{ + switch (ident) { + case TIMER_IDENT_DIALING: + return "DIALING"; + case TIMER_IDENT_RELEASE: + return "RELEASE"; + case TIMER_IDENT_HOOKFLASH: + return "HOOKFLASH"; + default: + return ""; + } +}; + +/* + * Endpoint instance + */ + +static void pstn_call_state(struct call *pstn_call, enum pstn_call_state state) +{ + PDEBUG(DTEL, DEBUG_DEBUG, "PSTN CALL state '%s' -> '%s'\n", pstn_call_state_name(pstn_call->state), pstn_call_state_name(state)); + pstn_call->state = state; +} + + +void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length); +static void dtmf_off(pstn_t *pstn); + +/* create interface instance */ +pstn_t *pstn_create(void) +{ + pstn_t *pstn; + int i; + + pstn = calloc(1, sizeof(*pstn)); + if (!pstn) { + PDEBUG(DTEL, DEBUG_ERROR, "No memory!\n"); + abort(); + } + + for (i = 0; i < 2; i++) { + pstn->call[i] = calloc(1, sizeof(struct call)); + if (!pstn->call[i]) { + PDEBUG(DTEL, DEBUG_ERROR, "No memory!\n"); + abort(); + } + pstn->call[i]->pstn = pstn; + } + + PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance created\n"); + + return pstn; +} + +static void pulse_on(pstn_t *pstn) +{ + if (pstn->pulse_on) + return; + + PDEBUG(DTEL, DEBUG_DEBUG, "Enable reception of pulse dialing.\n"); + + pstn->pulse_on = 1; +} + +static void pulse_off(pstn_t *pstn) +{ + if (!pstn->pulse_on) + return; + + PDEBUG(DTEL, DEBUG_DEBUG, "Disable reception of pulse dialing.\n"); + + pstn->pulse_on = 0; +} + +/* release call towards osmo-cc, if still connected and remove association with pstn instance */ +static void release_call(pstn_t *pstn, int hold, uint8_t isdn_cause) +{ + osmo_cc_call_t *cc_call; + osmo_cc_msg_t *new_msg; + struct call *pstn_call = pstn->call[hold]; + + PDEBUG(DTEL, DEBUG_INFO, "Release %s towards Osmo-CC with ISDN cause %d, if exists.\n", (hold) ? "call on hold" : "active call", isdn_cause); + + /* get call state */ + cc_call = osmo_cc_call_by_callref(&pstn->cc_ep, pstn_call->cc_callref); + if (cc_call) { + /* on release request, we confirm */ + /* create osmo-cc message */ + if (cc_call->state == OSMO_CC_STATE_RELEASING_OUT) + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_CNF); + else if (cc_call->state == OSMO_CC_STATE_INIT_OUT) + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + else + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); + /* cause */ + osmo_cc_add_ie_cause(new_msg, pstn->serving_location, isdn_cause, 0, 0); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); + /* unlink callref */ + pstn_call->cc_callref = 0; + } + + /* free session description */ + if (pstn_call->cc_session) { + osmo_cc_free_session(pstn_call->cc_session); + pstn_call->cc_session = NULL; + pstn_call->codec = NULL; + } + + /* free sdp */ + if (pstn_call->sdp) { + free((char *)pstn_call->sdp); + pstn_call->sdp = NULL; + } + + /* reset state */ + pstn_call_state(pstn_call, CALL_STATE_NULL); +} + +/* destroy interface instance and free all resource */ +void pstn_destroy(pstn_t *pstn) +{ + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + + /* release association with osmo-cc */ + release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_DEST_OOO); + release_call(pstn, PSTN_CALL_HOLD, OSMO_CC_ISDN_CAUSE_DEST_OOO); + + /* exit callerid */ + callerid_exit(&pstn->callerid); + + /* exit dtmf detector */ + dtmf_decode_exit(&pstn->dtmf_dec); + + /* close ph socket */ + ph_socket_exit(&pstn->ph_socket); + + /* free jitter buffer */ + jitter_destroy(&pstn->tx_dejitter); + + /* destroy timer */ + timer_exit(&pstn->timer); + + free(pstn->call[0]); + free(pstn->call[1]); + + free((char *)pstn->name); + + free(pstn); + + PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance destroyed\n"); +} + +static void pstn_timeout(struct timer *timer); +void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas); + +static void pstn_new_state(pstn_t *pstn, enum pstn_state state) +{ + PDEBUG(DTEL, DEBUG_DEBUG, "PSTN state '%s' -> '%s'\n", pstn_state_name(pstn->state), pstn_state_name(state)); + pstn->state = state; +} + +/* initialization and configuration of interface instance */ +int pstn_init(pstn_t *pstn, const char *name, const char *socketname, const char **subscribers, int subscriber_num, uint8_t serving_location, int tx_delay, int clip, int cid_bell, int cid_dtmf, int cid_date, int enblock, int recall, int *ringing_types_incoming, int ringing_type_hold, enum tones_type tones_type, char law) +{ + int i; + int rc; + + pstn->law = law; + pstn->name = strdup(name); + pstn->subscribers = subscribers; + pstn->subscriber_num = subscriber_num; + pstn->serving_location = serving_location; + pstn->tx_delay = tx_delay; + pstn->clip = clip; + pstn->cid_bell = cid_bell; + pstn->cid_dtmf = cid_dtmf; + pstn->cid_date = cid_date; + pstn->enblock = enblock; + pstn->recall = recall; + pstn->ringing_types_incoming = ringing_types_incoming; + pstn->ringing_type_hold = ringing_type_hold; + pstn->tones_type = tones_type; + + /* create ph socket */ + rc = ph_socket_init(&pstn->ph_socket, ph_socket_rx_msg, pstn, socketname, 0); + if (rc < 0) + return rc; + + /* init DTMF detector */ + rc = dtmf_decode_init(&pstn->dtmf_dec, pstn, recv_dtmf, 8000, db2level(6.0), db2level(-30.0)); + if (rc < 0) + return rc; + + /* create callerid generator */ + rc = callerid_init(&pstn->callerid, 8000, cid_bell, cid_dtmf); + if (rc < 0) + return rc; + + /* create timer */ + timer_init(&pstn->timer, pstn_timeout, pstn); + + /* allocate jitter buffer */ + if (pstn->tx_delay) + rc = jitter_create(&pstn->tx_dejitter, "tx", 8000, sizeof(uint8_t), (double)pstn->tx_delay / 1000.0, (double)pstn->tx_delay / 1000.0 * 2.0, JITTER_FLAG_NONE); + else + rc = jitter_create(&pstn->tx_dejitter, "tx", 8000, sizeof(uint8_t), JITTER_AUDIO); + if (rc < 0) + return rc; + + PDEBUG(DTEL, DEBUG_DEBUG, "PSTN instance initialized (name=%s socketname=%s subscribers=%d, serving_location=%d, tx_delay=%d, clip=%d, cid_bell=%d, cid_dtmf=%d, cid_date=%d, enblock=%d, recall=%d, ringing_type_hold=%d\n", name, socketname, subscriber_num, serving_location, tx_delay, clip, cid_bell, cid_dtmf, cid_date, enblock, recall, ringing_type_hold); + for (i = 0; i < subscriber_num; i++) + PDEBUG(DTEL, DEBUG_DEBUG, " -> subscriber '%s', ringing_type_incoming %d\n", subscribers[i], ringing_types_incoming[i]); + + /* bring into service */ + pstn_new_state(pstn, PSTN_STATE_BLOCKED); + + return 0; +} + +/* + * audio handling + */ + +/* take audio from CC (this is alaw or ulaw as specified) and store in jitter buffer */ +void rtp_receive(struct osmo_cc_session_codec *codec, uint8_t __attribute__((unused)) marker, uint16_t sequence, uint32_t timestamp, uint32_t ssrc, uint8_t *data, int len) +{ + struct call *pstn_call = codec->media->session->priv; + pstn_t *pstn = pstn_call->pstn; + + /* not the active call, drop audio */ + if (pstn_call != pstn->call[PSTN_CALL_ACTIVE]) + return; + + jitter_save(&pstn->tx_dejitter, data, len, 1, sequence, timestamp, ssrc); +} + + +void rtp_work(pstn_t *pstn) +{ + int i; + + for (i = 0; i < 2; i++) { + if (pstn->call[i]->cc_session) + osmo_cc_session_handle(pstn->call[i]->cc_session, pstn->call[i]); + } +} + +static void dtmf_on(pstn_t *pstn) +{ + if (pstn->dtmf_on) + return; + + PDEBUG(DTEL, DEBUG_DEBUG, "Turn DTMF detection on.\n"); + + /* our working level is 0dBm */ + dtmf_decode_reset(&pstn->dtmf_dec); + + pstn->dtmf_on = 1; +} + +static void dtmf_off(pstn_t *pstn) +{ + if (!pstn->dtmf_on) + return; + + PDEBUG(DTEL, DEBUG_DEBUG, "Turn DTMF detection off.\n"); + + pstn->dtmf_on = 0; +} + +static void timer_on(pstn_t *pstn, double timeout, enum timer_ident ident) +{ + PDEBUG(DTEL, DEBUG_DEBUG, "Start %s timer with %.1f seconds.\n", timer_ident_name(ident), timeout); + timer_start(&pstn->timer, timeout); + pstn->timer_ident = ident; +} + +static void timer_off(pstn_t *pstn) +{ + if (timer_running(&pstn->timer)) { + PDEBUG(DTEL, DEBUG_DEBUG, "Stop %s timer.\n", timer_ident_name(pstn->timer_ident)); + timer_stop(&pstn->timer); + } +} + +static void callerid_on(pstn_t *pstn, int cw, const char *callerid, uint8_t caller_type) +{ + /* DTMF on CW not supported */ + if (cw && pstn->cid_dtmf) { + PDEBUG(DTEL, DEBUG_INFO, "DTMF CID is not allowed for waiting call, don't sending CID.\n"); + return; + } + + PDEBUG(DTEL, DEBUG_DEBUG, "Schedule caller ID transmission. (cw=%d, callerid=%s)\n", cw, callerid); + + /* add DT_AS on waitng call */ + callerid_set(&pstn->callerid, cw, (cw) ? 1 : 0, callerid, caller_type, pstn->cid_date); + + if (cw) { + pstn->callerid_state = PSTN_CID_STATE_WAIT1; + /* add delay when cw */ + pstn->callerid_wait1 = 8000; + pstn->callerid_wait2 = 0; + } else { + pstn->callerid_state = PSTN_CID_STATE_WAIT1; + /* add delay when ringing */ + pstn->callerid_wait1 = 8000; + pstn->callerid_wait2 = 16000; + } + + /* turn DTMF on, to detect TE-ACK */ + if (cw) { + /* start DTMF */ + dtmf_on(pstn); + } +} + +static void callerid_off(pstn_t *pstn) +{ + if (pstn->callerid_state == PSTN_CID_STATE_OFF) + return; + + PDEBUG(DTEL, DEBUG_DEBUG, "Cancel caller ID transmission.\n"); + + pstn->callerid_state = PSTN_CID_STATE_OFF; + + /* stop DTMF */ + dtmf_off(pstn); +} + +static void tone_off(pstn_t *pstn) +{ + if (pstn->isdn_tone.tone != TONE_OFF) { + /* stop tone */ + PDEBUG(DTEL, DEBUG_DEBUG, "Stop tone.\n"); + isdn_tone_set(&pstn->isdn_tone, TONE_OFF); + } +} + +static void tone_on(pstn_t *pstn, int tone, const char *name) +{ + if (pstn->isdn_tone.tone != tone) { + PDEBUG(DTEL, DEBUG_DEBUG, "Set %s tone.\n", name); + isdn_tone_set(&pstn->isdn_tone, tone); + } +} + +static void setup_ind(pstn_t *pstn, int hold, const char *called, int complete); + +void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas) +{ + pstn_t *pstn = (pstn_t *)priv; + struct call *pstn_call = pstn->call[PSTN_CALL_ACTIVE]; + osmo_cc_msg_t *new_msg; + + PDEBUG(DTEL, DEBUG_INFO, "Received DTMF digit '%c'.\n", digit); + + /* send DTMF tones to caller ID process */ + if (pstn->callerid_state == PSTN_CID_STATE_SEND) { + callerid_te_ack(&pstn->callerid, digit); + return; + } + + /* stop pulse */ + pulse_off(pstn); + /* stop tone */ + tone_off(pstn); + /* stop timer */ + timer_off(pstn); + /* if we are receiving digits en block */ + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK) { + if (digit == '#') { + PDEBUG(DTEL, DEBUG_DEBUG, "Number is complete, send setup\n"); + /* setup (en block) */ + setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); + return; + } + PDEBUG(DTEL, DEBUG_DEBUG, "Storing digit, because we perform enblock dialing.\n"); + /* append digit */ + char called[2] = { digit, '\0' }; + strncat(pstn->dialing, called, sizeof(pstn->dialing) - 1); + PDEBUG(DTEL, DEBUG_DEBUG, "Appending digit, so dial string is now: '%s'.\n", pstn->dialing); + /* start dial timer */ + timer_on(pstn, (double)pstn->enblock, TIMER_IDENT_DIALING); + return; + } + PDEBUG(DTEL, DEBUG_INFO, "Sending INFO-IND with DTMF digit '%c' towards Osmo-CC\n", digit); + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); + /* called */ + char called[2] = { digit, '\0' }; + osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); +} + +#define COMFORT_NOISE (0.02 * SPEECH_LEVEL) /* audio level of comfort noise (relative to ISDN level) */ + +static void send_noise(struct call *pstn_call, int len) +{ + int16_t noise[len], r; + uint8_t *law_noise; + int len_noise; + int i; + + for (i = 0; i < len; i++) { + r = random(); + noise[i] = (double)r * COMFORT_NOISE; + } + if (pstn_call->pstn->law == 'a') + g711_encode_alaw_flipped((uint8_t *)noise, 160 * 2, &law_noise, &len_noise, NULL); + else + g711_encode_ulaw_flipped((uint8_t *)noise, 160 * 2, &law_noise, &len_noise, NULL); + osmo_cc_rtp_send(pstn_call->codec, law_noise, len_noise, 0, 1, len_noise, pstn_call); + free(law_noise); +} + +static void bchannel_rx_tx(pstn_t *pstn, uint8_t *data, int len) +{ + uint8_t *buffer = pstn->tx_buffer; + int *buffer_pos = &(pstn->tx_buffer_pos); + int i; + + /* reception */ + + if (pstn->dtmf_on) { + sample_t samples[len]; + int16_t *spl; + int spl_len; + if (pstn->law == 'a') + g711_decode_alaw_flipped(data, len, (uint8_t **)&spl, &spl_len, NULL); + else + g711_decode_ulaw_flipped(data, len, (uint8_t **)&spl, &spl_len, NULL); + int16_to_samples_1mw(samples, spl, len); + free(spl); + dtmf_decode(&pstn->dtmf_dec, samples, len); + } + + /* transmission */ + + /* mute when there is no active call OR when there is caller ID transmission */ + if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE || pstn->callerid_state == PSTN_CID_STATE_SEND) + memset(data, (pstn->law == 'a') ? 0x2a : 0xff, len); + + /* add to buffer and send via RTP */ + for (i = 0; i < len; i++) { + buffer[(*buffer_pos)++] = data[i]; + if (*buffer_pos == 160) { + *buffer_pos = 0; + /* only send, if codec is negotiated, send comfort noise for call on hold or ringing call on hold */ + if (pstn->call[PSTN_CALL_ACTIVE]->codec) { + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_HOLD) + send_noise(pstn->call[PSTN_CALL_ACTIVE], 160); + else + osmo_cc_rtp_send(pstn->call[PSTN_CALL_ACTIVE]->codec, buffer, 160, 0, 1, 160, pstn->call[PSTN_CALL_ACTIVE]); + } + if (pstn->call[PSTN_CALL_HOLD]->codec && pstn->call[PSTN_CALL_HOLD]->state == CALL_STATE_HOLD) + send_noise(pstn->call[PSTN_CALL_HOLD], 160); + } + } + + uint8_t init_data[len * 2]; + int offset = 0; + if (!pstn->b_transmitting) { + PDEBUG(DTEL, DEBUG_DEBUG, "First received b-channel data, filling FIFO with double data of %d bytes.\n", len * 2); + memset(init_data, 0xff, len); + data = init_data; + offset = len; + } + + /* load from TX jitter buffer and optionally overload with tones an with caller ID */ + jitter_load(&pstn->tx_dejitter, data + offset, len); + isdn_tone_copy(&pstn->isdn_tone, data + offset, len); + + switch (pstn->callerid_state) { + case PSTN_CID_STATE_OFF: + break; + case PSTN_CID_STATE_WAIT1: + /* wait before sending callerid */ + pstn->callerid_wait1 -= len; + if (pstn->callerid_wait1 <= 0) { + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { + PDEBUG(DTEL, DEBUG_DEBUG, "Stop ringing.\n"); + uint8_t ie[3] = { PSTN_V5_IE_STEADY_SIGNAL, 1, 0x80 | 0x0e}; + v5_sig_req(pstn, ie, sizeof(ie)); + } + PDEBUG(DTEL, DEBUG_DEBUG, "Start sending caller ID.\n"); + pstn->callerid_state = PSTN_CID_STATE_SEND; + } + break; + case PSTN_CID_STATE_SEND: + { + sample_t samples[len]; + int16_t spl[len]; + uint8_t *data_cid; + int len_cid; + int rc; + rc = callerid_send(&pstn->callerid, samples, len); + if (rc) { + samples_to_int16_1mw(spl, samples, rc); + if (pstn->law == 'a') + g711_encode_alaw_flipped((uint8_t *)spl, rc * 2, &data_cid, &len_cid, NULL); + else + g711_encode_ulaw_flipped((uint8_t *)spl, rc * 2, &data_cid, &len_cid, NULL); + memcpy(data + offset, data_cid, len_cid); + free(data_cid); + } + if (rc < len) { + /* caller ID transmission has finished */ + PDEBUG(DTEL, DEBUG_DEBUG, "Done sending caller ID.\n"); + /* stop DTMF */ + dtmf_off(pstn); + pstn->callerid_state = PSTN_CID_STATE_WAIT2; + } + break; + } + case PSTN_CID_STATE_WAIT2: + /* wait before sending callerid */ + pstn->callerid_wait2 -= len; + if (pstn->callerid_wait2 <= 0) { + pstn->callerid_state = PSTN_CID_STATE_OFF; + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { + PDEBUG(DTEL, DEBUG_DEBUG, "Continue with ringing.\n"); + uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_types_incoming[pstn->call[PSTN_CALL_ACTIVE]->subscriber_index] }; + v5_sig_req(pstn, ie, sizeof(ie)); + } + } + break; + } + + /* forward to interface */ + ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DATA_REQ, data, len + offset); + + pstn->b_transmitting = 1; +} + +/* + * handle message from CC + */ + +static void reject_ind(pstn_t *pstn, uint32_t callref, uint8_t isdn_cause) +{ + osmo_cc_msg_t *new_msg; + + PDEBUG(DTEL, DEBUG_INFO, "Sending REJ-IND with ISDN cause %d towards Osmo-CC.\n", isdn_cause); + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + /* cause */ + osmo_cc_add_ie_cause(new_msg, pstn->serving_location, isdn_cause, 0, 0); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, callref, new_msg); +} + +static void notify_ind(struct call *pstn_call, uint8_t notify) +{ + osmo_cc_msg_t *new_msg; + + if (pstn_call->on_hold && notify == OSMO_CC_NOTIFY_REMOTE_HOLD) + return; + if (!pstn_call->on_hold && notify == OSMO_CC_NOTIFY_REMOTE_RETRIEVAL) + return; + + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + /* notify the facility */ + osmo_cc_add_ie_notify(new_msg, notify); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn_call->pstn->cc_ep, pstn_call->cc_callref, new_msg); + + if (notify == OSMO_CC_NOTIFY_REMOTE_HOLD) + pstn_call->on_hold = 1; + if (notify == OSMO_CC_NOTIFY_REMOTE_RETRIEVAL) + pstn_call->on_hold = 1; +} + +static void set_tone_cause(pstn_t *pstn, uint8_t isdn_cause) +{ + switch (isdn_cause) { + case OSMO_CC_ISDN_CAUSE_USER_BUSY: + case OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND: + case OSMO_CC_ISDN_CAUSE_USER_ALERTING_NA: + tone_on(pstn, TONE_BUSY, "busy"); + break; + case OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR: + tone_on(pstn, TONE_HANGUP, "hangup"); + break; + case OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN: + tone_on(pstn, TONE_GASSENBESETZT, "congestion"); + break; + default: + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + } +} + +static void v5_est_req(pstn_t *pstn, uint8_t *ie, int length); + +void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) +{ + pstn_t *pstn = ep->priv; + struct osmo_cc_helper_audio_codecs *codecs; + osmo_cc_msg_t *new_msg; + uint8_t caller_type, caller_plan, caller_present, caller_screen; + uint8_t dialing_type, dialing_plan; + uint8_t capability, mode; + char callerid[128], dialing[128]; + uint8_t coding, location, progress, socket_cause; + uint8_t isdn_cause; + uint16_t sip_cause; + struct call *pstn_call = NULL; + const char *sdp; + int hold = 0; + int i; + int rc; + + /* hunt for callref */ + if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref == callref) { + pstn_call = pstn->call[PSTN_CALL_ACTIVE]; + hold = 0; + } + if (pstn->call[PSTN_CALL_HOLD]->cc_callref == callref) { + pstn_call = pstn->call[PSTN_CALL_HOLD]; + hold = 1; + } + + /* process SETUP */ + if (!pstn_call) { + if (msg->type != OSMO_CC_MSG_SETUP_REQ) { + PDEBUG(DTEL, DEBUG_ERROR, "received message without call instance, please fix!\n"); + goto done; + } + PDEBUG(DTEL, DEBUG_INFO, "Received new SETUP-REQ from Osmo-CC\n"); + /* called */ + rc = osmo_cc_get_ie_called(msg, 0, &dialing_type, &dialing_plan, dialing, sizeof(dialing)); + if (rc < 0) + dialing[0] = '\0'; + /* bearer capability */ + rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); + if (rc < 0) { + PDEBUG(DTEL, DEBUG_DEBUG, "No bearer capability given, assuming audio.\n"); + capability = OSMO_CC_CAPABILITY_AUDIO; + } + + /* search for interface */ + if (!pstn) { + // this will never happen, unless we implement multiple PSTN interface support + PDEBUG(DTEL, DEBUG_INFO, "No interface for subscriber '%s', rejecting call.\n", dialing); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_UNASSIGNED_NR); + goto done; + } + /* if not a voice/speech call */ + if (capability != OSMO_CC_CAPABILITY_AUDIO && capability != OSMO_CC_CAPABILITY_SPEECH) { + PDEBUG(DTEL, DEBUG_INFO, "Bearer is not voice/speech, rejecting call.\n"); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST); + goto done; + } + /* reject if port is out of service */ + if (pstn->state == PSTN_STATE_OOS) { + PDEBUG(DTEL, DEBUG_INFO, "Interface is out of service, rejecting call.\n"); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_DEST_OOO); + goto done; + } + /* reject if port is blocked */ + if (pstn->state == PSTN_STATE_BLOCKED) { + PDEBUG(DTEL, DEBUG_INFO, "Interface is blocked, rejecting call.\n"); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_DEST_OOO); + goto done; + } + /* reject, if busy */ + if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref && (pstn->call[PSTN_CALL_HOLD]->cc_callref || !pstn->recall)) { + PDEBUG(DTEL, DEBUG_INFO, "We are busy, rejecting call.\n"); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_USER_BUSY); + goto done; + } + /* reject if calle not in NULL or ACTIVE state */ + if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_NULL && pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE) { + PDEBUG(DTEL, DEBUG_INFO, "Call is not in NULL or active state, rejecting call.\n"); + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_USER_BUSY); + goto done; + } + /* select call and link with cc */ + if (!pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { + pstn_call = pstn->call[PSTN_CALL_ACTIVE]; + hold = 0; + } else { + pstn_call = pstn->call[PSTN_CALL_HOLD]; + hold = 1; + } + pstn_call->cc_callref = callref; + pstn_call->on_hold = 0; + } + + switch (msg->type) { + case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */ + /* calling */ + rc = osmo_cc_get_ie_calling(msg, 0, &caller_type, &caller_plan, &caller_present, &caller_screen, callerid, sizeof(callerid)); + if (rc < 0) + callerid[0] = '\0'; + /* called */ + rc = osmo_cc_get_ie_called(msg, 0, &dialing_type, &dialing_plan, dialing, sizeof(dialing)); + if (rc < 0) + dialing[0] = '\0'; + PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-REQ call (from '%s' to '%s') from Osmo-CC.\n", callerid, dialing); + /* now set subscriber index by looking at the dialed subscriber number. if not found, use index 0 */ + /* note: digits after subscriber number are ignored, so it matches anyway */ + for (i = 0; i < pstn->subscriber_num; i++) { + if (!strncasecmp(dialing, pstn->subscribers[i], strlen(pstn->subscribers[i]))) + break; + } + if (i == pstn->subscriber_num) + i = 0; + pstn_call->subscriber_index = i; + PDEBUG(DTEL, DEBUG_INFO, "After screening, call (from '%s' to '%s') is performed.\n", callerid, pstn->subscribers[pstn_call->subscriber_index]); + /* select codec */ + if (pstn->law == 'a') + codecs = codecs_alaw_ulaw; + else + codecs = codecs_ulaw_alaw; + /* sdp accept */ + sdp = osmo_cc_helper_audio_accept(&ep->session_config, pstn_call, codecs, rtp_receive, msg, &pstn_call->cc_session, &pstn_call->codec, 0); + if (!sdp) { + reject_ind(pstn, callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + break; + } + pstn_call->sdp = strdup(sdp); + PDEBUG(DTEL, DEBUG_INFO, "Sending ALERT-IND towards Osmo-CC\n"); + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); + /* if call is waiting, send notify to caller */ + if (hold) { + /* CW tone */ + tone_on(pstn, TONE_CW, "CW"); + if (pstn->clip) { + /* send callerid */ + callerid_on(pstn, 1, callerid, caller_type); + } + /* add notify to waiting call */ + PDEBUG(DTEL, DEBUG_INFO, "Adding call waiting notification towards Osmo-CC\n"); + osmo_cc_add_ie_notify(new_msg, OSMO_CC_NOTIFY_CALL_IS_A_WAITING_CALL); + } + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); + if (pstn->state == PSTN_STATE_NULL) { + /* send message to V5 */ + uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_types_incoming[pstn_call->subscriber_index] }; + v5_est_req(pstn, ie, sizeof(ie)); + if (pstn->clip) { + /* send callerid */ + callerid_on(pstn, 0, callerid, caller_type); + } + /* change state */ + pstn_new_state(pstn, PSTN_STATE_EST_LE); + } + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_ALERTING_SUB); + break; + case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */ + PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-ACK-REQ (overlap dialing) from Osmo-CC.\n"); + /* stop tone */ + tone_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + break; + } + /* set audio path */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + pstn->audio_path = 1; + /* if we do enblock dialing, number is already complete, so no dial tone required */ + if (!pstn->enblock) { + if (!pstn->audio_path) { + /* dial tone */ + tone_on(pstn, TONE_DIALTONE, "dial"); + } + } + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_OVERLAP_NET); + break; + case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */ + PDEBUG(DTEL, DEBUG_INFO, "Received PROC-REQ (proceeding) from Osmo-CC.\n"); + /* stop tone */ + tone_off(pstn); + /* stop timer */ + timer_off(pstn); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + break; + } + /* set audio path */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + pstn->audio_path = 1; + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_PROCEEDING_NET); + break; + case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */ + PDEBUG(DTEL, DEBUG_INFO, "Received ALERT-REQ (alerting) from Osmo-CC.\n"); + /* stop tone */ + tone_off(pstn); + /* stop timer */ + timer_off(pstn); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + break; + } + /* set audio path */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + pstn->audio_path = 1; + if (!pstn->audio_path) { + /* ringback tone */ + tone_on(pstn, TONE_RINGING, "ringback"); + } + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_ALERTING_NET); + break; + case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */ + PDEBUG(DTEL, DEBUG_INFO, "Received SETUP-RSP (answer) from Osmo-CC.\n"); + /* set audio path */ + pstn->audio_path = 1; + /* stop tone */ + tone_off(pstn); + /* stop timer */ + timer_off(pstn); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + break; + } + PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-COMP-IND towards Osmo-CC\n"); + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_ACTIVE); + break; + case OSMO_CC_MSG_SETUP_COMP_REQ: /* call of endpoint is connected */ + break; + case OSMO_CC_MSG_INFO_REQ: /* overlap dialing */ + break; + case OSMO_CC_MSG_PROGRESS_REQ: /* progress */ + PDEBUG(DTEL, DEBUG_INFO, "Received PROGRESS-REQ from Osmo-CC.\n"); + /* stop timer */ + timer_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + break; + } + /* set audio path */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + pstn->audio_path = 1; + break; + case OSMO_CC_MSG_NOTIFY_REQ: /* display and notifications */ + break; + case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */ + case OSMO_CC_MSG_REL_REQ: /* release call */ + PDEBUG(DTEL, DEBUG_INFO, "Received REJ-REQ/REL-REQ from Osmo-CC.\n"); + /* get cause */ + rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); + if (rc < 0) + isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + else + PDEBUG(DTEL, DEBUG_INFO, " -> ISDN cause = %d, SIP cause = %d, socket cause = %d\n", isdn_cause, sip_cause, socket_cause); + /* if call is on hold, stop call waiting signal and release */ + if (hold) { + PDEBUG(DTEL, DEBUG_INFO, "Releasing call on hold.\n"); + if (pstn->isdn_tone.tone == TONE_GERMAN_CW + || pstn->isdn_tone.tone == TONE_AMERICAN_CW) { + /* stop tone */ + tone_off(pstn); + } + release_call(pstn, hold, isdn_cause); + break; + } + /* stop timer */ + timer_off(pstn); + /* release ringing call (or call that rings because it is on hold) */ + if (pstn_call->state == CALL_STATE_ALERTING_SUB + || pstn_call->state == CALL_STATE_HOLD) { + release_call(pstn, hold, isdn_cause); + /* there is no more call, release V5 */ + v5_disc_req_and_cleanup(pstn); + break; + } + release_call(pstn, hold, isdn_cause); + /* play cause tone */ + set_tone_cause(pstn, isdn_cause); + break; + case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */ + PDEBUG(DTEL, DEBUG_INFO, "Received DISC-REQ from Osmo-CC.\n"); + /* get cause */ + rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); + if (rc < 0) + isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + else + PDEBUG(DTEL, DEBUG_INFO, " -> ISDN cause = %d, SIP cause = %d, socket cause = %d\n", isdn_cause, sip_cause, socket_cause); + /* if call is on hold, stop call waiting signal and release */ + if (hold) { + PDEBUG(DTEL, DEBUG_INFO, "Releasing call on hold.\n"); + if (pstn->isdn_tone.tone == TONE_GERMAN_CW + || pstn->isdn_tone.tone == TONE_AMERICAN_CW) { + /* stop tone */ + tone_off(pstn); + } + release_call(pstn, hold, isdn_cause); + break; + } + /* stop timer */ + timer_off(pstn); + /* negotiate audio */ + rc = osmo_cc_helper_audio_negotiate(msg, &pstn_call->cc_session, &pstn_call->codec); + if (rc < 0) { + release_call(pstn, hold, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + break; + } + /* release ringing call (or call that rings because it is on hold) */ + if (pstn_call->state == CALL_STATE_ALERTING_SUB + || pstn_call->state == CALL_STATE_HOLD) { + release_call(pstn, hold, isdn_cause); + /* there is no more call, release V5 */ + v5_disc_req_and_cleanup(pstn); + break; + } + /* set audio path */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (!rc && coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + pstn->audio_path = 1; + else + pstn->audio_path = 0; + /* release if no progress, also release, if call is on hold */ + if (!pstn->audio_path) { + PDEBUG(DTEL, DEBUG_INFO, "no audio after disconnect, releasing!\n"); + release_call(pstn, hold, isdn_cause); + /* play cause tone */ + set_tone_cause(pstn, isdn_cause); + break; + } + /* stop tone */ + tone_off(pstn); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + /* start release timer */ + timer_on(pstn, RELEASE_TO, TIMER_IDENT_RELEASE); + /* change call state */ + pstn_call_state(pstn_call, CALL_STATE_DISCONNECT_NET); + break; + default: + PDEBUG(DTEL, DEBUG_ERROR, "Received an unsupported Osmo-CC message: %d\n", msg->type); + } + +done: + osmo_cc_free_msg(msg); +} + +/* + * handle message from V5-interface + */ + +static void v5_est_ack_ind(pstn_t *pstn, uint8_t *data, int len) +{ + /* change state */ + pstn_new_state(pstn, PSTN_STATE_ACTIVE); + + /* activate b-channel */ + ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_ACT_REQ, NULL, 0); + + if (len) { + v5_sig_ind(pstn, data, len); + } +} + +static void v5_est_ind(pstn_t *pstn, uint8_t *data, int len) +{ + /* change state */ + pstn_new_state(pstn, PSTN_STATE_ACTIVE); + + /* activate b-channel */ + ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_ACT_REQ, NULL, 0); + + /* send message to V5 */ + v5_est_ack_req(pstn); + + if (len) { + v5_sig_ind(pstn, data, len); + } +} + +static void setup_ind(pstn_t *pstn, int hold, const char *called, int complete) +{ + struct call *pstn_call = pstn->call[hold]; + osmo_cc_msg_t *new_msg; + struct osmo_cc_helper_audio_codecs *codecs; + + /* always use index 0 for subscriber */ + pstn_call->subscriber_index = 0; + PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-IND (from '%s' to '%s) towards Osmo-CC\n", pstn->subscribers[0], called); + /* setup message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND); + /* network type */ + osmo_cc_add_ie_calling_network(new_msg, OSMO_CC_NETWORK_POTS_NONE, ""); + if (pstn->subscribers[pstn_call->subscriber_index][0]) { + /* calling number, if subscriber is not a null string */ + osmo_cc_add_ie_calling(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, pstn->subscribers[pstn_call->subscriber_index]); + } + if (called && called[0]) { + /* called number */ + osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); + } + /* select codec */ + if (pstn->law == 'a') + codecs = codecs_alaw_ulaw; + else + codecs = codecs_ulaw_alaw; + /* dialing complete */ + if (complete) + osmo_cc_add_ie_complete(new_msg); + /* sdp offer */ + pstn_call->cc_session = osmo_cc_helper_audio_offer(&pstn->cc_ep.session_config, pstn_call, codecs, rtp_receive, new_msg, 1); + if (!pstn_call->cc_session) { + PDEBUG(DTEL, DEBUG_NOTICE, "Failed to offer audio, call aborted.\n"); + osmo_cc_free_msg(new_msg); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + return; + } + osmo_cc_call_t *cc_call = osmo_cc_call_new(&pstn->cc_ep); + pstn_call->cc_callref = cc_call->callref; + pstn_call->on_hold = 0; + /* send message to CC */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); + /* set initial audio path to off */ + pstn->audio_path = 0; +} + +static void setup_cnf(pstn_t *pstn, int hold) +{ + struct call *pstn_call = pstn->call[hold]; + osmo_cc_msg_t *new_msg; + + PDEBUG(DTEL, DEBUG_INFO, "Sending SETUP-CNF towards Osmo-CC\n"); + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF); + /* sdp */ + if (pstn_call->sdp) { + osmo_cc_add_ie_sdp(new_msg, pstn_call->sdp); + free((char *)pstn_call->sdp); + pstn_call->sdp = NULL; + } + if (pstn->subscribers[pstn_call->subscriber_index][0]) { + /* connected ID: use called subscriber, but only if not a null string */ + osmo_cc_add_ie_calling(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, pstn->subscribers[pstn_call->subscriber_index]); + } + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn_call->cc_callref, new_msg); +} + +static void swap_active_hold(pstn_t *pstn) +{ + struct call *temp; + + /* 3-ways swap */ + temp = pstn->call[0]; + pstn->call[0] = pstn->call[1]; + pstn->call[1] = temp; + + /* reset jitter buffer */ + jitter_reset(&pstn->tx_dejitter); +} + +static void v5_sig_ind(pstn_t *pstn, uint8_t *data, int len) +{ + osmo_cc_msg_t *new_msg; + + if (len < 3 && data[1] < 1) { + PDEBUG(DTEL, DEBUG_ERROR, "Received short V5 signal message, ignoring!\n"); + return; + } + + switch (data[0]) { + case PSTN_V5_IE_STEADY_SIGNAL: + switch ((data[2] & 0x7f)) { + case PSTN_V5_STEADY_SIGNAL_OFF_HOOK: + PDEBUG(DTEL, DEBUG_INFO, "Received steady off-kook signal.\n"); + if (timer_running(&pstn->timer) && pstn->timer_ident == TIMER_IDENT_HOOKFLASH) { + PDEBUG(DTEL, DEBUG_INFO, "This was short, so this was a hookflash..\n"); + /* stop timer */ + timer_off(pstn); + goto hookflash; + } + /* clear dialing */ + pstn->dialing[0] = '\0'; + /* if we have ringing call, we answer it or notify that it is now active */ + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ALERTING_SUB) { + PDEBUG(DTEL, DEBUG_INFO, "Alerting subscriber answered.\n"); +#ifndef TEST_CALLERID + /* stop caller id process */ + callerid_off(pstn); +#endif + /* setup confirm */ + setup_cnf(pstn, PSTN_CALL_ACTIVE); + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); + /* send notify to active call */ + notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); + break; + } + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_HOLD) { + PDEBUG(DTEL, DEBUG_INFO, "Subscriber on hold is retrieved.\n"); +#ifndef TEST_CALLERID + /* stop caller id process */ + callerid_off(pstn); +#endif + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); + /* send notify to active call */ + notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); + break; + } + /* if there is no call, send setup, if not en block dialing */ + if (!pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { + /* start DTMF */ + dtmf_on(pstn); + /* start pulse */ + pulse_on(pstn); + if (!pstn->enblock) { + /* setup */ + setup_ind(pstn, PSTN_CALL_ACTIVE, "", 0); + } else { + /* start dial timer */ + timer_on(pstn, DIALTONE_TO, TIMER_IDENT_DIALING); + /* dial tone */ + tone_on(pstn, TONE_DIALTONE, "dial"); + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ENBLOCK); + } + } + break; + case PSTN_V5_STEADY_SIGNAL_ON_HOOK: + PDEBUG(DTEL, DEBUG_INFO, "Received steady on-kook signal.\n"); + /* start hookflash timer */ + if (pstn->recall && pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ACTIVE) + timer_on(pstn, HOOKFLASH_TO, TIMER_IDENT_HOOKFLASH); + else { + PDEBUG(DTEL, DEBUG_DEBUG, "No call, so we set hookflash timer to 0.\n"); + timer_on(pstn, 0.0, TIMER_IDENT_HOOKFLASH); + } + break; + } + break; + case PSTN_V5_IE_PULSED_SIGNAL: + switch ((data[2] & 0x7f)) { + case PSTN_V5_PULSED_SIGNAL_ON_HOOK: + PDEBUG(DTEL, DEBUG_INFO, "Received pulsed on-kook signal.\n"); +hookflash: + /* clear dialing */ + pstn->dialing[0] = '\0'; + /* if call on hold, swap calls */ + if (pstn->call[PSTN_CALL_HOLD]->cc_callref) { + PDEBUG(DTEL, DEBUG_INFO, "There is a call on hold, so swap both calls.\n"); + /* swap ACTIVE call and call on HOLD */ + swap_active_hold(pstn); + /* send notify to call on hold */ + notify_ind(pstn->call[PSTN_CALL_HOLD], OSMO_CC_NOTIFY_REMOTE_HOLD); + /* if waiting call, answer it */ + if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_ACTIVE + && pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_HOLD) { + /* setup confirm */ + setup_cnf(pstn, PSTN_CALL_ACTIVE); + } + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ACTIVE); + pstn_call_state(pstn->call[PSTN_CALL_HOLD], CALL_STATE_HOLD); + /* send notify to active call */ + notify_ind(pstn->call[PSTN_CALL_ACTIVE], OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); + break; + } + /* if active call, put it on hold and setup new call, if not en-block dialing */ + if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { + PDEBUG(DTEL, DEBUG_INFO, "There is a only an active call, so put it on hold and start a new one.\n"); + /* swap ACTIVE call and call on HOLD */ + swap_active_hold(pstn); + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_HOLD], CALL_STATE_HOLD); + /* send notify to call on hold */ + notify_ind(pstn->call[PSTN_CALL_HOLD], OSMO_CC_NOTIFY_REMOTE_HOLD); + /* start DTMF */ + dtmf_on(pstn); + /* start pulse */ + pulse_on(pstn); + if (!pstn->enblock) { + /* setup */ + setup_ind(pstn, PSTN_CALL_ACTIVE, "", 0); + } else { + /* start dial timer */ + timer_on(pstn, DIALTONE_TO, TIMER_IDENT_DIALING); + /* dial tone */ + tone_on(pstn, TONE_DIALTONE, "dial"); + /* change state */ + pstn_call_state(pstn->call[PSTN_CALL_ACTIVE], CALL_STATE_ENBLOCK); + } + break; + } + break; + } + break; + case PSTN_V5_IE_DIGIT_SIGNAL: + PDEBUG(DTEL, DEBUG_INFO, "Received digit signal.\n"); + if (!pstn->pulse_on) + break; + /* stop timer */ + timer_off(pstn); + /* stop DTMF */ + dtmf_off(pstn); + /* stop tone */ + tone_off(pstn); + /* convert pulses -> digit */ + char called[2] = { '\0', '\0' }; + switch (data[2] & 0x0f) { + case 0: + PDEBUG(DTEL, DEBUG_ERROR, "Received 0 pulses, ignoring!\n"); + break; + case 10: + called[0] = '0'; + break; + case 11: + called[0] = '*'; + break; + case 12: + called[0] = '#'; + break; + case 13: + called[0] = 'a'; + break; + case 14: + called[0] = 'b'; + break; + case 15: + called[0] = 'c'; + break; + default: + called[0] = '0' + (data[2] & 0x0f); + } + /* if we are receiving digits en block */ + if (pstn->call[PSTN_CALL_ACTIVE]->state == CALL_STATE_ENBLOCK) { + PDEBUG(DTEL, DEBUG_DEBUG, "Storing digit, because we perform enblock dialing.\n"); + /* append digit */ + strncat(pstn->dialing, called, sizeof(pstn->dialing) - 1); + PDEBUG(DTEL, DEBUG_DEBUG, "Appending digit, so dial string is now: '%s'.\n", pstn->dialing); + /* start dial timer */ + timer_on(pstn, (double)pstn->enblock, TIMER_IDENT_DIALING); + break; + } + if (pstn->call[PSTN_CALL_ACTIVE]->state != CALL_STATE_OVERLAP_NET) { + PDEBUG(DTEL, DEBUG_NOTICE, "Received pulse digit, but call is not in overlap state, ignoring!\n"); + break; + } + /* create osmo-cc message */ + new_msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); + /* called number (digits) */ + PDEBUG(DTEL, DEBUG_INFO, "Sending INFO-IND with digit '%s' towards Osmo-CC\n", called); + osmo_cc_add_ie_called(new_msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, called); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&pstn->cc_ep, pstn->call[PSTN_CALL_ACTIVE]->cc_callref, new_msg); + break; + default: + PDEBUG(DTEL, DEBUG_INFO, "Received unknown signal 0x%02x, ignoring!\n", data[0]); + } +} + +static void v5_disc_cpl_ind_and_cleanup(pstn_t *pstn) +{ + /* deactivate b-channel */ + ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DACT_REQ, NULL, 0); + + /* stop tone */ + tone_off(pstn); + + /* stop caller id process */ + callerid_off(pstn); + + /* stop DTMF */ + dtmf_off(pstn); + + /* release, if not already */ + release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); + + /* change state */ + pstn_new_state(pstn, PSTN_STATE_NULL); +} + +static void v5_block(pstn_t *pstn) +{ + /* release and change state */ + if (pstn->state != PSTN_STATE_BLOCKED) { + v5_disc_cpl_ind_and_cleanup(pstn); + pstn_new_state(pstn, PSTN_STATE_BLOCKED); + } +} + +static void v5_unblock(pstn_t *pstn) +{ + /* change state */ + if (pstn->state == PSTN_STATE_BLOCKED) + pstn_new_state(pstn, PSTN_STATE_NULL); +} + +/* receive V5 message */ +static void v5_receive(pstn_t *pstn, uint8_t *data, int len) +{ + uint8_t event; + + if (len < 1) { + PDEBUG(DTEL, DEBUG_ERROR, "Received data indication message from PH socket is too short, please fix!\n"); + return; + } + + event = *data++; + len--; + + PDEBUG(DTEL, DEBUG_INFO, "Received %s message from Osmo-V5.\n", pstn_event_name(event)); + + switch (event) { + case PSTN_EVENT_EST_ACK_IND: + if (pstn->state != PSTN_STATE_EST_LE) { + PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); + break; + } + /* we got an establish acknowledgement from PSTN interface */ + v5_est_ack_ind(pstn, data, len); + break; + case PSTN_EVENT_EST_IND: + /* in case of collision, just treat as ACK and proceed outgoing call */ + if (pstn->state != PSTN_STATE_EST_LE + && pstn->state != PSTN_STATE_NULL) { + PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); + break; + } + /* we got an establishment from PSTN interface */ + v5_est_ind(pstn, data, len); + break; + case PSTN_EVENT_SIG_IND: + if (pstn->state != PSTN_STATE_ACTIVE) { + PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); + break; + } + /* we got a line signal from PSTN interface */ + v5_sig_ind(pstn, data, len); + break; + case PSTN_EVENT_DISC_CPL_IND: + /* in NULL state we may receive a DISC, because of disconnect collision or confirm, so we ignore it */ + if (pstn->state == PSTN_STATE_NULL) + return; + if (pstn->state != PSTN_STATE_ACTIVE + && pstn->state != PSTN_STATE_EST_LE + && pstn->state != PSTN_STATE_EST_AN + && pstn->state != PSTN_STATE_DISC_REQ) { + PDEBUG(DTEL, DEBUG_NOTICE, "Received %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); + break; + } + /* we got a disconnect (reply) from PSTN interface */ + v5_disc_cpl_ind_and_cleanup(pstn); + break; + default: + PDEBUG(DTEL, DEBUG_NOTICE, "Received unssuports %s message in state %s, ignoring!\n", pstn_event_name(event), pstn_state_name(pstn->state)); + } +} + +/* message from PH socket */ +void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length) +{ + pstn_t *pstn = (pstn_t *)s->priv; + + switch (prim) { + case PH_PRIM_CTRL_IND: + if (channel == 0 && length >= 1) { + if (*data == PH_CTRL_BLOCK) { + PDEBUG(DTEL, DEBUG_INFO, "Received blocking from V5 interface!\n"); + v5_block(pstn); + } + if (*data == PH_CTRL_UNBLOCK) { + PDEBUG(DTEL, DEBUG_INFO, "Received unblocking from V5 interface!\n"); + v5_unblock(pstn); + } + } + break; + case PH_PRIM_DATA_IND: + if (channel == 0) + v5_receive(pstn, data, length); + if (channel == 1) + bchannel_rx_tx(pstn, data, length); + break; + case PH_PRIM_ACT_IND: + if (channel == 1) { + PDEBUG(DTEL, DEBUG_DEBUG, "Received b-channel activation from V5 interface!\n"); + pstn->b_transmitting = 0; + } + break; + case PH_PRIM_DACT_IND: + break; + } +} + +/* send V5 message */ +static void v5_send(pstn_t *pstn, uint8_t event, uint8_t *data, int length) +{ + uint8_t buffer[length + 1]; + + buffer[0] = event; + if (length) + memcpy(buffer + 1, data, length); + + PDEBUG(DTEL, DEBUG_INFO, "Sending %s message to Osmo-V5.\n", pstn_event_name(event)); + + ph_socket_tx_msg(&pstn->ph_socket, 0, PH_PRIM_DATA_REQ, buffer, length + 1); +} + +static void v5_est_req(pstn_t *pstn, uint8_t *ie, int length) +{ + v5_send(pstn, PSTN_EVENT_EST_REQ, ie, length); +} + +static void v5_est_ack_req(pstn_t *pstn) +{ + v5_send(pstn, PSTN_EVENT_EST_ACK_REQ, NULL, 0); +} + +static void v5_sig_req(pstn_t *pstn, uint8_t *ie, int length) +{ + v5_send(pstn, PSTN_EVENT_SIG_REQ, ie, length); +} + +static void v5_disc_req_and_cleanup(pstn_t *pstn) +{ + /* deactivate b-channel */ + ph_socket_tx_msg(&pstn->ph_socket, 1, PH_PRIM_DACT_REQ, NULL, 0); + + /* stop tone */ + tone_off(pstn); + + /* stop caller id process */ + callerid_off(pstn); + + /* stop DTMF */ + dtmf_off(pstn); + + /* release, if not already */ + release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); + + /* change state */ + pstn_new_state(pstn, PSTN_STATE_NULL); + + v5_send(pstn, PSTN_EVENT_DISC_REQ, NULL, 0); +} + + +/* + * timeout + */ + +static void pstn_timeout(struct timer *timer) +{ + pstn_t *pstn = timer->priv; + + switch (pstn->timer_ident) { + case TIMER_IDENT_DIALING: + PDEBUG(DTEL, DEBUG_INFO, "Timeout while enblock-dialing.\n"); + /* if no digit were dialed */ + if (pstn->dialing[0] == '\0') { + /* hangup tone */ + tone_on(pstn, TONE_HANGUP, "hangup"); + /* stop DTMF */ + dtmf_off(pstn); + /* stop pulse */ + pulse_off(pstn); + break; + } + /* setup (en block) */ + setup_ind(pstn, PSTN_CALL_ACTIVE, pstn->dialing, 1); + break; + case TIMER_IDENT_RELEASE: + PDEBUG(DTEL, DEBUG_INFO, "Timeout while in disconnect state, releasing.\n"); + if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { + release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); + /* SIT tone */ + tone_on(pstn, TONE_SPECIAL_INFO, "SIT"); + } + break; + case TIMER_IDENT_HOOKFLASH: + PDEBUG(DTEL, DEBUG_DEBUG, "Timeout while waiting for hook flash, releasing.\n"); + /* stop tone */ + tone_off(pstn); + /* stop timer */ + timer_off(pstn); + /* if active call, release it */ + if (pstn->call[PSTN_CALL_ACTIVE]->cc_callref) { + /* release towards osmo-cc */ + release_call(pstn, PSTN_CALL_ACTIVE, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR); + } + /* if call on hold, ring the phone again */ + if (pstn->call[PSTN_CALL_HOLD]->cc_callref) { + /* swap ACTIVE call and call on HOLD */ + swap_active_hold(pstn); + /* send message to V5 */ + uint8_t ie[3] = { PSTN_V5_IE_CADENCED_RINGING, 1, 0x80 | pstn->ringing_type_hold }; + v5_sig_req(pstn, ie, sizeof(ie)); + break; + } + /* there is no more call, release V5 */ + v5_disc_req_and_cleanup(pstn); + break; + } +} + +/* + * work + */ + +void pstn_work(pstn_t *pstn) +{ + ph_socket_work(&pstn->ph_socket); +} + diff --git a/src/pstn/pstn.h b/src/pstn/pstn.h new file mode 100644 index 0000000..5388752 --- /dev/null +++ b/src/pstn/pstn.h @@ -0,0 +1,149 @@ + +#include "../libtimer/timer.h" +#include "../libosmocc/endpoint.h" +#include "../libsample/sample.h" +#include "../libjitter/jitter.h" +#include "../libdtmf/dtmf_decode.h" +#include "../libph_socket/ph_socket.h" +#include "callerid.h" +#include "tones.h" + +enum pstn_state { + PSTN_STATE_OOS = 0, /* out of service */ + PSTN_STATE_NULL, /* no call */ + PSTN_STATE_EST_LE, /* requested establisment, waiting for reply */ + PSTN_STATE_EST_AN, /* not used here */ + PSTN_STATE_ACTIVE, /* path active */ + PSTN_STATE_DISC_REQ, /* requested disconnect, waiting for reply */ + PSTN_STATE_BLOCKED, /* not used here */ +}; + +enum pstn_event { + PSTN_EVENT_EST_REQ = 0x00, /* establishment towards interface */ + PSTN_EVENT_EST_ACK_IND = 0x03, /* establishment acknowledge from interface */ + PSTN_EVENT_EST_IND = 0x01, /* establishment from interface */ + PSTN_EVENT_EST_ACK_REQ = 0x02, /* establishment acknowledge towards interface */ + PSTN_EVENT_SIG_REQ = 0x10, /* line signal towards interface */ + PSTN_EVENT_SIG_IND = 0x11, /* line signal from interface */ + PSTN_EVENT_PARAM_REQ = 0x20, /* parameter request towards interface */ + PSTN_EVENT_DISC_REQ = 0x30, /* disconnect towards interface (ACTIVE sate) */ + PSTN_EVENT_DISC_CPL_REQ = 0x32, /* disconnect towards interface (PATH INITIATED states) */ + PSTN_EVENT_DISC_CPL_IND = 0x33, /* disconnect from interface */ +}; + +enum pstn_v5_ie { + PSTN_V5_IE_CADENCED_RINGING = 0x01, + PSTN_V5_IE_PULSED_SIGNAL = 0x02, + PSTN_V5_IE_STEADY_SIGNAL = 0x03, + PSTN_V5_IE_DIGIT_SIGNAL = 0x04, +}; + +enum pstn_v5_signal { + PSTN_V5_STEADY_SIGNAL_OFF_HOOK = 0x04, + PSTN_V5_STEADY_SIGNAL_ON_HOOK = 0x05, + PSTN_V5_STEADY_SIGNAL_STOP_RING = 0x0e, + PSTN_V5_PULSED_SIGNAL_ON_HOOK = 0x7c, +}; + +enum timer_ident { + TIMER_IDENT_DIALING, + TIMER_IDENT_RELEASE, + TIMER_IDENT_HOOKFLASH, +}; + +#define PSTN_CALL_ACTIVE 0 +#define PSTN_CALL_HOLD 1 + +enum pstn_call_state { + CALL_STATE_NULL = 0, + CALL_STATE_ENBLOCK, + CALL_STATE_ALERTING_SUB, + CALL_STATE_OVERLAP_NET, + CALL_STATE_PROCEEDING_NET, + CALL_STATE_ALERTING_NET, + CALL_STATE_ACTIVE, + CALL_STATE_HOLD, + CALL_STATE_DISCONNECT_NET, +}; + +enum pstn_cid_state { + PSTN_CID_STATE_OFF = 0, + PSTN_CID_STATE_WAIT1, + PSTN_CID_STATE_SEND, + PSTN_CID_STATE_WAIT2, +}; + +enum tones_type { + TONES_TYPE_AMERICAN, + TONES_TYPE_GERMAN, + TONES_TYPE_OLDGERMAN, +}; + +struct pstn; + +struct call { + struct pstn *pstn; + uint32_t cc_callref; + enum pstn_call_state state; + int subscriber_index; /* subscriber the calls is associated with */ + const char *sdp; + osmo_cc_session_t *cc_session; + osmo_cc_session_codec_t *codec; + int on_hold; /* track hold/retrieval notification */ +}; + +typedef struct pstn { + osmo_cc_endpoint_t cc_ep; + + /* settings */ + char law; /* 'a'-law or 'u'-law */ + const char *name; + const char **subscribers; /* subscriber number (caller ID of PSTN port) */ + int subscriber_num; /* number of subscribers and tinging types (incoming) */ + int serving_location; /* who we serve when sending causes towards interface */ + int tx_delay; /* delay to be used for fax/modem jitter buffering */ + int clip; /* send caller ID */ + int cid_bell; /* use V.23 or Bell 202 FSK tones */ + int cid_dtmf; /* use DTMF instead of FSK caller ID */ + int cid_date; /* send date with caller ID */ + int enblock; /* receive digits before transmitting them via SETUP message (timeout as given) */ + int recall; /* support recall / waiting call */ + int *ringing_types_incoming;/* cadenced rining on incoming call */ + int ringing_type_hold; /* cadenced rining on waiting call */ + enum tones_type tones_type; /* what tone set to use */ + + /* states */ + enum pstn_state state; + struct timer timer; /* timer for enblock dialing */ + enum timer_ident timer_ident; /* why is the timer running */ + char dialing[33]; /* register for en-block dialing */ + int dtmf_on; /* we are listening to DTMF */ + int pulse_on; /* we take pulses */ + enum pstn_cid_state callerid_state; /* transmitting callerid */ + int callerid_wait1; /* wait (number of samples) until transmitting caller ID */ + int callerid_wait2; /* wait (number of samples) after transmitting caller ID */ + int b_transmitting; /* transmitting on b-channel */ + + /* v5 interface */ + ph_socket_t ph_socket; + + /* osmo-cc states (active and hold ) */ + struct call *call[2]; + + /* audio states */ + int audio_path; /* (early/late) audio is connected */ + jitter_t tx_dejitter; /* jitter buffer */ + dtmf_dec_t dtmf_dec; /* DTMF tone detection */ + struct isdn_tone isdn_tone; /* tone generator */ + callerid_t callerid; /* caller ID transmission */ + uint8_t tx_buffer[160]; /* transmit audio buffer */ + int tx_buffer_pos; /* current position in transmit audio buffer */ +} pstn_t; + +void pstn_destroy(pstn_t *pstn_ep); +pstn_t *pstn_create(void); +int pstn_init(pstn_t *pstn, const char *name, const char *socketname, const char **subscribers, int subscriber_num, uint8_t serving_location, int tx_delay, int clip, int cid_bell, int cid_dtmf, int cid_date, int enblock, int recall, int *ringing_types_incoming, int ringing_type_hold, enum tones_type tones_type, char law); +void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg); +void rtp_work(pstn_t *pstn_ep); +void pstn_work(pstn_t *pstn_ep); + diff --git a/src/pstn/tones.c b/src/pstn/tones.c new file mode 100644 index 0000000..4e1c777 --- /dev/null +++ b/src/pstn/tones.c @@ -0,0 +1,477 @@ +/* + * Tone generation + * + * Copyright Andreas Eversberg (jolly@eversberg.eu) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libg711/g711.h" +#include "tones.h" + + +#define DATA_T 0 // transparent; do not overwrite buffer +#define SIZE_T (&sizeof_silence) +#define DATA_S sample_silence +#define SIZE_S (&sizeof_silence) +#define DATA_GA sample_german_all +#define SIZE_GA (&sizeof_german_all) +#define DATA_GO sample_german_old +#define SIZE_GO (&sizeof_german_old) +#define DATA_DT sample_american_dialtone +#define SIZE_DT (&sizeof_american_dialtone) +#define DATA_RI sample_american_ringing +#define SIZE_RI (&sizeof_american_ringing) +#define DATA_BU sample_american_busy +#define SIZE_BU (&sizeof_american_busy) +#define DATA_S1 sample_special1 +#define SIZE_S1 (&sizeof_special1) +#define DATA_S2 sample_special2 +#define SIZE_S2 (&sizeof_special2) +#define DATA_S3 sample_special3 +#define SIZE_S3 (&sizeof_special3) + +/***************/ +/* tones loops */ +/***************/ + +/* all tones are alaw encoded */ +/* the last sample+1 is in phase with the first sample. the error is low */ + +static uint8_t sample_german_all[] = { + 0x80, 0xab, 0x81, 0x6d, 0xfd, 0xdd, 0x5d, 0x9d, + 0x4d, 0xd1, 0x89, 0x88, 0xd0, 0x4c, 0x9c, 0x5c, + 0xdc, 0xfc, 0x6c, + 0x80, 0xab, 0x81, 0x6d, 0xfd, 0xdd, 0x5d, 0x9d, + 0x4d, 0xd1, 0x89, 0x88, 0xd0, 0x4c, 0x9c, 0x5c, + 0xdc, 0xfc, 0x6c, + 0x80, 0xab, 0x81, 0x6d, 0xfd, 0xdd, 0x5d, 0x9d, + 0x4d, 0xd1, 0x89, 0x88, 0xd0, 0x4c, 0x9c, 0x5c, + 0xdc, 0xfc, 0x6c, + 0x80, 0xab, 0x81, 0x6d, 0xfd, 0xdd, 0x5d, 0x9d, + 0x4d, 0xd1, 0x89, 0x88, 0xd0, 0x4c, 0x9c, 0x5c, + 0xdc, 0xfc, 0x6c, +}; +static int sizeof_german_all = sizeof(sample_german_all); + +static uint8_t sample_german_old[] = { + 0xec, 0x68, 0xe1, 0x6d, 0x6d, 0x91, 0x51, 0xed, + 0x6d, 0x01, 0x1e, 0x10, 0x0c, 0x90, 0x60, 0x70, + 0x8c, + 0xec, 0x68, 0xe1, 0x6d, 0x6d, 0x91, 0x51, 0xed, + 0x6d, 0x01, 0x1e, 0x10, 0x0c, 0x90, 0x60, 0x70, + 0x8c, + 0xec, 0x68, 0xe1, 0x6d, 0x6d, 0x91, 0x51, 0xed, + 0x6d, 0x01, 0x1e, 0x10, 0x0c, 0x90, 0x60, 0x70, + 0x8c, + 0xec, 0x68, 0xe1, 0x6d, 0x6d, 0x91, 0x51, 0xed, + 0x6d, 0x01, 0x1e, 0x10, 0x0c, 0x90, 0x60, 0x70, + 0x8c, +}; +static int sizeof_german_old = sizeof(sample_german_old); + +static uint8_t sample_american_dialtone[] = { + 0x2a, 0x18, 0x90, 0x6c, 0x4c, 0xbc, 0x4c, 0x6c, + 0x10, 0x58, 0x32, 0xb9, 0x31, 0x2d, 0x8d, 0x0d, + 0x8d, 0x2d, 0x31, 0x99, 0x0f, 0x28, 0x60, 0xf0, + 0xd0, 0x50, 0xd0, 0x30, 0x60, 0x08, 0x8e, 0x67, + 0x09, 0x19, 0x21, 0xe1, 0xd9, 0xb9, 0x29, 0x67, + 0x83, 0x02, 0xce, 0xbe, 0xee, 0x1a, 0x1b, 0xef, + 0xbf, 0xcf, 0x03, 0x82, 0x66, 0x28, 0xb8, 0xd8, + 0xe0, 0x20, 0x18, 0x08, 0x66, 0x8f, 0x09, 0x61, + 0x31, 0xd1, 0x51, 0xd1, 0xf1, 0x61, 0x29, 0x0e, + 0x98, 0x30, 0x2c, 0x8c, 0x0c, 0x8c, 0x2c, 0x30, + 0xb8, 0x33, 0x59, 0x11, 0x6d, 0x4d, 0xbd, 0x4d, + 0x6d, 0x91, 0x19, +}; +static int sizeof_american_dialtone = sizeof(sample_american_dialtone); + +static uint8_t sample_american_ringing[] = { + 0x2a, 0xe0, 0xac, 0x0c, 0xbc, 0x4c, 0x8c, 0x90, + 0x48, 0xc7, 0xc1, 0xed, 0xcd, 0x4d, 0xcd, 0xed, + 0xc1, 0xb7, 0x08, 0x30, 0xec, 0xcc, 0xcc, 0x8c, + 0x10, 0x58, 0x1a, 0x99, 0x71, 0xed, 0x8d, 0x8d, + 0x2d, 0x41, 0x89, 0x9e, 0x20, 0x70, 0x2c, 0xec, + 0x2c, 0x70, 0x20, 0x86, 0x77, 0xe1, 0x31, 0x11, + 0xd1, 0xf1, 0x81, 0x09, 0xa3, 0x56, 0x58, 0x00, + 0x40, 0xc0, 0x60, 0x38, 0x46, 0x43, 0x57, 0x39, + 0xd9, 0x59, 0x99, 0xc9, 0x77, 0x2f, 0x2e, 0xc6, + 0xd6, 0x28, 0xd6, 0x36, 0x26, 0x2e, 0x8a, 0xa3, + 0x43, 0x63, 0x4b, 0x4a, 0x62, 0x42, 0xa2, 0x8b, + 0x2f, 0x27, 0x37, 0xd7, 0x29, 0xd7, 0xc7, 0x2f, + 0x2e, 0x76, 0xc8, 0x98, 0x58, 0xd8, 0x38, 0x56, + 0x42, 0x47, 0x39, 0x61, 0xc1, 0x41, 0x01, 0x59, + 0x57, 0xa2, 0x08, 0x80, 0xf0, 0xd0, 0x10, 0x30, + 0xe0, 0x76, 0x87, 0x21, 0x71, 0x2d, 0xed, 0x2d, + 0x71, 0x21, 0x9f, 0x88, 0x40, 0x2c, 0x8c, 0x8c, + 0xec, 0x70, 0x98, 0x1b, 0x59, 0x11, 0x8d, 0xcd, + 0xcd, 0xed, 0x31, 0x09, 0xb6, 0xc0, 0xec, 0xcc, + 0x4c, 0xcc, 0xec, 0xc0, 0xc6, 0x49, 0x91, 0x8d, + 0x4d, 0xbd, 0x0d, 0xad, 0xe1, +}; +static int sizeof_american_ringing = sizeof(sample_american_ringing); + +static uint8_t sample_american_busy[] = { + 0x2a, 0x00, 0x6c, 0x4c, 0x4c, 0x6c, 0xb0, 0x66, + 0x99, 0x11, 0x6d, 0x8d, 0x2d, 0x41, 0xd7, 0x96, + 0x60, 0xf0, 0x70, 0x40, 0x58, 0xf6, 0x53, 0x57, + 0x09, 0x89, 0xd7, 0x5f, 0xe3, 0x2a, 0xe3, 0x5f, + 0xd7, 0x89, 0x09, 0x57, 0x53, 0xf6, 0x58, 0x40, + 0x70, 0xf0, 0x60, 0x96, 0xd7, 0x41, 0x2d, 0x8d, + 0x6d, 0x11, 0x99, 0x66, 0xb0, 0x6c, 0x4c, 0x4c, + 0x6c, 0x00, 0x2a, 0x01, 0x6d, 0x4d, 0x4d, 0x6d, + 0xb1, 0x67, 0x98, 0x10, 0x6c, 0x8c, 0x2c, 0x40, + 0xd6, 0x97, 0x61, 0xf1, 0x71, 0x41, 0x59, 0xf7, + 0x52, 0x56, 0x08, 0x88, 0xd6, 0x5e, 0xe2, 0x2a, + 0xe2, 0x5e, 0xd6, 0x88, 0x08, 0x56, 0x52, 0xf7, + 0x59, 0x41, 0x71, 0xf1, 0x61, 0x97, 0xd6, 0x40, + 0x2c, 0x8c, 0x6c, 0x10, 0x98, 0x67, 0xb1, 0x6d, + 0x4d, 0x4d, 0x6d, 0x01, +}; +static int sizeof_american_busy = sizeof(sample_american_busy); + +static uint8_t sample_special1[] = { + 0x2a, 0x2c, 0xbc, 0x6c, 0xd6, 0x71, 0xbd, 0x0d, + 0xd9, 0x80, 0xcc, 0x4c, 0x40, 0x39, 0x0d, 0xbd, + 0x11, 0x86, 0xec, 0xbc, 0xec, 0x0e, 0x51, 0xbd, + 0x8d, 0x89, 0x30, 0x4c, 0xcc, 0xe0, 0xe1, 0xcd, + 0x4d, 0x31, 0x88, 0x8c, 0xbc, 0x50, 0x0f, 0xed, + 0xbd, 0xed, 0x87, 0x10, 0xbc, 0x0c, 0x38, 0x41, + 0x4d, 0xcd, 0x81, 0xd8, 0x0c, 0xbc, 0x70, 0xd7, + 0x6d, 0xbd, 0x2d, +}; +static int sizeof_special1 = sizeof(sample_special1); + +static uint8_t sample_special2[] = { + 0x2a, 0xcc, 0x8c, 0xd7, 0x4d, 0x2d, 0x18, 0xbc, + 0x10, 0xc1, 0xbd, 0xc1, 0x10, 0xbc, 0x18, 0x2d, + 0x4d, 0xd7, 0x8c, 0xcc, 0x2a, 0xcd, 0x8d, 0xd6, + 0x4c, 0x2c, 0x19, 0xbd, 0x11, 0xc0, 0xbc, 0xc0, + 0x11, 0xbd, 0x19, 0x2c, 0x4c, 0xd6, 0x8d, 0xcd, + 0x2a, 0xcc, 0x8c, 0xd7, 0x4d, 0x2d, 0x18, 0xbc, + 0x10, 0xc1, 0xbd, 0xc1, 0x10, 0xbc, 0x18, 0x2d, + 0x4d, 0xd7, 0x8c, 0xcc, 0x2a, 0xcd, 0x8d, 0xd6, + 0x4c, 0x2c, 0x19, 0xbd, 0x11, 0xc0, 0xbc, 0xc0, + 0x11, 0xbd, 0x19, 0x2c, 0x4c, 0xd6, 0x8d, 0xcd, +}; +static int sizeof_special2 = sizeof(sample_special2); + +static uint8_t sample_special3[] = { + 0x2a, 0xbc, 0x18, 0xcd, 0x11, 0x2c, 0x8c, 0xc1, + 0x4d, 0xd6, 0xbc, 0xd6, 0x4d, 0xc1, 0x8c, 0x2c, + 0x11, 0xcd, 0x18, 0xbc, 0x2a, 0xbd, 0x19, 0xcc, + 0x10, 0x2d, 0x8d, 0xc0, 0x4c, 0xd7, 0xbd, 0xd7, + 0x4c, 0xc0, 0x8d, 0x2d, 0x10, 0xcc, 0x19, 0xbd, + 0x2a, 0xbc, 0x18, 0xcd, 0x11, 0x2c, 0x8c, 0xc1, + 0x4d, 0xd6, 0xbc, 0xd6, 0x4d, 0xc1, 0x8c, 0x2c, + 0x11, 0xcd, 0x18, 0xbc, 0x2a, 0xbd, 0x19, 0xcc, + 0x10, 0x2d, 0x8d, 0xc0, 0x4c, 0xd7, 0xbd, 0xd7, + 0x4c, 0xc0, 0x8d, 0x2d, 0x10, 0xcc, 0x19, 0xbd, +}; +static int sizeof_special3 = sizeof(sample_special3); + +static uint8_t sample_silence[] = { + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, +}; +static int sizeof_silence = sizeof(sample_silence); + +struct tones_samples { + int *len; + uint8_t *data; +}; +static struct +tones_samples samples[] = { + {&sizeof_german_all, sample_german_all}, + {&sizeof_german_old, sample_german_old}, + {&sizeof_american_dialtone, sample_american_dialtone}, + {&sizeof_american_ringing, sample_american_ringing}, + {&sizeof_american_busy, sample_american_busy}, + {&sizeof_special1, sample_special1}, + {&sizeof_special2, sample_special2}, + {&sizeof_special3, sample_special3}, + {NULL, NULL}, +}; + +/*********************************** + * generate ulaw from alaw samples * + ***********************************/ + +void isdn_tone_generate_ulaw_samples(void) +{ + int i; + int16_t *audio; + int audio_len; + uint8_t *data; + int len; + + i = 0; + while (samples[i].len) { + g711_decode_alaw_flipped(samples[i].data, *samples[i].len, (uint8_t **)&audio, &audio_len, NULL); + g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len, NULL); + memcpy(samples[i].data, data, len); + free(audio); + free(data); + i++; + } +} + + +/**************************** + * tone sequence definition * + ****************************/ + +static struct pattern { + int tone; + uint8_t *data[10]; + int *siz[10]; + int seq[10]; +} pattern[] = { + {TONE_GERMAN_DIALTONE, + {DATA_GA, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {1900, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDDIALTONE, + {DATA_GO, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {7956, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_DIALTONE, + {DATA_DT, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_DT, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {8008, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_DIALPBX, + {DATA_GA, DATA_S, DATA_GA, DATA_S, DATA_GA, DATA_S, NULL, NULL, NULL, + NULL}, + {SIZE_GA, SIZE_S, SIZE_GA, SIZE_S, SIZE_GA, SIZE_S, NULL, NULL, NULL, + NULL}, + {2000, 2000, 2000, 2000, 2000, 12000, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDDIALPBX, + {DATA_GO, DATA_S, DATA_GO, DATA_S, DATA_GO, DATA_S, NULL, NULL, NULL, + NULL}, + {SIZE_GO, SIZE_S, SIZE_GO, SIZE_S, SIZE_GO, SIZE_S, NULL, NULL, NULL, + NULL}, + {2000, 2000, 2000, 2000, 2000, 12000, 0, 0, 0, 0} }, + + {TONE_AMERICAN_DIALPBX, + {DATA_DT, DATA_S, DATA_DT, DATA_S, DATA_DT, DATA_S, NULL, NULL, NULL, + NULL}, + {SIZE_DT, SIZE_S, SIZE_DT, SIZE_S, SIZE_DT, SIZE_S, NULL, NULL, NULL, + NULL}, + {2000, 2000, 2000, 2000, 2000, 12000, 0, 0, 0, 0} }, + + {TONE_GERMAN_RINGING, + {DATA_GA, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {8000, 32000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDRINGING, + {DATA_GO, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {8000, 40000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_RINGING, + {DATA_RI, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_RI, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {8000, 32000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_RINGPBX, + {DATA_GA, DATA_S, DATA_GA, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_S, SIZE_GA, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 4000, 28000, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDRINGPBX, + {DATA_GO, DATA_S, DATA_GO, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, SIZE_S, SIZE_GO, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 4000, 28000, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_RINGPBX, + {DATA_RI, DATA_S, DATA_RI, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_RI, SIZE_S, SIZE_RI, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 4000, 28000, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_BUSY, + {DATA_GA, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDBUSY, + {DATA_GO, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {1000, 5000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_BUSY, + {DATA_BU, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_BU, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_HANGUP, + {DATA_GA, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_OLDHANGUP, + {DATA_GO, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {1000, 5000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_HANGUP, + {DATA_BU, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_BU, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {4000, 4000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_SPECIAL_INFO, + {DATA_S1, DATA_S2, DATA_S3, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_S1, SIZE_S2, SIZE_S3, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL}, + {2666, 2666, 2666, 8002, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_GASSENBESETZT, + {DATA_GA, DATA_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_S, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {2000, 2000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_AUFSCHALTTON, + {DATA_GO, DATA_T, DATA_GO, DATA_T, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GO, SIZE_T, SIZE_GO, SIZE_T, NULL, NULL, NULL, NULL, NULL, NULL}, + {1000, 5000, 1000, 17000, 0, 0, 0, 0, 0, 0} }, + + {TONE_GERMAN_CW, + {DATA_GA, DATA_T, DATA_GA, DATA_T, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_T, SIZE_GA, SIZE_T, NULL, NULL, NULL, NULL, NULL, NULL}, + {1600, 1600, 1600, 40000, 0, 0, 0, 0, 0, 0} }, + + {TONE_AMERICAN_CW, // actually not the 440 Hz, but 421 is close enough + {DATA_GA, DATA_T, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {SIZE_GA, SIZE_T, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {2400, 80000, 0, 0, 0, 0, 0, 0, 0, 0} }, + + {0, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, +}; + +/****************** + * copy tone data * + ******************/ + +/* the count will be changed and may begin from 0 each pattern period. + * the clue is to precalculate the pointers and legths to use only one + * memcpy per function call, or two memcpy if the tone sequence changes. + * + * pattern - the type of the pattern + * count - the sample from the beginning of the pattern (phase) + * len - the number of bytes + */ +void isdn_tone_copy(struct isdn_tone *t, uint8_t *data, int len) +{ + int index, count, start, num; + struct pattern *pat; + + /* if we have no tone, we do not overwrite data */ + if (!t->tone) + return; + + /* process pattern */ + pat = (struct pattern *)t->pattern; + /* points to the current pattern */ + index = t->index; /* gives current sequence index */ + count = t->count; /* gives current sample */ + + /* copy sample */ + while (len) { + /* find sample to start with */ + while (42) { + /* wrap around */ + if (!pat->seq[index]) { + count = 0; + index = 0; + } + /* check if we are currently playing this tone */ + if (count < pat->seq[index]) + break; + count -= pat->seq[index]; + index++; + } + /* calculate start and number of samples */ + start = count % (*(pat->siz[index])); + num = len; + if (num + count > pat->seq[index]) + num = pat->seq[index] - count; + if (num + start > (*(pat->siz[index]))) + num = (*(pat->siz[index])) - start; + /* copy memory, only if tone is not transparent */ + if (pat->data[index]) + memcpy(data, pat->data[index] + start, num); + /* reduce length */ + data += num; + count += num; + len -= num; + } + + t->index = index; + t->count = count; +} + + +/******************** + * set/release tone * + ********************/ + +int isdn_tone_set(struct isdn_tone *t, int tone) +{ + struct pattern *pat; + int i; + + /* we turn off the tone */ + if (!tone) { + t->tone = 0; + return 0; + } + + pat = NULL; + i = 0; + while (pattern[i].tone) { + if (pattern[i].tone == tone) { + pat = &pattern[i]; + break; + } + i++; + } + if (!pat) { + PDEBUG(DDSP, DEBUG_ERROR, "given tone 0x%x is invalid\n", tone); + return -EINVAL; + } + PDEBUG(DDSP, DEBUG_DEBUG, "playing given tone 0x%x\n", tone); + t->tone = tone; + t->pattern = pat; + t->index = 0; + t->count = 0; + + return 0; +} diff --git a/src/pstn/tones.h b/src/pstn/tones.h new file mode 100644 index 0000000..bcd5a13 --- /dev/null +++ b/src/pstn/tones.h @@ -0,0 +1,40 @@ + +enum { + TONE_OFF = 0, + TONE_GERMAN_DIALTONE, + TONE_GERMAN_OLDDIALTONE, + TONE_AMERICAN_DIALTONE, + TONE_GERMAN_DIALPBX, + TONE_GERMAN_OLDDIALPBX, + TONE_AMERICAN_DIALPBX, + TONE_GERMAN_RINGING, + TONE_GERMAN_OLDRINGING, + TONE_AMERICAN_RINGING, + TONE_GERMAN_RINGPBX, + TONE_GERMAN_OLDRINGPBX, + TONE_AMERICAN_RINGPBX, + TONE_GERMAN_BUSY, + TONE_GERMAN_OLDBUSY, + TONE_AMERICAN_BUSY, + TONE_GERMAN_HANGUP, + TONE_GERMAN_OLDHANGUP, + TONE_AMERICAN_HANGUP, + TONE_SPECIAL_INFO, + TONE_GERMAN_GASSENBESETZT, + TONE_GERMAN_AUFSCHALTTON, + TONE_GERMAN_CW, + TONE_AMERICAN_CW, +}; + +struct isdn_tone { + int tone; + void *pattern; + int count; + int index; +}; + +void isdn_tone_generate_ulaw_samples(void); +void isdn_tone_copy(struct isdn_tone *t, uint8_t *data, int len); +int isdn_tone_set(struct isdn_tone *t, int tone); + +