https://wiki.innovaphone.com/api.php?action=feedcontributions&user=Ckl&feedformat=atominnovaphone wiki - User contributions [en]2024-03-29T10:57:15ZUser contributionsMediaWiki 1.39.1https://wiki.innovaphone.com/index.php?title=Reference8:TAPI_Service_Provider&diff=71288Reference8:TAPI Service Provider2024-03-20T10:15:09Z<p>Ckl: /* Tweaks */</p>
<hr />
<div>The ''innovaphone PBX V8.0 based TAPI Service Provider'' (TSP) is an enhanced version of the [[ Reference7:Unified Win32 and x64 TAPI Service Provider | previous TSP version ]] that takes advantage of the [[ Reference8:SOAP API | new SOAP features ]] provided with version 8 PBX firmware. It implements - like the previous versions - TAPI Version 3.0 without MSP (Media Service Provider).<br />
<br />
The [[Reference7:Unified Win32 and x64 TAPI Service Provider| previous TSP ]] still must be used for V7 PBX systems. <br />
<br />
This article describes how to install and use it as well how to configure the PBX in order for the TSP to work properly.<br />
<br />
==Applies To==<br />
This information applies to<br />
* innovaphone PBX V8.0 based TAPI Service Provider, Build 8001 and later<br />
* innovaphone PBX V8 and later (that is, this Service Provider runs with PBX Firmware V8 and later)<br />
<br />
==More Information==<br />
<br />
<br />
<br />
=== Enhancements ===<br />
; Support for V8 firmware [[Reference8:Administration/PBX/Objects#Devices | devices]] : Each user object device is represented as a TAPI line (all sharing an identical address, the objects extension a.k.a. ''Number'' property). This now allows to select individual registration devices when multiple devices are registered with the same PBX. Please note that in V8, [[ Reference8:Administration/PBX/Objects/Edit_Forks | mobility devices ]] are not shown and thus not represented by TAPI line device. This was introduced in V9 only.<br />
; Support for presence based lines : These are TAPI lines shown which reflect the users presence state. Presence state ''open'' is mapped to a TAPI device status ''in service''. Presence activity ''on-the-phone'' is mapped to a virtual call in state ''connected''.<br />
: '''NB''': V8 PBX Firmware did set presence activity ''on-the-phone'' for each user object having a call. Unfortunately, as this does create performance issues, this behaviour has been removed from V9 PBX Firmware. The ''presence line'' feature may be useless with V9 PBX when the application did rely on this particular V8 PBX behaviour.<br />
<br />
===System Requirements===<br />
The TSP will install on any Windows 32bit and x64 platform down to Windows XP/Server 2003. For older systems, you must use a deprecated [[Reference7:TAPI_Service_Provider | previous TSP version]]. For systems running PBX firmware version 6 or earlier, you must use the even older [[Reference:TAPI_Service_Provider | version 5 based TSP]]. Note that there is no x64 version of the version 5 TSP!<br />
<br />
The TSP needs to maintain parallel connections to each individual PBX in the system. For larger systems (i.e. systems with a huge number of PBXs), this may create substantial load to the underlying windows machine. The number of parallel activities scheduled by the TSP is thus limited as a function of the available main memory and number of processors. In particular, a maximum of 20 activities per available processor is allowed (up to build 8088, the limit is 60/processor for later builds). If this limit is exceeded, the TSP will issue performance warnings of class WARNINGS in the TSP log file and system TAPI performance will be poor. Use a more capable machine then.<br />
<br />
Microsoft Windows operating system version for desktop clients (as opposed to server systems) limit the number and performance of TCP/IP connections. This may lead to bad performance or occasional request failures. We generally recommend to use server operating systems for 3rd party TAPI installations thus.<br />
<br />
No special PBX licenses are required to use the innovaphone TSP.<br />
<br />
=== Download === <br />
The TSP will be available on the tab ''Software'' in the [https://store.innovaphone.com/ Store].<br />
<br />
===Installation===<br />
Windows cannot run a Win32 TSP on an x64 platform (although it can run Win32 applications on such platforms). This is why there are 2 versions of the setup, the x64 installer (<code>setup64-release.msi</code>) and the Win32 installer (<code>setup32-release.msi</code>.<br />
<br />
The ''release'' setup packages provided will install the retail version. This is recommended for production purposes but provides no debug options whatsoever. To track down possible problems, support may instruct you to install the debug version. <br />
<br />
The TSP may be installed on each machine where a desired TAPI based<br />
application is to be run. If for example, Outlook is to be used, then<br />
each client PC running Outlook may have the TSP installed. Although<br />
this is typical for a 1st party configuration, all clients may have full<br />
3rd party functionality, that is, they may control all existing lines.<br />
<br />
As an alternative, the TSP can be installed on a single machine and a<br />
3rd party TAPI server product (such as the IXI-Call Server available as<br />
a separate product) may be used to provide the network clients with a<br />
TAPI interface. Also, Microsoft’s Remote TAPI Server should work but is not being tested, so you use it on your own risk.<br />
<br />
To install, <br />
* select and download the <code>setup32-release.msi</code> install packages on 32bit platforms or the <code>setup64-release.msi</code> install package on 64bit platforms.<br />
* double-click the install package to launch the installer<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - zipcontent.png]]<br />
* accept the license agreement<br />
* select the target folder<br />
* complete the installer<br />
<br />
When the installer has copied all files to the target machine, you need to add the TSP to the machine's TAPI system<br />
* open the ''Telephone and Modem'' control panel<br />
* Switch to the rightmost tab (''Extras'')<br />
* Click on the ''Add...'' button<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - Control Panel Add.png]]<br />
* Select the innovaphone TAPI provider driver<br />
* Fill out the configuration dialogue<br />
* Install the provider by clicking ''OK''<br />
<br />
==== File Organization ====<br />
Windows forces all TAPI service provider files to reside in Windows' ''System Folder''. This is the <code>system32</code> folder in your windows install directory (usually <code>C:\windows\system32</code>), even if you are running an x64 platform! The installer will thus copy these files (<code>tsp8.tsp</code> and <code>tsp8UI.dll</code>) in to this directory. All other files however will be copied to the <code>innovaphone AG\innovaphone® PBX V8 TAPI Service Provider</code> folder underneath your systems ''Program Files Folder''. <br />
<br />
The subdirectories <code>Debug</code> and <code>Logs</code> are created. <code>Debug</code> contains the driver's debug version, <code>Logs</code> will receive the log files when a debug version is used.<br />
<br />
On x64 platform systems, the Win32 version of the configuration DLL (<code>tsp8UI.dll</code>) will be installed to the windows ''Windows on Windows64 System Folder'' (<code>SysWOW64</code>).<br />
<br />
====Upgrading to a newer Version====<br />
When you attempt to upgrade the TSP from a previous version, the Windows<br />
installer will first remove any previous installation. When the new<br />
software is installed then, the TSP will be installed again into the<br />
TAPI system. <br />
<br />
There is no need to remove the driver from the ''Telephone and Modem Control Panel'', as this would make you loose your driver configuration.<br />
<br />
====Upgrading from the old Win32-only Version====<br />
If you upgrade from the older Win32-only versions of the TSP (soap-appl/tapi/7.00 or soap-appl/tapi/5.00), you must first remove the old TSP from ''telephone and modem'' control panel and uninstall the old product from the ''Software'' (or ''Programs and Functions'') control panel.<br />
<br />
When you upgrade from the old V7 64-bit TSP (soap-appl/tapi/7.00-64), you should first update this older version to the latest build available.<br />
<br />
This procedure ensures that during the upgrade your TAPI lines retain their internal identifiers and thus their meaning in your TAPI application's configuration.<br />
<br />
==== Uninstalling the TSP ====<br />
To uninstall the TSP proceed as follows<br />
* Remove the TAPI driver from the TAPI system using ''Telephony and Modem Control Panel''<br />
* close ''Telephony and Modem Control Panel''<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* remove the installation using windows ''Programs Control Panel'' or by invoking the original .msi again<br />
<br />
You will notice that Windows may fail to remove the driver files from the ''Windows System Directory''. To clean up remove <code>tsp8.tsp</code> and <code>tsp8UI.dll</code> from the <code>system32</code> folder as well as - on x64 systems - from the <code>SysWOW64</code> folder.<br />
<br />
Also, if you did file tracing, any remaining debug files in the <code>Logs</code> folder are left over and need to be removed manually.<br />
<br />
The TSP will create some entries in the windows registry which will not be removed on uninstall. It is recommended to leave these entries as is. Only if you are sure you will never install the TSP again on this system or you are sure you will never use it with the PBX installation you used so far, you may want to delete them from the registry.<br />
<br />
==== Rolling out First Party TSPs to multiple PCs ====<br />
Normally, when multiple users require CTI and hence TAPI functionality, the best way is to use a server based, multi-client 3rd party CTI application. This will share all functions among all client PCs. If this is not an option, e.g. because the TAPI application in use does not support it, you may want to consider using Microsoft's TAPI Server and remote TSP. See Microsoft's [http://technet.microsoft.com/en-us/library/cc786297%28v=ws.10%29.aspx Telephony service providers overview] and [http://technet.microsoft.com/en-us/library/cc770373.aspx Manage Telephony Servers] documents. <br />
<br />
If you still want to use the native innovaphone TSP on a number of PCs (instead of once on a server), you can roll out the TSP using some of the [http://support.microsoft.com/kb/816102/EN-US software deployment schemes Microsoft Server provide]. In such a case, you will likely want to deploy identical configurations to all these PCs. While this theoretically can be done by distributing registry settings, there will be a problem with the PBX access credentials. These are stored in encrypted format in the registry and can only be decrypted on the PC on which they have been set. That is, deploying such registry settings to other PCs will result in a non-functional setup.<br />
<br />
To work around this problem, you may store the credentials in clear text in the registry. To do so, you would put the ''Password'' in a REG_SZ value named <code>admin1-free</code> and the ''Account'' into a REG_SZ value named <code>admin2-free</code>. If such a value is found in the proper place in the registry, the values configured using the telephony control panel are ignored. <br />
<br />
[[Image:TAPI Service Provider-tsp8-nocrypt.png]]<br />
<br />
'''Keep in mind that having credentials in clear in the registry presents a security risk!'''<br />
<br />
This feature is available in build 8079 and later.<br />
<br />
=== Upgrade from Build 8164 or earlier ===<br />
From build 8165, the TAPI configuration has been moved to a different registry location. This has been done because latest windows versions (namely, Windows 10) do not allow the TSP to access registry information in the location used so far (<code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Telephony\innovaphone® PBX V8 TAPI Service Provider</code>). To work around this issue, it is now stored in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code>.<br />
<br />
The TSP configuration dialogue however runs with user privileges (as opposed to the TSP which runs with limited service privileges). It can therefore read the old configuration information and copy it to the new location. From build 8165, the configuration dialogue will thus copy any old configuration information to the new location when it is opened. When you upgrade an existing installation and open the configuration dialogue, you will see no change. However, when the configuration is saved, it is then present in the new location.<br />
<br />
'''To upgrade an installation from build 8164 or earlier (that is TAPI 'V8 TAPI Service Provider (32 and 64bit) - hotfix14'' available in the ''V8 applications hotfix20'' package or earlier), proceed as follows:'''<br />
* stop the telephony service (e.g. <code>net stop tapisrv</code>)<br />
* install the new TSP<br />
* open the TSP configuration dialogue<br />
* verify the configuration data<br />
* save the configuration<br />
* (re) start your TAPI application<br />
<br />
If you fail to migrate the configuration as described, the TSP will start, but not work!<br />
<br />
===Configuration===<br />
The TSP configuration dialogue looks like this:<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - Config UI.png]]<br />
<br />
* The ''VERIFY'' button will verify the configuration. Note that the ''Username'' drop down list will only be populated after a successful verify. <br />
* The ''OK'' button will save the configuration.<br />
* The ''CANCEL'' button will quit the configuration without saving any changes. If it is the initial configuration while you add the TSP via the telephone and modem control panel, the TSP will not be added <br />
<br />
TAPI talks to the PBX using [[Reference7:SOAP_API | SOAP ]] which in turn uses HTTP for communication. Both secure (https) and non-secure (http) communication is supported. In any case, HTTP basic authentication is used. <br />
To be able to connect to the PBX, the TSP needs to know proper credentials to use during HTTP authentication. These are referred to as ''PBX Account'' and ''PBX Password''. Also, a suitable ''TAPI User'' must be selected.<br />
<br />
==== Controlling the Line Devices handled by TAPI ====<br />
<br />
TAPI connects to your PBX as a PBX user referred to as ''TAPI User''. It will see all PBX objects that are members of groups in which the ''TAPI User'' is an active member. If the ''TAPI User'' is not an active member in any group, TAPI will see the ''TAPI User'' object only. This may be useful in a [http://en.wikipedia.org/wiki/Telephony_Application_Programming_Interface 1st party TAPI scenario]. PBX objects are represented as TAPI lines. <br />
<br />
The PBX object you use as ''TAPI User'' needs to have at least ''Viewing only'' [[Reference:Administration/PBX/Objects/Edit Rights | PBX user rights]]. Its ''Long Name'' property is used as ''TAPI User Username'', its ''Name'' property as ''PBX Account'' and the password as ''PBX Password''. This is why the PBX object used as ''TAPI User'' must have a password configured. <br />
<br />
===== 1st Party Configuration =====<br />
In a 1st party scenario, the TSP will only work on a single PBX object. This will typically be a user's phone and thus the user itself will be used as the ''TAPI User''. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - FirstParty.png ]]<br />
<br />
In such a configuration the TSP is typically installed on the users PC and the CTI software is accessing TAPI directly (such as e.g. Microsoft Outlook does). <br />
<br />
The TSP configuration dialogue will check for the number of lines seen. If it is only one, then it will issue a warning message:<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - OnlyOne.png]]<br />
<br />
This is because 3rd party configurations are much more common and this situation often indicates a configuration problem. In a first party configuration, you can safely ignore this message. <br />
<br />
===== 3rd Party Configuration =====<br />
In a 3rd party configuration, the TSP will work with multiple PBX objects. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - ThirdParty.png]]<br />
<br />
Typically, you will share a single TSP instance on a server system for use by several users on their desktop PCs. This is done by virtue of a ''TAPI Server''. There are various TAPI server products available on the market, including but not limit to the Estos ProCall product and the remote TAPI server included in Microsoft Windows server operating systems. <br />
<br />
In this scenario, it is recommended to create a pseudo PBX user object for use as the ''TAPI User''. This pseudo user is often called <code>_TAPI_</code>. You would create a dedicated group to control the list of PBX objects the TSP creates a line device for. This group is often called <code>tapi</code>.<br />
<br />
==== Selective Call Forwards ====<br />
Many CTI applications support distinct call forwards for internal and/or external calls. The TSP will translate such requests to a call forward on the PBX which has the [[Reference7:Administration/PBX/Objects/Edit_CFs | ''Only'' or ''Only not'' property]] set to the number of the trunk line. For this to work, it needs to know this number. To know the number, the trunk line PBX object must be seen by the TSP (see above). If this is not the case, the TSP configuration dialogue will issue a warning:<br />
<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - NoTrunk.png]]<br />
<br />
In a vanilla first party scenario, this is obviously not the case. If you don't care, you can safely ignore this issue. Attempts to set such call forward will be rejected then as ''operation unavailable''.<br />
<br />
In a Master/Slave PBX scenario where there is no Trunk Line object on the Master PBX, but only on the Slave PBXs, a workaround is required to suppress the warning and enable selective call forwarding on the Slave PBXs. In this case, create a dummy "Trunk Line" object on the Master PBX with the same extension number used by the "Trunk Line" objects on the Slave PBXs (usually 0).<br />
<br />
==== Multi Site Configuration ====<br />
In a system with multiple PBXs, the TSP needs to connect to each of the individual PBXs. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - MultiSite.png]]<br />
<br />
In this case, you would specify your master PBX as ''PBX Master''. There is no need to configure the complete PBX tree to the TSP as it is determined dynamically on runtime by analysing the PBX/Node objects in the master PBX and their registration status. If a registered PBX is found, this PBX is added to the list of active PBXs and a new connection is established. The PBX tree is built and maintained dynamically. A reconfiguration or restart of the TSP is not required on changes thus.<br />
For this to work<br />
<br />
* the PBX object used as ''TAPI User'' must exist in each PBX with same properties (including name and the password). You may want to use <code>_TAPI_</code> as ''Name''/''Long Name''. This object must be ''active'' member in a group (which you may want to call <code>tapi</code>). A password must be set. The object must have at least ''Viewing only'' rights<br />
* the slave PBX-objects (or in older firmware versions the slave PBX Node-objects) must be visible to the TSP (that is, must be non-active member of the group the ''TAPI User'' object is an active member of, e.g. <code>tapi</code>) so that the slaves are made known to the TSP.<br />
<br />
<br />
In a replicated scenario, you would create a ''TAPI user'' as recommended above and [[Reference8:Administration/PBX/Objects#Objects_with_empty_node_or_PBX | leave the ''PBX'' and ''Node'' properties empty ]]. This user should then be used as both ''PBX Account'' and ''TAPI User Username'' in the TAPI configuration dialog.<br />
<br />
''Further relevant settings'':<br />
<br />
You can disable slave detection by checking ''Do not monitor slaves'' property in the TSP configuration dialogue. <br />
<br />
TAPI maintains an ''address'' property per line device. This usually is the lines extension. In a multi site configuration, the address property will be set to the ''Number'' property of the node the respective PBX object is configured in, plus the ''Number'' property of the object itself. So if an object has ''Number'' <code>42</code> and lives in node <code>801</code>, then its correspondence line device will have address <code>80142</code>. By checking the ''Use pure node extensions'' property in the TSP configuration dialogue, you change the algorithm so that only the objects own ''Number'' is used (<code>42</code> in our example).<br />
<br />
==== Standby Configurations ====<br />
In a system with standby PBX for the master PBX, you need to specify the ''PBX Standby'' IP address. This will be connected if the master is unavailable. Note that there is no need to explicitly configure slave-standby PBXs.<br />
<br />
==== Working with multiple, unrelated PBXs ====<br />
When working with multiple, unrelated PBXs (that is, PBXs that do ''not'' form a PBX tree as slaves and masters do), the TSP cannot derive the list of PBXs to track from the registration status. To support such a configuration, you will need to configure the extraneous master PBXs manually using regedit. Please note that using regedit may harm your system and may even cause inability to boot!<br />
<br />
To find and edit the right registry entries, proceed as follows:<br />
<br />
* open the key <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/><br />
* open its subkey <code>Device</code>''n'' where ''n'' is the provider id of the installed innovaphone TSP (for builds before 8164 only)<br />
* Open the <code>FQDN</code> key and add all extra PBXs<br />
* For those PBXs that have a standby PBX, add a value to the <code>StandbyFQDN</code> key (please note: these are parallel lists. So master and standby need to have the same respective index in the <code>FQDN</code> and <code>StandbyFQDN</code> value lists)<br />
<br />
The standard configuration UI will not show these extra values. However, it will also not touch them. So even if you use the standard UI to edit, only the first value in the lists will be changed and the remainder left unchanged. You can thus safely use the standard UI to edit all other values. As with multi site configurations, all PBXs need to be accessible using the same ''TAPI User''.<br />
<br />
Please note that this method is deprecated and will likely be removed from future versions of the TSP. <br />
<br />
==== Setting the Line Device Name ====<br />
The TSP will derive the line device's name from the properties of the respective PBX object. By default the name will be the objects ''Long Name'' followed by the name of the PBX the user ought to register with in parentheses. So if the users ''Long Name'' is <code>Foo Bar</code> and the registration PBX is <code>branch1</code>, the line device will be called <code>Foo Bar [branch1]</code>. You can specify a different pattern by changing the ''TAPI Line Names'' property of the TSP configuration dialogue. The following replacement characters are available:<br />
<br />
{|<br />
|+<br />
| Meta || Replacement<br />
|+<br />
| %c || The objects ''Long Name'' (cn)<br />
|+<br />
| %C || The objects ''Long Name'' (cn) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Long Name''<br />
|+<br />
| %d || The objects ''Display Name'' (dn)<br />
|+<br />
| %D || The objects ''Display Name'' (dn) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Display Name''<br />
|+<br />
| %h || The objects ''Name'' (h323 alias)<br />
|+<br />
| %H || The objects ''Name'' (h323 alias) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Name''<br />
|+<br />
| %t || The ''Name'' of the ''Device'' the line represents<br />
|+<br />
| %T || The ''Hardware Id'' of the ''Device'' the line represents (from build 8181)<br />
|+<br />
| %e || The objects extension (e164)<br />
|+<br />
| %E || The objects extension (e164) prefixed with the objects node number<br />
|+<br />
| %N || The line address as reported to TAPI<br />
|+<br />
| %n || host name (of master pbx)<br />
|+<br />
| %p || ''':'''''port-number'' (of master pbx's http access, empty if 80)<br />
|+<br />
| %P || raw port number of master pbx<br />
|+<br />
| %u || url-like user name ('''''user''@''host''''')<br />
|+<br />
| %U || user url as per draft-levin-iptel-h323-url-scheme-04 ('''h323:://''user''@''host'':''port''''')<br />
|+<br />
| %X || the PBX name the user is registered with (note that using this pattern may result in a change of the name when a standby situation occurs)<br />
|+<br />
| %x || the PBX name the user is reported by (note that using this pattern may result in a change of the name when the users ''PBX'' attribute changes)<br />
|}<br />
<br />
The default pattern is <code>%C (%x)</code>.<br />
<br />
<br />
<!-- rufumleitung, extern, intern, amtsleitung, vom amt, zum amt --><br />
<br />
==== Miscellaneous Flags ====<br />
; Show full E164 Numbers : when set, the TSP will announce the full calling number from the root when indicated by the PBX in the TAPI ''calling party name'' attribute. The number will be appended to the calling name with a <code>@</code>.<br />
; Do not show parked Calls : will hide parked calls from the TAPI application (as some get confused and don't know how to handle them)<br />
; Map PBX Devices to Lines : if set, the TSP will create one TAPI line for each individual [[Reference9:PBX/Objects#Devices|PBX device]] configured in a user object. See [[#Enhancements]] above. If you do not set this flag, see [[Support:How should TAPI line device be registered?]]<br />
; Map Presence Status to TAPI Lines : if set, the TSP will create one extra TAPI line for each PBX device object. See [[#Enhancements]] above.<br />
; No special Disconnect Behaviour : normally, lines monitored by TAPI will behave slightly different when a call is terminated. With no TAPI on the line, the call will be disconnected immediately (from a PBX point of view). In cases where the phone would play some tones after termination of the call (e.g. a busy tone when the call has never been connected to the far end), this state is simulated by the phone and not visible to TAPI. Therefore, it is not possible to use TAPI functions on this call any more (as it in reality does not exist any more). When this flag is set, monitored lines will not be treated specially. Available from hotfix 6. To prevent the IP-Phone to play any tones after the call is disconnected, the phone preferences option '''Go onhook if final call is released by remote side even if handset is lifted''' can be activated. This is the feature of the IP-Phone is useful in a e.g. call center at agent phones and can be configured here [[Reference11r1:Phone/Preferences]].<br />
: To prevent an innovaphone IP-Phone from playing any tones after a call is disconnected, the phone preferences option '''Go onhook if final call is released by remote side even if handset is lifted''' can be activated. This feature is useful for agent phones in a call center for example and can be configured in [[Reference11r1:Phone/Preferences]]<br />
<br />
===Trouble Shooting===<br />
The TSP will (from build 8095) write messages of type INFO (''informational messages'') or WARNING (''Warnings/Errors'') to the windows event log. Such events are always logged to the application log and event source is the provider name (''innovaphone® PBX V8 TAPI Service Provider'').<br />
<br />
==== Turning on Debugging ====<br />
There are 2 versions of the TSP, debug and retail, which are both copied to the machine during setup. The retail version is installed into the system directories, whereas the debug version is stored in a separate directory. This is available through a short cut in the Start menu.<br />
<br />
The TSP can produce a number of debugging messages which can be helpful to debug issues (in both ''retail'' and ''debug'' builds). By default, debug messages are written to the systems debug buffer mechanism (using OutputDebugString()). Such messages can be examined using standard debugging tools, such as for example <code>dbgview</code> which is [http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx available from Microsoft]. <br />
<br />
Debug messages have a class associated with it and the amount of messages written can be controlled by enabling or disabling specific classes. This is done by setting a bitmask in the registry value <code>TraceLevel</code> in the <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> key. <br />
<br />
The easiest way to set the TSP's debug level is with the ''TSP Control'' utility (''tsptray.exe'') which is installed with the TSP.<br />
<br />
The available classes include<br />
<br />
{|<br />
| Bit in <code>TraceLevel</code> || Class <br />
|-<br />
| 0x00000001 || Minimum tracing <br />
|-<br />
| 0x00000002 || TAPI api traces <br />
|-<br />
| 0x00000020 || Basic telephony object creation/destruction <br />
|-<br />
| 0x00000040 || Thread creation/destruction <br />
|-<br />
| 0x00000080 || Request creation/destruction <br />
|-<br />
| 0x00000100 || call related messages <br />
|-<br />
| 0x00000200 || Call id map <br />
|-<br />
| 0x00000400 || Warnings/Errors <br />
|-<br />
| 0x00000800 || Worker thread execution <br />
|-<br />
| 0x00001000 || Full lock/unlock notifications <br />
|-<br />
| 0x00002000 || Win32 Critical section create/destroy <br />
|-<br />
| 0x00004000 || Agent proxy support <br />
|-<br />
| 0x00008000 || constructor/destructor debug <br />
|-<br />
| 0x00010000 || development debugs <br />
|-<br />
| 0x00100000 || verbose debugging <br />
|-<br />
| 0x00200000 || SOAP trace <br />
|-<br />
| 0x00400000 || SOAP message dumps <br />
|-<br />
| 0x01000000 || ATL debug <br />
|-<br />
| 0x02000000 || line related messages <br />
|-<br />
| 0x04000000 || informational messages <br />
|-<br />
| 0x10000000 || dump call infos <br />
|-<br />
| 0x20000000 || dump PBX infos <br />
|-<br />
| 0x80000000 || output log messages to file <br />
|}<br />
<br />
If <code>TraceLevel</code> is not set, it defaults to (hex) <code>04000400</code> in retail builds and to (hex) FFFFFFFF in debug builds. A nice value to use is (hex) <code>92200580</code>. The <code>TraceLevel</code> can be changed during runtime of the provider (it may take up to 10 seconds though for the setting to take effect). In normal scenarios there is no need to install the debug version.<br />
<br />
Both ''informational messages'' and ''Warnings/Errors'' are always turned on (from build 8095).<br />
<br />
: A problem has been reported on Server 2008 x64 systems which may also apply to others. On such systems, the value of <code>TraceLevel</code> may be reset to its default every 10 seconds. A workaround is to change the account the Telephony service is running in from its default (usually ''Network Service'') to e.g. ''Local System''.<br />
<br />
===== Crash Dumps =====<br />
From build 8063 the TSP will write crash dumps to the log directory (usually something like <code>C:\Program Files\innovaphone AG\innovaphone® PBX V8 TSP\Logs</code>) in case of a trap. In release installs, these are not very useful. For debug builds though, they include helfpul information. Please provide these files (the can be zipped to a great extent) to support if requested. A crash dump file will be called something like <code>TSP8<i>build</i>-<i>hour</i>#<i>minute</i>-<i>day</i>.<i>month</i>#<i>seqnr</i>.dmp</code>.<br />
<br />
===== Forcing a Crash Dump =====<br />
In rare cases, it may be useful to force a TAPI crash for debug reasons. This can be done by issueing a <code>lineMakeCall</code> with called number <code>0815</code>, called-subaddress <code>4711</code> and called-name <code>!crash!</code> or simply calling <code>0815|4711^!crash!</code> from phone.exe.<br />
<br />
==== Saving Log Messages to a File ====<br />
The <code>0x80000000</code> value is special, as it does not denote a message class. Instead, it turns on log file writing, both in retail and debug versions. For this to work, the registry value <code>DoTraceFile</code> must be set to <code>1</code> in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> when the TSP starts. If this value is not present, it defaults to <code>1</code> in both retail and debug builds. Setting it to 0 disables log file writing regardless of any other setting. This may save some CPU cycles for installations with a real large number of lines.<br />
<br />
The TSP will keep 24 log files (a days worth) by default. This value can be changed using the <code>NumLogFiles</code> value in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>. Older log files will be removed. Also, when the TSP shuts down, it by default will remove all log files it created so far. However, any TSP will only remove log files created by itself. This ensures that if the TSP or the system terminates prematurely, the log files will be kept even if a new instance is started and terminated later on. Form TSP V8 hotfix 8 on, this behaviour can be tweaked by setting the DWORD registry value <code>KeepLogFiles</code> in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>:<br />
{|<br />
| 0 || default || remove all log files on termination<br />
|-<br />
| 1 || || keep the last ''n'' log files (depending on <code>NumLogFiles</code>) on termination<br />
|-<br />
| 2 || || keep all log files<br />
|}<br />
<br />
Larger systems (500 monitored lines and more) can slow down considerably when using the debug drivers.<br />
<br />
==== Installing the Debug Version ====<br />
To install the debug version, you first install the retail version as outlined above. You then copy the debug driver files from the <code>Debug</code> directory to your windows system directory <code>system32</code>. You may want to use the shortcut to the <code>Debug</code> folder which has been installed to the start menu for convenience. <br />
<br />
For the copy to work, proceed as follows<br />
* close ''Telephony and Modem Control Panel'' if you have it open<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* copy the debug driver files to your system directory. On x64 systems, be sure to use a 64bit application such as ''Windows File Explorer for'' (<code>explorer.exe</code>) for this. Windows will silently redirect any 32bit application to the <code>SysWOW64</code> directory when accessing <code>system32</code>.(if the copying fails, we recommend to deactivate the telephony service. But don’t forget to reset it to automatically if you are done copying). Overwrite the existing files: <code>installer.dll, installer.pdb, TSP8.pdb, TSP8.tsp</code><br />
* if the copy does not succeed or the debug driver is not shown n the modem control panel after, it might be that a TAPI application re-starts the windows telephony before you actually to the copy. If so, you can set the ''Startup type'' of the Windows ''Telephony'' service to <code>Disabled</code> instead of ''Manual'' in the services control panel (''services.msc''). You can then ''Stop'' the services, copy the file and finally set the services mode back to <code>Manual</code><br />
<br />
When you open ''Telephony and Modem Control Panel'' again, you should notice that the TSP driver name has changed to the debug version.<br />
<br />
==== Switching back to the Retail Version ====<br />
To go back from the debug to the release version, proceed as follows:<br />
* close ''Telephony and Modem Control Panel'' if you have it open<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* delete the debug driver files from your system directory. On x64 systems, be sure to use a 64bit application such as ''Windows File Explorer for'' (<code>explorer.exe</code>) for this. Windows will silently redirect any 32bit application to the <code>SysWOW64</code> directory when accessing <code>system32</code><br />
* repair the installation using windows ''Programs Control Panel'' or by invoking the original .msi again<br />
<br />
There is no need to remove the driver from the ''Telephone and Modem Control Panel'', as this would make you loose your driver configuration.<br />
<br />
Please note that simply re-installing the driver from the original .msi without removing it from the system directory will not work.<br />
<br />
<br />
<br />
===References===<br />
The test applications provided with this setup comes from [http://www.julmar.com Julmar Technology Inc].<br />
<br />
=== Limitations ===<br />
<br />
Please note that there currently is a limitation to 2000 users being in<br />
all active groups of a particular user. Thus, you cannot configure more<br />
than 2000 users to be handled by TAPI on a single PBX. This is a PBX<br />
limitation (and applies for all PBX groups).<br />
<br />
TAPI has a flat line model. That is, all line numbers (aka<br />
''extensions'') are considered to live in a single name space. As a<br />
result, lines with identical numbers cannot be distinguished in TAPI<br />
(although they can exist). All extensions in all nodes of a PBX<br />
numbering tree are represented as TAPI lines. When the TSP works with a<br />
PBX that implements a hierarchical numbering tree, then some lines may<br />
receive identical numbers (their node-local extension which may overlap<br />
between nodes depending on the setting of the ''Use pure node extensions'' property). When a TAPI application uses these numbers to initiate<br />
calls to such lines, the call will work or not work depending on the<br />
calling lines position in the numbering tree (that is, lines within the<br />
same node as the called line will be fine, others may fail).<br />
<br />
The PBX's SOAP interface (on top of which the TSP is built) has no primitives to initiate a directed call pick-up based on a target number. The TSP will reject such requests with ''Operation not available''. Pickup by name (which is understood as a group name) or unspecific is supported though.<br />
<br />
==== Citrix Environments ====<br />
What happens is that all Citrix sessions share the same TAPI driver. This allows all users in the Citrix sessions to select "their" line from the set of all TAPI lines. Then everything works as desired. It is important to note that theoretically one user can select the line of another user.<br />
You can use the "Microsoft Remote TAPI Server", to implement access rights for lines for separate Citrix sessions.<br />
<br />
===Known Problems===<br />
<br />
<br />
* The TSP is not tested with Microsoft’s Remote Tapi Server. While some installation have reported this to work fine, others have encountered problems. This scenario is not supported by innovaphone<br />
* The TSP will read its configuration when it is loaded by the system. Thus, configuration changes require a re-load of the TSP. Unfortunately, there is no reliable way to force the system to unload the TSP, so you may have to reboot the system for changes to take effect. See the [[ Howto:Troubleshooting_the_TAPI_service_provider | TAPI trouble shooting article ]] for details<br />
* The TSP will use HTTP basic authentication to talk the PBX. So if you disable basic authentication in the PBX's configuration, the TSP will not work. It is recommended to use HTTPS<br />
* TAPI requires the TSP to assign a unique id to each line device. This ID must not change between re-boots of the system or between upgrades of the TSP. This is done by keeping a persistent table in the windows registry (in the <code>lineGUIDs</code> subkey of <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>) that maps the PBX's line GUID to a fixed integer value (known as ''permanent line id'' in TAPI speak). This key is retained even on uninstall. To get rid of it, you must remove it manually<br />
* Microsoft installer fails to remove driver files installed to the windows system folder. This is why the TSP driver files are still present after an uninstall of the software. To get rid of them, remove <code>TSP8.tsp</code> and <code>TSP8UI.dll</code> from both ''%windir%''<code>\system32</code> and ''%windir%''<code>\sysWOW64</code><br />
* From Version 9, hotfix 1, the PBX firmware will not list objects tagged as [[Reference9:PBX/Objects#General_Object_Properties | ''hide from LDAP'' ]] in the result of a [[Reference9:Concept_SOAP_API#UserInfo.5B.5D_FindUser.28string_v501.2C_string_v700.2C_string_v800.2C_string_cn.2C_string_h323.2C_string_e164.2C_integer_count.2C_integer_next.29 | FindUser() ]] call. As a result, such objects will not be shown in the TAPI configuration dialogue ''Username'' drop-down. If you want to use such an object as ''TAPI User'' you can simply type in the name without selecting it from the drop down.<br />
* Sometimes, setting configuration with ''TSP Control'' (not the telephone control panel dialogue) does not work due to windows' ''User Access Control (UAC)'', see [[Howto:TAPI_TSP_Control_Windows7]] for Details<br />
* Various users have reported that first party TSP installations do not work reliably with Microsoft Outlook. Symptoms reported are spurious error pop-ups from Outlook although there was no real problem. We do not recommend first party installations anyway (see [[#Rolling_out_First_Party_TSPs_to_multiple_PCs]] for a reasoning), but these issues are related to Microsoft Outlook only. No such problems have been reported with other first party TAPI applications. Consider using solutions like Estos ProCall instead.<br />
* The number of parallel threads used within the TSP is limited to max. 60 per CPU. As each connected PBX (amongst other things) is handled by a separate thread, if you have a large number of PBXs connected and only have a single CPU, you may run out of threads, resulting in a WARNING messages ''about to spawn new workerthread (n requests, m threads) -- but limit l exceeded''. This usually happens when you run the TSP on a virtualized platform such as vmWare and only dedicate a single CPU. Ignoring this warning results in sluggish behaviour. <br />
* The TSP can work with dynamic PBXs too. However, the TSP needs to determine the slave PBXs IP addresses from the master. If the slave is a dynamic PBX and it is hosted on the same device as the master and it is registered to the master using 127.0.0.1, the TSP will only learn the 127.0.0.1 as the slave's IP address. Of course, connection to the slave PBX will fail then. <br />
<br />
==== TAPI Operation with 3rd party Phones or innovaphone Phones registered with SIP ====<br />
Various TAPI functions rely on use of the [[Reference:Remote Control Facility|Remote Control Facility]] message. This message is not supported in 3rd party phones and also not fully supported in innovaphone Phones when they are registered to the PBX using SIP. Full TAPI functionality is thus only available for innovaphone phones registered to the PBX using H.323.<br />
<br />
Known Limitations:<br />
* Accepting an incoming call (lineAnswer)<br />
* Automatic initiation of an outgoing call in handsfree mode (instead, the calling phone first rings and starts the outgoing call upon of-hook)<br />
* toggle between 2 calls active on a phone (lineSwapHold)<br />
* initiation and termination of a 3PTY (lineCompleteTransfer with mode LINETRANSFERMODE_CONFERENCE, lineDrop on a conference call)<br />
<br />
==== TAPI on Windows8 / Server 2012 ====<br />
<br />
Windows8 doesn't let you disable ''User Access Control'' (UAC) completely. This has the disadvantage that ''TSP Control'' (tsptray.exe) cannot change system registry entries, which it needs to do to e.g. change debug settings for the TSP.<br />
<br />
There are 2 ways around it: <br />
# use the hidden ''Administrator'' account on Windows 8<br />
# elevate the tsptray.exe application<br />
<br />
To use the elevated ''Administrator'' account on Windows 8 you first need to un-hide it:<br />
* log in to an account with administrator rights <br />
* use the ''Windows-Key+X'' shortcut and select ''Command Prompt (Administrator)'' (''Eingabeaufforderung (Administrator)''). An elevated cmd prompt appears<br />
* type <code>net user administrator /active:yes</code><br />
* the fully elevated ''Administrator'' account is now available and you can log-in to this account as usual<br />
* '''Please make sure the account has a password set - by default, it does not!!'''<br />
<br />
To elevate the tsptray.exe application;<br />
* log in to an account with administrator rights <br />
* start a windows explorer<br />
* change directory to <code>C:\Program Files\innovaphone AG\innovaphone® PBX V8-x64 TSP</code><br />
* right click on <code>tsptray.exe</code> and open the ''properties'' (''Eigenschaften'') menu<br />
* switch to the ''Compatibility'' (''Kompatibilität'') tab<br />
* tick the ''Run as administrator'' (''Program als Administrator ausführen'') check mark<br />
* save the settings<br />
<br />
Please note that Windows 8 will not let you run any "Metro App" in elevated mode!<br />
<br />
===== HTTPS ===== <br />
The TSP will not be able to talk to the PBX using HTTPS with Windows8 or Server2012. This [[Reference8:Release_Notes_TAPI_V8#3722_-_HTTPS_SOAP_connections_did_not_work_on_Windows8_.2F_Server_2012|has been fixed]] with V8 TAPI Service Provider (32 and 64bit) - hotfix10.<br />
===== .Net 3.5 missing on Server 2012 =====<br />
Server 2012 has no support for .Net 3.5 by default. Even more, it cannot be installed just by downloading it. Instead, the ''NetFX3 Feature'' needs to be enabled. Here is how:<br />
<br />
Microsoft Windows [Version 6.2.9200]<br />
(c) 2012 Microsoft Corporation. Alle Rechte vorbehalten.<br />
<br />
C:\Users\Administrator>dism /online /enable-feature /featurename:NetFX3 /all /Source:''<path to windows setup, e.g. d:>''\sources\sxs /LimitAccess<br />
<br />
See also<br />
* [http://blogs.technet.com/b/aviraj/archive/2012/08/04/windows-8-enable-net-framework-3-5-includes-net-2-0-and-3-0-i-e-netfx3-feature-in-online-amp-offline-mode.aspx?PageIndex=2 Windows 8: Enable .NET Framework 3.5]<br />
* [http://msdn.microsoft.com/en-us/library/hh506443.aspx Installing the .NET Framework 3.5 on Windows 8]<br />
<br />
==== TAPI dialer fails from Outlook when Lync client was/is installed ====<br />
We have received reports that 1st-party TAPI dial-out from Outlook does not work anymore when a Lync client was installed. Some users report that this phenomenon occurred only after an upgrade to Windows 8. Reportedly, this can be [http://support.microsoft.com/kb/959625/en-us fixed by setting a registry key as outlined in Microsoft's knowledge-base]. You may want to give this a try. However, we have not seen this issue ourselves and thus can't comment on it further.<br />
<br />
==== Slow Network Performance ====<br />
TAPI is pretty sensitive to slow network performance. While the TSP is multi-threaded, some internal locking must be done so that slow requests to a PBX my block other pending requests, resulting in unpleasant performance. The TSP will create ''Windows Event Log'' entries of type ''slow SOAP call performance'' if this is detected. You should inspect your event log regularly and resolve the reason for such network performance.<br />
<br />
Long PBX request round-trips may have a number of reasons:<br />
<br />
; slow WAN performance : of course, if the WAN is slow or unstable, bad performance is the result<br />
; inappropriate QoS : SOAP is using HTTP/HTTPS. In a QoS enabled network, call signalling and RTP may work fine, whereas HTTP/HTTPS is considered to be of no importance. You need to keep in mind that such QoS policy may lead to bad TAPI performance, as the SOAP traffic used by the TSP (being based on HTTP/HTTPS) may be delayed. You should assign SOAP traffic a similar priority as you do for VoIP signalling<br />
; overloaded PBX : HTTP traffic is treated on a low priority level in the PBX. If the PBX runs near to 100% CPU load, while all RTP and VoIP signalling will work well still, HTTP traffic may get severely delayed. You should make sure your PBX runs well under 100% CPU load to get good TAPI performance. See [[Reference:Device Health Check]] for more details<br />
<br />
==== TAPI on Windows 10 ====<br />
In Windows 10, the TSP is not allowed any longer to read/write its own registry tree. For this reason, it is not possible to store or read the configuration. As a result, the TSP will not work. To fix this, you need to modify the registry access rights so that the TSP has read/write access to <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>.<br />
<br />
<br />
<br />
Note on windows registry paths<ref name="regkey"><br />
Due to a change in ''Windows Registry Access Rights'' in Windows 10, from Build 8165, the configuration is stored in <br />
: <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><br />
(a place that is suitable for Windows 10 too). Before, the configuration was stored in <br />
: <code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Telephony\innovaphone® PBX V8 TAPI Service Provider</code><br />
<br />
See [[#Upgrade_from_Build_8164_or_earlier| Upgrade from Build 8164 or_earlier ]] above for more details.<br />
</ref>:<br />
<references/><br />
<br />
=== Tweaks ===<br />
There are a few configuration options which should be used rarely. They can be enabled by setting an appropriate registry key.<br />
<br />
Since Hotfix 15 the location to set the interop tweaks has been changed to <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code>:<br />
<br />
; ProcessorMask : REG_DWORD. If set, the TSP will use <code>SetProcessAffinityMask(GetCurrentProcess(), set)</code> to limit TSP execution to one or more of the existing processors. Set to <code>0xff</code> by default.<br />
<br />
; LineTimeOut : REG_DWORD. Can be set to a number of seconds the TSP should wait for the determination of lines to finish (default 90). On a large PBX, or a PBX with slow slaves, the determination might not finish in the default time frame. If this happens, TAPI applications may show ''Line unnamed'' line entries in their line list. If this is a problem, set it to a larger value (e.g. <code>120</code>).<br />
<br />
; IgnoreDevStatus : REG_DWORD. Some applications get confused if TAPI reports line to be disconnected or out-of-service dynamically. Setting this to a non-zero value will disable any such notification.<br />
<br />
; ClearFwdOnSet : REG_DWORD. If set to a non-zero value, the TSP will clear all current call forward settings before a lineForward request is executed. This implies that all call forward settings are ''replaced'' by the new settings. Standard behaviour is to only modify the settings, that is, replace all current settings of the same type with the settings found in the lineForward request (for example, if there is only one setting in the request that defines a CFU, then the current CFU settings are replaced by the new one provided. All others, such as e.g. CFNR settings, are left untouched).<br />
<br />
: Tapi spec is pretty clear on how this should work: ''The provider should "unforward everything" prior to setting the new forwarding instructions''. Even though, for historical reasons, ClearFwdOnSet=0 has been the default in all TAPI versions prior to V8 hotfix 6, from hotfix 6 on, the default changes to 1, as this has turned out to be the widely accepted interpretation.<br />
<br />
; No3PTY : REG_DWORD. See [[Reference8:TAPI_Service_Provider#Notes_on_3PTY_Conferences | Notes_on_3PTY_Conferences]] below.<br />
<br />
; HiddenRecordingNumber: REG_SZ. Active recording in an innovaphone PBX is implemented as an implicit 3PTY conference with the recording sink as one of the parties. These will be shown as usual in the TAPI callstate. However, some applications get confused as this results in a situation, where a normal endpoint (read: phone) has more than one active (i.e. ''not on hold'') call. To suppress such calls in the TAPI call states, you can set this tweak to the number of the recording sink as configured in the phone's recording configuration tab. If so, any outgoing call from an endpoint that is registered with a PBX user object with a called number that equals the setting of this tweak, will be suppressed. So if your recording sink has the number <code>44</code>, you would set this registry value to <code>44</code> (this is available from TAPI V8 hotfix 8). Please note that this feature actually suppresses any call info for such calls, however, it does not revert previously announced call infos. So if the call is initiated using ''overlapped dialling'', all call states will be shown until the called number matches the value of <code>HiddenRecordingNumber</code> and all subsequent call states will be suppressed. This will probably leave the call shown in ''dialling'' state. The feature should be used for destinations which are called using ''block dialling'' only thus. <br />
<br />
; SilentHold : REG_DWORD. When a call is put on hold using <code>lineHold</code>, the held party will hear ''music on hold'' emitted by the PBX. The hold-initiator will hear a dialling tone (PBX firmware v11r2 and up, with older versions the initiator would hear ''music on hold''). When <code>SilentHold</code> is set to a non-zero value, the initiating party will hear silence - i.e. no dialling tone (this is available from TAPI V8 hotfix 8).<br />
<br />
<!-- There are some more values in a key underneath <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> called <code>Device</code>''XX'': --><br />
<br />
; NoDuplicateConnectedCalls : REG_DWORD. If set to 1, the TSP will never show more than one call per line to be active. Extra active calls will be shown as ''on-hold''. The situation occurs e.g. when a user initiates a 3PTY conference on the phone. This essentially is a bug fix for Microsoft's OCS client which forces any extra active call on-hold, thereby disturbing the 3PTY function. If your are using [http://www.estos.de/download/software/eccg.htm ESTOS' Call Control Gateway]: from version ''2.0.1.1474 - 05.11.2008'' there is a fix for this same problem available. This option thus is deprecated.<br />
<br />
; UseFirstLeg2 : REG_DWORD. TAPI allows only for a single leg-2 info (redirection information in case of a call forward). If a call is forwarded mutliple times, by default the last redirection is shown in TAPI. If <code>UseFirstLeg2</code> is set to 1 however, the first redirection is shown.<br />
<br />
; UseNameForDeviceGuid : REG_DWORD. The TSP will create a TAPI line for each ''Device'' of each PBX object (if ''MapDevices'' is set). The internal unique identifier includes the ''Hardware Id'' found in the ''Device''. If this changes, the TAPI line representing the ''Device'' is considered new and a new TAPI ''permanent line id'' is allocated. This may be annoying cause when a device serial number (e.g. for a telephone) is used as ''Hardware Id'', replacing the device creates a new TAPI line. If ''UseNameForDeviceGuid'' is set to 1, the TSP will use the ''Device''s ''Name'' instead (which usually not changes). While this might be more convenient, be aware that you ''must'' make sure that no PBX object has duplicate ''Name''s! Otherwise, TAPI will get confused. Available from build 8178<br />
<br />
; ShowConfGUID : REG_DWORD. If set and not 0, the TSP will implicitly set each call's CallData to a string such as "<code>conf-guid:</code>''call-guid''". ''call-guid'' is a 66-byte Unicode16 string (32 hex characters plus terminating 0). Note that this will interfere with an application's use of the lineSetCallData function (tapi.h) and is therefore disabled by default. Available from build 8190<br />
<br />
; NoFix : REG_DWORD. If set and not 0, the TSP will not trigger a full re-synchronization if it receives implausible state messages. Available from build 8197.<br />
<br />
===List of supported TSPI functions===<br />
<br />
The TSP supports the following TAPI Service Provider Interface Functions. Note that these do not map one to one with TAPI user functions. For more information, see the Microsoft TAPI documentation.<br />
<br />
*TSPI_lineAnswer<br />
*TSPI_lineBlindTransfer<br />
*TSPI_lineClose <br />
*TSPI_lineCloseCall <br />
*TSPI_lineCompleteTransfer<br />
*TSPI_lineDevSpecific<br />
*TSPI_lineDevSpecificFeature<br />
*TSPI_lineDial<br />
*TSPI_lineDrop <br />
*TSPI_lineForward<br />
*TSPI_lineGenerateDigits <br />
*TSPI_lineGetAddressCaps <br />
*TSPI_lineGetAddressID <br />
*TSPI_lineGetAddressStatus <br />
*TSPI_lineGetCallAddressID <br />
*TSPI_lineGetCallHubTracking<br />
*TSPI_lineGetCallIDs<br />
*TSPI_lineGetCallInfo <br />
*TSPI_lineGetCallStatus <br />
*TSPI_lineGetDevCaps <br />
*TSPI_lineGetExtensionID<br />
*TSPI_lineGetID <br />
*TSPI_lineGetLineDevStatus <br />
*TSPI_lineGetNumAddressIDs<br />
*TSPI_lineHold<br />
*TSPI_lineMakeCall <br />
*TSPI_lineNegotiateExtVersion<br />
*TSPI_lineNegotiateTSPIVersion <br />
*TSPI_lineOpen <br />
*TSPI_linePark<br />
*TSPI_linePickup<br />
*TSPI_lineRedirect<br />
*TSPI_lineSelectExtVersion<br />
*TSPI_lineSendUserUserInfo<br />
*TSPI_lineSetAppSpecific <br />
*TSPI_lineSetCallData<br />
*TSPI_lineSetCallHubTracking<br />
*TSPI_lineSetCallParams <br />
*TSPI_lineSetCurrentLocation <br />
*TSPI_lineSetDefaultMediaDetection <br />
*TSPI_lineSetMediaMode <br />
*TSPI_lineSetStatusMessages <br />
*TSPI_lineSetupTransfer<br />
*TSPI_lineSwapHold<br />
*TSPI_lineUnhold<br />
*TSPI_lineUnpark<br />
<br />
<br />
*TSPI_providerConfig <br />
*TSPI_providerCreateLineDevice<br />
*TSPI_providerEnumDevices <br />
*TSPI_providerGenericDialogData<br />
*TSPI_providerInit <br />
*TSPI_providerInstall <br />
*TSPI_providerRemove <br />
*TSPI_providerShutdown <br />
*TSPI_providerUIIdentify <br />
=====Notes on Consultation Calls===== <br />
This TSP supports the <code>lineSetupTransfer</code> function. However, strictly speaking, this function is not needed and should not be used. It is merely intended for applications which do not offer a consultation call interface to the user when this function is not available. Instead, <code>TSPI_lineMakeCall</code> can be used where <code>lineSetupTransfer</code> would be otherwise. The notion of a special ''consultation call'' is an idiosyncrasy forced by legacy PBX technologies which were not able to transfer two arbitrary calls. In these scenarios, a potentially to-be-transferred call needed to be linked to the primary call. The PBX can transfer two arbitrary calls and thus there is no need for <code>lineSetupTransfer</code>. This ability is indicated by the <code>LINEADDRCAPFLAGS_TRANSFERMAKE</code> and <code>LINEADDRCAPFLAGS_CONFERENCEMAKE </code> flags. The transfer is then requested by using <code>TSPI_lineCompleteTransfer</code>.<br />
<br />
=====Notes on 3PTY Conferences=====<br />
The TSP supports the management of 3PTY conferences. These can be initiated by using <code>TSPI_lineCompleteTransfer</code> with <code>LINETRANSFERMODE_CONFERENCE</code> instead of <code>LINETRANSFERMODE_TRANSFER</code>. This is indicated by the flags <code>LINEADDRCAPFLAGS_CONFERENCEHELD</code> and <code>LINEADDRCAPFLAGS_CONFERENCEMAKE</code>. The 3PTY function is actually implemented by the innovaphone IP phones (such as IP2xx). Therefore, these capabilities are only advertised if the line is based on a PBX user object. Still, some lines where the capabilities are advertised cannot do 3PTY (such as e.g. DECT lines, analogue lines, 3rd party devices, ...). So the call to <code>TSPI_lineCompleteTransfer</code> will succeed but the conference will not be initiated and the subsequent call states will not indicate a conference (as there is no). <br />
<br />
Also, if non-telephone devices are registered with a PBX user object and these device have multiple concurrent calls (e.g. a call center connected with a multi-channel XCapi), these will be viewed as 3PTY calls (as this is the way such calls appear to the TAPI: a line with more than one non-held call). If these calls are in fact no 3PTY, this may lead to confusing call indications from TAPI. It is better to register such objects as a PBX gateway object. Special treatment of 3PTY calls can be disabled by setting the registry flag <code>No3PTY</code> (from build 8087 onwards).<br />
<br />
A 3PTY conference will create a new call handle for the conference. The only operation available for such a handle is <code>TSPI_lineDrop</code>. This will terminate the conference and restore the state active before the conference was initiated (that is, the phone that implemented the conference will now have 2 calls, one active and one on hold). This is indicated by not setting <code>LINEADDRCAPFLAGS_CONFDROP</code>.<br />
<br />
A <code>lineDrop()</code> on one of the 2 ''real'' calls involved will of course cancel this call and thus remove the 3PTY.<br />
<br />
======Notes on Intrusion Calls======<br />
A special case of 3PTY is an intrusion call initiated by a [[Reference9:Phone/User/Function-Keys/Partner | partner function key]]. To TAPI, this looks like a 3PTY (which it in fact is). However, phones before firmware version 9 hotfix 19 will not be able to release this 3PTY upon request. Thus, you will see a 3PTY in TAPI which cannot be dropped. From phone firmware 9 hotfix 19 on, the request to drop this 3PTY will cancel the intrusion call. Note that TAPI will nevertheless ''not'' set <code>LINEADDRCAPFLAGS_CONFDROP</code> on such conferences (as TAPI does not know that this is an intrusion call).<br />
<br />
======Notes on Recording Calls======<br />
A special case of 3PTY is a recording call. This effectively creates an user-invisible 3PTY on the phone. To TAPI, this looks like a 3PTY (which it in fact is). However, phones before firmware version 9 hotfix 19 will not be able to release this 3PTY upon request. Thus, you will see a 3PTY in TAPI which cannot be dropped. From phone firmware 9 hotfix 19 on, the request to drop this 3PTY will cancel the recording call. Note that TAPI will nevertheless ''not'' set <code>LINEADDRCAPFLAGS_CONFDROP</code> on such conferences (as TAPI does not know that this is a recording call).<br />
<br />
Recording calls are shown as normal conferences (unless <code>No3PTY</code> is set). This might be confusing to applications and/or users. See the <code>HiddenRecordingNumber</code> tweak above for a method to hide such calls.<br />
<br />
=====Notes on parked Calls=====<br />
The PBX can park calls from and to any existing PBX object, e.g. by virtue of [[Reference8:Configuration/Registration/Function-Keys/Park | specific feature keys]]. They are parked to/from a numeric ''park position''. In SOAP however, calls are parked to an object by providing the objects '''UserInitialize()''' handle and a park position. Parked calls are then reported as straight calls with a distinct signalling state (8). The park position is not conveyed as part of the '''CallInfo''' record. To unpark, '''[[Reference8:SOAP_API#integer_UserPickup.28int_user.2C_string_cn.2C_integer_call.2C_string_group.2C_int_reg.2C_InfoArray_info.29 | UserPickup]]''' can be used providing the parked calls call handle as '''call''' argument. <br />
<br />
The TAPI specification is unclear on how the address information required to '''lineUnpark()''' a call can be obtained by an application (except that it is returned from '''linePark()''' if the call was parked using TAPI). The TSP thus indicates parked calls on a line with a call reason <code>LINECALLREASON_PARKED</code>. The caller id information is set to the parked calls call handle. This way, the application can take the caller id information and provide it to '''lineUnpark()''' to unpark a call. If the appication does not support this mode of operation but supports '''lineUnpark()''' and lets the user input the park identifier, the user can just provide the caller id information.<br />
<br />
Some applications do not care for parked calls (by examining the call reason) and just blindly report these calls like ordinary connected calls (thereby confusing the user). To work around this problem, reporting parked calls can be turned off in the TSP configuration dialogue.<br />
<br />
=====Notes on sending User to User Information =====<br />
The TAPI specification for sending UserUserInfo closely resembles the way ISDN defines this service. Although the innovaphone PBX supports this service both with H.323 and SIP, it is ''not'' supported in this TSP. Instead, sending user to user information is implemented using H.323/SIP messaging. However, there are some important deviations and limitations caused hereby:<br />
* data is ''not transparent''. That is, the TSP only allows for null-terminated unicode to be sent. The data must be of even length and the last two bytes must be null. <br />
* data is ''not sent within the call'', but by initiating a separate call. This implies that the target number used to send the message carrying the data to is 'guessed' from the available call data. For example, if the call is in a connected state and a connected number is known, the message is sent to the connected number. If the call is in the ringback state, then either the called number or - if available - the redirection number is used and so forth.<br />
* message calls are not indicated by the SOAP CallInfo records. As a result, messages (that is, user to user information) cannot be received with TAPI and ''lineReleaseUserUserInfo is not supported''<br />
<br />
=====Notes on sending proprietary innovaphone Remote Control Facilities =====<br />
The SOAP [[Reference10:SOAP_API#UserRc.28integer_call.2C_integer_rc.29|UserRc]] function allows to send various [[Reference:Remote Control Facility|facility messages]] to an innovaphone phone device. In some cases, it is useful to be able to invoke this function through a standard TAPI mechanism. This can be achieved by using the TAPI '''lineBlindTransfer''' function. If the ''called party name'' provided as destination for the ''lineBlindTransfer'' (in ''lpszDestAddress'') has the magic value <code>USERRC</code>, then the ''called subaddress'' is converted to an integer and the result is sent as remote control facility to the call the blind transfer is applied for. For details on how to code the ''called party name'' and the ''called subaddress'' into ''lpszDestAddress'' see [http://msdn.microsoft.com/en-us/library/windows/desktop/ms726017%28v=vs.85%29.aspx Microsoft's ''Canonical Address'' specification]. This works from TAPI8 hotfix 4.<br />
<br />
For example, initiating a blind transfer to <code>|16^USERRC</code> (empty address, subaddress 16 (that is, [[Reference:Remote Control Facility|''change to handset mode'']]), called name <code>USERRC</code>) will switch the phone having the call to be transferred into handset mode (and <code>|18^USERRC</code> will switch it back to handsfree mode). Of course, no actual transfer will happen in these cases (''lineBlindTransfer'' is merely used as a vehicle to send the facility to the proper call).<br />
<br />
Such proprietary facility message can also be sent with ''lineMakeCall'' (that is, in SOAP's [[Reference10:SOAP_API#integer_UserCall.28integer_user.2C_string_cn.2C_string_e164.2C_string_h323.2C_int_reg.2C_InfoArray_info.2C_int_rc.2C_string_srce164.29|UserCall]] function). For example, setting up a call to <code>123|21^USERRC</code> will send an intrusion call to extension 123 and intrude any call that might be active there. This works from TAPI8 hotfix 6. Please note that the innovaphone TAPI service provider does not support TAPI's ''lineCompleteCall'' function with <code>LINECALLCOMPLMODE_INTRUDE</code> though (as TAPI's call model here does not fit with the PBX's call model). Please also note that intrusion calls look like 3PTY calls to TAPI (see [[#Notes_on_Intrusion_Calls|notes on intrusion calls]] above).<br />
<br />
Remote control facilities are implemented by the telephone devices, so these functions are subject to the phone firmware in use.<br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
<br />
== Related Articles ==<br />
* [[Howto:Troubleshooting the TAPI service provider]]<br />
* [[Reference8:Release Notes TAPI]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference10:Concept_SOAP_API&diff=71254Reference10:Concept SOAP API2024-03-19T10:04:42Z<p>Ckl: /* bool UserReroute(integer call, string cn, string e164, string h323) */</p>
<hr />
<div>innovaphone®'s PBX web services, also known as SOAP API, while no longer actively extended, remains available and functional. A transition to WebSocket API, specifically the RCC API (https://sdk.innovaphone.com/13r3/doc/appwebsocket/RCC.htm), was introduced with version 13 firmware.<br />
<br />
For ongoing development, we recommend utilizing [http://www.innovaphone.com/wsdl/pbx10_00.wsdl WSDL version 10 ''pbx10_00.wsdl''] (also available on the gateway <code>http://xx.xx.xx.xx/pbx10_00.wsdl</code>) in conjunction with this Wiki article. It's advisable to disregard the higher versions of WSDL for your development.<br />
<br />
For upcoming developments the WebSocket API should be used, as it represents the current and future direction of our API development.<br />
<br />
==Overview==<br />
<br />
For an overview of the basic architecture, see the [[Reference:SOAP_API_%28pbx501.wsdl%29#Overview|corresponding chapter in the pbx501.wsdl related article]].<br />
<br />
[[Category:Concept|{{PAGENAME}}]]<br />
<br />
== Definition of PBX object (WSDL) == <br />
Within the SOAP framework there is a mechanism to formally decribe the definition of remote objects. This is done by so called WSDL (Web Service Description Language) files. This [[http://www.innovaphone.com/wsdl/pbx10_00.wsdl wsdl file]] defines the PBX web services described in this document.<br />
<br />
NB: while you can access and retrieve the WSDL file through this URL on runtime of your application, we'd rather recommend to install a local copy with your application and use this on runtime. This way, your application will be immune against failures of www.innovaphone.com.<br />
<br />
<small>There is also an [[http://www.innovaphone.com/wsdl/pbx900.wsdl old version of the wsdl]] available which has less interface functions. This interface can still be accessed by legacy applications and is activated on the PBX by calling the '''Initialize''' Function with fewer arguments ('''Integer Initialize(string user, string appl, out key)'''). It should not be used for new develoments though.</small><br />
<br />
=== SOAP URL ===<br />
The SOAP service URI is '''/PBX0/user.soap''' for the standard PBX and '''/PBX-''id''/user.soap''' for a dynamic PBX (where ''id'' corresponds to the ''Id'' field in the [[Reference10:PBX/Dyn-PBXs]] configuration dialog). SOAP is accessible via plain HTTP or HTTPS, so valid URLs might be <code>http://172.16.0.1/PBX0/user.soap</code> or <code>https://172.16.0.1/PBX0-mydynpbxid/user.soap</code>.<br />
<br />
==PBX Objects and Methods==<br />
<br />
This section contains a language agnostic description of the PBX API’s object model. Despite of the fact that we are discussing an object model, the PBX API is in fact not an object oriented API. This is because SOAP itself is not really object oriented. In particular, there is no object creation, activation or lifetime concept. This is left up to the service designer. SOAP is more a message exchange mechanism than an object method invocation mechanism. This is reflected in the API structure.<br />
<br />
Specifically, objects are represented through handles, which are integers. Objects are created and destroyed using dedicated methods and it’s the users responsibility to manage the lifetime of all objects.<br />
<br />
The syntax shown here actually is no valid syntax in any existing language. Please refer to the various sample codes for working syntax.<br />
<br />
===Session===<br />
<br />
All PBX API methods are executed in the context of a session. A session is created using the '''initialize''' method and is identified by a handle. This handle must be provided to all subsequent method calls.<br />
<br />
A session is owned by the PBX API user, i.e. there is no way to have access to a session of another application. Each session has a scope, which defines the view of the PBX the session user has. The scope determines the set of PBX registrations seen by the session.<br />
<br />
Scopes are defined and configured in the PBX and are bound to particular PBX users. Thus, a session has a user attribute, which defines the scope. It includes all users which are members of groups '''user''' is active member of. If '''user''' is not an active member of any group, the scope is the user itself.<br />
<br />
<br />
====Initialize(string user, string appl, bool $v, bool $v501, bool v700, bool v800, bool vx1000, out int key)====<br />
<br />
To access the current web services implementation, '''v''', '''v501''', '''v700''', '''v800''' and '''vx10000''' must be present and set to '''true'''. <br />
These parameters actually convey the wsdl version used by the client. Different versions of the interface use different parameter sets for the Initialize call. Applications should always use the wsdl version current at the time of writing the application.<br />
<br />
The method creates a session. The session will have the user '''user'''’s scope ('''user''' is the ''Long Name'' of a PBX user). The session handle is returned and is 0 for failure and positive for a valid session handle. '''appl''' specifies the name of the calling application and is used for administrative purposes. The output parameter '''key''' is a random number associated to the session. It may be used in subsequent '''Echo''' operations.<br />
<br />
When a session is created, '''UserInfo''' events for all PBX registrations in the scope can be received by the '''Poll''' function. Initially, one '''UserInfo''' per registration within the session’s scope is received (the list is terminated by a '''UserInfo''' with empty '''cn''' and may require multiple '''Poll''' calls to retrieve). Subsequently, '''UserInfo''' events are received when a registrations state changes.<br />
<br />
The underlying transport session (HTTP) must authenticate itself either as '''user''' (using the users long name and PBX password) or as the admin user (using the gateway administrator account name and password) to perform an '''Initialize''' and any session related function. Note that if you use a PBX user account, the user needs to have at least ''Group/Call Forwards'' rights. <br />
<br />
Note that the method to force the SOAP system you are using to authenticate to the PBX is entirely up to the system itself. Some systems even do not support authentication at all. If your SOAP implementation does not support digest authentication, make sure the gateway accepts basic authentication by setting the “''allow HTTP basic authentication''” in the “''General settings''”.<br />
<br />
Note that although many HTTP connections may be used in a single session, at least one HTTP connection must remain open during the lifetime of the session. When the last connection disappears, the logical session is terminated after a short timeout. Some SOAP libraries may per default always close the HTTP connection and reconnect on subsequent SOAP calls, which will not work. The SOAP library should either be configured to keep at least one HTTP connection alive or, if this is not an option, the application should take care to always have a an active request pending (such as '''Poll''').<br />
<br />
====Echo(integer session, integer key)====<br />
<br />
Verifies a session. You need to supply the ''session'' identifier and ''key'' returned by a previous call to '''Initialize'''. Returns nonzero if successful.<br />
<br />
<br />
====End(integer session)====<br />
<br />
Terminates the session referenced by ''session''. No further events will be received.<br />
<br />
<br />
====Integer Version(out string gkId, out string location, out string firmware, out string serial)====<br />
<br />
Returns the version number of the WSDL file the PBX supports. The first released WSDL file had version number 500. The version described by this document has version number 501. Also delivers information about the connected PBX (in ''gkId'', ''location'', ''firmware'' and ''serial'').<br />
<br />
<br />
====AnyInfo Poll(integer session)====<br />
<br />
Returns pending events for the session referenced by ''session''. ''AnyInfo'' is a struct with four arrays as members: ''user'', ''call'', ''reg'' and ''info''. ''user'' is an array of type ''UserInfo'' and call is an array of type CallInfo. The other two arrays ''reg'' and ''info'' are currently not used.<br />
<br />
After a successful '''Initialize()'' there will be a ''UserInfo'' event for each defined and visible user in the system. This allows the application to synchronize on the state of all visible users. The list will be terminated by an ''UserInfo'' event for a user with an empty ''cn'' which normally cannot happen since a user entry must have a cn.<br />
<br />
===User===<br />
<br />
A user represents a configured object within the PBX (a “PBX user”). The PBX API provides the '''UserInitialize''' method to obtain a handle to the user.<br />
<br />
====UserInfo====<br />
The user’s properties are stored in a ''UserInfo'' structure, which has the following elements:<br />
<br />
*'''boolean active'''<br />
<br />
true if the user exists. The only case where active can be false is when a user is moved out of the session context. This may happen if the users group assignment is changed or the user is deleted. A single UserInfo event will be posted with active set to false then.<br />
<br />
*'''integer state'''<br />
<br />
1 if the user is registered, 0 otherwise.<br />
<br />
*'''integer channel'''<br />
<br />
number of current calls.<br />
<br />
*'''integer alert'''<br />
<br />
number of alerting calls<br />
<br />
*'''string type'''<br />
<br />
the type of the users device. Currently defined are “'''ep'''” (it is an endpoint), “'''gw'''” (it is a gateway, for example a trunk line), “'''waiting'''” (a call queue) or “'''broadcast'''” (a group). Others may be defined over time.<br />
<br />
*'''string guid'''<br />
<br />
the users GUID. This is a globally unique identifier for the user.<br />
<br />
*'''string cn'''<br />
<br />
the common name of the user. This is what the PBX’s LDAP server recognizes as the CN of this user. The user’s name in the PBX configuration applet.<br />
<br />
*'''string e164'''<br />
<br />
the extension number the user is registered with.<br />
<br />
*'''string h323'''<br />
<br />
the alias the user is registered with.<br />
<br />
*'''string dn'''<br />
<br />
the users display name.<br />
<br />
*'''string domain'''<br />
<br />
the PBX domain if the Gatekeeper ID is used as domain<br />
<br />
*'''boolean h323email'''<br />
<br />
If true, the h323 name shall be used as primary email address when sending emails to this user.<br />
<br />
*'''string email[]'''<br />
<br />
email addresses of the user. If 'h323email' is not set, the first address in this list shall be used as primary email address when sending emails to this user.<br />
<br />
*'''Group groups'''<br />
<br />
An array of '''Group''' records (see below).<br />
<br />
*'''Presence presence'''<br />
<br />
An array of '''Presence''' records (see below).<br />
<br />
*'''boolean cfg'''<br />
<br />
If set to true indicates that the config of the user has changed<br />
<br />
*'''string object'''<br />
<br />
The type of the user object<br />
<br />
*'''string loc'''<br />
<br />
If the object described is not local to the PBX the '''UserInfo''' is sent from, the name of the location the object is homed in is given in this element. See '''LocationUrl''' to find out how to proceed further.<br />
<br />
*'''string node'''<br />
<br />
The node of the object.<br />
<br />
*'''string nodenum'''<br />
<br />
The object nodes node number (prefix).<br />
<br />
*'''Info'''<br />
An '''Info''' record describing various aspects of the user. The type of the information described in an individual '''Info''' record is determined by the value of its ''type'' member. Currently defined values for ''type'' are:<br />
<br />
:*'''fake'''<br />
: The value configured as 'Send Number'<br />
<br />
*'''groups'''<br />
<br />
The users group memberships are stored in a '''Group''' record which has the following elements:<br />
<br />
:*'''string group'''<br />
<br />
: the name of the group the user is a member of<br />
<br />
:*'''bool active'''<br />
<br />
: true if the user is an active member of the group<br />
<br />
*'''presence'''<br />
<br />
The users presence status is stored in a '''Presence''' record which has the following elements:<br />
<br />
:*'''string status'''<br />
<br />
: either '''open''' or '''closed'''<br />
<br />
:*'''string activity'''<br />
<br />
: The users current activity (optional)<br />
<br />
:*'''string note'''<br />
<br />
: The user provided additional note (optional)<br />
<br />
====integer UserInitialize(integer session, string user, bool xfer, bool disc, string hw)====<br />
<br />
Returns a handle to the named ''user'' (0 on failure). String <code>user</code> must be a Long Name(cn). Phone number(e164) is not supported. Use FindUser instead, to retrieve cn to specified e164 number.<br />
<br />
Once a user handle is obtained with '''UserInitialize''', '''CallInfo''' events will be posted and retrieved via '''Poll''' for all calls related to the user. If '''xfer''' is set to '''true''', '''CallInfo''' events will also be posted for calls which are transferred away from '''user'''. Otherwise, such events will be posted only for the user handle which the call has been transferred to. Thus, without setting follow to true, an application will generally not be able to track calls after a transfer unless it has called '''UserInitialize''' for any PBX object a call may be transferred to and matches the new call on the transferred-to user via the '''conf''' information in the '''Info''' record of the new call (which will be identical to the '''conf''' information for the transferred call).<br />
<br />
When '''xfer''' is set to '''true''', transferred calls will show up in the '''CallInfo''' records with a '''No''' element of type '''xfer''' that indicates the number the call has been transferred to.<br />
<br />
When '''disc''' is set to '''true''', calls to phones of the monitored user are cleared only after the user hangs up the phone. This way it can be avoided that the SOAP application assumes the phone is free but is still off-hook. This only works with innovaphone H.323 endpoints since the Disconnect message used to do this is not defined in the SIP standard.<br />
<br />
When a '''hw''' argument is provided only the device identified by this is monitored. Please note that a valid handle will be returned even if there is no matching device for '''user'''.<br />
<br />
Also, the user handle can be used to create and control calls on behalf of the user.<br />
<br />
====void UserEnd(integer user)====<br />
<br />
Frees the handle ''user'' obtained with '''UserInitialize'''. No events will be posted for this user anymore.<br />
<br />
====int SetPresence(inno:Presence presence, bool im, string contact, string guid, string h323)====<br />
<br />
Sets the presence of a PBX user.<br />
<br />
* '''presence''': <br />
:* '''inno:PresenceStatus''': the status '''closed'''/'''open''' or empty<br />
:* '''inno:PresenceActivity''': the activity like '''busy'''<br />
:* string '''note''': the presence note<br />
* '''im''': from im client?<br />
* '''contact''': the presence contact like '''tel:''', '''calendar:'''...<br />
* '''guid''': the user guid<br />
* '''h323''': the h323 name of the user<br />
<br />
You can use '''guid''' or '''h323''' to identify the user! If no guid is known, use '''*''' as guid value.<br />
<br><br />
If the setting of the presence was ok, the method returns '''1'''. Otherwise '''0'''.<br />
<br />
===Device===<br />
<br />
For each User object multiple devices can be configured within the PBX. A device represents the physical endpoint used for calls (e.g. phones). Any registration to the PBX is associated to a configured device by the name/number used for the registration. Even thou it is possible that one configured device in the PBX accepts multiple registrations, it is recommened to configure the PBX in a way that there is one registration to a device. This way an application can control which physical endpoint is used for a call.<br />
<br />
====Device[] Devices(int session, string user)====<br />
<br />
This function returns an array of devices configured for a user identified by its cn (long name). The '''cfg''' flag in '''UserInfo''' is set when this information is changed. The structure 'Device' contains the following members:<br />
<br />
{|<br />
|valign=top nowrap=true|'''hw'''<br />
|The Hardware Id used to identify the device. This string is used to associate any calls to devices.<br />
|-<br />
|valign=top nowrap=true|'''text'''<br />
|A text configured as a description of the device. It can be used to be presented to the user for the user to select a device.<br />
|}<br />
<br />
===Call===<br />
A call represents one leg of an existing call in the PBX. <br />
<br />
====CallInfo====<br />
The calls attributes are stored in a '''CallInfo''' structure, which has the following elements:<br />
<br />
*'''int user'''<br />
<br />
the user handle the call belongs to<br />
<br />
*'''int call'''<br />
the call handle<br />
<br />
*'''int reg'''<br />
<br />
currently unused<br />
<br />
*'''bool active'''<br />
<br />
'''true''' if the call exists, '''false''' if not. The only case where ''active'' can be '''false''' is when a call is terminated. A single '''CallInfo''' event will be posted with ''active'' set to '''false''' then<br />
<br />
*'''integer state'''<br />
<br />
A calls state. A bit field made up as follows:<br />
{|border="2" cellspacing="4" cellpadding="3" rules="all" style="margin:1em 1em 1em 0; border:solid 1px #AAAAAA; border-collapse:collapse;empty-cells:show;"<br />
|+<br />
| Value <br />
| Mask <br />
| Meaning<br />
|-<br />
| 1 <br />
| 0xF <br />
| setup<br />
|-<br />
| 2 || 0xF || setup-ack<br />
|-<br />
| 3 || 0xF || call-proc<br />
|-<br />
| 4 || 0xF || Alert<br />
|-<br />
| 5 || 0xF || Connect<br />
|-<br />
| 6 || 0xF || disconnect sent<br />
|-<br />
| 7 || 0xF || disconnect received<br />
|-<br />
| 8 || 0xF || parked<br />
|-<br />
| 0 || 0x80 || inbound call<br />
|-<br />
| 128 || 0x80 || outbound call<br />
|-<br />
| 0 || 0x100 || active call<br />
|-<br />
| 256 || 0x100 || call on hold<br />
|-<br />
| 0 || 0x200 || active call<br />
|-<br />
| 512 || 0x200 || active call put on hold by peer<br />
|}<br />
<br />
Note that the PBX API is PBX-centric, not terminal centric. As such, it considers a call ''from'' the PBX ''to'' the terminal as ''outbound''.<br />
<br />
Refer to the [[Howto:SOAP API PHP5 Sample Code#Working with Call States from CallInfo]] Article to learn how to work with the call state values.<br />
<br />
*'''string msg'''<br />
A textual representation of the signalling message causing this event. E.g. “'''x-setup'''”.<br />
<br />
<br />
*'''No No'''<br />
<br />
An array of '''No''' records (see below). This can include information about various peers related to the call itself. The type of the peer described in an individual '''No''' record is determined by the value of its '''type''' member. Currently defined values for '''type''' are:<br />
<br />
:*'''peer'''<br />
<br />
: The current remote end of the call (the local end is determined by the UserInfo identified through the user handle above)<br />
<br />
:*'''leg2'''<br />
<br />
: The last diverting user (if any)<br />
<br />
:*'''leg2orig'''<br />
<br />
: The 1st diverting user (if any, from on v9hf1)<br />
<br />
:* '''leg1'''<br />
<br />
: Indicates a call-forward/blind-xfer(?) to the calling end<br />
<br />
:*'''ct'''<br />
<br />
: The last user having transferred the call (if any)<br />
<br />
:*'''parked-to'''<br />
<br />
: The call was parked to the indicated endpoint. This is sent with the 'r-rel' message indicating the clearing of this call.<br />
<br />
:*'''picking'''<br />
<br />
: The call was picked by the indicated endpoint. Usually empty because sent with the new call which is created at the picking endpoint, but the fact that it is present indicates that this is a picked up call. The endpoint where this call is picked from is indicated with a ''ct'' No within the same call_info.<br />
*'''Info info'''<br />
<br />
An '''Info''' record describing various aspects of the call. The type of the information described in an individual '''Info''' record is determined by the value of its ''type'' member. Currently defined values for ''type'' are:<br />
<br />
:*'''conf'''<br />
<br />
: A string holding the ''conference id'' (a GUID) of the call the reported call leg (all legs of a certain call share the same ''conference id''). Also, CDRs for a call show this ''conference id'' (''ref'' for [[Reference:Call_Detail_Record_CDR|gateway CDRs]], ''conf'' in the ''<event>'' tag for [[Reference10:Concept_Call_Detail_Record_CDR_PBX|PBX CDRs]]).<br />
:*'''cause'''<br />
<br />
: The latest [[Reference:ISDN_Cause_Codes | cause code ]] which has been notified on the call<br />
<br />
===== CallInfo for Boolean Objects =====<br />
A ''Boolean'' will send a CallInfo event when it's status changes. From the indicated number, you can derive the status as follows:<br />
<br />
00 - Auto-Off<br />
01 - Auto-On<br />
10 - Manual-Off<br />
11 - Manual-On<br />
<br />
====No Record====<br />
<br />
Peer information is stored in a '''No''' record with the following elements:<br />
<br />
*'''string type'''<br />
<br />
the type of the peer described by the record<br />
<br />
*'''string cn'''<br />
<br />
the peer’s PBX’s objects common name (if any)<br />
<br />
*'''string e164'''<br />
<br />
the peer’s phone number<br />
<br />
*'''string h323'''<br />
<br />
the peer’s h323 alias<br />
<br />
*'''string dn'''<br />
<br />
the peer’s display name<br />
<br />
====Info record====<br />
Various information is stored in '''Info''' records with the following elements:<br />
<br />
*'''string type'''<br />
<br />
the type of the information described by the record<br />
<br />
*'''string vals'''<br />
<br />
a string value associated with this information (if any, the ''type'' of the element determines if an '''Info''' element has a string or an integer value) <br />
<br />
*'''integer vali'''<br />
<br />
an integer value associated with this information (if any)<br />
<br />
<br />
<br />
Note that the call related functions do not return a meaningful value. This is because the operations success is reflected in the subsequent CallInfo events.<br />
<br />
<br />
====integer UserCall(integer user, string cn, string e164, string h323, int reg, InfoArray info, int rc, string srce164)====<br />
<br />
Creates an outgoing call from the '''user''' (which is a handle obtained by a call to '''UserInitialize''') to the destination described by ''cn'', ''e164'' and ''h323''. The argument '''srce164''' may be used to override the calling party number (note that this overrides the callers extension, not the full calling party number). Arguments ''reg'' and ''info'' are currently ignored. The argument ''rc'' is usually 0, unless special behavior is required by using other [[Reference8:SOAP_API#Remote_Control_Facilities|Remote Control Facilities]]. Returns a handle to the call (0 on failure).<br />
<br />
The called number (''e164'') is interpreted in the context of the user object the call is placed for (''user'').<br />
<br />
From V8HF3 on, if the srce164 argument of UserCall starts with 'r' or 'R', the call is sent with CLIR (calling line identification restricted).<br />
<br />
Depending on the nature of the device the user is registered with, the device may actually place the call or the PBX may place a call and once it is accepted, it places another call to the destination.<br />
<br />
====UserConnect(integer call)====<br />
Connects an existing ''call''. This forces the device the user is registered with to accept the call. It may then go into hands-free mode. Incapable (i.e. non-innovaphone) devices may simply ignore this call.<br />
<br />
====UserTransfer (int acall, integer bcall)====<br />
''acall'' and ''bcall'' are both calls a single user currently has active. This method will connect ''acall'' with ''bcall'', leaving the user without both calls.<br />
<br />
====UserMediaTransfer (integer acall, integer bcall, boolean user, boolean peer)====<br />
<br />
This function establishes a media connection between two parties currently active in a call independent of the signalling connection. ''acall'' and ''bcall'' are both active (connected) calls. If ''user'' is true, the user sides of the calls are connected together, if ''peer'' is set to true the peer sides of the calls are connected together. If neither ''user'' nor ''peer'' is set the calls are connected to their respective signalling peers.<br />
<br />
====bool UserRedirect(integer call, string cn, string e164, string h323, InfoArray info, int rc)====<br />
Places a call to the destination described by ''cn'', ''e164'' and ''h323'' and connects ''call'' to this destination. Argument ''info'' is currently ignored.<br />
<br />
tbd: meaning of ''rc''.<br />
<br />
Any call forwarding configured for the destination will be ignored. See [[Reference8:SOAP_API#bool_UserReroute.28integer_call.2C_string_cn.2C_string_e164.2C_string_h323.29|UserReroute()]] below.<br />
<br />
Note: in 2009, the semantics of UserRedirect has accidentally been changed so that call forwarding will no longer be ignored. From 14r1 Service Release 4, the original behavior can be restored by specifying 999 as ''rc''.<br />
<br />
====bool UserReroute(integer call, string cn, string e164, string h323)====<br />
Performs a rerouting of the call. Call forwardings set for the destination will be obeyed.<br />
<br />
Note: rerouting calls on a waiting queue or call broadcast object works only if the ''Execute Operator CFB/CFNR'' or ''Execute Group Member Diversions'' option is turned on.<br />
<br />
====integer UserPickup(int user, string cn, integer call, string group, int reg, InfoArray info)====<br />
Redirects a call such that it appears as a new call at ''user''. The call to be redirected can be specified by its ''call'' handle. Alternatively, calls can be picked up by a users ''cn'' or by a ''group'' name. If all parameters are null, an implicit pickup is done. The new call handle is returned. Arguments ''reg'' and ''info'' are currently ignored.<br />
<br />
====UserClear(integer call, integer cause, InfoArray info)====<br />
Disconnects the ''call'' providing ''cause'' as disconnect reason. For example the cause code ''26 non-selected user clearing'' could be used to disconnect the call immediately on local phone, so no disconnect tone is played.<br />
<br />
Cause is coded as a 7bit integer according to the table found in [[Reference:ISDN Cause Codes]].<br />
Argument ''info'' is currently ignored.<br />
<br />
====UserCtComplete(integer call, string e164, string h323)====<br />
<br />
Sends a notification to the device ''call'' is active on that the remote peer has changed to ''e164'' and ''h323''. This resembles the notification a device may receive if its remote peer transfers the call to the new destination. The device may update its display and/or call data accordingly. This call is often used to force the devices (i.e. telephones) display to show application specific data.<br />
<br />
====UserHold(integer call, bool remote)====<br />
Sets the call on hold. The device may or may not display the hold status. In any case, the media channel is disconnected until a UserRetrieve is called.<br />
<br />
In ''V8 hotfix23'' an additional parameter '''remote''' was added. If set to '''true''', '''remote''' will force to play MOH only to remote user (who is being held), set to '''false''' maintains the default behaviour (MOH played on remote user and a dialing tone to local user (depending on the pbx firmware, versions prior to v11r2 will play music on hold)). To use this parameter the recent WSDL 8.00 file must be retrieved.<br />
<br />
====UserRetrieve(integer call)====<br />
Retrieves the ''call'' on hold. The device may or may not display the new status. In any case, the media channel is reconnected.<br />
<br />
====integer UserPark(integer call, string cn, integer position)====<br />
Parks the call at the PBX object '''cn''' and local user on postion '''position'''. A position of -1 means any position is allowed. <!-- The parked call will show up as new call at the user in '''CallInfo''' records returned by '''Poll()''' if the '''xfer''' flag of the corresponding '''UserInitialize()''' call has been set to true. - no, not ture - ckl -- -->The return value is the handle of the new call.<br />
<br />
To unpark calls, use the returned call handle with the '''UserPickup()''' function.<br />
<br />
The '''cn''' argument can be used to identify another object, where the call shall be parked to.<br />
<br />
====UserDTMF(integer call, bool recv, string dtmf)====<br />
Sends DTMF digits on behalf of the user. If '''recv''' is set to ''true'' the DTMF is sent to the local user.<br />
<br />
====UserUUI(integer call, bool recv, string uui)====<br />
Sends User-User_information on behalf of the user. If '''recv''' is set to ''true'' the uui is sent to the local user.<br />
<br />
====UserInfo(integer call, bool recv, string cdpn, string key, string dsp)====<br />
Sends INFO message on behalf of the user. If '''recv''' is set to ''true'' the INFO message is sent to the local user. This function can be used to send overlap dialing information.<br />
<br />
====UserRc(integer call, integer rc)====<br />
Sends remote control facility to an innovaphone IP phone. It will be ignored by 3rd-party endpoints.<br />
<br />
A remote control facility can be sent to an innovaphone IP phone to activate additional call handling on the device.<br />
<br />
The current set of remote control facility function codes actually depends on the firmware used on the telephone, not the WSDL version used for SOAP. The current set is documented in [[Reference:Remote Control Facility]].<br />
<br />
This function can be used for example to establish a three-party conference on an innovaphone IP phone. For this use the function UserRc on an active call using the '''rc''' value 4, while a second call is on hold. See also the ''rc'' argument to [[#integer_UserCall.28integer_user.2C_string_cn.2C_string_e164.2C_string_h323.2C_int_reg.2C_InfoArray_info.2C_int_rc.2C_string_srce164.29|UserCall()]].<br />
<br />
===Messaging===<br />
<br />
====integer UserMessage(integer user, string e164, string h323, string msg, string src_e164, string src_h323 )====<br />
Sends an message from the '''user''' (which is a handle obtained by a call to '''UserInitialize''') to the destination described by ''e164'' and ''h323''. The parameters ''src_e164'' and ''src_h323'' may be used to override the calling party's information, in order to present a different callback information to the recipient of the message. Returns a handle to the call (0 on failure), which can be used to track the delivery of the message. A event of type 'msg-sent' indicates the delivery of the message.<br />
<br />
===Status Retrieval===<br />
<br />
Instead of monitoring calls using the ''Poll'' mechanics, there are some functions to retrieve the current at a certain point in time.<br />
<br />
====CallInfo[] Calls(integer session, string user)====<br />
Returns an array of '''CallInfo''' records for the calls currently active at the registration defined by ''user''. Please note that this function may be called without having called '''UserInitialize ()''' before. Thus, the call handle information in the '''CallInfo''' records returned is meaningless.<br />
<br />
====UserInfo[] FindUser(string v501, string v700, string v800, string vx1000, string cn, string h323, string e164, integer count, integer next, boolean nohide)====<br />
Returns an array of at most ''count'' '''UserInfo''' records for the users matching ''cn'', ''h323'' or ''e164''. Only one of ''cn'', ''h323'' and ''e164'' may be specified, except that ''e164'' and ''cn'' may be specified. In this case the number in ''e164'' is interpreted in the Node of the user specified with ''cn''. The search string will be used as a starting point into the alphabetically sorted list of objects, that is, a search for “'''A'''” will yield entries starting with “'''A'''” but also – depending on ''count'' – the following entries. Neither search string may be empty. ''v501'', ''v700'', ''v800'' and ''vx1000'' must be set to a non-empty value. In case ''cn'' is set to the empty value, '''FindUser''' returns results starting from the first object in the PBX.<br />
<br />
To call '''FindUser''', no session is required, however, you need a valid HTTP authentication.<br />
<br />
Be aware that large values for ''count'' may fail and even crash the PBX. If you need to retrieve the whole user list, you should be using 20 as ''count'', provide the last ''cn'' retrieved as new start cn and loop until '''FindUser''' returns no more results, passing '''true''' as value for ''next'' for all but the first calls.<br />
<br />
If the parameter ''nohide'' is true, all objects regardless if marked as ''Hide from LDAP'' or not are returned by the '''FindUser''' function.<br />
<br />
====bool UserFindDestination(integer user, string e164, string h323, out UserInfo user)====<br />
This function checks if a destination can be reached from a given user by either a given number or a given name. ''user'' is the user handle from which PBX user the searching is started through all PBX nodes up and down. ''e164'' is the number, ''h323'' is a name.<br />
<br />
The function returns '''true''', if the number or name is incomplete, otherwise '''false'''. If a destination is found, a '''UserInfo''' ''user'' is delivered.<br />
<br />
No session is required to call '''UserFindDestination''', however, you need a valid HTTP authentication.<br />
<br />
====string License(integer session, string name)====<br />
This function is for internal use only.<br />
<br />
====string LocationUrl(string v501, string v700, string v800, string vx1000, string location, bool tls)====<br />
Returns a string with the HTTP URL for the PBX named location to which a SOAP session can be created. Typically, ''location'' is retrieved from the ''vals'' element of an '''Info''' record with ''type'' '''loc''' in an '''UserInfo''' record.<br />
<br />
''location'' needs to be specified as the '''h323''' attributes value of the PBX node '''UserInfo''' data you are interested in.<br />
<br />
If ''tls'' is set to '''true''', a HTTPS URL is returned.<br />
<br />
====string UserLocalNum(int user, string num)====<br />
Converts a given number '''num''' into a number diallable from the location of a specific '''user'''. Useful in inter-node scenarios where a destination node number and -extension are known, but the required escape prefix digits are unknown.<br />
;user:The id of the specified user<br />
;num:The number to be localized into the spefified user's location<br />
;result:The localized number, including escape prefixes<br />
<br />
===Administration===<br />
The SOAP interface can be used for administrational purposes also. This is done via the Admin call. All kinds of PBX objects can be created, read or modified. <br />
<br />
====string Admin(string xml)====<br />
<br />
Sends the administrational command ''xml'' to the PBX. The command is executed and any result is returned. <br />
<br />
This command allows you to query and modify the PBX object configuration (e.g. to query and set a users call forwarding). The scope and format of the commands valid for ''xml'' is beyond the scope of this document, however, there is a [[Howto:Using the SOAP Admin Function | separate article on that ]].<br />
<br />
To call '''Admin''', no session is required. However, you must be authenticated as an admin user on the underlying HTTP layer.<br />
<br />
[[Howto:Using the SOAP Admin Function]]<br />
<br />
==Typical design of a PBX SOAP Application==<br />
Please refer to the [[Reference:SOAP_API_%28pbx501.wsdl%29#Typical_design_of_a_PBX_SOAP_Application|pbx501.wsdl]] article for a discussion of the typical design of a SOAP application.<br />
<br />
==References==<br />
* [http://www.innovaphone.com/wsdl/pbx10_00.wsdl Version10-wsdl].<br />
<br />
Applications based on this wsdl will work with V10 PBX firmware (and up) only. <br />
<br />
Applications written to the previous pbx501.wsdl, pbx700.wsdl and pbx900.wsdl will continue to work with V10 firmware. <br />
<br />
For your reference the old version files are still available:<br />
* [http://www.innovaphone.com/wsdl/pbx501.wsdl Version5-wsdl]<br />
* [http://www.innovaphone.com/wsdl/pbx700.wsdl Version7-wsdl]<br />
* [http://www.innovaphone.com/wsdl/pbx900.wsdl Version9-wsdl]<br />
<br />
==Known Problems==<br />
Please read [[Howto:Authentication in the SOAP interface]] in case you have problems to authenticate SOAP access.<br />
<br />
==System Requirements==<br />
The PBX SOAP API requires a working PBX.<br />
<br />
To work with the PBX API in this version, you must run at least version 7.00 software on your PBX. Also, you must run at least version 5 software on the phones.<br />
<br />
==Installation==<br />
There is no specific installation required, as the PBX API is integral part of the PBX (although licenses are required to operate the PBX).<br />
<br />
==Configuration==<br />
To prepare an PBX for PBX API testing<br />
*setup a separate PBX<br />
*install current firmware (at least 8.00)<br />
*configure the PBX as usually<br />
*add an user object called '''API''' , define a ''password'' for this object<br />
*create a new group (e.g. called '''all users'''), by adding a group tag to '''API''', make it ''active''<br />
*add a similar group tag to all other test users<br />
*call '''Initialize()''' and use '''API''' as user and '''API''' and its ''password'' as http credentials<br />
<br />
==Known Issues==<br />
We have reports that the PBX wsdl is incompatible to some of the contemporary SOAP platforms available in the market. Most notably, Silverlight and Java seem to be affected. These problems are fixed in the v11 implementation of the PBX SOAP interface ''pbx11_00.wsdl'' (available at [http://www.innovaphone.com/wsdl/pbx11_00.wsdl www.innovaphone.com/wsdl/pbx11_00.wsdl ]).<br />
<br />
Note: This wdsl file is not available directly from the PBX (e.g. at <code>http://xx.xx.xx.xx/pbx11_00.wsdl</code>)<br />
<br />
However, we have had indications that for Java, solutions are possible based on Apache Axis 1.4, Netbeans 6.8 or JAX-RPC.<br />
<br />
The PBX does not expect the SOAP client to disconnect the session right after a UserCall. If this is the case (e.g. in a script which merely creates a call on behalf of a user and then terminates), it might happen that the outgoing call from the device will not be created correctly (a matter of timing). In this case, just wait for one second.<br />
<br />
==Related Articles==<br />
:[[Howto:Authentication in the SOAP interface]]<br />
:[[Howto:Clear a call completely with SOAP using UserClear]]<br />
:[[Howto:PBX SOAP Api C sample code]]<br />
:[[Howto:SOAP Api VisualBasic.Net Sample Code]]<br />
:[[Support:TAPI or other SOAP Application fails to function properly if PBX users have special characters in their long or short user name]]<br />
:[[Howto:How to monitor configuration changes in the SOAP interface ]]<br />
:[[Howto:SOAP with PHP5 ]]<br />
:[[Howto:SOAP API Java Sample Code ]]<br />
:[[Howto:Using the SOAP Admin Function]]<br />
<br />
[http://wiki.innovaphone.com/index.php?search=soap Search for more articles about SOAP]<br />
<br />
<br />
<!-- keywords: sopa soap --></div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference10:Concept_SOAP_API&diff=71253Reference10:Concept SOAP API2024-03-19T09:57:27Z<p>Ckl: /* bool UserRedirect(integer call, string cn, string e164, string h323, InfoArray info) */</p>
<hr />
<div>innovaphone®'s PBX web services, also known as SOAP API, while no longer actively extended, remains available and functional. A transition to WebSocket API, specifically the RCC API (https://sdk.innovaphone.com/13r3/doc/appwebsocket/RCC.htm), was introduced with version 13 firmware.<br />
<br />
For ongoing development, we recommend utilizing [http://www.innovaphone.com/wsdl/pbx10_00.wsdl WSDL version 10 ''pbx10_00.wsdl''] (also available on the gateway <code>http://xx.xx.xx.xx/pbx10_00.wsdl</code>) in conjunction with this Wiki article. It's advisable to disregard the higher versions of WSDL for your development.<br />
<br />
For upcoming developments the WebSocket API should be used, as it represents the current and future direction of our API development.<br />
<br />
==Overview==<br />
<br />
For an overview of the basic architecture, see the [[Reference:SOAP_API_%28pbx501.wsdl%29#Overview|corresponding chapter in the pbx501.wsdl related article]].<br />
<br />
[[Category:Concept|{{PAGENAME}}]]<br />
<br />
== Definition of PBX object (WSDL) == <br />
Within the SOAP framework there is a mechanism to formally decribe the definition of remote objects. This is done by so called WSDL (Web Service Description Language) files. This [[http://www.innovaphone.com/wsdl/pbx10_00.wsdl wsdl file]] defines the PBX web services described in this document.<br />
<br />
NB: while you can access and retrieve the WSDL file through this URL on runtime of your application, we'd rather recommend to install a local copy with your application and use this on runtime. This way, your application will be immune against failures of www.innovaphone.com.<br />
<br />
<small>There is also an [[http://www.innovaphone.com/wsdl/pbx900.wsdl old version of the wsdl]] available which has less interface functions. This interface can still be accessed by legacy applications and is activated on the PBX by calling the '''Initialize''' Function with fewer arguments ('''Integer Initialize(string user, string appl, out key)'''). It should not be used for new develoments though.</small><br />
<br />
=== SOAP URL ===<br />
The SOAP service URI is '''/PBX0/user.soap''' for the standard PBX and '''/PBX-''id''/user.soap''' for a dynamic PBX (where ''id'' corresponds to the ''Id'' field in the [[Reference10:PBX/Dyn-PBXs]] configuration dialog). SOAP is accessible via plain HTTP or HTTPS, so valid URLs might be <code>http://172.16.0.1/PBX0/user.soap</code> or <code>https://172.16.0.1/PBX0-mydynpbxid/user.soap</code>.<br />
<br />
==PBX Objects and Methods==<br />
<br />
This section contains a language agnostic description of the PBX API’s object model. Despite of the fact that we are discussing an object model, the PBX API is in fact not an object oriented API. This is because SOAP itself is not really object oriented. In particular, there is no object creation, activation or lifetime concept. This is left up to the service designer. SOAP is more a message exchange mechanism than an object method invocation mechanism. This is reflected in the API structure.<br />
<br />
Specifically, objects are represented through handles, which are integers. Objects are created and destroyed using dedicated methods and it’s the users responsibility to manage the lifetime of all objects.<br />
<br />
The syntax shown here actually is no valid syntax in any existing language. Please refer to the various sample codes for working syntax.<br />
<br />
===Session===<br />
<br />
All PBX API methods are executed in the context of a session. A session is created using the '''initialize''' method and is identified by a handle. This handle must be provided to all subsequent method calls.<br />
<br />
A session is owned by the PBX API user, i.e. there is no way to have access to a session of another application. Each session has a scope, which defines the view of the PBX the session user has. The scope determines the set of PBX registrations seen by the session.<br />
<br />
Scopes are defined and configured in the PBX and are bound to particular PBX users. Thus, a session has a user attribute, which defines the scope. It includes all users which are members of groups '''user''' is active member of. If '''user''' is not an active member of any group, the scope is the user itself.<br />
<br />
<br />
====Initialize(string user, string appl, bool $v, bool $v501, bool v700, bool v800, bool vx1000, out int key)====<br />
<br />
To access the current web services implementation, '''v''', '''v501''', '''v700''', '''v800''' and '''vx10000''' must be present and set to '''true'''. <br />
These parameters actually convey the wsdl version used by the client. Different versions of the interface use different parameter sets for the Initialize call. Applications should always use the wsdl version current at the time of writing the application.<br />
<br />
The method creates a session. The session will have the user '''user'''’s scope ('''user''' is the ''Long Name'' of a PBX user). The session handle is returned and is 0 for failure and positive for a valid session handle. '''appl''' specifies the name of the calling application and is used for administrative purposes. The output parameter '''key''' is a random number associated to the session. It may be used in subsequent '''Echo''' operations.<br />
<br />
When a session is created, '''UserInfo''' events for all PBX registrations in the scope can be received by the '''Poll''' function. Initially, one '''UserInfo''' per registration within the session’s scope is received (the list is terminated by a '''UserInfo''' with empty '''cn''' and may require multiple '''Poll''' calls to retrieve). Subsequently, '''UserInfo''' events are received when a registrations state changes.<br />
<br />
The underlying transport session (HTTP) must authenticate itself either as '''user''' (using the users long name and PBX password) or as the admin user (using the gateway administrator account name and password) to perform an '''Initialize''' and any session related function. Note that if you use a PBX user account, the user needs to have at least ''Group/Call Forwards'' rights. <br />
<br />
Note that the method to force the SOAP system you are using to authenticate to the PBX is entirely up to the system itself. Some systems even do not support authentication at all. If your SOAP implementation does not support digest authentication, make sure the gateway accepts basic authentication by setting the “''allow HTTP basic authentication''” in the “''General settings''”.<br />
<br />
Note that although many HTTP connections may be used in a single session, at least one HTTP connection must remain open during the lifetime of the session. When the last connection disappears, the logical session is terminated after a short timeout. Some SOAP libraries may per default always close the HTTP connection and reconnect on subsequent SOAP calls, which will not work. The SOAP library should either be configured to keep at least one HTTP connection alive or, if this is not an option, the application should take care to always have a an active request pending (such as '''Poll''').<br />
<br />
====Echo(integer session, integer key)====<br />
<br />
Verifies a session. You need to supply the ''session'' identifier and ''key'' returned by a previous call to '''Initialize'''. Returns nonzero if successful.<br />
<br />
<br />
====End(integer session)====<br />
<br />
Terminates the session referenced by ''session''. No further events will be received.<br />
<br />
<br />
====Integer Version(out string gkId, out string location, out string firmware, out string serial)====<br />
<br />
Returns the version number of the WSDL file the PBX supports. The first released WSDL file had version number 500. The version described by this document has version number 501. Also delivers information about the connected PBX (in ''gkId'', ''location'', ''firmware'' and ''serial'').<br />
<br />
<br />
====AnyInfo Poll(integer session)====<br />
<br />
Returns pending events for the session referenced by ''session''. ''AnyInfo'' is a struct with four arrays as members: ''user'', ''call'', ''reg'' and ''info''. ''user'' is an array of type ''UserInfo'' and call is an array of type CallInfo. The other two arrays ''reg'' and ''info'' are currently not used.<br />
<br />
After a successful '''Initialize()'' there will be a ''UserInfo'' event for each defined and visible user in the system. This allows the application to synchronize on the state of all visible users. The list will be terminated by an ''UserInfo'' event for a user with an empty ''cn'' which normally cannot happen since a user entry must have a cn.<br />
<br />
===User===<br />
<br />
A user represents a configured object within the PBX (a “PBX user”). The PBX API provides the '''UserInitialize''' method to obtain a handle to the user.<br />
<br />
====UserInfo====<br />
The user’s properties are stored in a ''UserInfo'' structure, which has the following elements:<br />
<br />
*'''boolean active'''<br />
<br />
true if the user exists. The only case where active can be false is when a user is moved out of the session context. This may happen if the users group assignment is changed or the user is deleted. A single UserInfo event will be posted with active set to false then.<br />
<br />
*'''integer state'''<br />
<br />
1 if the user is registered, 0 otherwise.<br />
<br />
*'''integer channel'''<br />
<br />
number of current calls.<br />
<br />
*'''integer alert'''<br />
<br />
number of alerting calls<br />
<br />
*'''string type'''<br />
<br />
the type of the users device. Currently defined are “'''ep'''” (it is an endpoint), “'''gw'''” (it is a gateway, for example a trunk line), “'''waiting'''” (a call queue) or “'''broadcast'''” (a group). Others may be defined over time.<br />
<br />
*'''string guid'''<br />
<br />
the users GUID. This is a globally unique identifier for the user.<br />
<br />
*'''string cn'''<br />
<br />
the common name of the user. This is what the PBX’s LDAP server recognizes as the CN of this user. The user’s name in the PBX configuration applet.<br />
<br />
*'''string e164'''<br />
<br />
the extension number the user is registered with.<br />
<br />
*'''string h323'''<br />
<br />
the alias the user is registered with.<br />
<br />
*'''string dn'''<br />
<br />
the users display name.<br />
<br />
*'''string domain'''<br />
<br />
the PBX domain if the Gatekeeper ID is used as domain<br />
<br />
*'''boolean h323email'''<br />
<br />
If true, the h323 name shall be used as primary email address when sending emails to this user.<br />
<br />
*'''string email[]'''<br />
<br />
email addresses of the user. If 'h323email' is not set, the first address in this list shall be used as primary email address when sending emails to this user.<br />
<br />
*'''Group groups'''<br />
<br />
An array of '''Group''' records (see below).<br />
<br />
*'''Presence presence'''<br />
<br />
An array of '''Presence''' records (see below).<br />
<br />
*'''boolean cfg'''<br />
<br />
If set to true indicates that the config of the user has changed<br />
<br />
*'''string object'''<br />
<br />
The type of the user object<br />
<br />
*'''string loc'''<br />
<br />
If the object described is not local to the PBX the '''UserInfo''' is sent from, the name of the location the object is homed in is given in this element. See '''LocationUrl''' to find out how to proceed further.<br />
<br />
*'''string node'''<br />
<br />
The node of the object.<br />
<br />
*'''string nodenum'''<br />
<br />
The object nodes node number (prefix).<br />
<br />
*'''Info'''<br />
An '''Info''' record describing various aspects of the user. The type of the information described in an individual '''Info''' record is determined by the value of its ''type'' member. Currently defined values for ''type'' are:<br />
<br />
:*'''fake'''<br />
: The value configured as 'Send Number'<br />
<br />
*'''groups'''<br />
<br />
The users group memberships are stored in a '''Group''' record which has the following elements:<br />
<br />
:*'''string group'''<br />
<br />
: the name of the group the user is a member of<br />
<br />
:*'''bool active'''<br />
<br />
: true if the user is an active member of the group<br />
<br />
*'''presence'''<br />
<br />
The users presence status is stored in a '''Presence''' record which has the following elements:<br />
<br />
:*'''string status'''<br />
<br />
: either '''open''' or '''closed'''<br />
<br />
:*'''string activity'''<br />
<br />
: The users current activity (optional)<br />
<br />
:*'''string note'''<br />
<br />
: The user provided additional note (optional)<br />
<br />
====integer UserInitialize(integer session, string user, bool xfer, bool disc, string hw)====<br />
<br />
Returns a handle to the named ''user'' (0 on failure). String <code>user</code> must be a Long Name(cn). Phone number(e164) is not supported. Use FindUser instead, to retrieve cn to specified e164 number.<br />
<br />
Once a user handle is obtained with '''UserInitialize''', '''CallInfo''' events will be posted and retrieved via '''Poll''' for all calls related to the user. If '''xfer''' is set to '''true''', '''CallInfo''' events will also be posted for calls which are transferred away from '''user'''. Otherwise, such events will be posted only for the user handle which the call has been transferred to. Thus, without setting follow to true, an application will generally not be able to track calls after a transfer unless it has called '''UserInitialize''' for any PBX object a call may be transferred to and matches the new call on the transferred-to user via the '''conf''' information in the '''Info''' record of the new call (which will be identical to the '''conf''' information for the transferred call).<br />
<br />
When '''xfer''' is set to '''true''', transferred calls will show up in the '''CallInfo''' records with a '''No''' element of type '''xfer''' that indicates the number the call has been transferred to.<br />
<br />
When '''disc''' is set to '''true''', calls to phones of the monitored user are cleared only after the user hangs up the phone. This way it can be avoided that the SOAP application assumes the phone is free but is still off-hook. This only works with innovaphone H.323 endpoints since the Disconnect message used to do this is not defined in the SIP standard.<br />
<br />
When a '''hw''' argument is provided only the device identified by this is monitored. Please note that a valid handle will be returned even if there is no matching device for '''user'''.<br />
<br />
Also, the user handle can be used to create and control calls on behalf of the user.<br />
<br />
====void UserEnd(integer user)====<br />
<br />
Frees the handle ''user'' obtained with '''UserInitialize'''. No events will be posted for this user anymore.<br />
<br />
====int SetPresence(inno:Presence presence, bool im, string contact, string guid, string h323)====<br />
<br />
Sets the presence of a PBX user.<br />
<br />
* '''presence''': <br />
:* '''inno:PresenceStatus''': the status '''closed'''/'''open''' or empty<br />
:* '''inno:PresenceActivity''': the activity like '''busy'''<br />
:* string '''note''': the presence note<br />
* '''im''': from im client?<br />
* '''contact''': the presence contact like '''tel:''', '''calendar:'''...<br />
* '''guid''': the user guid<br />
* '''h323''': the h323 name of the user<br />
<br />
You can use '''guid''' or '''h323''' to identify the user! If no guid is known, use '''*''' as guid value.<br />
<br><br />
If the setting of the presence was ok, the method returns '''1'''. Otherwise '''0'''.<br />
<br />
===Device===<br />
<br />
For each User object multiple devices can be configured within the PBX. A device represents the physical endpoint used for calls (e.g. phones). Any registration to the PBX is associated to a configured device by the name/number used for the registration. Even thou it is possible that one configured device in the PBX accepts multiple registrations, it is recommened to configure the PBX in a way that there is one registration to a device. This way an application can control which physical endpoint is used for a call.<br />
<br />
====Device[] Devices(int session, string user)====<br />
<br />
This function returns an array of devices configured for a user identified by its cn (long name). The '''cfg''' flag in '''UserInfo''' is set when this information is changed. The structure 'Device' contains the following members:<br />
<br />
{|<br />
|valign=top nowrap=true|'''hw'''<br />
|The Hardware Id used to identify the device. This string is used to associate any calls to devices.<br />
|-<br />
|valign=top nowrap=true|'''text'''<br />
|A text configured as a description of the device. It can be used to be presented to the user for the user to select a device.<br />
|}<br />
<br />
===Call===<br />
A call represents one leg of an existing call in the PBX. <br />
<br />
====CallInfo====<br />
The calls attributes are stored in a '''CallInfo''' structure, which has the following elements:<br />
<br />
*'''int user'''<br />
<br />
the user handle the call belongs to<br />
<br />
*'''int call'''<br />
the call handle<br />
<br />
*'''int reg'''<br />
<br />
currently unused<br />
<br />
*'''bool active'''<br />
<br />
'''true''' if the call exists, '''false''' if not. The only case where ''active'' can be '''false''' is when a call is terminated. A single '''CallInfo''' event will be posted with ''active'' set to '''false''' then<br />
<br />
*'''integer state'''<br />
<br />
A calls state. A bit field made up as follows:<br />
{|border="2" cellspacing="4" cellpadding="3" rules="all" style="margin:1em 1em 1em 0; border:solid 1px #AAAAAA; border-collapse:collapse;empty-cells:show;"<br />
|+<br />
| Value <br />
| Mask <br />
| Meaning<br />
|-<br />
| 1 <br />
| 0xF <br />
| setup<br />
|-<br />
| 2 || 0xF || setup-ack<br />
|-<br />
| 3 || 0xF || call-proc<br />
|-<br />
| 4 || 0xF || Alert<br />
|-<br />
| 5 || 0xF || Connect<br />
|-<br />
| 6 || 0xF || disconnect sent<br />
|-<br />
| 7 || 0xF || disconnect received<br />
|-<br />
| 8 || 0xF || parked<br />
|-<br />
| 0 || 0x80 || inbound call<br />
|-<br />
| 128 || 0x80 || outbound call<br />
|-<br />
| 0 || 0x100 || active call<br />
|-<br />
| 256 || 0x100 || call on hold<br />
|-<br />
| 0 || 0x200 || active call<br />
|-<br />
| 512 || 0x200 || active call put on hold by peer<br />
|}<br />
<br />
Note that the PBX API is PBX-centric, not terminal centric. As such, it considers a call ''from'' the PBX ''to'' the terminal as ''outbound''.<br />
<br />
Refer to the [[Howto:SOAP API PHP5 Sample Code#Working with Call States from CallInfo]] Article to learn how to work with the call state values.<br />
<br />
*'''string msg'''<br />
A textual representation of the signalling message causing this event. E.g. “'''x-setup'''”.<br />
<br />
<br />
*'''No No'''<br />
<br />
An array of '''No''' records (see below). This can include information about various peers related to the call itself. The type of the peer described in an individual '''No''' record is determined by the value of its '''type''' member. Currently defined values for '''type''' are:<br />
<br />
:*'''peer'''<br />
<br />
: The current remote end of the call (the local end is determined by the UserInfo identified through the user handle above)<br />
<br />
:*'''leg2'''<br />
<br />
: The last diverting user (if any)<br />
<br />
:*'''leg2orig'''<br />
<br />
: The 1st diverting user (if any, from on v9hf1)<br />
<br />
:* '''leg1'''<br />
<br />
: Indicates a call-forward/blind-xfer(?) to the calling end<br />
<br />
:*'''ct'''<br />
<br />
: The last user having transferred the call (if any)<br />
<br />
:*'''parked-to'''<br />
<br />
: The call was parked to the indicated endpoint. This is sent with the 'r-rel' message indicating the clearing of this call.<br />
<br />
:*'''picking'''<br />
<br />
: The call was picked by the indicated endpoint. Usually empty because sent with the new call which is created at the picking endpoint, but the fact that it is present indicates that this is a picked up call. The endpoint where this call is picked from is indicated with a ''ct'' No within the same call_info.<br />
*'''Info info'''<br />
<br />
An '''Info''' record describing various aspects of the call. The type of the information described in an individual '''Info''' record is determined by the value of its ''type'' member. Currently defined values for ''type'' are:<br />
<br />
:*'''conf'''<br />
<br />
: A string holding the ''conference id'' (a GUID) of the call the reported call leg (all legs of a certain call share the same ''conference id''). Also, CDRs for a call show this ''conference id'' (''ref'' for [[Reference:Call_Detail_Record_CDR|gateway CDRs]], ''conf'' in the ''<event>'' tag for [[Reference10:Concept_Call_Detail_Record_CDR_PBX|PBX CDRs]]).<br />
:*'''cause'''<br />
<br />
: The latest [[Reference:ISDN_Cause_Codes | cause code ]] which has been notified on the call<br />
<br />
===== CallInfo for Boolean Objects =====<br />
A ''Boolean'' will send a CallInfo event when it's status changes. From the indicated number, you can derive the status as follows:<br />
<br />
00 - Auto-Off<br />
01 - Auto-On<br />
10 - Manual-Off<br />
11 - Manual-On<br />
<br />
====No Record====<br />
<br />
Peer information is stored in a '''No''' record with the following elements:<br />
<br />
*'''string type'''<br />
<br />
the type of the peer described by the record<br />
<br />
*'''string cn'''<br />
<br />
the peer’s PBX’s objects common name (if any)<br />
<br />
*'''string e164'''<br />
<br />
the peer’s phone number<br />
<br />
*'''string h323'''<br />
<br />
the peer’s h323 alias<br />
<br />
*'''string dn'''<br />
<br />
the peer’s display name<br />
<br />
====Info record====<br />
Various information is stored in '''Info''' records with the following elements:<br />
<br />
*'''string type'''<br />
<br />
the type of the information described by the record<br />
<br />
*'''string vals'''<br />
<br />
a string value associated with this information (if any, the ''type'' of the element determines if an '''Info''' element has a string or an integer value) <br />
<br />
*'''integer vali'''<br />
<br />
an integer value associated with this information (if any)<br />
<br />
<br />
<br />
Note that the call related functions do not return a meaningful value. This is because the operations success is reflected in the subsequent CallInfo events.<br />
<br />
<br />
====integer UserCall(integer user, string cn, string e164, string h323, int reg, InfoArray info, int rc, string srce164)====<br />
<br />
Creates an outgoing call from the '''user''' (which is a handle obtained by a call to '''UserInitialize''') to the destination described by ''cn'', ''e164'' and ''h323''. The argument '''srce164''' may be used to override the calling party number (note that this overrides the callers extension, not the full calling party number). Arguments ''reg'' and ''info'' are currently ignored. The argument ''rc'' is usually 0, unless special behavior is required by using other [[Reference8:SOAP_API#Remote_Control_Facilities|Remote Control Facilities]]. Returns a handle to the call (0 on failure).<br />
<br />
The called number (''e164'') is interpreted in the context of the user object the call is placed for (''user'').<br />
<br />
From V8HF3 on, if the srce164 argument of UserCall starts with 'r' or 'R', the call is sent with CLIR (calling line identification restricted).<br />
<br />
Depending on the nature of the device the user is registered with, the device may actually place the call or the PBX may place a call and once it is accepted, it places another call to the destination.<br />
<br />
====UserConnect(integer call)====<br />
Connects an existing ''call''. This forces the device the user is registered with to accept the call. It may then go into hands-free mode. Incapable (i.e. non-innovaphone) devices may simply ignore this call.<br />
<br />
====UserTransfer (int acall, integer bcall)====<br />
''acall'' and ''bcall'' are both calls a single user currently has active. This method will connect ''acall'' with ''bcall'', leaving the user without both calls.<br />
<br />
====UserMediaTransfer (integer acall, integer bcall, boolean user, boolean peer)====<br />
<br />
This function establishes a media connection between two parties currently active in a call independent of the signalling connection. ''acall'' and ''bcall'' are both active (connected) calls. If ''user'' is true, the user sides of the calls are connected together, if ''peer'' is set to true the peer sides of the calls are connected together. If neither ''user'' nor ''peer'' is set the calls are connected to their respective signalling peers.<br />
<br />
====bool UserRedirect(integer call, string cn, string e164, string h323, InfoArray info, int rc)====<br />
Places a call to the destination described by ''cn'', ''e164'' and ''h323'' and connects ''call'' to this destination. Argument ''info'' is currently ignored.<br />
<br />
tbd: meaning of ''rc''.<br />
<br />
Any call forwarding configured for the destination will be ignored. See [[Reference8:SOAP_API#bool_UserReroute.28integer_call.2C_string_cn.2C_string_e164.2C_string_h323.29|UserReroute()]] below.<br />
<br />
Note: in 2009, the semantics of UserRedirect has accidentally been changed so that call forwarding will no longer be ignored. From 14r1 Service Release 4, the original behavior can be restored by specifying 999 as ''rc''.<br />
<br />
====bool UserReroute(integer call, string cn, string e164, string h323)====<br />
Performs a rerouting of the call. Call forwardings set for the destination will be obeyed.<br />
<br />
====integer UserPickup(int user, string cn, integer call, string group, int reg, InfoArray info)====<br />
Redirects a call such that it appears as a new call at ''user''. The call to be redirected can be specified by its ''call'' handle. Alternatively, calls can be picked up by a users ''cn'' or by a ''group'' name. If all parameters are null, an implicit pickup is done. The new call handle is returned. Arguments ''reg'' and ''info'' are currently ignored.<br />
<br />
====UserClear(integer call, integer cause, InfoArray info)====<br />
Disconnects the ''call'' providing ''cause'' as disconnect reason. For example the cause code ''26 non-selected user clearing'' could be used to disconnect the call immediately on local phone, so no disconnect tone is played.<br />
<br />
Cause is coded as a 7bit integer according to the table found in [[Reference:ISDN Cause Codes]].<br />
Argument ''info'' is currently ignored.<br />
<br />
====UserCtComplete(integer call, string e164, string h323)====<br />
<br />
Sends a notification to the device ''call'' is active on that the remote peer has changed to ''e164'' and ''h323''. This resembles the notification a device may receive if its remote peer transfers the call to the new destination. The device may update its display and/or call data accordingly. This call is often used to force the devices (i.e. telephones) display to show application specific data.<br />
<br />
====UserHold(integer call, bool remote)====<br />
Sets the call on hold. The device may or may not display the hold status. In any case, the media channel is disconnected until a UserRetrieve is called.<br />
<br />
In ''V8 hotfix23'' an additional parameter '''remote''' was added. If set to '''true''', '''remote''' will force to play MOH only to remote user (who is being held), set to '''false''' maintains the default behaviour (MOH played on remote user and a dialing tone to local user (depending on the pbx firmware, versions prior to v11r2 will play music on hold)). To use this parameter the recent WSDL 8.00 file must be retrieved.<br />
<br />
====UserRetrieve(integer call)====<br />
Retrieves the ''call'' on hold. The device may or may not display the new status. In any case, the media channel is reconnected.<br />
<br />
====integer UserPark(integer call, string cn, integer position)====<br />
Parks the call at the PBX object '''cn''' and local user on postion '''position'''. A position of -1 means any position is allowed. <!-- The parked call will show up as new call at the user in '''CallInfo''' records returned by '''Poll()''' if the '''xfer''' flag of the corresponding '''UserInitialize()''' call has been set to true. - no, not ture - ckl -- -->The return value is the handle of the new call.<br />
<br />
To unpark calls, use the returned call handle with the '''UserPickup()''' function.<br />
<br />
The '''cn''' argument can be used to identify another object, where the call shall be parked to.<br />
<br />
====UserDTMF(integer call, bool recv, string dtmf)====<br />
Sends DTMF digits on behalf of the user. If '''recv''' is set to ''true'' the DTMF is sent to the local user.<br />
<br />
====UserUUI(integer call, bool recv, string uui)====<br />
Sends User-User_information on behalf of the user. If '''recv''' is set to ''true'' the uui is sent to the local user.<br />
<br />
====UserInfo(integer call, bool recv, string cdpn, string key, string dsp)====<br />
Sends INFO message on behalf of the user. If '''recv''' is set to ''true'' the INFO message is sent to the local user. This function can be used to send overlap dialing information.<br />
<br />
====UserRc(integer call, integer rc)====<br />
Sends remote control facility to an innovaphone IP phone. It will be ignored by 3rd-party endpoints.<br />
<br />
A remote control facility can be sent to an innovaphone IP phone to activate additional call handling on the device.<br />
<br />
The current set of remote control facility function codes actually depends on the firmware used on the telephone, not the WSDL version used for SOAP. The current set is documented in [[Reference:Remote Control Facility]].<br />
<br />
This function can be used for example to establish a three-party conference on an innovaphone IP phone. For this use the function UserRc on an active call using the '''rc''' value 4, while a second call is on hold. See also the ''rc'' argument to [[#integer_UserCall.28integer_user.2C_string_cn.2C_string_e164.2C_string_h323.2C_int_reg.2C_InfoArray_info.2C_int_rc.2C_string_srce164.29|UserCall()]].<br />
<br />
===Messaging===<br />
<br />
====integer UserMessage(integer user, string e164, string h323, string msg, string src_e164, string src_h323 )====<br />
Sends an message from the '''user''' (which is a handle obtained by a call to '''UserInitialize''') to the destination described by ''e164'' and ''h323''. The parameters ''src_e164'' and ''src_h323'' may be used to override the calling party's information, in order to present a different callback information to the recipient of the message. Returns a handle to the call (0 on failure), which can be used to track the delivery of the message. A event of type 'msg-sent' indicates the delivery of the message.<br />
<br />
===Status Retrieval===<br />
<br />
Instead of monitoring calls using the ''Poll'' mechanics, there are some functions to retrieve the current at a certain point in time.<br />
<br />
====CallInfo[] Calls(integer session, string user)====<br />
Returns an array of '''CallInfo''' records for the calls currently active at the registration defined by ''user''. Please note that this function may be called without having called '''UserInitialize ()''' before. Thus, the call handle information in the '''CallInfo''' records returned is meaningless.<br />
<br />
====UserInfo[] FindUser(string v501, string v700, string v800, string vx1000, string cn, string h323, string e164, integer count, integer next, boolean nohide)====<br />
Returns an array of at most ''count'' '''UserInfo''' records for the users matching ''cn'', ''h323'' or ''e164''. Only one of ''cn'', ''h323'' and ''e164'' may be specified, except that ''e164'' and ''cn'' may be specified. In this case the number in ''e164'' is interpreted in the Node of the user specified with ''cn''. The search string will be used as a starting point into the alphabetically sorted list of objects, that is, a search for “'''A'''” will yield entries starting with “'''A'''” but also – depending on ''count'' – the following entries. Neither search string may be empty. ''v501'', ''v700'', ''v800'' and ''vx1000'' must be set to a non-empty value. In case ''cn'' is set to the empty value, '''FindUser''' returns results starting from the first object in the PBX.<br />
<br />
To call '''FindUser''', no session is required, however, you need a valid HTTP authentication.<br />
<br />
Be aware that large values for ''count'' may fail and even crash the PBX. If you need to retrieve the whole user list, you should be using 20 as ''count'', provide the last ''cn'' retrieved as new start cn and loop until '''FindUser''' returns no more results, passing '''true''' as value for ''next'' for all but the first calls.<br />
<br />
If the parameter ''nohide'' is true, all objects regardless if marked as ''Hide from LDAP'' or not are returned by the '''FindUser''' function.<br />
<br />
====bool UserFindDestination(integer user, string e164, string h323, out UserInfo user)====<br />
This function checks if a destination can be reached from a given user by either a given number or a given name. ''user'' is the user handle from which PBX user the searching is started through all PBX nodes up and down. ''e164'' is the number, ''h323'' is a name.<br />
<br />
The function returns '''true''', if the number or name is incomplete, otherwise '''false'''. If a destination is found, a '''UserInfo''' ''user'' is delivered.<br />
<br />
No session is required to call '''UserFindDestination''', however, you need a valid HTTP authentication.<br />
<br />
====string License(integer session, string name)====<br />
This function is for internal use only.<br />
<br />
====string LocationUrl(string v501, string v700, string v800, string vx1000, string location, bool tls)====<br />
Returns a string with the HTTP URL for the PBX named location to which a SOAP session can be created. Typically, ''location'' is retrieved from the ''vals'' element of an '''Info''' record with ''type'' '''loc''' in an '''UserInfo''' record.<br />
<br />
''location'' needs to be specified as the '''h323''' attributes value of the PBX node '''UserInfo''' data you are interested in.<br />
<br />
If ''tls'' is set to '''true''', a HTTPS URL is returned.<br />
<br />
====string UserLocalNum(int user, string num)====<br />
Converts a given number '''num''' into a number diallable from the location of a specific '''user'''. Useful in inter-node scenarios where a destination node number and -extension are known, but the required escape prefix digits are unknown.<br />
;user:The id of the specified user<br />
;num:The number to be localized into the spefified user's location<br />
;result:The localized number, including escape prefixes<br />
<br />
===Administration===<br />
The SOAP interface can be used for administrational purposes also. This is done via the Admin call. All kinds of PBX objects can be created, read or modified. <br />
<br />
====string Admin(string xml)====<br />
<br />
Sends the administrational command ''xml'' to the PBX. The command is executed and any result is returned. <br />
<br />
This command allows you to query and modify the PBX object configuration (e.g. to query and set a users call forwarding). The scope and format of the commands valid for ''xml'' is beyond the scope of this document, however, there is a [[Howto:Using the SOAP Admin Function | separate article on that ]].<br />
<br />
To call '''Admin''', no session is required. However, you must be authenticated as an admin user on the underlying HTTP layer.<br />
<br />
[[Howto:Using the SOAP Admin Function]]<br />
<br />
==Typical design of a PBX SOAP Application==<br />
Please refer to the [[Reference:SOAP_API_%28pbx501.wsdl%29#Typical_design_of_a_PBX_SOAP_Application|pbx501.wsdl]] article for a discussion of the typical design of a SOAP application.<br />
<br />
==References==<br />
* [http://www.innovaphone.com/wsdl/pbx10_00.wsdl Version10-wsdl].<br />
<br />
Applications based on this wsdl will work with V10 PBX firmware (and up) only. <br />
<br />
Applications written to the previous pbx501.wsdl, pbx700.wsdl and pbx900.wsdl will continue to work with V10 firmware. <br />
<br />
For your reference the old version files are still available:<br />
* [http://www.innovaphone.com/wsdl/pbx501.wsdl Version5-wsdl]<br />
* [http://www.innovaphone.com/wsdl/pbx700.wsdl Version7-wsdl]<br />
* [http://www.innovaphone.com/wsdl/pbx900.wsdl Version9-wsdl]<br />
<br />
==Known Problems==<br />
Please read [[Howto:Authentication in the SOAP interface]] in case you have problems to authenticate SOAP access.<br />
<br />
==System Requirements==<br />
The PBX SOAP API requires a working PBX.<br />
<br />
To work with the PBX API in this version, you must run at least version 7.00 software on your PBX. Also, you must run at least version 5 software on the phones.<br />
<br />
==Installation==<br />
There is no specific installation required, as the PBX API is integral part of the PBX (although licenses are required to operate the PBX).<br />
<br />
==Configuration==<br />
To prepare an PBX for PBX API testing<br />
*setup a separate PBX<br />
*install current firmware (at least 8.00)<br />
*configure the PBX as usually<br />
*add an user object called '''API''' , define a ''password'' for this object<br />
*create a new group (e.g. called '''all users'''), by adding a group tag to '''API''', make it ''active''<br />
*add a similar group tag to all other test users<br />
*call '''Initialize()''' and use '''API''' as user and '''API''' and its ''password'' as http credentials<br />
<br />
==Known Issues==<br />
We have reports that the PBX wsdl is incompatible to some of the contemporary SOAP platforms available in the market. Most notably, Silverlight and Java seem to be affected. These problems are fixed in the v11 implementation of the PBX SOAP interface ''pbx11_00.wsdl'' (available at [http://www.innovaphone.com/wsdl/pbx11_00.wsdl www.innovaphone.com/wsdl/pbx11_00.wsdl ]).<br />
<br />
Note: This wdsl file is not available directly from the PBX (e.g. at <code>http://xx.xx.xx.xx/pbx11_00.wsdl</code>)<br />
<br />
However, we have had indications that for Java, solutions are possible based on Apache Axis 1.4, Netbeans 6.8 or JAX-RPC.<br />
<br />
The PBX does not expect the SOAP client to disconnect the session right after a UserCall. If this is the case (e.g. in a script which merely creates a call on behalf of a user and then terminates), it might happen that the outgoing call from the device will not be created correctly (a matter of timing). In this case, just wait for one second.<br />
<br />
==Related Articles==<br />
:[[Howto:Authentication in the SOAP interface]]<br />
:[[Howto:Clear a call completely with SOAP using UserClear]]<br />
:[[Howto:PBX SOAP Api C sample code]]<br />
:[[Howto:SOAP Api VisualBasic.Net Sample Code]]<br />
:[[Support:TAPI or other SOAP Application fails to function properly if PBX users have special characters in their long or short user name]]<br />
:[[Howto:How to monitor configuration changes in the SOAP interface ]]<br />
:[[Howto:SOAP with PHP5 ]]<br />
:[[Howto:SOAP API Java Sample Code ]]<br />
:[[Howto:Using the SOAP Admin Function]]<br />
<br />
[http://wiki.innovaphone.com/index.php?search=soap Search for more articles about SOAP]<br />
<br />
<br />
<!-- keywords: sopa soap --></div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference:Release_Notes_Scripts_and_Tools&diff=70882Reference:Release Notes Scripts and Tools2024-02-19T15:15:51Z<p>Ckl: </p>
<hr />
<div>These release notes describe all Scripts and Tools which do not fit elsewhere, e.g. all the items found in the [http://download.innovaphone.com/ice/wiki-src/ Wiki Sources].<br/><br />
<br/><br />
<br />
Please see ''[[Reference:What_are_the_Release_Notes_Documents?|the disclaimer]]'' before using the information presented here!<br />
<br />
__NOEDITSECTION__<br />
{{#invoke-url: http://wiki.innovaphone.com/extensions/InvokeUrlFunction/projectListWebsocket.php?project=Scripts%20and%20Tools}}<br />
[[Category:Release Notes|Tools]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:PHP_based_Update_Server_V2&diff=70873Howto:PHP based Update Server V22024-02-19T10:53:46Z<p>Ckl: /* Requirements */</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
* web server running PHP 5 (e.g. Linux Application Platform)<br />
<br />
All Versions prior to 13r1 (for 13r1 and later, see [[Reference13r1:Concept App Service Devices]] instead). <br />
<br />
By default, the [[Reference10:Concept_Update_Server | Update Manager]] mechanism reads a file that corresponds to the device type (e.g. <code>update-ip222.htm</code>). While this makes sense (update scripts may vary by device type), it is sometimes tedious, as you have to edit a huge amount of files which typically are (at least partly) identical.<br />
<br />
Here is a PHP script that can be used as an ''Update Server'' that allows you to simplify the handling of update scripts. It also includes a mechanism that makes sure that all devices always have the same firmware installed as a given ''master device'' has. Finally, it implements a straight forward configuration backup scheme.<br />
<br />
This article describes version 2 of this update server (build 2006 and up). The previous version is described in [[Howto:PHP_based_Update_Server]]. The enhancements are<br />
* Status user interface showing all known devices<br />
* Ability to roll out custom device certificates<br />
* Ability to provide configuration files to MTLS-authenticated devices only (e.g. in order to keep certain configuration settings secure)<br />
* Hide configuration files from public read access<br />
<br />
==More Information==<br />
=== Requirements ===<br />
The update server script requires a web server with working PHP 5.3 or higher platform. It has been tested with Apache and ligHTTPD (on a [[Reference10:Concept Linux Application Platform | Linux Application Platform]]). On IIS, neither the certificate roll-out nor the MTLS authentication or configuration backup works with IIS.<br />
<br />
The platform running the PHP scripts must have a valid time setting!<br />
<br />
PHP 8 is supported from build 2023.<br />
<br />
=== Features ===<br />
The update server can<br />
* update all your devices with boot code and firmware that corresponds to the versions running on a reference device of your choice<br />
* save backups of your devices configuration. Backups are saved only if the configuration changed since the last backup<br />
* invoke your own update scripts depending on <br />
** various phases (e.g. you can have ''staging'' scripts which are only executed once and normal scripts which are executed in normal operation later on)<br />
** devices classes (e.g. you can have different scripts for phones and gateways)<br />
** environments (e.g. you can have scripts for your internal devices and others for devices in home offices)<br />
** the device serial number<br />
* roll out customer specific device certificates<br />
* roll out update scripts to devices only that have identified themselves with a valid device certificate (MTLS)<br />
* maintain a list of devices in your installation along with some vital information about each individual device<br />
<br />
=== Installation ===<br />
<br />
==== On the Linux Application Platform ====<br />
Here is how you would install it on the [[Reference10:Concept_Linux_Application_Platform|LAP]]. Some of the steps may not be necessary if you don't want to use all features.<br />
<br />
On the ''Linux Application Platform'', you would <br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files<br />
* create a new root directory underneath <code>/var/www/innovaphone/mtls</code>, e.g. <code>/var/www/innovaphone/mtls/update</code><br />
* download the complete file package of scripts and files [http://download.innovaphone.com/ice/wiki-src/#php-update-server here] <br />
* copy all files and directories in to this new directory<br />
** create your local config files. We will never overwrite these in further updates.<br />
***Rename <code>config/user-config-sample.xml</code> to <code>config/user-config.xml</code><br />
***Rename <code>scripts/all-all-all-sample.txt</code> to <code>scripts/all-all-all.txt</code><br />
* change owner and group of all files and directories to <code>www-data</code>, change mode to 0600 for all files and 0700 for all directories (see [[#Migrating_from_build_2000_an_newer | below]] for how to do this with WinSCP)<br />
<br />
* log in to the LAP's admin UI (if you have not yet changed it, the default credentials will be <code>admin/linux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]) and open its ''Administration/Web Server/Change web server properties and public access to the web/webdav'' configuration UI. Add the following paths to ''Public Web Paths'' (assuming your base directory is called <code>update</code>):<br />
** <code>/mtls/update</code><br />
** <code>/mtls/update/web</code><br />
** <code>/mtls/update/admin</code><br />
** <code>/mtls/update/fw/</code> (note the trailing slash!)<br />
: make sure that no other sub-directories of ''update'' are listed<br />
: make sure you have no trailing '/' in any of these paths (except ''fw/')<br />
: this will make sure the update server's admin user interface is not accessible to the public<br />
<br />
At this point, you should be able to access the update server's admin user interface, e.g. <code>http://update.yourcompany.com/mtls/update/admin/admin.php</code>. However, the only thing you see is a login page. Please note that your browser should ask you for a password for this page (unless you already entered it before). Cookies must be enabled for the login page to work properly. <br />
<br />
===== If you want to provide HTTPS Acess to the Update Server =====<br />
For HTTPS access to the update server, you may want to install your own server certificate under ''Administration/Certificates/Current server certificate''. However, this is not strictly required (you will experience some warning messages when using your browser to access the update server, however, calling devices will work just fine). <br />
<br />
===== If you want to setup MTLS-restricted Delivery of Update Scripts =====<br />
If you think you have sensitive information in your update scripts, you should make sure to deliver such scripts to your own verified devices only. This can be done by authenticating calling devices with ''mutual TLS'' (MTLS). In this case, the calling device must use HTTPS to retrieve the update script and present a trusted client certificate that identifies itself as the calling device (that is, has the device serial as the certificate's ''common name'' (CN)). <br />
<br />
For this feature, you need to ''Configure mutual TLS'' in the LAP's ''Administration/Web Server'' panel. Simply tick the ''Active'' check-mark and select an appropriate ''MTLS Port''. The port must not conflict with any other TCP port used on the LAP, so neither 80 nor 443 is a good choice. 444 is a good choice. Although it [http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=8 is assigned to the snpp protocol] by IANA, it is not used by the LAP (and probably rarely used anyway).<br />
<br />
If MTLS is already activated on your LAP, simply take note of the currently configured ''MTLS Port''.<br />
<br />
<br/><br />
Please note that MTLS access is ''not possible'' through a ''reverse proxy'' (RP). This is because the RP always will terminate the incoming TLS connection and establish its own to the target. Therefore, the client certificate presented to the target server is the RP's certificate, not the original clients certificate. In our context - where MTLS is used to verify the identity of the original caller - clients must not access the update server through an RP.<br />
<br />
You can either expose your update server directly to the internet (and carefully think through the security implications) or create specific TCP port forwarding towards the update server in your NAT-router/firewall. In this case, we recommend to use non-standard HTTP and HTTPS ports on the NAT router (cause this will already keep most of the HTTP port scanners out there in the internet from functioning).<br />
<br />
<br/><br />
Also, you will need the public key of all the CAs you will trust. These must be configured in to the web server so it can trust the certificates your devices will present to it. See [[#Enforcing_Trust]] for details.<br />
<br />
==== On an Apache Server running on the Windows Operating System ====<br />
The update server will run on Apache too. However, as we did not test this setup thoroughly, we recommend to use the LAP's LigHTTPD instead. <br />
<br />
* For hints on using MTLS on an Apache Web Server, see [[Reference10:Concept_Provisioning#Enforcing_mutual_TLS_on_Apache | Enforcing mutual TLS on Apache]]<br />
* For hints on getting the ''innovaphone device certificate authority'' public keys (which you may or may not need), see [[Reference10:Concept_Provisioning#How_to_get_inno-dev-ca-certificate.crt | How to get inno-dev-ca-certificate.crt ]]<br />
<br />
==== On Microsoft's IIS ====<br />
We do not recommend to use IIS as <br />
* it does not support PUT easily<br />
* it is not compatible with the innovaphone device's MTLS implementation<br />
<br />
===Update existing installation===<br />
<br />
* download the [http://download.innovaphone.com/ice/wiki-src/#php-update-server new sources]<br />
* copy all files and directories to your existing installation folder (overwrite existing files)<br />
* change owner and group of all files and directories to www-data, change mode to 0600 for all files and 0700 for all directories<br />
<br />
===Configuration===<br />
<br />
If you intend to deliver your update scripts with MTLS (that is, with HTTPS and mutual certificate check)<br />
* you will need to have the full name of your ''certificate Authority'' (CA) as it is noted as ''Common Name'' (CN) in your CA's certificate<br />
* also, you will need a PEM version of your CA's public key (a ''PEM version'' of a certificate is a text file that begins with a line like <code>-----BEGIN CERTIFICATE-----</code>)<br />
<br />
Also, you need to know the ''MTLS Port'' configured in your LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts| If you want to setup MTLS-restricted Delivery of Update Scripts ]] above).<br />
<br />
All tweakable parameters are set in <code>user-config.xml</code>. You may want to use an XML-capable editor such as notepad++, netbeans or Visual-Studio for editing. if your editor supports it, you benefit from the ''document type description'' (DTD) provided in <code>full-config.dtd</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="false" debugcerts="false" debugscript="false"><br />
</config><br />
</syntaxhighlight><br />
<br />
The attributes of the <code>config</code> tag control some aspects of debugging. For now, simply set all 3 to <code>"true"</code> instead of <code>"false"</code>. Do not forget to revert them back to <code>"false"</code> when everything runs smoothly, large log files will result if not. <br />
<br />
==== Securing Access ====<br />
To control access to the update server's data, you should set a login. This can be done in the ''master'' tag by specifying both ''user'' and ''password'':<br />
<br />
<syntaxhighlight lang="html5"><br />
<master ... user="myadmin" password="mysecret"/><br />
</syntaxhighlight><br />
Default username and password are admin/password.<br />
<br />
==== Delivering Firmware and Boot Code ====<br />
At this point, you can simulate a device by requesting an update script using an URL like <code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code> in your browser (please note that this URL should not ask you for a password). Most likely, your browser will wait a little while and then say something like:<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# cannot get PBX info: cannot access http://yourmasterdevice.youdomain.tld/CMD0/box_info.xml, reading cache<br />
# cannot get cached PBX info: cannot access cache/master-info.xml - exit<br />
<br />
This is because you did not yet specify the ''master device'' which always runs the reference firmware. <br />
<br />
The idea here is that you have one innovaphone device in your system where the firmware is always manually updated. All other devices shall follow this reference firmware. The update server will deliver the firmware that runs on the master device to calling devices. For this to work, you need to set the ''info'' attribute of the [[#master|''master'']] tag and leave everything else ''as is''. <br />
<br />
Remember that you do all your local configuration changes in <code>user-config.xml</code>.<br />
<br />
As ''master'' is a first level tag, you can add it directly underneath the opening ''<config>'' tag. The only thing we need to define right now is the path to read the firmware information from your master device. Let's say your reference device has the IP address <code>172.16.0.10</code>, you end up with <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true" debugcerts="true" debugscript="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml" user="myadmin" password="mysecret"/><br />
</config><br />
</syntaxhighlight><br />
(you could use a DNS name instead of the IP address of course). <br />
The ''info'' URL is used by the update server itself only, it is never used by devices requesting an update script.<br />
<br />
When you refresh the page that simulates a device requesting an update script, the output should now change to something like<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# current firmware (unknown) does not match required firmware (130178)<br />
...<br />
<br />
This is to say that your master device runs firmware 130178 and the calling device does not (actually, as the calling client is simulated by your browser, it does not transmit its current firmware to the update server so it is ''(unknown)'').<br />
<br />
In order to actually update the clients with firmware and boot code, the files must be made available to the calling client somehow. This is done by specifying an URL in the [[Reference10:Concept_Update_Server#Prot_command | prot ]] and [[Reference10:Concept_Update_Server#Boot_command | boot ]] commands sent to the calling device:<br />
<br />
...<br />
# current firmware (unknown) does not match required firmware (130178)<br />
mod cmd UP0 prot http://update.yourcompany.com/mtls/update/fw/130178/ ser 130178<br />
# current boot code (unknown) does not match required boot code (130112)<br />
mod cmd UP0 boot http://update.yourcompany.com/mtls/update/fw/130112/ ser 130112<br />
...<br />
<br />
By default, the URL generated points to a sub-directory of your update server called ''fw'': <code>http://update.yourcompany.com/mtls/update/fw/130178/</code>. Note that this default URL expects sub-directories underneath the ''fw'' directory which correspond to the firmware and boot code build number. The file structure thus would be like<br />
<br />
/var/www/innovaphone/mtls/update/fw<br />
/130178<br />
/ip232.bin<br />
/130112<br />
/boot232.bin<br />
<br />
Note that the URL ends with a slash (<code>/</code>). This instructs the calling device to append the appropriate file name itself when retrieving the file (see [[Reference10:Concept_Update_Server#Prot_command | the prot command ]] for details). <br />
<br />
However, you can also specify any other URL by setting the ''url'' attribute in the ''fwstorage'' tag of your <code>user-config.xml</code>. In this attribute, certain meta words will be replaced (see [[#fwstorage | the ''fwstorage'' tag ]] for details). The default setting for example is <code>fw/{build}/</code>.<br />
<br />
You can disable firmware and boot code updates altogether by setting the ''info'' attribute in the ''master'' tag to an empty value.<br />
<br />
==== Your own Update Script Files ====<br />
The update server will deliver update scripts to calling devices which are synthesized from a number of ''update script snippets'' which you define yourself. The idea is that many devices share parts of their configuration while other parts are different. This depends on<br />
* the device ''class'' (e.g. a phone or a gateway)<br />
: the device class is automatically derived from its model. In fact, a single device may be in multiple classes. For example, an IP232 is in these classes: ''phone, pre_opus_phone, phone_newui'' which basically says that its a phone and has no OPUS codec yet but a "new" user interface (as opposed to the old one found e.g. on an IP110). A number of useful classes are pre-defined, but you can add your own in <code>user-config.xml</code>.<br />
* the device's environment (e.g. ''home-office'', ''branch-office'')<br />
: These reflect the fact that devices may need different configuration if they are in different locations (or environments). The update server does not know about a device's environment, which is why you need to configure it in to the devices update URL if you need this. By default, there is a single environment defined called ''default'', but of course, you can add your own<br />
* phase<br />
: configuring devices may need multiple phases to go through. If more than one phase is defined, the device will go through all phases sequentially, which is why a ''phase'' has a numerical ''seq'' attribute. Phases are went through in order of this attribute. When the device has reached the last phase, it will stay there. By default, there is only one phase called ''update''<br />
<br />
You can define update script snippets for any combination of device, environment and phase. You do so simply by placing appropriate files in to the ''scripts'' directory on your update server.<br />
<br />
Let's have a closer look at the web page we used to simulate a device calling for an update script before (<code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code>). It will show you all the possible files it would deliver to the device:<br />
<br />
# possible files (from scripts):<br />
# scripts/update-all-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-all-default.txt does not exist<br />
# scripts/update-phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone-default.txt does not exist<br />
# scripts/update-pre_opus_phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-pre_opus_phone-default.txt does not exist<br />
# scripts/update-phone_newui-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
The update script snippet files need to have proper names to define when they should be delivered. As you can see, they all start with <code>update-</code> which means that they apply to devices which are in the ''update'' phase. As by default there is only a single phase defined, all possible file names start with <code>update-</code>. <br />
<br />
The next part here is either <code>phone-</code>, <code>pre_opus_phone-</code>, <code>phone_newui-</code> or <code>all-</code>. This is because the calling IP232 is in three classes, so all respective snippets will be delivered to it. ''all'' however is a wild-card. That is, such files will be delivered to all devices, no matter what class they are in. You may wonder why there was no ''all'' for the phase. This is because there is only a single phase defined. If there would be more, the ''all''-variation of the phase-part of the file name would be appear.<br />
<br />
The third part finally is either <code>default</code> or <code>00_90_33_00_00_00</code> in our case. ''default'' is the name of the only ''environment'' that is defined by default. ''00_90_33_00_00_00'' is the device's serial number which is treated as an implicitly defined environment name. This allows you to deliver certain snippets to a single device only.<br />
<br />
Now let's create an update script snippet that is delivered to all phones in the default environment when they are in the update phase. The file name (which looks like ''phase''<code>-</code>''class''<code>-</code>''environment''<code>.txt</code>) has to be <code>update-phone-default.txt</code> therefore. Just for an exercise, let us set the device name (as shown in the browser's title bar) to ''This is a Phone!''.<br />
The command to do so is <code>config add CMD0 /name This+is+a+Phone%21</code>, so your file may look like this<br />
<br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
(btw: did you observe that config line arguments need to be url-encoded?). Now create the file and upload it in to the ''scripts'' directory of you update server. When you now refresh the page which simulates the device calling for an update script, you will see something like this:<br />
<br />
...<br />
# newest script (scripts/update-phone-default.txt) 11s old<br />
# files being updated (scripts/update-phone-default.txt 11s old, waiting for at least 90s to expire)<br />
<br />
When you update multiple script snippets, it is often undesirable that a device retrieves an inconsistent state of the scripts you are working on (e.g. if you already have saved one but not yet the other). To avoid this, the update server will look at the files modification dates and if one of those that needs to be delivered to the device is younger than 90 seconds, it will not send any of them at all. <br />
<br />
So when you wait for the 90 seconds to pass and refresh the page again, you will see something like this:<br />
<br />
# firmware build info cache is current<br />
# phase: update, nextphase: , environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
...<br />
# scripts/update-phone-default.txt 4.1 mins<br />
...<br />
# scripts/update-phone-default.txt<br />
# { begin script 'scripts/update-phone-default.txt' <br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
<br />
# end script 'scripts/update-phone-default.txt' }<br />
<br />
# trigger reset if required<br />
config write<br />
config activate<br />
iresetn<br />
<br />
As you can see, your snippet has been delivered by the update script. However, the update server has also added some trailing commands which write back the config to the devices flash memory (<code>config write</code>), activate it (<code>config activate</code>) and trigger a reset if needed (<code>iresetn</code>).<br />
<br />
If there are multiple snippets available for a calling device, all of them are concatenated in the sequence shown in the header of the returned script (where the possible file names are listed)<br />
<br />
==== Device Status ====<br />
When you have a lot of devices which are served by the update server, you may want to have a list of these devices along with some useful information regarding each devices. <br />
<br />
You can enable this by defining the ''status'' tag in your <code>user-config.xml</code>. So open the file and add the tag right next to the ''master'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
/><br />
</config><br />
</syntaxhighlight><br />
<br />
When you have done so, please refresh the page that simulates your calling device and then open <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>. You will now see a device list (well, a list with a single device, the one you simulated using your browser). The list will show some information regarding the devices that requested an update script lately. For more options, see [[#status]].<br />
<br />
==== Device Backup ====<br />
It is generally useful to have recent backups of all of your device configurations. The update server supports that if you enable it by defining the ''backup'' tag in your <code>user-config.xml</code>. Simply put it right next to the ''master'' or ''status'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<backup<br />
dir="backup"<br />
/><br />
</syntaxhighlight><br />
<br />
If you have done so and then refresh the page that simulates your calling device, you will now see <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
mod cmd UP0 scfg http://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m<br />
<br />
instead of <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
# Backups not enabled in config<br />
<br />
When a client requests an update script the next time, this will initiate a configuration backup which is stored underneath the ''backup'' directory. For each device, a sub-directory is created and all backup files are stored therein. By default, the last 10 differing configuration backups are kept.<br />
<br />
==== Controlling the Time-frames when Snippets are delivered ====<br />
The update client built-in to innovaphone devices allows to restrict updates to certain time frames (e.g. only during the night). This can be controlled using the [[Reference10:Concept_Update_Server#Times_command | times ]] command.<br />
<br />
You can configure the update server to use the time commands by setting the ''allow'' and ''initial'' attributes in the ''times'' tag in your <code>user-config.xml</code>. <br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"/><br />
</syntaxhighlight><br />
<br />
If either the ''allow'' or ''initial'' attribute is present, the update script will contain a line like<br />
<br />
...<br />
# Restricted times<br />
mod cmd UP1 times /allow 22,23,0,1,2,3,4 /initial 1<br />
<br />
In the example above, all update script activity will occur only between 10pm and 5am (local device time). For more details, see [[Reference10:Concept_Update_Server#Times_command | Concept Update Server]]<br />
<br />
==== Using and Enforcing HTTPS ====<br />
Your devices can either use HTTP or HTTPS to access the update server. In normal operation, the update server will generate URLs (e.g. the URLs used to retrieve firmware or to backup configurations) for the same protocol. <br />
<br />
However, you can enforce use of HTTPS by setting the ''forcehttps'' attribute in the [[Howto:PHP_based_Update_Server_V2#times | ''times'' ]] tag to <code>true</code>. If so, a device calling in with HTTP will be re-configured to use HTTPS:<br />
<br />
...<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
<br />
# changed state query args: polling, phase<br />
# new url=https://update.yourcompany.com/mtls/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i<br />
...<br />
(note that if you are using a non-standard port for HTTPS, you must define it using the ''httpsport'' attribute).<br />
<br />
==== Delivering Custom Certificates ====<br />
innovaphone devices come with pre-defined, trustworthy device certificates. However, all ''soft'' devices (such as the softwarephone, myPBX for Android/iOS or the IPVA) do not. Also, in many scenarios customers run their own ''public key infrastructure'' (PKI) and request their own certificates to be used instead of the pre-defined. This is why there is a need to roll-out custom certificates to devices. <br />
<br />
The update server supports this task to an extend. It can<br />
* check if a calling device has a proper certificate<br />
* instruct a calling device without proper certificate to create a ''certificate signing request'' (CSR)<br />
* download the CSR to the update server for easy access by the administrator<br />
* upload a signed CSR to the device<br />
* upload the public key(s) of the CA in to the device's trust list<br />
<br />
In order to roll-out custom certificates with the update server, the administrator needs to<br />
* run his own PKI (a.k.a. ''certificate authority'' (CA))<br />
* monitor CSRs that appear in the update server's device list<br />
* approve the request by manually submitting it to his own CA<br />
* upload the signed CSR to the update server<br />
<br />
Also, all devices must run ''V12r1 SR8'' or newer before the new certificate can be uploaded. Devices with older firmware will be updated automatically (see [[#Delivering_Firmware_and_Boot_Code]] above). However, the new firmware must be ''V12r1 SR8'' or later. <br />
<br />
To learn how you can create proper device certificates with your own windows CA, see [[Howto:Creating custom Certificates using a Windows Certificate Authority]].<br />
<br />
By default, custom certificates are turned off and you will see a note like<br />
<br />
# certificates: either certificate handling or state tracking not enabled - not doing any certificate checking<br />
<br />
in the delivered update script.<br />
<br />
Custom certificates are turned on by adding a ''customcerts'' tag to your <code>user-config.xml</code>:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
The page that simulates your calling device will now include a line saying:<br />
<br />
# certificates: we dont know anything about the current certificate state - not doing any certificate checking <br />
<br />
In order to decide if or if not a calling device has a valid certificate, the device needs to send its current public key to the update server. This is done by enabling ''queries'' in your <code>user-config.xml</code>. ''Queries'' is a means to have the device submit certain information to the update server. In the update server's default configuration, two queries are defined but not enabled:<br />
<br />
* the query ''certificates'' sends the current certificate state <br />
* the query ''admin'' sends the current device name (as set in ''General/Admin'')<br />
<br />
To enable a query, you must specify the class a calling device must be in in order to execute the query. This is done by adding an ''applies'' tag for the respective query in your configuration:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
</query><br />
</queries><br />
...<br />
</syntaxhighlight><br />
<br />
This basically says that both queries should be executed by devices of just any class. Unless you enable queries, your update script will include lines such as:<br />
<br />
# no queries defined for any of callers classes: phone+pre_opus_phone+phone_newui<br />
<br />
Once you have enabled them, you will see something like<br />
<br />
...<br />
# query 'certificates'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=certificates ser nop /always mod%20cmd%20X509%20xml-info<br />
# query 'admin'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=admin ser nop /always mod%20cmd%20CMD0%20xml-info<br />
...<br />
<br />
You can display the data sent by a query in the device status display. To do so, you need to define one or more ''show'' tags as part of the respective ''query'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<query id="certificates"><br />
<applies>*</applies><br />
<!-- show device certificate CNs and Issuer CNs in status page --><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
...<br />
</syntaxhighlight><br />
<br />
Two steps however are still missing: <br />
<br />
* a) you need to tell the update server the names of all the CAs you consider trustworthy. This is done by listing their names in the ''CAname'' attribute of the ''customcerts'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2,innovaphone-INNO-DC-W2K8-Zertifizierungsserver"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
: The example shown defines that you will accept both of innovaphone's device certificate authorities and also your own CA called ''innovaphone-INNO-DC-W2K8-Zertifizierungsserver''. Devices with device certificates issued by one of these CAs will be left untouched. For all other devices (for example, any ''myPBX for Android/iOS'' device that has a self-signed certificate), the generation of a custom certificate will be initiated.<br />
<br />
* And b) you need to provide the public key of your CA (so it can be uploaded to the calling device's trust list). You need to place the DER (not PEM) encoded public key in to a file called <code>certs/CAkey-01.cer</code> on your update server (if you want to upload the whole certificate chain, put all of the public keys in the chain in to separate additional files called <code>certs/CAkey-02.cer</code> and so forth.<br />
<br />
For more details on how to approve certificate signing requests, see [[#Approving Certificate Signing Requests]] below.<br />
<br />
==== Enforcing Trust ====<br />
Update script snippets may include sensitive information which you do not want to disclose to the public. Even though you can force the use of HTTPS (see above), this still does not keep your data secure. After all, an attacker could simply request an update script from your update server using HTTPS. To secure your data, you need to make sure that snippets are only sent to devices which identify themselves using a trusted certificate with a correct ''common name'' (CN). This is done using ''mutual transport layer security'' (MTLS). Therefore, you need to configure MTLS in the LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts | If you want to setup MTLS-restricted Delivery of Update Scripts]] above). <br />
<br />
You can enable this by setting ''forcetrust'' to <code>true</code> and ''httpsport'' to the port configured as ''MTLS Port'' in your web server. This is done in the ''times'' tag of your <code>user-config.xml</code>.<br />
<syntaxhighlight lang="html5"><br />
<times <br />
...<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
</syntaxhighlight><br />
Please note that ''forcetrust'' does nothing unless ''forcehttps'' is also set!<br />
<br />
If so, the update server will deliver update script snippets only to clients which identify themselves with a proper certificate. For this to work, you will need to add the public key of your certificate authority to your web server's list of trusted certificates. <br />
<br />
When using the ''Linux Application Platform'' (LAP), you need to add the PEM-encoded public key of your certificate authority to the ''innovaphone-ca.pem'' file in <code>/home/root/ssl_cert</code>. When you have installed the LAP, this file will contain the PEM-encoded public keys of the 2 innovaphone device certificate authorities. You can simply add the public key of your CA to the end of this file:<br />
<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca2 public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...your-ca public key...<br />
-----END CERTIFICATE-----<br />
<br />
You will need to restart the LigHTTPD then (most easily done by restarting the entire LAP (''Diagnose/Reset'') or by issuing the command <code>/etc/rc2.d/S02lighttpd restart</code> from the linux root command prompt). Finally, you must set the ''forcetrust'' attribute. <br />
(Note that the ''innovaphone-ca.pem'' file may be overwritten when a new LAP version is installed. It is thus a good idea to keep a copy and check it after an upgrade).<br />
<br />
When ''forcetrust'' is effective and the calling device does not use HTTPS, it will be reconfigured to do so:<br />
<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
...<br />
# new url=https://update.yourcompany.com:444/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i&env=default<br />
<br />
If the calling device uses HTTPS but sends no certificate, you will see a message like<br />
<br />
# cannot verify CN with HTTPS: SSL_CLIENT_S_DN_CN not present - fix web server configuration or use MTLS-enabled port!<br />
<br />
This is probably because it is using a non-MTLS enabled port for HTTPS (e.g. the standard port 443) although MTLS is configured for a different port. <br />
<br />
If the calling device uses HTTPS and sends a certificate but the CN does not match the serial number of the device, you will see a message such as<br />
<br />
# Device claims to be 'IP232-30-00-af' but identifies as 'CKL-CELSIUS-W10.innovaphone.sifi' by TLS<br />
<br />
If the calling device uses HTTPS and sends a certificate but the certificate is not trusted because its issuer is not listed in ''innovaphone-ca.pem'' (see above), the connection will be refused <br />
<br />
In all such cases, no snippet will be delivered. If the certificate is good, you will see a message like <br />
<br />
# good certificate(CN='IP232-30-00-af')<br />
<br />
followed by the appropriate snippets.<br />
<br />
==== Using multiple Phases ====<br />
When you configure multiple phases, all phases up to the final phase are stepped through and when all associated update script snippets for all phases are done, the device will ultimately stay in the final phase. This can be used e.g. to implement update script snippets which are executed once only when the device is initialized. Settings made in all but the last phase can later be overridden by the user or an administrator. The settings done in the update script settings for the final phase however are re-executed whenever one of your update script files for this phase changes.<br />
The process of deploying some initial settings is often called ''staging''.<br />
<br />
To enable staging, you therefore create a new phase that comes before the default phase (which is ''update''). Phases are sorted numerical by an attribute called ''seq''. As the default phase ''update'' is defined with ''seq=200'', your new staging phase must be defined with a lower seq value:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
...<br />
</syntaxhighlight><br />
<br />
When you define such a phase, there will be new possible update script snippet file names: <br />
<br />
# phase: staging, nextphase: update, environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
# scripts/all-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-all-default.txt does not exist<br />
# scripts/all-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone-default.txt does not exist<br />
# scripts/all-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-pre_opus_phone-default.txt does not exist<br />
# scripts/all-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone_newui-default.txt does not exist<br />
# scripts/staging-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-all-default.txt does not exist<br />
# scripts/staging-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone-default.txt does not exist<br />
# scripts/staging-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-pre_opus_phone-default.txt does not exist<br />
# scripts/staging-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone_newui-default.txt does not exist<br />
<br />
First of all, there are new names for this particular phase. Furthermore, as we now have multiple phases, the new wild-card name ''all'' is possible. <br />
<br />
An example for a staging script snippet might be a file called <code>staging-all-all.txt</code>: <br />
<br />
# change admin password<br />
config add CMD0 /user admin,my-admin-password<br />
config activate<br />
config rem CMD0 /user<br />
<br />
This will set the admin password to <code>my-admin-password</code> during staging (it should be obvious that such staging should only be done in combination with [[#Enforcing_Trust|Enforcing Trust]], see above).<br />
<br />
=== A complete <code>user-config.xml</code> Sample ===<br />
Here is a complete sample of a configuration file. <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
missing="1000"<br />
/><br />
<backup<br />
dir="backup"<br />
/><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2"<br />
CSRsan-dns-1="{name}.company.com"<br />
CSRsan-dns-2="{hwid}.company.com"<br />
CSRsan-dns-3="{rdns}"<br />
CSRsan-ip-1="{realip}"<br />
/><br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
<environments><br />
<environment id="intranet"/><br />
<environment id='sifi'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='berlin'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='verona'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='homeoffice'/><br />
<environment id='mobile'/><br />
</environments<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
</queries><br />
</config><br />
</syntaxhighlight><br />
<br />
=== Operation ===<br />
<br />
==== Configuring and Debugging a real Device ====<br />
To configure your device for use with the update server, you simply set <code>http://update.yourcompany.com/mtls/update/update.php</code> as ''Command File URL'' in ''Services/Update'' (you can optionally add a <code>?env=envname1,envname2</code> query argument if you want to set one or more specific environments for your device). The update server will re-configure the device later on so that it uses all the required query arguments.<br />
<br />
Generally, there are the following methods to set the ''Command File URL''<br />
* configure it manually using the admin GUI<br />
* provide it to the device using DHCP (this will however not work for the softwarephone or ''myPBX for Android/iOS'')<br />
* use the innovaphone provisioning service (see [[Reference10:Concept_Provisioning|Reference10:Concept Provisioning]] for details)<br />
<br />
Once the ''Command File URL'' is set on the device, you can debug what the update client in the device does, using the normal trace mechanism. For this, you will want to open the ''Debug'' page (<code>http://x.x.x.x/debug.xml</code>) and set the ''Update/Polling'' and ''Update/Execution'' check-marks. When the update client queries the update server, you will see lines like <br />
<br />
0:0826:465:5 - upd_poll: state IDLE -> RECV<br />
0:0826:465:7 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:466:0 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:695:0 - upd_poll: state=RECV sent()<br />
0:0826:752:0 - upd_poll: status 200 headercomplete=1 contentlength=0<br />
0:0826:754:0 - upd_poll: recv_data(2199)<br />
0:0826:754:1 - upd_poll: recv_data(0) EOF<br />
0:0826:754:1 - upd_poll: GET EOF - state=RECV http-status=200 length=2199<br />
0:0826:754:1 - upd_poll: do commands<br />
0:0826:754:1 - upd_poll: state RECV -> EXEC<br />
<br />
in the trace. Commands included in the update script will look like<br />
<br />
0:0826:754:5 - script::get_line: >line(mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m)<br />
0:0826:754:5 - upd_poll: pass 'mod cmd UP0 /sync scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m')<br />
<br />
Finally, you do not need to wait for the next time the device decides to contact the update server. Instead, you can force this by sending an <code>http://</code>''your-device-ip<code>/!mod cmd UP1 poll</code> to the device.<br />
<br />
==== The Device Status Update Page ====<br />
If status tracking is enabled (see [[#Device_Status | Device Status]] above), the update server will keep a list of devices it knows of. You can see this list by calling <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>.<br />
<br />
By default, the list will not be updated unless you refresh the page. However, if you set the ''refresh'' attribute in the ''status'' tag in your ''user-config.xml'' file to <code>60</code>, all entries in this list will be refreshed every 60 seconds (however, the list itself will not be reloaded, so to see new devices, you need to refresh the entire page). Devices that have not been seen for more than 7 days are shown in a different colour. Devices that have not been seen for longer than 90 days will be silently removed from the list.<br />
<br />
===== Filtering =====<br />
From build 2011, you can filter the devices shown using the ''device filter'' field in the ''Device'' column header. The filter string is matched against ip-address, serial number, type, name, phase, class, environment, firmware and bootcode. Also, you can filter by using the keywords ''present'' and ''missing'' (based on the ''missing'' attribute of the ''status'' tag in your configuration file). If one of these attributes match your filter expression, the device is shown.<br />
<br />
A filter expression must match a complete ''word''. For example, if you filter by <code>16</code> this would match the ip-address <code>172.</code>16<code>.0.20</code> but it would not match <code>192.</code>16<code>8.0.1</code>.<br />
<br />
If you specify multiple filter expressions (separated by white space), only devices which match all of the expressions will be shown. For example, if you filter by <code>172.16. ip222</code> this might match all IP222 in your 172.16.*.* network. Filter expressions are case insensitive.<br />
<br />
==== Approving Certificate Signing Requests ====<br />
When you use custom certificate roll-out, you will see a column ''Certificates'' in the device status list, showing the devices certificate status. <br />
<br />
[[Image:PHP_based_Update_Server_V2_Device_Status.png]]<br />
<br />
If a CSR has been created on the device, this will be available for download in the ''Files'' section of the ''Info'' column. You can submit the CSR to your CA and then upload the signed request (using the ''Upload'' button in the ''Files'' section of the ''Info'' column). The signed request will eventually be uploaded to the device then. On the device itself, the CSR is shown in ''General/Certificates''.<br />
<br />
[[Image:PHP_based_Update_Server_V2_CSR.png]]<br />
<br />
<br />
==== Certificate Errors ====<br />
When a signed certificate request uploaded to the device can not be installed on the device, you will see a message like <code>Certificate upload error - certificate handling stopped</code> in the ''Certificates'' columns for the device. In this case, any further processing will be stopped. Most likely, the reason is that you uploaded the wrong file as signed certificate request (either not a signed certificate at all or a signed request for another device).<br />
<br />
In this case<br />
* remove any certificate request from the device<br />
* remove the <code>X509/REQUESTERROR</code> from the device configuration (i.e. <code>vars del X509/REQUESTERROR</code>)<br />
: these 2 steps can be easily done by resetting the device to factory defaults and then set the ''Update URL'' again<br />
* remove any stored files for the device on the update server (shown in the ''Certificates'' column)<br />
<br />
The normal process will start all over again then.<br />
<br />
=== Migrating old PHP Update Server Installations ===<br />
==== Migrating from build 2000 and newer ====<br />
* Copy all files and folders, '''except''' the ''scripts'' and ''config'' folder, from the source to your destination<br />
* make sure all files and directories have correct owner (''www-data''), group (''www-data'') and mode (''read''/''write'' plus ''execute'' for directories)<br />
: for example, in WinSCP you can use the ''Properties (F9)'' dialogue on the installations root folder:<br />
: [[Image:PHP based Update Server V2-WinSCP-Properties.png]]<br />
* open the ''StatusPage'' and make sure you refresh all cached files in your browser (depending on your browser, this may happen with Ctrl-R or Ctrl-F5)<br />
<br />
==== Migrating from build 1011 and older ====<br />
To upgrade from the [http://download.innovaphone.com/ice/wiki-src/index.php?urloffset=php-update-server%2F&name=php-update-server+%28all+available+builds%29&reverselevel=0&maxbuilds=999&root=c%3A%2Finetpub%2Fwwwroot%2Fdownload%2Fice%2Fdownload%2Fp%2Fwiki-src%2Fphp-update-server%2F1010+%28PHP+based+Update+Server+final%29%2F.. old version (builds up to 1011)] of the PHP based update server (as described in [[Howto:PHP based Update Server]]), do the following<br />
<br />
* diff your <code>config.xml</code> to the one originally shipped (you can download it at [http://download.innovaphone.com/ice/download/p/wiki-src/php-update-server/1011%20(PHP%20based%20Update%20Server%20final)/php-update-server-1011.zip download.innovaphone.com/ice/wiki-src])<br />
: take note of all the changes you made to it<br />
* install the new version of the update server to a different URL<br />
* configure it according to your needs by modifying <code>user-config.xml</code>. Do not modify the new <code>config.xml</code>!<br />
** apply all modifications you did to your old <code>config.xml</code> to your new <code>user-config.xml</code><br />
** if you have used a custom setting for fwstorage/@url, you need to change it a bit. Change for example <code>url="http://myfwserver.mycompany.com</code> to <code>http://myfwserver.mycompany.com/{build}/</code> (note the trailing slash!)<br />
* copy all your update script snippet files from the old server (these are all the .txt files in the old server's root directory) to the <code>scripts</code> directory of your new server<br />
* copy the complete <code>fw</code> tree from the old server to the <code>fw</code> directory of your new server<br />
* thoroughly test your new installation with some test devices<br />
* when satisfied, edit <code>update-migrate.php</code> and change the code as described in the comment inside the file<br />
* copy <code>update-migrate.php</code> from the new installation to the old installation<br />
* on one of the devices which are currently served by the old update server, change the ''Command File URL'' so that it points to <code>update-migrate.php</code> instead of <code>update.php</code>. Do not change anything else in the URL. So if it currently is set to e.g. <br />
: <code>https://172.16.0.90/update/update.php?type=#t&env=sifi&polling=0&phase=update</code>, change it to<br />
: <code>https://172.16.0.90/update/update-migrate.php?type=#t&env=sifi&polling=0&phase=update</code><br />
* verify that this device properly switches to your new installation<br />
** the ''Command File URL'' is changed so that it points to the new installation<br />
** the device shows up in the device status page of your new server<br />
<br />
* rename <code>update.php</code> to <code>update-old.php</code> in your old update server<br />
* rename <code>update-migrate.php</code> to <code>update.php</code> in your old update server<br />
* verify that all of your devices start showing up in the new server's status page<br />
<br />
=== Complete Parameter Reference ===<br />
Note: attributes are marked with an ''at'' (<code>@</code>) prefix. So ''master/@info'' refers to the ''info'' attribute in the ''master'' tag.<br />
<br />
The following tags and attributes are available in the <code>user-config.xml</code> file, their default values are configured in <code>config.xml</code> (which you should not modify):<br />
<br />
==== master ====<br />
Defines the reference device where the firmware and boot code information is taken from.<br />
; master/@info : the URL to the device info data. Set it to an URL like <code>http://</code>''your-reference-device''<code>/CMD0/box_info.xml</code>. Please note that this device must not have the ''Password protect all HTTP pages'' set in ''Services/HTTP/Server''. If you use the literal <code>none</code>, the master device will not be queried<br />
; master/@expire : the firmware info cache lifetime. The firmware information is cached in a file (to make sure this information is present even if the reference device is off-line). The cache will not be refreshed before the ''expire'' time (in seconds) is expired<br />
; master/@cache : the cache file name. The web server must be able to write this file (see [[#Installation | ''Installation'' ]] above)<br />
; master/@user : if non-empty, this is the user name required to access the update server's web ui<br />
; master/@password : the password<br />
<br />
From build 2009, you can control the firmware and bootcode version to use on update:<br />
; master/@firmware : if set, it may be one of <br />
:: <code>master</code> (default if not set) : the firmware version is derived from the master device<br />
:: <code>none</code> : the firmware update is generally disabled<br />
:: ''build-number'' : the firmware is set to a certain ''build-number'' regardless of the firmware running on the master device<br />
; master/@bootcode: if set, it may be one of <br />
:: <code>master</code> (default if not set) : the bootcode version is derived from the master device<br />
:: <code>firmware</code> : the bootcode version is set to the firmware version. This may be useful if the master device has no bootcode (e.g. the ipva)<br />
:: <code>none</code> : the bootcode update is generally disabled<br />
:: ''build-number'' : the bootcode is set to a certain ''build-number'' regardless of the bootcode running on the master device<br />
<br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"<br />
/><br />
</syntaxhighlight><br />
<br />
==== master/applies ====<br />
Available from build 2009.<br />
<br />
Boot code and firmware updates are only executed by devices which match all of the given <applies> conditions. A condition is met if the device belongs to the class that is given as tag content. If the ''env'' attribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; master/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in <br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"><br />
<!-- applied to phones in berlin only --><br />
<applies>phone</applies><br />
<applies env="">berlin</applies><br />
</master><br />
</syntaxhighlight><br />
<br />
==== fwstorage ====<br />
Defines from where the device will retrieve the firmware files for updates.<br />
; fwstorage/@url : defines the URL to retrieve the files from. In this URL, the following meta words will be replaced as follows:<br />
:* <code>{build}</code> - the requested firmware or boot-code <br />
:* <code>{model}</code> - the devices type (e.g. <code>IP232</code>). This is derived from the ''type'' query argument used in the update URL which is set to the value <code>#t</code> which in turn is expanded to the [[Reference10:Concept_Update_Server#Scfg_command|''device type'']] of the device requesting the update script<br />
:* <code>{filetype}</code> - the type of the required file, either <code>boot</code> or <code>prot</code><br />
:* <code>{filename}</code> - (from build 2014) the boot code or firmware filename for the device in lower case. Required if you use the LAP as firmware storage and firmware files are falsely requested in uppercase letters (as URLs on the LAP are case sensitive and if the files are requested with upper case names, the original files with lowercase name are not found). This also allows you to use alternate firmware file names (such as e.g. <code>ip110-sip.bin</code>). See the ''filenames'' tag<br />
Note that if the ''url'' ends with a slash (<code>/</code>), the calling device will automatically [[Reference10:Concept_Update_Server#Prot_command|append the name of the requested file]]. In this case, the file system the URL points to must therefore contain sub-directories for each firmware build which contain the corresponding firmware builds (e.g, ''fw/12345/ip232.bin''). If ''url'' is not an URL (as is the case for the default value <code>fw/{build}/</code>), it is treated as a sub-directory underneath the update server's root directory<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="fw/{build}/"/><br />
</syntaxhighlight><br />
<br />
===== Loading firmware files from innovaphone.com =====<br />
You can also load firmware from the innovaphone.com web site. To do so, set ''url'' like so:<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="http://webbuild.innovaphone.com/{build}/"/><br />
</syntaxhighlight><br />
<br />
Please note that ''this is a voluntary service, no guarantee of availability, no service level agreement''.<br />
<br />
[[User:Ckl|ckl]] 15:40, 21 February 2023 (CET) This service has been discontinued. You may consider using <code>https://store.innovaphone.com/release/download/{build}/</code> instead.<br />
<br />
==== backup ====<br />
Defines where backup files are kept.<br />
; backup/@dir : the directory underneath the script directory where backups are stored<br />
; backup/@nbackups : the number of different backup files kept<br />
; backup/@scfg : the target URL for the scfg command. If this is not an url, it is interpreted relative to the script directory URL. You can add more options for the ''scfg'' command, like in <code>scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m ser hourly /force 1"</code> (this example will make sure backups are attempted only once per hour, so as to reduce load on the server).<br />
<br />
<syntaxhighlight lang="html5"><br />
<backup <br />
dir="backup"<br />
nbackups="10"<br />
scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m"<br />
/><br />
</syntaxhighlight><br />
<br />
==== status ====<br />
Defines details regarding the state kept for a device requesting an update script.<br />
; status/@dir : the directory the status files are kept in (e.g. <code>status</code>). Used as the name for a sub-directory underneath your install directory on your web server. You must make sure that files in this directory cannot be read by anyone without proper authentication, as such files may contain sensitive information.<br />
; status/@expire : if set and not empty, devices which have not requested an update script will be removed from the inventory after ''n'' seconds. For example, ''expire="7776000"'' will forget about devices after 90 days with no contact<br />
; status/@missing : devices are shown in a different colour (indicating that they are ''missing'') after ''n'' seconds. For example, ''missing="604800"'' will flag devices as missing after 7 days with no contact<br />
; status/@refresh : if set and not empty and larger than zero, the admin user interface showing the known devices will refresh the status of all shown devices each ''n'' seconds<br />
; status/@logkeep : number of seconds log messages for individual devices will be kept<br />
; status/usehttpsdevlinks : if set to true, links to devices in the status page are created using the https:// scheme<br />
<syntaxhighlight lang="html5"><br />
<status <br />
dir="status" <br />
expire="7776000" <br />
missing="200" <br />
refresh="10"<br />
logkeep="90"<br />
usehttpsdevlinks="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== times ====<br />
Defines details regarding the delivery of update scripts to requesting devices. <br />
<br />
; times/@dir : the directory the update scripts are stored in. For security reasons, you may want to make sure that files in this directory cannot be read without proper authentication.<br />
<br />
These attributes define the arguments of the [[Reference10:Concept_Update_Server#Times_command | times command]]. If neither ''allow'' nor ''initial'' is set, no ''times'' command is used (that is, the update scripts will be processed at all times).<br />
; times/@allow : the hours to be used in the ''times'' command (as in <code>mod cmd UP1 times /allow </code>''hours'')<br />
; times/@initial <br />
: the minutes to be used in the ''times'' command (as in <code>mod cmd UP1 times /initial </code>''minutes''). If you want to use initial staging of virgin devices ''forcestaging'' must be <code>true</code>.<br />
<br />
Delivered update scripts can include a [[Reference10:Concept_Update_Server#Check_command | check command ]] if the ''check'' attribute is set to <code>true</code>. . In this case, the devices will executed the scripts again only when you have edited/changed them.<br />
; times/@check : if set to true, update scripts will be executed once only (unless their contents change)<br />
<br />
When editing a number of update scripts, it is often not desirable if one changed script is already executed before another is changed too. When the ''grace'' attribute is set to a positive value, no script at all is delivered to requesting devices unless ''n'' seconds have expired since the last edit. For example, setting ''grace'' to 90 gives you one and a half minute to finish all your edits. <br />
<br />
; times/@grace : defines the number of seconds that must expire before update script changes take effect<br />
<br />
The update server works in either ''fast'' or ''normal'' mode. In ''fast'' mode, update scripts have to be requested more frequently, for example to step through multiple staging phases faster. This is enforced by modifying the calling device's ''Interval'' setting in [[Reference11r1:Services/Update | ''Services/Update '']] and setting it to 1 in ''fast'' mode.<br />
<br />
; times/@interval : polling interval in minutes for normal operation (that is, when all staging is done)<br />
; times/@polling : when you are using multiple ''phases'' (e.g. ''staging'' and ''update''), this defines the number of seconds to wait before the device reads the scripts for the next phase (see [[Reference10:Concept_Update_Server#Provision_command | provision command]]) (please do not modify the default value)<br />
<br />
In some installations it may be desired to deliver update scripts with HTTPS only or even only to calling devices which have identified themselves with a proper TLS certificate. <br />
<br />
; times/@forcehttps : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS. If the device is ready to receive an update script and still calls in with HTTP only, its ''Command File URL'' will be re-configured to use HTTPS automatically.<br />
; times/@httpsport : if you use a non-standard port for HTTPS access to update scripts, it can be defined here<br />
; times/@httpsurlmod : if your HTTPS URL differs from the HTTP URL, you can specifiy a ''modifier''. It has the form <code>!</code>''pattern''<code>!</code>''replacement''<code>!</code> where ''pattern'' is a [http://docs.php.net/manual/en/pcre.pattern.php PHP PCRE pattern]. For example, <code>!mtls/!!i</code> will remove the string <code>mtls/</code> from the URL used by the device to create the HTTPS URL to use. The delimiter (in this case "!") can be changed according to your own wishes, the first character defines the delimiter. The "i" at the end ignores uppercase.<br />
<br />
Note: If you want to change the hostname of your URL, you have to add the Port 444 to your hostname. The code should then look like this: <code>!hostname1:444/mtls/!hostname2:444/!i</code>.<br />
; times/@forcetrust : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS and a valid and trusted client certificate that identifies itself as the devices it claims to be<br />
; times/@forcestaging : before build 2009, the restrictions imposed by the times/@allow settings affected all update ''phases''. This however makes staging cumbersome, as you usually want to restrict normal configuration changes to off-working-hours. If it is <code>true</code>, the restriction will be applied only to the last phase (that is, the ''staging'' phases will execute immediately). Also, the final phase will be executed once by staged devices. <br />
: Since build 2021 the standard value was changed to <code>true</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
dir="scripts" <br />
allow="" <br />
initial="" <br />
check="true" <br />
polling="5" <br />
interval="15" <br />
grace="90" <br />
forcehttps="true" <br />
httpsport="444" <br />
forcetrust="false" <br />
httpsurlmod="!mtls/!!i"<br />
forcestaging="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== phases ==== <br />
Defines the scripting phases. In many installations, there are initializations which should be performed once only. This is known as the ''staging'' phase. Once this is done, the devices continue with the execution of the normal update scripts (this phase is known as ''update'' by default). In the (unlikely) event that you want more or less phases, you can add/remove ''phase'' tags.<br />
==== phases/phase ====<br />
; phases/phase/@id : the name for the phase<br />
; phases/phase/@seq: the sequential number for the phase. Phases are stepped-through in the numeric order defined by their ''seq'' attribute. <br />
<br />
By virtue of the ''seq'' attribute, you can insert phases in to the set of phases defined by default, which is<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<!-- sequence is important! --><br />
<phase id="staging" seq="100"/><br />
<phase id="update" seq="200"/><br />
</phases><br />
</syntaxhighlight><br />
For example,<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<phase id="phase" seq="150"></phase><br />
</phases><br />
</syntaxhighlight><br />
will insert a custom phase ''myphase'' as second phase.<br />
<br />
==== environments ====<br />
Defines the distinguished environments. Devices can have zero or more ''environments'' associated. The environments a device is considered to be in must be passed as <code>?env=</code>''name1,name2,..'' URL query arg in the update url. <br />
<br />
Update scripts for all environments listed will be applied.<br />
First the environment scripts itself, then all implied environments in the same order as the ''implies'' tags are listed in the config. <br />
<br />
The default config file defines the environments ''intern'' and ''homeoffice'' but you can define your own if you like.<br />
<br />
==== environments/environment ====<br />
; environments/environment/@id : the name for the environment<br />
<br />
==== environments/environment/implies ====<br />
Each ''environment'' may have a number of sub-tags of type ''implies''. It contains the ''id'' of another ''environment''. If a device is in an environment with an ''implies'' sub-tag, it is assumed that it is in the implied environment also. <br />
<syntaxhighlight lang="html5"><br />
<environments><br />
<environment id='hq'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='intranet'/><br />
<environment id='homeoffice'/><br />
</environments><br />
</syntaxhighlight><br />
The <code>implies</code> tag handles no recursion (therefore, in the above example, if ''intranet'' would have an implies, a device in environment ''hq'' would not inherit this implication. Instead, you must list it explicitly for the ''hq'' environment).<br />
<br />
If multiple environments will be used they will be applied also in the same order as the ''environment'' tags are listed in the config.<br />
<br />
;There are some special things to keep in mind when adding multiple environments in the ''env'' attribute and they use the ''implies'' tag in the config.<br />
:- The first environment specified in the arg ''env'' will be processed as expected with all children.<br />
:- Recursive ''implies'' from an environment added via ''implies'' is not performed. (No children's children)<br />
:- Only the first environment which is specified in the ''env'' arg with implies is processed, all additional values in the arg ''env'' will be ignored.<br />
:- If the environment in the arg ''env'' it is not the first value, then it must also use the ''implies'' config to implies itself. Otherwise only child are processed.<br />
<br />
==== classes ====<br />
Defines the available device classes. Devices of different classes (e.g. phones and gateways) can have different update scripts. The device class is derived from the <code>#t</code> (device type) query arg of the update script URL (which is why you must configure it in the update URL). By default, the classes ''phone'', ''opus_phone'', ''pre_opus_phone'', ''phone_oldui'', ''phone_newui'', ''mobile'', ''gw'', ''fxogw'', ''fxsgw'', ''brigw'', ''prigw'' are recognized (newer versions may include more and/or updated class definitions). Also, any device is considered to be member of a class that has the device type as class-name. For example, an IP112 is in a class called ''ip112''.<br />
<br />
A given device can be in multiple classes and will execute all update scripts available for these classes.<br />
<br />
==== classes/class ====<br />
Each ''class'' has a number of sub-tags of type ''model''. It contains the value replaced for the <code>#t</code> query arg. The models listed belong to the class.<br />
; classes/class/@id : the name of the class<br />
<br />
<syntaxhighlight lang="html5"><br />
<classes><br />
<class id="gw"><br />
<model>ip0010</model><br />
...<br />
</class><br />
<class id="phone"><br />
<model>ip110</model><br />
<model>ip232</model><br />
...<br />
</class><br />
<class id="phone_oldui"><br />
<model>ip110</model><br />
...<br />
</class><br />
<class id="phone_newui"><br />
<model>ip232</model><br />
...<br />
</class><br />
</classes><br />
</syntaxhighlight><br />
<br />
==== nobootdev ====<br />
Device types listed here do not receive boot code updates.<br />
===== nobootdev/model =====<br />
Each model tag defines a device type that shall not receive boot code updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nobootdev> <br />
<model>ipva</model><br />
<model>ipvadec</model><br />
<model>swphone</model><br />
<model>mypbxa</model><br />
<model>mypbxi</model><br />
</nobootdev><br />
</syntaxhighlight><br />
<br />
==== nofirmdev ====<br />
Device types listed here do not receive firmware updates.<br />
===== nofirmdev/model =====<br />
Each model tag defines a device type that shall not receive firmware updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nofirmdev> <br />
<model>swphone</model><br />
<model>mypbxi</model><br />
</nofirmdev><br />
</syntaxhighlight><br />
<br />
==== filenames ====<br />
Available from build 2014. Allows you to define alternate firmware and boocode names for use with the <code>{filename}</code> replacement (see the ''fwstorage'' tag), if required.<br />
<br />
===== filenames/model =====<br />
Each model defines a set of alternative file names for a certain device type.<br />
; filenames/model/@id : the (lowercase) device type identfier (e.g. <code>ip112</code>). If this matches the device type of the calling device, the alternate file names are replaced<br />
; filenames/model/@prot: the alternate firmware file name<br />
; filenames/model/@boot: the alternate boot file name<br />
<syntaxhighlight lang="html5"><br />
<filenames><br />
<model id="mypbxa" prot="mypbx.apk"/><br />
</filenames><br />
</syntaxhighlight><br />
<br />
defines the firmware file name <code>mypbx.apk</code> for the device type <code>mypbxa</code>.<br />
<br />
==== stdargs ====<br />
Better don't touch :-)<br />
<br />
==== customcerts ====<br />
innovaphone hardware devices come with a device specific, trust-able ''device certificate''. However, in many situations it is desirable to roll-out customer-created ''custom certificates'' to all devices. The update server can do this. This will replace the shipped devices certificates both on hardware devices (where all such certificates are derived from innovaphone's certificate authority) as well as on soft devices which run with a self.-signed certificate by default (such as the software-phone or ''myPBX for Android/iOS'').<br />
<br />
; customcerts/@dir : the name of the directory underneath your installation directory which stores device certificates. you should make sure that this directory cannot be accessed without proper authentication<br />
; customcerts/@CAkeys : a file name pattern (e.g. <code>CAkey*</code>). All files in ''customcerts/@dir'' which match the pattern must contain DER or PEM encoded public keys which form the ''certificate chain'' of your CA (usually it is only one and the public key of your CA). One key per file. The files, when sorted by name, must contain the CA key itself in the last file (any intermediate certificates, if any, in the other files in correct order)<br />
; customcerts/@CAtype : must be <code>manual</code> currently<br />
; customcerts/@CAname : a comma-separated list of CA names. Devices presenting a certificate signed by one of these CAs will be considered as having a valid certificate. Otherwise, they wil be instructed to create a ''certificate signing request'' (CSR) for subsequent submission to your CA<br />
; customcerts/@CAnamesep : defines the name separator for ''customcerts/@CAname''. The default is '<code>,</code>' and you must change it to a different value if one of your CAs includes a comma in its name<br />
; customcerts/@renew : if set and not 0, certificates are replaced by new ones ''n'' days before they expire<br />
; customcerts/@CAwildcard : normally, the update server will accept a certificate only if its CN matches the device's serial number. However, sometimes, so-called ''wildcard certificates'' such as <code>*.innovaphone.com</code> are used on a device (e.g. on a PBX or on a ''reverse proxy''). If set, the update server will also accept a certificate that contains a CN which equals the value of ''customcerts/@CAwildcard''. Note that the CN must match literally. For example, if ''''customcerts/@CAwildcard'' is set to <code>*.innovaphone.com</code> a CN like <code>host.innovaphone.com</code> is not accepted<br />
<br />
<br />
When the update server determines that a device calls in with an un-trusted or expired certificate, it will have it create a signing-request for a new certificate. The requested certificate properties can be defined:<br />
<br />
; customcerts/@CSRkey : the key size, e.g. <code>2048-bit</code>. Note that we do not recommend keys larger than 2048 bit (see [[Reference7:Certificate_management#Certificate_Key_Length_and_CPU_Usage]] for details)<br />
; customcerts/@CSRsignature : the signature algorithm, e.g. <code>SH256</code><br />
; customcerts/@CSRdn-cn : the CN (''common name'') part used for the DN<br />
; customcerts/@CSRdn-ou : the OU (''organizational unit'') part used for the DN<br />
; customcerts/@CSRdn-o : the O (''organization'') part used for the DN<br />
; customcerts/@CSRdn-l : the L (''locality'') part used for the DN<br />
; customcerts/@CSRdn-st : the ST (''state or province'') part used for the DN<br />
; customcerts/@CSRdn-c : the C (''country'') part used for the DN (note: ''CSRdn'' for many CAs, must be a 2-letter ISO country code)<br />
; customcerts/@CSRsan-dns-1 : the first of up to three DNS names <br />
; customcerts/@CSRsan-ip-1 : the first of up to two IP addresses<br />
<br />
Within the set of CSR attributes, the following meta-words will be replaced:<br />
<br />
* <code>{realip}</code> : the real IP address of the device as reported in the <code>ip=#i</code> query argument<br />
* <code>{ip}</code> : the IP address of the device as seen by the update server<br />
* <code>{proxy}</code> : the IP address of the reverse proxy the device used to reach the update server<br />
* <code>{sn}</code> : the serial number of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>009033030df0</code>)<br />
* <code>{hwid}</code> : the hardware-id of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>IP1200-03-0d-f0</code>)<br />
* <code>{name}</code> : the ''Device Name'' (set in ''General/Admin'') of the device<br />
* <code>{rdns}</code> : the DNS name found in a reverse DNS lookup for the real-ip address reported by the device (see ''{realip}'') above<br />
<br />
<syntaxhighlight lang="html5"><br />
<customcerts <br />
dir="certs" <br />
CAkeys="CAkey*" <br />
CAtype="manual" <br />
CAname="innovaphone-INNO-DC-W2K8-Zertifizierungsserver" <br />
CAnamesep="," <br />
CAwildcard="*.innovaphone.com"<br />
CSRkey="2048-bit" <br />
CSRsignature="SH256" <br />
CSRdn-cn="{hwid}" <br />
CSRdn-ou="" <br />
CSRdn-o="" <br />
CSRdn-l="" <br />
CSRdn-st="" <br />
CSRdn-c="" <br />
CSRsan-dns-1="{name}.company.com" <br />
CSRsan-dns-2="{hwid}.company.com" <br />
CSRsan-dns-3="{rdns}" <br />
CSRsan-ip-1="{realip}" <br />
CSRsan-ip-2="" <br />
renew="90" /><br />
</syntaxhighlight><br />
<br />
==== queries ====<br />
If queries are defined and applicable for the calling device, it will be instructed to send certain information to the update server which will be stored in the device status file kept on the server. Each piece of such information is defined using a separate ''query'' tag.<br />
<br />
; queries/@scfg : defines the URL used by the device to send the information. Normally not changed from the default<br />
<br />
==== queries/query ====<br />
Defines one piece of information to be submitted to the update server.<br />
<br />
; queries/query/@id : a unique name for the query<br />
; queries/query/@title : a title for this piece of information shown in the device status<br />
<br />
<syntaxhighlight lang="html5">.<br />
<!-- retrieve and show some details from IP4/General/STUN page --><br />
<query id="media" title="Media"><br />
<cmd>mod cmd MEDIA xml-info</cmd><br />
<applies>*</applies><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
<show title="Server">concat('STUN: ', /state/queries/media/info/@stun, ' TURN: ', /state/queries/media/info/@turn, ' (', /state/queries/media/info/@turn-user, ')')</show><br />
</query><br />
<br />
<!-- show primary phone registration --><br />
<query id="registration" title="Registration"><br />
<cmd>mod cmd PHONE/USER phone-regs /cmd phone-regs</cmd><br />
<applies>phone</applies><br />
<show title="State: User/Number">concat(/state/queries/registration/info/reg[@id = "0"]/@state, ': ' , /state/queries/registration/info/reg[@id = "0"]/@h323, '/', /state/queries/registration/info/reg[@id = "0"]/@e164)</show><br />
<show title="PBX/ID">concat(/state/queries/registration/info/reg[@id = "0"]/@gk-addr, '/', /state/queries/registration/info/reg[@id = "0"]/@gk-id)</show><br />
</query><br />
</syntaxhighlight><br />
<br />
==== queries/query/cmd ====<br />
The content of this tag defines the command to be executed on the device which outputs the requested information. These are ''mod cmd''s typically.<br />
<br />
<syntaxhighlight lang="html5"><br />
<!-- get info for custom certificates --><br />
<cmd>mod cmd X509 xml-info</cmd><br />
</syntaxhighlight><br />
<br />
See [[Howto:Effect_arbitrary_Configuration_Changes_using_a_HTTP_Command_Line_Client_or_from_an_Update#Using_the_Mechanism_for_Device_Status_Queries ]] for details on how to find out which commands to use.<br />
<br />
==== queries/query/applies ====<br />
Defined queries are only executed by devices which belong to the class that is given as tag content. If the ''env'' atribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; queries/query/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in (unfortunately, you can not combine both)<br />
<syntaxhighlight lang="html5"><br />
<!-- applied to phones only --><br />
<applies>phone</applies><br />
</syntaxhighlight><br />
<br />
==== queries/query/show====<br />
If one or more ''show'' tags are defined for the ''query'' tag, the data will be shown in the device status page. The values shown are defined by the tag content which is an XPath expression selecting the data from the device state. The XPath expression always begins with <code>/state/queries/</code>''query-id'' (as defined by the ''id'' attribute in the query tag). <br />
<br />
; queries/query/show/@title : used as title when the values are displayed in the status page<br />
<br />
<syntaxhighlight lang="html5"><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
</syntaxhighlight><br />
<br />
== Download ==<br />
*[http://download.innovaphone.com/ice/wiki-src#php-update-server http://download.innovaphone.com/ice/wiki-src/] - download the complete file package of scripts and files described in this article<br />
: you need to use build 2000 or higher. Older versions are described in [[Howto:PHP_based_Update_Server]]<br />
<br />
== Hints for writing your update snippets ==<br />
Writing update scripts is largely undocumented and sometimes tricky. Here are some general guidelines.<br />
<br />
=== Using <code>config add/del</code> ===<br />
Many setting are done using <code>config change</code> commands. So these are very useful in update scripts too.<br />
<br />
The straight forward method to find out which commands you need is as follows:<br />
* get a device of the type in question and do factory reset<br />
* save the configuration to a file<br />
* do the desired settings using the normal web admin UI<br />
* save the resulting configuration to another file<br />
* compare the saved files<br />
<br />
Usually, you want to set only specific parts of the configuration line. For example, assume you want to set the default coder to ''OPUS-WB''. Your saved configuration line might look like<br />
<br />
config change PHONE SIG /prot SH323 /gk-addr pbx.innovaphone.com /gk-id innovaphone.com /tones 0 /lcoder OPUS-WB,20, /coder OPUS-WB,20,k1<br />
<br />
To set only the coder settings, you would set the relevant options with the <code>config add</code> command as follows:<br />
<br />
config add PHONE SIG /lcoder OPUS-WB,20,<br />
config add PHONE SIG /coder OPUS-WB,20,k1<br />
<br />
You can also remove specific options, as in <br />
<br />
config rem PHONE SIG /tones<br />
<br />
which would remove the <code>/tones 0</code> from the configuration line for <code>PHONE SIG</code>. <br />
<br />
Note that the PHP update server will add the<br />
<br />
config write<br />
config activate <br />
iresetn<br />
<br />
at the end of the delivered script automatically, so you do not need to do it yourself. In fact, it is not recommended to have any reset command in your snippets.<br />
<br />
==== <code>config change</code> Lines with associated Passwords ====<br />
Sometimes, there is a password associated with certain configurations. Consider the STUN/TURN configuration: <br />
<br />
config change MEDIA /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd 2 /nat-detect 61<br />
<br />
The TURN password, being sensitive, needs to be encrypted in your configuration. This is why it is stored as a ''VAR'', which provides encryption support (see below):<br />
<br />
vars create MEDIA/TURN-PWD pc ....<br />
<br />
Unfortunately, the relationship between an option and an associated VAR needs to be guessed. You can be sure that the module name in the <code>config change</code> matches the modules name in the <code>vars create</code> command. However, the variable name must be guessed. <br />
<br />
You can also set the password in your script using the <code>vars create</code><!-- , but this does make sense only in staging (that is pre-update-phase) scripts (see below for a discussion why). If you need to do it in a script snippet for the update phase, you should -->, however, you may also consider using a <code>mod cmd</code> (see below) such as e.g.<br />
<br />
mod cmd MEDIA form /cmd form /del %2Fstun-slow /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd my-turn-pw /nat-detect 61<br />
<br />
=== Using <code>vars create</code> ===<br />
<br />
Sometimes it makes sense to use <code>vars create</code> commands. The syntax is<br />
<br />
<code> vars create </code>''<name> <flags> <value>''<br />
<br />
A ''<name>'' is a module name followed by a variable name, optionally followed by an index. For example, a phone will store the currently selected main registration as <code>vars create PHONE/ACTIVE-USER p </code>''<index>''. So the module is <code>PHONE</code> and the variable name is <code>ACTIVE-USER</code>. When the variable is multi-valued, an index is added to the name. For example, the registration settings for all but the first registration are stored as indexed variable, as in <code>vars create PHONE/USER-REG/00001 p </code>''<settings>'', where <code>00001</code> is the index (registrations count from 0 in this case).<br />
<br />
There are a number of flags which can be combined, the most prominent used in update scripts are:<br />
; p : permanent. The var is stored in flash memory (you will almost always use this flag in update scripts)<br />
; c : crypted. The var value needs to be encrypted and the ''<value>'' given is crypted<br />
; x : crypted, plain value. The var needs to be encrypted but the ''<value>'' is given as plain (that is, un-encrypted) text. This allows you to set an encrypted variable without encrypting the desired value<br />
; b : binary. The var value is binary. Its ''<value>'' is given as a bin2hex string<br />
<br />
Please note that using <code>vars create</code> will ''always'' set the internal ''reset needed condition'' in the device (regardless which ''<value>'' is set). This means that a subsequent <code>resetn</code> or <code>iresetn</code> will always trigger a reset. So if you use it, make sure that you use the ''times/@check'' ([[#times | see above]]) to protect your script from re-executing the snippet (and thus rebooting the device) all the time. <br />
<!--<br />
When you use a <code>mod cmd UP1 check ...</code> command in your script (see times/@check [[#times | above]]), this will create an endless loop if you use <code>vars create</code> followed by one of the <code>resetn</code> commands in the code guarded by the <code>check</code> command. This code will never be fully executed, as the ''reset needed'' condition will always be set and the reset will always happen. You can use it in ''pre-update-phase'' scripts (that is, during the staging process) as this is not protected by a <code>check</code> command.<br />
--><br />
<br />
=== Using <code>mod cmd</code> ===<br />
See [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]] for a discussion of how to use <code>mod cmd</code> in update scripts.<br />
<br />
== Writing your own Dynamic Scripting Code ==<br />
NB: this feature is available from build 2020.<br />
<br />
By default, the update server delivers update scripts which are composed from a number of static files (so-called ''snippets''). Sometimes it makes sense however, to create such snippets dynamically (e.g. based on a database lookup). Here is how to do this.<br />
<br />
Dynamic scripting is implemented by a user-supplied custom <code>class CustomUpdateSnippet</code>. The code for this class must be placed in to the file <code>config/scripting.class.php</code>. As a starter, sample class code is available in <code>config/scripting.class-sample.php</code>.<br />
<br />
<syntaxhighlight lang="php"><br />
<?php<br />
<br />
/**<br />
* to provide your own runtime generated snippets, rename this file to 'scripting.class.php' and <br />
* implement the member functions<br />
* $property will have one member called <br />
*/<br />
<br />
class CustomUpdateSnippet extends UpdateSnippet {<br />
<br />
/**<br />
* snippet to deliver after standard text snippets<br />
* @return array of string<br />
*/<br />
public function getPostSnippet() {<br />
return parent::getPostSnippet();<br />
}<br />
<br />
/**<br />
* snippet to deliver before standard text snippets<br />
* @return array of strings<br />
*/<br />
public function getPreSnippet() {<br />
return parent::getPreSnippet();<br />
}<br />
<br />
/** <br />
* do we want to suppress the standard text snippets?<br />
* @return boolean<br />
*/<br />
public function sendStandardSnippets() {<br />
return parent::sendStandardSnippets();<br />
}<br />
}<br />
?><br />
</syntaxhighlight><br />
<br />
The sample code overrides of the classes member functions just call the base classes which do nothing at all. So if you just copy the sample code into <code>scripting.class.php</code>, nothing will change. However, if <code>getPreSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''before'' the standard snippets are sent. Likewise, if <code>getPostSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''after'' the standard snippets are sent. If <code>sendStandardSnippets()</code> returns false, the standard snippets will not be sent.<br />
<br />
To determine the dynamic snippets to send, the user-provided <code>class CustomUpdateSnippet</code> has access to the members of its base <code>class UpdateSnippet</code>, namely the <code>var $statexml</code> member. This member contains a <code>SimpleXMLElement</code> object with all state information available for the calling device.<br />
<br />
Here is a sample state found in <code>$statexml</code> (as output by the <code>dump()</code> member function): <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0"?><br />
<state seen="1522065435" requested="130774bootcode" phase="update"><br />
<device sn="00-90-33-3e-0c-57" type="IP112" classes="phone, opus_phone, phone_newui, hstrace, ip112" ip="127.0.0.1" rp="false" phase="update"<br />
environments="sifi, 172net" certstat="either certificate handling or state tracking not enabled - not doing any certificate checking" hwid="IP222-46-5c-77" realip="172.16.80.241" requestDownloaded="false"/><br />
<firmware requested="130986" requested-at="1522065435"/><br />
<bootcode requested-at="1522065435"/><br />
<config filename="scripts/all-all-all.txt" delivered="1522065426" version="1486559970"/><br />
</state><br />
</syntaxhighlight><br />
<br />
The most interesting elements in this XML are probably the attributes of the <code>state</code> and the <code>device</code> tags.<br />
<br />
== Hints for using custom SSL Certificates ==<br />
<br />
Using your own SSL certificates can be useful depending on the scenario.<br />
<br />
We have two methods to accomplish this:<br />
<br />
=== Standard scenario as described in this wiki article ===<br />
The idea is that each certificate request is checked manually, edited and the UpdateServer is provided with the new device certificate.<br />
<br />
=== Special scenario with automated certificate renewal ===<br />
The idea is that every new/unknown (untrusted) device connects to the staging server. Here the device is released by manual checking/authorization by getting its first certificate. All further extensions can happen automatically afterwards.<br />
<br />
'''Important: All important settings concerning file permissions, access and co must be used from the above article and are only skipped in this part!'''<br />
<br />
We will create two Update-Server instances with the following configuration.<br />
<br />
# Staging Server (HTTPS/443)<br />
#*Put the sources in <code>/var/www/innovaphone/update</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]].<br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!update/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code| above]]<br />
#* '''Important''': Do not submit any further customer specific device configurations in the staging Server<br />
# Final MTLS / 444 update server<br />
#*Put the sources in <code>/var/www/innovaphone/mts</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]] with <code>renew = "x"</code><br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!mtls/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code | above]]<br />
#*Store all customer specific device configurations<br />
<br />
<br />
The meaning of the two different instances is:<br />
*The staging server <code>https://[ip]/update/admin/admin.php</code> will generate the provisioning URLs for new devices.<br />
*All new/unknown (untrusted) devices are only in the staging server.<br />
**Here you can check the devices and provide them a valid certificate after check/authorization.<br />
*With a valid certificate, the UpdateURL is automatically changed to the MTLS instance.<br />
*As soon as a device connects via MTLS we trust this device and we can use an automatic extension of the certificates.<br />
** Waiting CSR requests are in the configured folder ''certs'' in the notation <code>"[mac]-request.p10.pem"</code><br />
** If you operate your own CA, feel free to automate the reissue of the certificates via a sheduled Cronjob<br />
*** Help for Linux: [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
*** Help for Windows: [[Howto:Creating_custom_Certificates_using_a_Windows_Certificate_Authority]]<br />
** If a signed certificate with the format <code>"[mac]-signedrequest.cer"</code> is placed in the certs folder, the certificate is automatically updated on the device.<br />
<br />
== Optional Configurations on the Linux Application Platform ==<br />
===Support for more Connections===<br />
By default the LAP's web server lighttpd allows for 512 concurrent connections with 1024 open file descriptors. When you serve a huge number of devices with the update server, then this might be too low. You will see some devices not being able to contact the update server for a while. This should not be a real issue as the devices will retry. However, updating all devices may take long due to this.<br />
<br />
In the lighttpd log (in <code>/var/log/lighttpd</code> on the LAP), you will see entries like this:<br />
<br />
2017-10-16 13:45:18: (network_linux_sendfile.c.140) open failed: Too many open files <br />
2017-10-16 13:45:18: (mod_fastcgi.c.3075) write failed: Too many open files 24 <br />
2017-10-16 13:45:18: (server.c.1434) [note] sockets disabled, out-of-fds <br />
2017-10-16 16:23:25: (response.c.634) file not found ... or so: Too many open files /mtls/updatev2/web/innovaphone_logo_claim_fisch.png -><br />
2017-10-16 17:57:45: (server.c.1432) [note] sockets disabled, connection limit reached <br />
<br />
If you experience such issues, proceed as follows:<br />
<br />
* connect to the LAP with SFTP (e.g. using WinSCP)<br />
* change directory to <code>/etc/lighttpd</code><br />
* edit <code>lighttpd.conf</code><br />
* search for the 2 lines which set <code>server.max-fds</code> and <code>server.max-connections</code><br />
* replace the standard values. We recommend to set the number of file descriptors to 4 times the number of requests when using the update server, e.g. <br />
: old<br />
:: server.max-fds = 1024<br />
:: server.max-connections = 512<br />
: new<br />
:: server.max-fds = 8000<br />
:: server.max-connections = 2000<br />
* save the file<br />
* restart linux (''Diagnostics/Reset/Reboot'')<br />
<br />
Be sure to closely monitor the ''Diagnostics/Status'' page for a while after such configuration. <br />
<br />
Also, when you update the LAP, you will have to re-do the changes (as always when you change something ''under the hood'').<br />
<br />
=== Debug files rotation based on logrotate ===<br />
If you have to debug during operation and a lot of devices access the PHP Update Server V2, you have to keep track of the debug files or automatically limit them. For this you can use logrotate on the innovaphone Linux AP. With logrotate you can apply time-based or size-based rules when files are packed and when they are deleted.<br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files. <br />
* Under the path <code>/etc/logrotate.d</code> create a file e.g. <code>updateserver</code>.<br />
<br />
<code>/etc/logrotate.d/updateserver</code><br />
<br />
In this file now enter the following content and save it.<br />
<br />
<code><br />
/var/www/innovaphone/mtls/update/debug/*.txt {<br />
size 5M<br />
missingok<br />
rotate 2<br />
compress<br />
delaycompress<br />
notifempty<br />
create 644 www-data www-data<br />
}<br />
</code><br />
<br />
This will include all *.txt files from the debug directory of the PHP Update Server V2 in the logrotate process of the operating system.<br />
<br />
; size 5M : means every 5MB the file is rotated or zipped<br />
; size 5k : means every 5kB the file is rotated or zipped<br />
Alternatively, you can also rotate the file based on time<br />
; daily : means that every day the file is rotated or zipped<br />
; weekly : means that the file is rotated or zipped daily<br />
<br />
; missingok : if a debug file does not exist, it will be ignored<br />
; rotate n : files are removed before the last ones are deleted.<br />
; compress : compresses the old debug files<br />
; delaycompress : compresses the debug data only after it has been moved once<br />
; notifempty : empty log files are not rotated<br />
; create 644 www-data www-data : creates a new, empty debug file with appropriate permissions<br />
<br />
== Update Server and Reverse Proxy ==<br />
It is possible to implement access to the update server through a ''reverse proxy'' (RP). However, you have to keep some issues in mind:<br />
* ''Mutual Transport Layer Security'' (MTLS) is not possible<br />
: MTLS requires a direct TLS connection between the two parties. An RP however would terminate the TLS connection and entertain two independent connections to the parties. This breaks MTLS. If you want to use MTLS and serve external clients, you must therefore provide a direct access to the update server (that is an external IP address either directly or through port forwarding)<br />
: see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts]] for details<br />
* When using the RP, you can choose to forward encrypted traffic (HTTPS) arriving from external to the update server using HTTP (unencrypted). The update server however eventually rewrites the update URL in the calling devices configuration. In order to do this, it determines the type of the internal connection. So if the connection from the RP to the update server is using HTTP, it would create a ''http://...'' URL. Otherwise it would create an ''https://..'' URL (also, it would be using the same port). This way, if the RP forwards requests to the update server with HTTP, the synthesized new URL for the calling client would use HTTP too, even though the original request was sent with HTTPS. This may or may not be what you want (as all of your clients would end up using non-encrypted traffic). Even worse, it might not work at all, if the RP does not accept HTTP for example.<br />
<br />
== Related Articles ==<br />
* [[Reference10:Concept_Update_Server]]<br />
* [[Reference12r1:DHCP_client]]<br />
* [[Reference10:Concept_Linux_Application_Platform]]<br />
* [[Reference10:Concept_Provisioning]]<br />
* [[Howto:PHP_based_Update_Server]] (old version)<br />
* [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
* [[Howto:Creating custom Certificates using a Windows Certificate Authority]]<br />
* [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]]<br />
<br />
<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference8:TAPI_Service_Provider&diff=70843Reference8:TAPI Service Provider2024-02-14T14:23:42Z<p>Ckl: /* Tweaks */</p>
<hr />
<div>The ''innovaphone PBX V8.0 based TAPI Service Provider'' (TSP) is an enhanced version of the [[ Reference7:Unified Win32 and x64 TAPI Service Provider | previous TSP version ]] that takes advantage of the [[ Reference8:SOAP API | new SOAP features ]] provided with version 8 PBX firmware. It implements - like the previous versions - TAPI Version 3.0 without MSP (Media Service Provider).<br />
<br />
The [[Reference7:Unified Win32 and x64 TAPI Service Provider| previous TSP ]] still must be used for V7 PBX systems. <br />
<br />
This article describes how to install and use it as well how to configure the PBX in order for the TSP to work properly.<br />
<br />
==Applies To==<br />
This information applies to<br />
* innovaphone PBX V8.0 based TAPI Service Provider, Build 8001 and later<br />
* innovaphone PBX V8 and later (that is, this Service Provider runs with PBX Firmware V8 and later)<br />
<br />
==More Information==<br />
<br />
<br />
<br />
=== Enhancements ===<br />
; Support for V8 firmware [[Reference8:Administration/PBX/Objects#Devices | devices]] : Each user object device is represented as a TAPI line (all sharing an identical address, the objects extension a.k.a. ''Number'' property). This now allows to select individual registration devices when multiple devices are registered with the same PBX. Please note that in V8, [[ Reference8:Administration/PBX/Objects/Edit_Forks | mobility devices ]] are not shown and thus not represented by TAPI line device. This was introduced in V9 only.<br />
; Support for presence based lines : These are TAPI lines shown which reflect the users presence state. Presence state ''open'' is mapped to a TAPI device status ''in service''. Presence activity ''on-the-phone'' is mapped to a virtual call in state ''connected''.<br />
: '''NB''': V8 PBX Firmware did set presence activity ''on-the-phone'' for each user object having a call. Unfortunately, as this does create performance issues, this behaviour has been removed from V9 PBX Firmware. The ''presence line'' feature may be useless with V9 PBX when the application did rely on this particular V8 PBX behaviour.<br />
<br />
===System Requirements===<br />
The TSP will install on any Windows 32bit and x64 platform down to Windows XP/Server 2003. For older systems, you must use a deprecated [[Reference7:TAPI_Service_Provider | previous TSP version]]. For systems running PBX firmware version 6 or earlier, you must use the even older [[Reference:TAPI_Service_Provider | version 5 based TSP]]. Note that there is no x64 version of the version 5 TSP!<br />
<br />
The TSP needs to maintain parallel connections to each individual PBX in the system. For larger systems (i.e. systems with a huge number of PBXs), this may create substantial load to the underlying windows machine. The number of parallel activities scheduled by the TSP is thus limited as a function of the available main memory and number of processors. In particular, a maximum of 20 activities per available processor is allowed (up to build 8088, the limit is 60/processor for later builds). If this limit is exceeded, the TSP will issue performance warnings of class WARNINGS in the TSP log file and system TAPI performance will be poor. Use a more capable machine then.<br />
<br />
Microsoft Windows operating system version for desktop clients (as opposed to server systems) limit the number and performance of TCP/IP connections. This may lead to bad performance or occasional request failures. We generally recommend to use server operating systems for 3rd party TAPI installations thus.<br />
<br />
No special PBX licenses are required to use the innovaphone TSP.<br />
<br />
=== Download === <br />
The TSP will be available on the tab ''Software'' in the [https://store.innovaphone.com/ Store].<br />
<br />
===Installation===<br />
Windows cannot run a Win32 TSP on an x64 platform (although it can run Win32 applications on such platforms). This is why there are 2 versions of the setup, the x64 installer (<code>setup64-release.msi</code>) and the Win32 installer (<code>setup32-release.msi</code>.<br />
<br />
The ''release'' setup packages provided will install the retail version. This is recommended for production purposes but provides no debug options whatsoever. To track down possible problems, support may instruct you to install the debug version. <br />
<br />
The TSP may be installed on each machine where a desired TAPI based<br />
application is to be run. If for example, Outlook is to be used, then<br />
each client PC running Outlook may have the TSP installed. Although<br />
this is typical for a 1st party configuration, all clients may have full<br />
3rd party functionality, that is, they may control all existing lines.<br />
<br />
As an alternative, the TSP can be installed on a single machine and a<br />
3rd party TAPI server product (such as the IXI-Call Server available as<br />
a separate product) may be used to provide the network clients with a<br />
TAPI interface. Also, Microsoft’s Remote TAPI Server should work but is not being tested, so you use it on your own risk.<br />
<br />
To install, <br />
* select and download the <code>setup32-release.msi</code> install packages on 32bit platforms or the <code>setup64-release.msi</code> install package on 64bit platforms.<br />
* double-click the install package to launch the installer<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - zipcontent.png]]<br />
* accept the license agreement<br />
* select the target folder<br />
* complete the installer<br />
<br />
When the installer has copied all files to the target machine, you need to add the TSP to the machine's TAPI system<br />
* open the ''Telephone and Modem'' control panel<br />
* Switch to the rightmost tab (''Extras'')<br />
* Click on the ''Add...'' button<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - Control Panel Add.png]]<br />
* Select the innovaphone TAPI provider driver<br />
* Fill out the configuration dialogue<br />
* Install the provider by clicking ''OK''<br />
<br />
==== File Organization ====<br />
Windows forces all TAPI service provider files to reside in Windows' ''System Folder''. This is the <code>system32</code> folder in your windows install directory (usually <code>C:\windows\system32</code>), even if you are running an x64 platform! The installer will thus copy these files (<code>tsp8.tsp</code> and <code>tsp8UI.dll</code>) in to this directory. All other files however will be copied to the <code>innovaphone AG\innovaphone® PBX V8 TAPI Service Provider</code> folder underneath your systems ''Program Files Folder''. <br />
<br />
The subdirectories <code>Debug</code> and <code>Logs</code> are created. <code>Debug</code> contains the driver's debug version, <code>Logs</code> will receive the log files when a debug version is used.<br />
<br />
On x64 platform systems, the Win32 version of the configuration DLL (<code>tsp8UI.dll</code>) will be installed to the windows ''Windows on Windows64 System Folder'' (<code>SysWOW64</code>).<br />
<br />
====Upgrading to a newer Version====<br />
When you attempt to upgrade the TSP from a previous version, the Windows<br />
installer will first remove any previous installation. When the new<br />
software is installed then, the TSP will be installed again into the<br />
TAPI system. <br />
<br />
There is no need to remove the driver from the ''Telephone and Modem Control Panel'', as this would make you loose your driver configuration.<br />
<br />
====Upgrading from the old Win32-only Version====<br />
If you upgrade from the older Win32-only versions of the TSP (soap-appl/tapi/7.00 or soap-appl/tapi/5.00), you must first remove the old TSP from ''telephone and modem'' control panel and uninstall the old product from the ''Software'' (or ''Programs and Functions'') control panel.<br />
<br />
When you upgrade from the old V7 64-bit TSP (soap-appl/tapi/7.00-64), you should first update this older version to the latest build available.<br />
<br />
This procedure ensures that during the upgrade your TAPI lines retain their internal identifiers and thus their meaning in your TAPI application's configuration.<br />
<br />
==== Uninstalling the TSP ====<br />
To uninstall the TSP proceed as follows<br />
* Remove the TAPI driver from the TAPI system using ''Telephony and Modem Control Panel''<br />
* close ''Telephony and Modem Control Panel''<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* remove the installation using windows ''Programs Control Panel'' or by invoking the original .msi again<br />
<br />
You will notice that Windows may fail to remove the driver files from the ''Windows System Directory''. To clean up remove <code>tsp8.tsp</code> and <code>tsp8UI.dll</code> from the <code>system32</code> folder as well as - on x64 systems - from the <code>SysWOW64</code> folder.<br />
<br />
Also, if you did file tracing, any remaining debug files in the <code>Logs</code> folder are left over and need to be removed manually.<br />
<br />
The TSP will create some entries in the windows registry which will not be removed on uninstall. It is recommended to leave these entries as is. Only if you are sure you will never install the TSP again on this system or you are sure you will never use it with the PBX installation you used so far, you may want to delete them from the registry.<br />
<br />
==== Rolling out First Party TSPs to multiple PCs ====<br />
Normally, when multiple users require CTI and hence TAPI functionality, the best way is to use a server based, multi-client 3rd party CTI application. This will share all functions among all client PCs. If this is not an option, e.g. because the TAPI application in use does not support it, you may want to consider using Microsoft's TAPI Server and remote TSP. See Microsoft's [http://technet.microsoft.com/en-us/library/cc786297%28v=ws.10%29.aspx Telephony service providers overview] and [http://technet.microsoft.com/en-us/library/cc770373.aspx Manage Telephony Servers] documents. <br />
<br />
If you still want to use the native innovaphone TSP on a number of PCs (instead of once on a server), you can roll out the TSP using some of the [http://support.microsoft.com/kb/816102/EN-US software deployment schemes Microsoft Server provide]. In such a case, you will likely want to deploy identical configurations to all these PCs. While this theoretically can be done by distributing registry settings, there will be a problem with the PBX access credentials. These are stored in encrypted format in the registry and can only be decrypted on the PC on which they have been set. That is, deploying such registry settings to other PCs will result in a non-functional setup.<br />
<br />
To work around this problem, you may store the credentials in clear text in the registry. To do so, you would put the ''Password'' in a REG_SZ value named <code>admin1-free</code> and the ''Account'' into a REG_SZ value named <code>admin2-free</code>. If such a value is found in the proper place in the registry, the values configured using the telephony control panel are ignored. <br />
<br />
[[Image:TAPI Service Provider-tsp8-nocrypt.png]]<br />
<br />
'''Keep in mind that having credentials in clear in the registry presents a security risk!'''<br />
<br />
This feature is available in build 8079 and later.<br />
<br />
=== Upgrade from Build 8164 or earlier ===<br />
From build 8165, the TAPI configuration has been moved to a different registry location. This has been done because latest windows versions (namely, Windows 10) do not allow the TSP to access registry information in the location used so far (<code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Telephony\innovaphone® PBX V8 TAPI Service Provider</code>). To work around this issue, it is now stored in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code>.<br />
<br />
The TSP configuration dialogue however runs with user privileges (as opposed to the TSP which runs with limited service privileges). It can therefore read the old configuration information and copy it to the new location. From build 8165, the configuration dialogue will thus copy any old configuration information to the new location when it is opened. When you upgrade an existing installation and open the configuration dialogue, you will see no change. However, when the configuration is saved, it is then present in the new location.<br />
<br />
'''To upgrade an installation from build 8164 or earlier (that is TAPI 'V8 TAPI Service Provider (32 and 64bit) - hotfix14'' available in the ''V8 applications hotfix20'' package or earlier), proceed as follows:'''<br />
* stop the telephony service (e.g. <code>net stop tapisrv</code>)<br />
* install the new TSP<br />
* open the TSP configuration dialogue<br />
* verify the configuration data<br />
* save the configuration<br />
* (re) start your TAPI application<br />
<br />
If you fail to migrate the configuration as described, the TSP will start, but not work!<br />
<br />
===Configuration===<br />
The TSP configuration dialogue looks like this:<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - Config UI.png]]<br />
<br />
* The ''VERIFY'' button will verify the configuration. Note that the ''Username'' drop down list will only be populated after a successful verify. <br />
* The ''OK'' button will save the configuration.<br />
* The ''CANCEL'' button will quit the configuration without saving any changes. If it is the initial configuration while you add the TSP via the telephone and modem control panel, the TSP will not be added <br />
<br />
TAPI talks to the PBX using [[Reference7:SOAP_API | SOAP ]] which in turn uses HTTP for communication. Both secure (https) and non-secure (http) communication is supported. In any case, HTTP basic authentication is used. <br />
To be able to connect to the PBX, the TSP needs to know proper credentials to use during HTTP authentication. These are referred to as ''PBX Account'' and ''PBX Password''. Also, a suitable ''TAPI User'' must be selected.<br />
<br />
==== Controlling the Line Devices handled by TAPI ====<br />
<br />
TAPI connects to your PBX as a PBX user referred to as ''TAPI User''. It will see all PBX objects that are members of groups in which the ''TAPI User'' is an active member. If the ''TAPI User'' is not an active member in any group, TAPI will see the ''TAPI User'' object only. This may be useful in a [http://en.wikipedia.org/wiki/Telephony_Application_Programming_Interface 1st party TAPI scenario]. PBX objects are represented as TAPI lines. <br />
<br />
The PBX object you use as ''TAPI User'' needs to have at least ''Viewing only'' [[Reference:Administration/PBX/Objects/Edit Rights | PBX user rights]]. Its ''Long Name'' property is used as ''TAPI User Username'', its ''Name'' property as ''PBX Account'' and the password as ''PBX Password''. This is why the PBX object used as ''TAPI User'' must have a password configured. <br />
<br />
===== 1st Party Configuration =====<br />
In a 1st party scenario, the TSP will only work on a single PBX object. This will typically be a user's phone and thus the user itself will be used as the ''TAPI User''. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - FirstParty.png ]]<br />
<br />
In such a configuration the TSP is typically installed on the users PC and the CTI software is accessing TAPI directly (such as e.g. Microsoft Outlook does). <br />
<br />
The TSP configuration dialogue will check for the number of lines seen. If it is only one, then it will issue a warning message:<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - OnlyOne.png]]<br />
<br />
This is because 3rd party configurations are much more common and this situation often indicates a configuration problem. In a first party configuration, you can safely ignore this message. <br />
<br />
===== 3rd Party Configuration =====<br />
In a 3rd party configuration, the TSP will work with multiple PBX objects. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - ThirdParty.png]]<br />
<br />
Typically, you will share a single TSP instance on a server system for use by several users on their desktop PCs. This is done by virtue of a ''TAPI Server''. There are various TAPI server products available on the market, including but not limit to the Estos ProCall product and the remote TAPI server included in Microsoft Windows server operating systems. <br />
<br />
In this scenario, it is recommended to create a pseudo PBX user object for use as the ''TAPI User''. This pseudo user is often called <code>_TAPI_</code>. You would create a dedicated group to control the list of PBX objects the TSP creates a line device for. This group is often called <code>tapi</code>.<br />
<br />
==== Selective Call Forwards ====<br />
Many CTI applications support distinct call forwards for internal and/or external calls. The TSP will translate such requests to a call forward on the PBX which has the [[Reference7:Administration/PBX/Objects/Edit_CFs | ''Only'' or ''Only not'' property]] set to the number of the trunk line. For this to work, it needs to know this number. To know the number, the trunk line PBX object must be seen by the TSP (see above). If this is not the case, the TSP configuration dialogue will issue a warning:<br />
<br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - NoTrunk.png]]<br />
<br />
In a vanilla first party scenario, this is obviously not the case. If you don't care, you can safely ignore this issue. Attempts to set such call forward will be rejected then as ''operation unavailable''.<br />
<br />
In a Master/Slave PBX scenario where there is no Trunk Line object on the Master PBX, but only on the Slave PBXs, a workaround is required to suppress the warning and enable selective call forwarding on the Slave PBXs. In this case, create a dummy "Trunk Line" object on the Master PBX with the same extension number used by the "Trunk Line" objects on the Slave PBXs (usually 0).<br />
<br />
==== Multi Site Configuration ====<br />
In a system with multiple PBXs, the TSP needs to connect to each of the individual PBXs. <br />
<br />
[[Image:Unified Win32 and x64 TAPI Service Provider - MultiSite.png]]<br />
<br />
In this case, you would specify your master PBX as ''PBX Master''. There is no need to configure the complete PBX tree to the TSP as it is determined dynamically on runtime by analysing the PBX/Node objects in the master PBX and their registration status. If a registered PBX is found, this PBX is added to the list of active PBXs and a new connection is established. The PBX tree is built and maintained dynamically. A reconfiguration or restart of the TSP is not required on changes thus.<br />
For this to work<br />
<br />
* the PBX object used as ''TAPI User'' must exist in each PBX with same properties (including name and the password). You may want to use <code>_TAPI_</code> as ''Name''/''Long Name''. This object must be ''active'' member in a group (which you may want to call <code>tapi</code>). A password must be set. The object must have at least ''Viewing only'' rights<br />
* the slave PBX-objects (or in older firmware versions the slave PBX Node-objects) must be visible to the TSP (that is, must be non-active member of the group the ''TAPI User'' object is an active member of, e.g. <code>tapi</code>) so that the slaves are made known to the TSP.<br />
<br />
<br />
In a replicated scenario, you would create a ''TAPI user'' as recommended above and [[Reference8:Administration/PBX/Objects#Objects_with_empty_node_or_PBX | leave the ''PBX'' and ''Node'' properties empty ]]. This user should then be used as both ''PBX Account'' and ''TAPI User Username'' in the TAPI configuration dialog.<br />
<br />
''Further relevant settings'':<br />
<br />
You can disable slave detection by checking ''Do not monitor slaves'' property in the TSP configuration dialogue. <br />
<br />
TAPI maintains an ''address'' property per line device. This usually is the lines extension. In a multi site configuration, the address property will be set to the ''Number'' property of the node the respective PBX object is configured in, plus the ''Number'' property of the object itself. So if an object has ''Number'' <code>42</code> and lives in node <code>801</code>, then its correspondence line device will have address <code>80142</code>. By checking the ''Use pure node extensions'' property in the TSP configuration dialogue, you change the algorithm so that only the objects own ''Number'' is used (<code>42</code> in our example).<br />
<br />
==== Standby Configurations ====<br />
In a system with standby PBX for the master PBX, you need to specify the ''PBX Standby'' IP address. This will be connected if the master is unavailable. Note that there is no need to explicitly configure slave-standby PBXs.<br />
<br />
==== Working with multiple, unrelated PBXs ====<br />
When working with multiple, unrelated PBXs (that is, PBXs that do ''not'' form a PBX tree as slaves and masters do), the TSP cannot derive the list of PBXs to track from the registration status. To support such a configuration, you will need to configure the extraneous master PBXs manually using regedit. Please note that using regedit may harm your system and may even cause inability to boot!<br />
<br />
To find and edit the right registry entries, proceed as follows:<br />
<br />
* open the key <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/><br />
* open its subkey <code>Device</code>''n'' where ''n'' is the provider id of the installed innovaphone TSP (for builds before 8164 only)<br />
* Open the <code>FQDN</code> key and add all extra PBXs<br />
* For those PBXs that have a standby PBX, add a value to the <code>StandbyFQDN</code> key (please note: these are parallel lists. So master and standby need to have the same respective index in the <code>FQDN</code> and <code>StandbyFQDN</code> value lists)<br />
<br />
The standard configuration UI will not show these extra values. However, it will also not touch them. So even if you use the standard UI to edit, only the first value in the lists will be changed and the remainder left unchanged. You can thus safely use the standard UI to edit all other values. As with multi site configurations, all PBXs need to be accessible using the same ''TAPI User''.<br />
<br />
Please note that this method is deprecated and will likely be removed from future versions of the TSP. <br />
<br />
==== Setting the Line Device Name ====<br />
The TSP will derive the line device's name from the properties of the respective PBX object. By default the name will be the objects ''Long Name'' followed by the name of the PBX the user ought to register with in parentheses. So if the users ''Long Name'' is <code>Foo Bar</code> and the registration PBX is <code>branch1</code>, the line device will be called <code>Foo Bar [branch1]</code>. You can specify a different pattern by changing the ''TAPI Line Names'' property of the TSP configuration dialogue. The following replacement characters are available:<br />
<br />
{|<br />
|+<br />
| Meta || Replacement<br />
|+<br />
| %c || The objects ''Long Name'' (cn)<br />
|+<br />
| %C || The objects ''Long Name'' (cn) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Long Name''<br />
|+<br />
| %d || The objects ''Display Name'' (dn)<br />
|+<br />
| %D || The objects ''Display Name'' (dn) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Display Name''<br />
|+<br />
| %h || The objects ''Name'' (h323 alias)<br />
|+<br />
| %H || The objects ''Name'' (h323 alias) followed by the ''Name'' of the ''Device'' the line represents in braces <code>[name]</code> if it differs from the ''Name''<br />
|+<br />
| %t || The ''Name'' of the ''Device'' the line represents<br />
|+<br />
| %T || The ''Hardware Id'' of the ''Device'' the line represents (from build 8181)<br />
|+<br />
| %e || The objects extension (e164)<br />
|+<br />
| %E || The objects extension (e164) prefixed with the objects node number<br />
|+<br />
| %N || The line address as reported to TAPI<br />
|+<br />
| %n || host name (of master pbx)<br />
|+<br />
| %p || ''':'''''port-number'' (of master pbx's http access, empty if 80)<br />
|+<br />
| %P || raw port number of master pbx<br />
|+<br />
| %u || url-like user name ('''''user''@''host''''')<br />
|+<br />
| %U || user url as per draft-levin-iptel-h323-url-scheme-04 ('''h323:://''user''@''host'':''port''''')<br />
|+<br />
| %X || the PBX name the user is registered with (note that using this pattern may result in a change of the name when a standby situation occurs)<br />
|+<br />
| %x || the PBX name the user is reported by (note that using this pattern may result in a change of the name when the users ''PBX'' attribute changes)<br />
|}<br />
<br />
The default pattern is <code>%C (%x)</code>.<br />
<br />
<br />
<!-- rufumleitung, extern, intern, amtsleitung, vom amt, zum amt --><br />
<br />
==== Miscellaneous Flags ====<br />
; Show full E164 Numbers : when set, the TSP will announce the full calling number from the root when indicated by the PBX in the TAPI ''calling party name'' attribute. The number will be appended to the calling name with a <code>@</code>.<br />
; Do not show parked Calls : will hide parked calls from the TAPI application (as some get confused and don't know how to handle them)<br />
; Map PBX Devices to Lines : if set, the TSP will create one TAPI line for each individual [[Reference9:PBX/Objects#Devices|PBX device]] configured in a user object. See [[#Enhancements]] above. If you do not set this flag, see [[Support:How should TAPI line device be registered?]]<br />
; Map Presence Status to TAPI Lines : if set, the TSP will create one extra TAPI line for each PBX device object. See [[#Enhancements]] above.<br />
; No special Disconnect Behaviour : normally, lines monitored by TAPI will behave slightly different when a call is terminated. With no TAPI on the line, the call will be disconnected immediately (from a PBX point of view). In cases where the phone would play some tones after termination of the call (e.g. a busy tone when the call has never been connected to the far end), this state is simulated by the phone and not visible to TAPI. Therefore, it is not possible to use TAPI functions on this call any more (as it in reality does not exist any more). When this flag is set, monitored lines will not be treated specially. Available from hotfix 6. To prevent the IP-Phone to play any tones after the call is disconnected, the phone preferences option '''Go onhook if final call is released by remote side even if handset is lifted''' can be activated. This is the feature of the IP-Phone is useful in a e.g. call center at agent phones and can be configured here [[Reference11r1:Phone/Preferences]].<br />
: To prevent an innovaphone IP-Phone from playing any tones after a call is disconnected, the phone preferences option '''Go onhook if final call is released by remote side even if handset is lifted''' can be activated. This feature is useful for agent phones in a call center for example and can be configured in [[Reference11r1:Phone/Preferences]]<br />
<br />
===Trouble Shooting===<br />
The TSP will (from build 8095) write messages of type INFO (''informational messages'') or WARNING (''Warnings/Errors'') to the windows event log. Such events are always logged to the application log and event source is the provider name (''innovaphone® PBX V8 TAPI Service Provider'').<br />
<br />
==== Turning on Debugging ====<br />
There are 2 versions of the TSP, debug and retail, which are both copied to the machine during setup. The retail version is installed into the system directories, whereas the debug version is stored in a separate directory. This is available through a short cut in the Start menu.<br />
<br />
The TSP can produce a number of debugging messages which can be helpful to debug issues (in both ''retail'' and ''debug'' builds). By default, debug messages are written to the systems debug buffer mechanism (using OutputDebugString()). Such messages can be examined using standard debugging tools, such as for example <code>dbgview</code> which is [http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx available from Microsoft]. <br />
<br />
Debug messages have a class associated with it and the amount of messages written can be controlled by enabling or disabling specific classes. This is done by setting a bitmask in the registry value <code>TraceLevel</code> in the <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> key. <br />
<br />
The easiest way to set the TSP's debug level is with the ''TSP Control'' utility (''tsptray.exe'') which is installed with the TSP.<br />
<br />
The available classes include<br />
<br />
{|<br />
| Bit in <code>TraceLevel</code> || Class <br />
|-<br />
| 0x00000001 || Minimum tracing <br />
|-<br />
| 0x00000002 || TAPI api traces <br />
|-<br />
| 0x00000020 || Basic telephony object creation/destruction <br />
|-<br />
| 0x00000040 || Thread creation/destruction <br />
|-<br />
| 0x00000080 || Request creation/destruction <br />
|-<br />
| 0x00000100 || call related messages <br />
|-<br />
| 0x00000200 || Call id map <br />
|-<br />
| 0x00000400 || Warnings/Errors <br />
|-<br />
| 0x00000800 || Worker thread execution <br />
|-<br />
| 0x00001000 || Full lock/unlock notifications <br />
|-<br />
| 0x00002000 || Win32 Critical section create/destroy <br />
|-<br />
| 0x00004000 || Agent proxy support <br />
|-<br />
| 0x00008000 || constructor/destructor debug <br />
|-<br />
| 0x00010000 || development debugs <br />
|-<br />
| 0x00100000 || verbose debugging <br />
|-<br />
| 0x00200000 || SOAP trace <br />
|-<br />
| 0x00400000 || SOAP message dumps <br />
|-<br />
| 0x01000000 || ATL debug <br />
|-<br />
| 0x02000000 || line related messages <br />
|-<br />
| 0x04000000 || informational messages <br />
|-<br />
| 0x10000000 || dump call infos <br />
|-<br />
| 0x20000000 || dump PBX infos <br />
|-<br />
| 0x80000000 || output log messages to file <br />
|}<br />
<br />
If <code>TraceLevel</code> is not set, it defaults to (hex) <code>04000400</code> in retail builds and to (hex) FFFFFFFF in debug builds. A nice value to use is (hex) <code>92200580</code>. The <code>TraceLevel</code> can be changed during runtime of the provider (it may take up to 10 seconds though for the setting to take effect). In normal scenarios there is no need to install the debug version.<br />
<br />
Both ''informational messages'' and ''Warnings/Errors'' are always turned on (from build 8095).<br />
<br />
: A problem has been reported on Server 2008 x64 systems which may also apply to others. On such systems, the value of <code>TraceLevel</code> may be reset to its default every 10 seconds. A workaround is to change the account the Telephony service is running in from its default (usually ''Network Service'') to e.g. ''Local System''.<br />
<br />
===== Crash Dumps =====<br />
From build 8063 the TSP will write crash dumps to the log directory (usually something like <code>C:\Program Files\innovaphone AG\innovaphone® PBX V8 TSP\Logs</code>) in case of a trap. In release installs, these are not very useful. For debug builds though, they include helfpul information. Please provide these files (the can be zipped to a great extent) to support if requested. A crash dump file will be called something like <code>TSP8<i>build</i>-<i>hour</i>#<i>minute</i>-<i>day</i>.<i>month</i>#<i>seqnr</i>.dmp</code>.<br />
<br />
===== Forcing a Crash Dump =====<br />
In rare cases, it may be useful to force a TAPI crash for debug reasons. This can be done by issueing a <code>lineMakeCall</code> with called number <code>0815</code>, called-subaddress <code>4711</code> and called-name <code>!crash!</code> or simply calling <code>0815|4711^!crash!</code> from phone.exe.<br />
<br />
==== Saving Log Messages to a File ====<br />
The <code>0x80000000</code> value is special, as it does not denote a message class. Instead, it turns on log file writing, both in retail and debug versions. For this to work, the registry value <code>DoTraceFile</code> must be set to <code>1</code> in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> when the TSP starts. If this value is not present, it defaults to <code>1</code> in both retail and debug builds. Setting it to 0 disables log file writing regardless of any other setting. This may save some CPU cycles for installations with a real large number of lines.<br />
<br />
The TSP will keep 24 log files (a days worth) by default. This value can be changed using the <code>NumLogFiles</code> value in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>. Older log files will be removed. Also, when the TSP shuts down, it by default will remove all log files it created so far. However, any TSP will only remove log files created by itself. This ensures that if the TSP or the system terminates prematurely, the log files will be kept even if a new instance is started and terminated later on. Form TSP V8 hotfix 8 on, this behaviour can be tweaked by setting the DWORD registry value <code>KeepLogFiles</code> in <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>:<br />
{|<br />
| 0 || default || remove all log files on termination<br />
|-<br />
| 1 || || keep the last ''n'' log files (depending on <code>NumLogFiles</code>) on termination<br />
|-<br />
| 2 || || keep all log files<br />
|}<br />
<br />
Larger systems (500 monitored lines and more) can slow down considerably when using the debug drivers.<br />
<br />
==== Installing the Debug Version ====<br />
To install the debug version, you first install the retail version as outlined above. You then copy the debug driver files from the <code>Debug</code> directory to your windows system directory <code>system32</code>. You may want to use the shortcut to the <code>Debug</code> folder which has been installed to the start menu for convenience. <br />
<br />
For the copy to work, proceed as follows<br />
* close ''Telephony and Modem Control Panel'' if you have it open<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* copy the debug driver files to your system directory. On x64 systems, be sure to use a 64bit application such as ''Windows File Explorer for'' (<code>explorer.exe</code>) for this. Windows will silently redirect any 32bit application to the <code>SysWOW64</code> directory when accessing <code>system32</code>.(if the copying fails, we recommend to deactivate the telephony service. But don’t forget to reset it to automatically if you are done copying). Overwrite the existing files: <code>installer.dll, installer.pdb, TSP8.pdb, TSP8.tsp</code><br />
* if the copy does not succeed or the debug driver is not shown n the modem control panel after, it might be that a TAPI application re-starts the windows telephony before you actually to the copy. If so, you can set the ''Startup type'' of the Windows ''Telephony'' service to <code>Disabled</code> instead of ''Manual'' in the services control panel (''services.msc''). You can then ''Stop'' the services, copy the file and finally set the services mode back to <code>Manual</code><br />
<br />
When you open ''Telephony and Modem Control Panel'' again, you should notice that the TSP driver name has changed to the debug version.<br />
<br />
==== Switching back to the Retail Version ====<br />
To go back from the debug to the release version, proceed as follows:<br />
* close ''Telephony and Modem Control Panel'' if you have it open<br />
* shut down windows telephony service. This can be done from the Windows ''Service Control Panel'' or by invoking <code>net stop tapisrv</code> from a command prompt<br />
* delete the debug driver files from your system directory. On x64 systems, be sure to use a 64bit application such as ''Windows File Explorer for'' (<code>explorer.exe</code>) for this. Windows will silently redirect any 32bit application to the <code>SysWOW64</code> directory when accessing <code>system32</code><br />
* repair the installation using windows ''Programs Control Panel'' or by invoking the original .msi again<br />
<br />
There is no need to remove the driver from the ''Telephone and Modem Control Panel'', as this would make you loose your driver configuration.<br />
<br />
Please note that simply re-installing the driver from the original .msi without removing it from the system directory will not work.<br />
<br />
<br />
<br />
===References===<br />
The test applications provided with this setup comes from [http://www.julmar.com Julmar Technology Inc].<br />
<br />
=== Limitations ===<br />
<br />
Please note that there currently is a limitation to 2000 users being in<br />
all active groups of a particular user. Thus, you cannot configure more<br />
than 2000 users to be handled by TAPI on a single PBX. This is a PBX<br />
limitation (and applies for all PBX groups).<br />
<br />
TAPI has a flat line model. That is, all line numbers (aka<br />
''extensions'') are considered to live in a single name space. As a<br />
result, lines with identical numbers cannot be distinguished in TAPI<br />
(although they can exist). All extensions in all nodes of a PBX<br />
numbering tree are represented as TAPI lines. When the TSP works with a<br />
PBX that implements a hierarchical numbering tree, then some lines may<br />
receive identical numbers (their node-local extension which may overlap<br />
between nodes depending on the setting of the ''Use pure node extensions'' property). When a TAPI application uses these numbers to initiate<br />
calls to such lines, the call will work or not work depending on the<br />
calling lines position in the numbering tree (that is, lines within the<br />
same node as the called line will be fine, others may fail).<br />
<br />
The PBX's SOAP interface (on top of which the TSP is built) has no primitives to initiate a directed call pick-up based on a target number. The TSP will reject such requests with ''Operation not available''. Pickup by name (which is understood as a group name) or unspecific is supported though.<br />
<br />
==== Citrix Environments ====<br />
What happens is that all Citrix sessions share the same TAPI driver. This allows all users in the Citrix sessions to select "their" line from the set of all TAPI lines. Then everything works as desired. It is important to note that theoretically one user can select the line of another user.<br />
You can use the "Microsoft Remote TAPI Server", to implement access rights for lines for separate Citrix sessions.<br />
<br />
===Known Problems===<br />
<br />
<br />
* The TSP is not tested with Microsoft’s Remote Tapi Server. While some installation have reported this to work fine, others have encountered problems. This scenario is not supported by innovaphone<br />
* The TSP will read its configuration when it is loaded by the system. Thus, configuration changes require a re-load of the TSP. Unfortunately, there is no reliable way to force the system to unload the TSP, so you may have to reboot the system for changes to take effect. See the [[ Howto:Troubleshooting_the_TAPI_service_provider | TAPI trouble shooting article ]] for details<br />
* The TSP will use HTTP basic authentication to talk the PBX. So if you disable basic authentication in the PBX's configuration, the TSP will not work. It is recommended to use HTTPS<br />
* TAPI requires the TSP to assign a unique id to each line device. This ID must not change between re-boots of the system or between upgrades of the TSP. This is done by keeping a persistent table in the windows registry (in the <code>lineGUIDs</code> subkey of <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>) that maps the PBX's line GUID to a fixed integer value (known as ''permanent line id'' in TAPI speak). This key is retained even on uninstall. To get rid of it, you must remove it manually<br />
* Microsoft installer fails to remove driver files installed to the windows system folder. This is why the TSP driver files are still present after an uninstall of the software. To get rid of them, remove <code>TSP8.tsp</code> and <code>TSP8UI.dll</code> from both ''%windir%''<code>\system32</code> and ''%windir%''<code>\sysWOW64</code><br />
* From Version 9, hotfix 1, the PBX firmware will not list objects tagged as [[Reference9:PBX/Objects#General_Object_Properties | ''hide from LDAP'' ]] in the result of a [[Reference9:Concept_SOAP_API#UserInfo.5B.5D_FindUser.28string_v501.2C_string_v700.2C_string_v800.2C_string_cn.2C_string_h323.2C_string_e164.2C_integer_count.2C_integer_next.29 | FindUser() ]] call. As a result, such objects will not be shown in the TAPI configuration dialogue ''Username'' drop-down. If you want to use such an object as ''TAPI User'' you can simply type in the name without selecting it from the drop down.<br />
* Sometimes, setting configuration with ''TSP Control'' (not the telephone control panel dialogue) does not work due to windows' ''User Access Control (UAC)'', see [[Howto:TAPI_TSP_Control_Windows7]] for Details<br />
* Various users have reported that first party TSP installations do not work reliably with Microsoft Outlook. Symptoms reported are spurious error pop-ups from Outlook although there was no real problem. We do not recommend first party installations anyway (see [[#Rolling_out_First_Party_TSPs_to_multiple_PCs]] for a reasoning), but these issues are related to Microsoft Outlook only. No such problems have been reported with other first party TAPI applications. Consider using solutions like Estos ProCall instead.<br />
* The number of parallel threads used within the TSP is limited to max. 60 per CPU. As each connected PBX (amongst other things) is handled by a separate thread, if you have a large number of PBXs connected and only have a single CPU, you may run out of threads, resulting in a WARNING messages ''about to spawn new workerthread (n requests, m threads) -- but limit l exceeded''. This usually happens when you run the TSP on a virtualized platform such as vmWare and only dedicate a single CPU. Ignoring this warning results in sluggish behaviour. <br />
* The TSP can work with dynamic PBXs too. However, the TSP needs to determine the slave PBXs IP addresses from the master. If the slave is a dynamic PBX and it is hosted on the same device as the master and it is registered to the master using 127.0.0.1, the TSP will only learn the 127.0.0.1 as the slave's IP address. Of course, connection to the slave PBX will fail then. <br />
<br />
==== TAPI Operation with 3rd party Phones or innovaphone Phones registered with SIP ====<br />
Various TAPI functions rely on use of the [[Reference:Remote Control Facility|Remote Control Facility]] message. This message is not supported in 3rd party phones and also not fully supported in innovaphone Phones when they are registered to the PBX using SIP. Full TAPI functionality is thus only available for innovaphone phones registered to the PBX using H.323.<br />
<br />
Known Limitations:<br />
* Accepting an incoming call (lineAnswer)<br />
* Automatic initiation of an outgoing call in handsfree mode (instead, the calling phone first rings and starts the outgoing call upon of-hook)<br />
* toggle between 2 calls active on a phone (lineSwapHold)<br />
* initiation and termination of a 3PTY (lineCompleteTransfer with mode LINETRANSFERMODE_CONFERENCE, lineDrop on a conference call)<br />
<br />
==== TAPI on Windows8 / Server 2012 ====<br />
<br />
Windows8 doesn't let you disable ''User Access Control'' (UAC) completely. This has the disadvantage that ''TSP Control'' (tsptray.exe) cannot change system registry entries, which it needs to do to e.g. change debug settings for the TSP.<br />
<br />
There are 2 ways around it: <br />
# use the hidden ''Administrator'' account on Windows 8<br />
# elevate the tsptray.exe application<br />
<br />
To use the elevated ''Administrator'' account on Windows 8 you first need to un-hide it:<br />
* log in to an account with administrator rights <br />
* use the ''Windows-Key+X'' shortcut and select ''Command Prompt (Administrator)'' (''Eingabeaufforderung (Administrator)''). An elevated cmd prompt appears<br />
* type <code>net user administrator /active:yes</code><br />
* the fully elevated ''Administrator'' account is now available and you can log-in to this account as usual<br />
* '''Please make sure the account has a password set - by default, it does not!!'''<br />
<br />
To elevate the tsptray.exe application;<br />
* log in to an account with administrator rights <br />
* start a windows explorer<br />
* change directory to <code>C:\Program Files\innovaphone AG\innovaphone® PBX V8-x64 TSP</code><br />
* right click on <code>tsptray.exe</code> and open the ''properties'' (''Eigenschaften'') menu<br />
* switch to the ''Compatibility'' (''Kompatibilität'') tab<br />
* tick the ''Run as administrator'' (''Program als Administrator ausführen'') check mark<br />
* save the settings<br />
<br />
Please note that Windows 8 will not let you run any "Metro App" in elevated mode!<br />
<br />
===== HTTPS ===== <br />
The TSP will not be able to talk to the PBX using HTTPS with Windows8 or Server2012. This [[Reference8:Release_Notes_TAPI_V8#3722_-_HTTPS_SOAP_connections_did_not_work_on_Windows8_.2F_Server_2012|has been fixed]] with V8 TAPI Service Provider (32 and 64bit) - hotfix10.<br />
===== .Net 3.5 missing on Server 2012 =====<br />
Server 2012 has no support for .Net 3.5 by default. Even more, it cannot be installed just by downloading it. Instead, the ''NetFX3 Feature'' needs to be enabled. Here is how:<br />
<br />
Microsoft Windows [Version 6.2.9200]<br />
(c) 2012 Microsoft Corporation. Alle Rechte vorbehalten.<br />
<br />
C:\Users\Administrator>dism /online /enable-feature /featurename:NetFX3 /all /Source:''<path to windows setup, e.g. d:>''\sources\sxs /LimitAccess<br />
<br />
See also<br />
* [http://blogs.technet.com/b/aviraj/archive/2012/08/04/windows-8-enable-net-framework-3-5-includes-net-2-0-and-3-0-i-e-netfx3-feature-in-online-amp-offline-mode.aspx?PageIndex=2 Windows 8: Enable .NET Framework 3.5]<br />
* [http://msdn.microsoft.com/en-us/library/hh506443.aspx Installing the .NET Framework 3.5 on Windows 8]<br />
<br />
==== TAPI dialer fails from Outlook when Lync client was/is installed ====<br />
We have received reports that 1st-party TAPI dial-out from Outlook does not work anymore when a Lync client was installed. Some users report that this phenomenon occurred only after an upgrade to Windows 8. Reportedly, this can be [http://support.microsoft.com/kb/959625/en-us fixed by setting a registry key as outlined in Microsoft's knowledge-base]. You may want to give this a try. However, we have not seen this issue ourselves and thus can't comment on it further.<br />
<br />
==== Slow Network Performance ====<br />
TAPI is pretty sensitive to slow network performance. While the TSP is multi-threaded, some internal locking must be done so that slow requests to a PBX my block other pending requests, resulting in unpleasant performance. The TSP will create ''Windows Event Log'' entries of type ''slow SOAP call performance'' if this is detected. You should inspect your event log regularly and resolve the reason for such network performance.<br />
<br />
Long PBX request round-trips may have a number of reasons:<br />
<br />
; slow WAN performance : of course, if the WAN is slow or unstable, bad performance is the result<br />
; inappropriate QoS : SOAP is using HTTP/HTTPS. In a QoS enabled network, call signalling and RTP may work fine, whereas HTTP/HTTPS is considered to be of no importance. You need to keep in mind that such QoS policy may lead to bad TAPI performance, as the SOAP traffic used by the TSP (being based on HTTP/HTTPS) may be delayed. You should assign SOAP traffic a similar priority as you do for VoIP signalling<br />
; overloaded PBX : HTTP traffic is treated on a low priority level in the PBX. If the PBX runs near to 100% CPU load, while all RTP and VoIP signalling will work well still, HTTP traffic may get severely delayed. You should make sure your PBX runs well under 100% CPU load to get good TAPI performance. See [[Reference:Device Health Check]] for more details<br />
<br />
==== TAPI on Windows 10 ====<br />
In Windows 10, the TSP is not allowed any longer to read/write its own registry tree. For this reason, it is not possible to store or read the configuration. As a result, the TSP will not work. To fix this, you need to modify the registry access rights so that the TSP has read/write access to <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/>.<br />
<br />
<br />
<br />
Note on windows registry paths<ref name="regkey"><br />
Due to a change in ''Windows Registry Access Rights'' in Windows 10, from Build 8165, the configuration is stored in <br />
: <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><br />
(a place that is suitable for Windows 10 too). Before, the configuration was stored in <br />
: <code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Telephony\innovaphone® PBX V8 TAPI Service Provider</code><br />
<br />
See [[#Upgrade_from_Build_8164_or_earlier| Upgrade from Build 8164 or_earlier ]] above for more details.<br />
</ref>:<br />
<references/><br />
<br />
=== Tweaks ===<br />
There are a few configuration options which should be used rarely. They can be enabled by setting an appropriate registry key.<br />
<br />
Since Hotfix 15 the location to set the interop tweaks has been changed to <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code>:<br />
<br />
; ProcessorMask : REG_DWORD. If set, the TSP will use <code>SetProcessAffinityMask(GetCurrentProcess(), set)</code> to limit TSP execution to one or more of the existing processors. Set to <code>0xff</code> by default.<br />
<br />
; LineTimeOut : REG_DWORD. Can be set to a number of seconds the TSP should wait for the determination of lines to finish (default 90). On a large PBX, or a PBX with slow slaves, the determination might not finish in the default time frame. If this happens, TAPI applications may show ''Line unnamed'' line entries in their line list. If this is a problem, set it to a larger value (e.g. <code>120</code>).<br />
<br />
; IgnoreDevStatus : REG_DWORD. Some applications get confused if TAPI reports line to be disconnected or out-of-service dynamically. Setting this to a non-zero value will disable any such notification.<br />
<br />
; ClearFwdOnSet : REG_DWORD. If set to a non-zero value, the TSP will clear all current call forward settings before a lineForward request is executed. This implies that all call forward settings are ''replaced'' by the new settings. Standard behaviour is to only modify the settings, that is, replace all current settings of the same type with the settings found in the lineForward request (for example, if there is only one setting in the request that defines a CFU, then the current CFU settings are replaced by the new one provided. All others, such as e.g. CFNR settings, are left untouched).<br />
<br />
: Tapi spec is pretty clear on how this should work: ''The provider should "unforward everything" prior to setting the new forwarding instructions''. Even though, for historical reasons, ClearFwdOnSet=0 has been the default in all TAPI versions prior to V8 hotfix 6, from hotfix 6 on, the default changes to 1, as this has turned out to be the widely accepted interpretation.<br />
<br />
; No3PTY : REG_DWORD. See [[Reference8:TAPI_Service_Provider#Notes_on_3PTY_Conferences | Notes_on_3PTY_Conferences]] below.<br />
<br />
; HiddenRecordingNumber: REG_SZ. Active recording in an innovaphone PBX is implemented as an implicit 3PTY conference with the recording sink as one of the parties. These will be shown as usual in the TAPI callstate. However, some applications get confused as this results in a situation, where a normal endpoint (read: phone) has more than one active (i.e. ''not on hold'') call. To suppress such calls in the TAPI call states, you can set this tweak to the number of the recording sink as configured in the phone's recording configuration tab. If so, any outgoing call from an endpoint that is registered with a PBX user object with a called number that equals the setting of this tweak, will be suppressed. So if your recording sink has the number <code>44</code>, you would set this registry value to <code>44</code> (this is available from TAPI V8 hotfix 8). Please note that this feature actually suppresses any call info for such calls, however, it does not revert previously announced call infos. So if the call is initiated using ''overlapped dialling'', all call states will be shown until the called number matches the value of <code>HiddenRecordingNumber</code> and all subsequent call states will be suppressed. This will probably leave the call shown in ''dialling'' state. The feature should be used for destinations which are called using ''block dialling'' only thus. <br />
<br />
; SilentHold : REG_DWORD. When a call is put on hold using <code>lineHold</code>, the held party will hear ''music on hold'' emitted by the PBX. The hold-initiator will hear a dialling tone (PBX firmware v11r2 and up, with older versions the initiator would hear ''music on hold''). When <code>SilentHold</code> is set to a non-zero value, the initiating party will hear silence - i.e. no dialling tone (this is available from TAPI V8 hotfix 8).<br />
<br />
<!-- There are some more values in a key underneath <code>HKEY_LOCAL_MACHINE\SOFTWARE\innovaphone\innovaphone® PBX V8 TAPI Service Provider</code><ref name="regkey"/> called <code>Device</code>''XX'': --><br />
<br />
; NoDuplicateConnectedCalls : REG_DWORD. If set to 1, the TSP will never show more than one call per line to be active. Extra active calls will be shown as ''on-hold''. The situation occurs e.g. when a user initiates a 3PTY conference on the phone. This essentially is a bug fix for Microsoft's OCS client which forces any extra active call on-hold, thereby disturbing the 3PTY function. If your are using [http://www.estos.de/download/software/eccg.htm ESTOS' Call Control Gateway]: from version ''2.0.1.1474 - 05.11.2008'' there is a fix for this same problem available. This option thus is deprecated.<br />
<br />
; UseFirstLeg2 : REG_DWORD. TAPI allows only for a single leg-2 info (redirection information in case of a call forward). If a call is forwarded mutliple times, by default the last redirection is shown in TAPI. If <code>UseFirstLeg2</code> is set to 1 however, the first redirection is shown.<br />
<br />
; UseNameForDeviceGuid : REG_DWORD. The TSP will create a TAPI line for each ''Device'' of each PBX object (if ''MapDevices'' is set). The internal unique identifier includes the ''Hardware Id'' found in the ''Device''. If this changes, the TAPI line representing the ''Device'' is considered new and a new TAPI ''permanent line id'' is allocated. This may be annoying cause when a device serial number (e.g. for a telephone) is used as ''Hardware Id'', replacing the device creates a new TAPI line. If ''UseNameForDeviceGuid'' is set to 1, the TSP will use the ''Device''s ''Name'' instead (which usually not changes). While this might be more convenient, be aware that you ''must'' make sure that no PBX object has duplicate ''Name''s! Otherwise, TAPI will get confused. Available from build 8178<br />
<br />
; ShowConfGUID : REG_DWORD. If set and not 0, the TSP will implicitly set each call's CallData to a string such as "<code>conf-guid:</code>''call-guid''". ''call-guid'' is a 66-byte Unicode16 string (32 hex characters plus terminating 0). Note that this will interfere with an application's use of the lineSetCallData function (tapi.h) and is therefore disabled by default. Available from build 8190<br />
<br />
; NoFix : REG_DWORD. If set and not 0, the TSP will not trigger a full re-synchronization if it receives implausible state messages. Experimental.<br />
<br />
===List of supported TSPI functions===<br />
<br />
The TSP supports the following TAPI Service Provider Interface Functions. Note that these do not map one to one with TAPI user functions. For more information, see the Microsoft TAPI documentation.<br />
<br />
*TSPI_lineAnswer<br />
*TSPI_lineBlindTransfer<br />
*TSPI_lineClose <br />
*TSPI_lineCloseCall <br />
*TSPI_lineCompleteTransfer<br />
*TSPI_lineDevSpecific<br />
*TSPI_lineDevSpecificFeature<br />
*TSPI_lineDial<br />
*TSPI_lineDrop <br />
*TSPI_lineForward<br />
*TSPI_lineGenerateDigits <br />
*TSPI_lineGetAddressCaps <br />
*TSPI_lineGetAddressID <br />
*TSPI_lineGetAddressStatus <br />
*TSPI_lineGetCallAddressID <br />
*TSPI_lineGetCallHubTracking<br />
*TSPI_lineGetCallIDs<br />
*TSPI_lineGetCallInfo <br />
*TSPI_lineGetCallStatus <br />
*TSPI_lineGetDevCaps <br />
*TSPI_lineGetExtensionID<br />
*TSPI_lineGetID <br />
*TSPI_lineGetLineDevStatus <br />
*TSPI_lineGetNumAddressIDs<br />
*TSPI_lineHold<br />
*TSPI_lineMakeCall <br />
*TSPI_lineNegotiateExtVersion<br />
*TSPI_lineNegotiateTSPIVersion <br />
*TSPI_lineOpen <br />
*TSPI_linePark<br />
*TSPI_linePickup<br />
*TSPI_lineRedirect<br />
*TSPI_lineSelectExtVersion<br />
*TSPI_lineSendUserUserInfo<br />
*TSPI_lineSetAppSpecific <br />
*TSPI_lineSetCallData<br />
*TSPI_lineSetCallHubTracking<br />
*TSPI_lineSetCallParams <br />
*TSPI_lineSetCurrentLocation <br />
*TSPI_lineSetDefaultMediaDetection <br />
*TSPI_lineSetMediaMode <br />
*TSPI_lineSetStatusMessages <br />
*TSPI_lineSetupTransfer<br />
*TSPI_lineSwapHold<br />
*TSPI_lineUnhold<br />
*TSPI_lineUnpark<br />
<br />
<br />
*TSPI_providerConfig <br />
*TSPI_providerCreateLineDevice<br />
*TSPI_providerEnumDevices <br />
*TSPI_providerGenericDialogData<br />
*TSPI_providerInit <br />
*TSPI_providerInstall <br />
*TSPI_providerRemove <br />
*TSPI_providerShutdown <br />
*TSPI_providerUIIdentify <br />
=====Notes on Consultation Calls===== <br />
This TSP supports the <code>lineSetupTransfer</code> function. However, strictly speaking, this function is not needed and should not be used. It is merely intended for applications which do not offer a consultation call interface to the user when this function is not available. Instead, <code>TSPI_lineMakeCall</code> can be used where <code>lineSetupTransfer</code> would be otherwise. The notion of a special ''consultation call'' is an idiosyncrasy forced by legacy PBX technologies which were not able to transfer two arbitrary calls. In these scenarios, a potentially to-be-transferred call needed to be linked to the primary call. The PBX can transfer two arbitrary calls and thus there is no need for <code>lineSetupTransfer</code>. This ability is indicated by the <code>LINEADDRCAPFLAGS_TRANSFERMAKE</code> and <code>LINEADDRCAPFLAGS_CONFERENCEMAKE </code> flags. The transfer is then requested by using <code>TSPI_lineCompleteTransfer</code>.<br />
<br />
=====Notes on 3PTY Conferences=====<br />
The TSP supports the management of 3PTY conferences. These can be initiated by using <code>TSPI_lineCompleteTransfer</code> with <code>LINETRANSFERMODE_CONFERENCE</code> instead of <code>LINETRANSFERMODE_TRANSFER</code>. This is indicated by the flags <code>LINEADDRCAPFLAGS_CONFERENCEHELD</code> and <code>LINEADDRCAPFLAGS_CONFERENCEMAKE</code>. The 3PTY function is actually implemented by the innovaphone IP phones (such as IP2xx). Therefore, these capabilities are only advertised if the line is based on a PBX user object. Still, some lines where the capabilities are advertised cannot do 3PTY (such as e.g. DECT lines, analogue lines, 3rd party devices, ...). So the call to <code>TSPI_lineCompleteTransfer</code> will succeed but the conference will not be initiated and the subsequent call states will not indicate a conference (as there is no). <br />
<br />
Also, if non-telephone devices are registered with a PBX user object and these device have multiple concurrent calls (e.g. a call center connected with a multi-channel XCapi), these will be viewed as 3PTY calls (as this is the way such calls appear to the TAPI: a line with more than one non-held call). If these calls are in fact no 3PTY, this may lead to confusing call indications from TAPI. It is better to register such objects as a PBX gateway object. Special treatment of 3PTY calls can be disabled by setting the registry flag <code>No3PTY</code> (from build 8087 onwards).<br />
<br />
A 3PTY conference will create a new call handle for the conference. The only operation available for such a handle is <code>TSPI_lineDrop</code>. This will terminate the conference and restore the state active before the conference was initiated (that is, the phone that implemented the conference will now have 2 calls, one active and one on hold). This is indicated by not setting <code>LINEADDRCAPFLAGS_CONFDROP</code>.<br />
<br />
A <code>lineDrop()</code> on one of the 2 ''real'' calls involved will of course cancel this call and thus remove the 3PTY.<br />
<br />
======Notes on Intrusion Calls======<br />
A special case of 3PTY is an intrusion call initiated by a [[Reference9:Phone/User/Function-Keys/Partner | partner function key]]. To TAPI, this looks like a 3PTY (which it in fact is). However, phones before firmware version 9 hotfix 19 will not be able to release this 3PTY upon request. Thus, you will see a 3PTY in TAPI which cannot be dropped. From phone firmware 9 hotfix 19 on, the request to drop this 3PTY will cancel the intrusion call. Note that TAPI will nevertheless ''not'' set <code>LINEADDRCAPFLAGS_CONFDROP</code> on such conferences (as TAPI does not know that this is an intrusion call).<br />
<br />
======Notes on Recording Calls======<br />
A special case of 3PTY is a recording call. This effectively creates an user-invisible 3PTY on the phone. To TAPI, this looks like a 3PTY (which it in fact is). However, phones before firmware version 9 hotfix 19 will not be able to release this 3PTY upon request. Thus, you will see a 3PTY in TAPI which cannot be dropped. From phone firmware 9 hotfix 19 on, the request to drop this 3PTY will cancel the recording call. Note that TAPI will nevertheless ''not'' set <code>LINEADDRCAPFLAGS_CONFDROP</code> on such conferences (as TAPI does not know that this is a recording call).<br />
<br />
Recording calls are shown as normal conferences (unless <code>No3PTY</code> is set). This might be confusing to applications and/or users. See the <code>HiddenRecordingNumber</code> tweak above for a method to hide such calls.<br />
<br />
=====Notes on parked Calls=====<br />
The PBX can park calls from and to any existing PBX object, e.g. by virtue of [[Reference8:Configuration/Registration/Function-Keys/Park | specific feature keys]]. They are parked to/from a numeric ''park position''. In SOAP however, calls are parked to an object by providing the objects '''UserInitialize()''' handle and a park position. Parked calls are then reported as straight calls with a distinct signalling state (8). The park position is not conveyed as part of the '''CallInfo''' record. To unpark, '''[[Reference8:SOAP_API#integer_UserPickup.28int_user.2C_string_cn.2C_integer_call.2C_string_group.2C_int_reg.2C_InfoArray_info.29 | UserPickup]]''' can be used providing the parked calls call handle as '''call''' argument. <br />
<br />
The TAPI specification is unclear on how the address information required to '''lineUnpark()''' a call can be obtained by an application (except that it is returned from '''linePark()''' if the call was parked using TAPI). The TSP thus indicates parked calls on a line with a call reason <code>LINECALLREASON_PARKED</code>. The caller id information is set to the parked calls call handle. This way, the application can take the caller id information and provide it to '''lineUnpark()''' to unpark a call. If the appication does not support this mode of operation but supports '''lineUnpark()''' and lets the user input the park identifier, the user can just provide the caller id information.<br />
<br />
Some applications do not care for parked calls (by examining the call reason) and just blindly report these calls like ordinary connected calls (thereby confusing the user). To work around this problem, reporting parked calls can be turned off in the TSP configuration dialogue.<br />
<br />
=====Notes on sending User to User Information =====<br />
The TAPI specification for sending UserUserInfo closely resembles the way ISDN defines this service. Although the innovaphone PBX supports this service both with H.323 and SIP, it is ''not'' supported in this TSP. Instead, sending user to user information is implemented using H.323/SIP messaging. However, there are some important deviations and limitations caused hereby:<br />
* data is ''not transparent''. That is, the TSP only allows for null-terminated unicode to be sent. The data must be of even length and the last two bytes must be null. <br />
* data is ''not sent within the call'', but by initiating a separate call. This implies that the target number used to send the message carrying the data to is 'guessed' from the available call data. For example, if the call is in a connected state and a connected number is known, the message is sent to the connected number. If the call is in the ringback state, then either the called number or - if available - the redirection number is used and so forth.<br />
* message calls are not indicated by the SOAP CallInfo records. As a result, messages (that is, user to user information) cannot be received with TAPI and ''lineReleaseUserUserInfo is not supported''<br />
<br />
=====Notes on sending proprietary innovaphone Remote Control Facilities =====<br />
The SOAP [[Reference8:SOAP_API#UserRc.28integer_call.2C_integer_rc.29|UserRc]] function allows to send various [[Reference:Remote Control Facility|facility messages]] to an innovaphone phone device. In some cases, it is useful to be able to invoke this function through a standard TAPI mechanism. This can be achieved by using the TAPI '''lineBlindTransfer''' function. If the ''called party name'' provided as destination for the ''lineBlindTransfer'' (in ''lpszDestAddress'') has the magic value <code>USERRC</code>, then the ''called subaddress'' is converted to an integer and the result is sent as remote control facility to the call the blind transfer is applied for. For details on how to code the ''called party name'' and the ''called subaddress'' into ''lpszDestAddress'' see [http://msdn.microsoft.com/en-us/library/windows/desktop/ms726017%28v=vs.85%29.aspx Microsoft's ''Canonical Address'' specification]. This works from TAPI8 hotfix 4.<br />
<br />
For example, initiating a blind transfer to <code>|16^USERRC</code> (empty address, subaddress 16 (that is, [[Reference:Remote Control Facility|''change to handset mode'']]), called name <code>USERRC</code>) will switch the phone having the call to be transferred into handset mode (and <code>|18^USERRC</code> will switch it back to handsfree mode). Of course, no actual transfer will happen in these cases (''lineBlindTransfer'' is merely used as a vehicle to send the facility to the proper call).<br />
<br />
Such proprietary facility message can also be sent with ''lineMakeCall'' (that is, in SOAP's [[Reference8:SOAP_API#integer_UserCall.28integer_user.2C_string_cn.2C_string_e164.2C_string_h323.2C_int_reg.2C_InfoArray_info.2C_int_rc.2C_string_srce164.29|UserCall]] function). For example, setting up a call to <code>123|21^USERRC</code> will send an intrusion call to extension 123 and intrude any call that might be active there. This works from TAPI8 hotfix 6. Please note that the innovaphone TAPI service provider does not support TAPI's ''lineCompleteCall'' function with <code>LINECALLCOMPLMODE_INTRUDE</code> though (as TAPI's call model here does not fit with the PBX's call model). Please also note that intrusion calls look like 3PTY calls to TAPI (see [[#Notes_on_Intrusion_Calls|notes on intrusion calls]] above).<br />
<br />
Remote control facilities are implemented by the telephone devices, so these functions are subject to the phone firmware in use.<br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
<br />
== Related Articles ==<br />
* [[Howto:Troubleshooting the TAPI service provider]]<br />
* [[Reference8:Release Notes TAPI]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r3:PBX/Config/General&diff=70827Reference13r3:PBX/Config/General2024-02-13T10:03:13Z<p>Ckl: /* Common */</p>
<hr />
<div>The fundamental operating modes of the PBX are configured on this page. <br />
<br />
== Configuration ==<br />
<br />
=== Common ===<br />
<br />
;PBX Mode: The PBX operating mode<br />
:* '''Off''' - The PBX is disabled. After enabling the PBX a browser refresh is needed to activate additional PBX webpages.<br />
:* '''Master''' - The PBX on this device acts as Master. Within a multisite installation exactly one PBX must be configured as Master.<br />
:* '''Slave''' - The PBX on this device acts as Slave. Within a multisite installation several PBXes can be configured as Slave.<br />
:* '''Standby''' - The PBX on this device acts as Standby for the Master. As long as the master is available, this PBX is not active, but just monitors the Master. If the Master is not available this PBX is active.<br />
:* '''Standby-Slave''' - The PBX on this device acts as Standby for a Slave. As long as the slave is available, this PBX is not active, but just monitors the slave. If the slave is not available this PBX is active.<br />
<br />
;System Name: The system Name. On all PBX within a multisite installation the same System Name must be configured. For H.323 endpoints this name is the gatekeeper identifier, for SIP endpoints it is the server name.<br />
<br />
;Use as Domain: Uses the ''System Name'' as domain name, together with the name field in the user object the PBX constructs the email address (used for sending emails out ox myPBX). This mechanism is also used for Federation, to federate with other domains.<br />
<br />
;PBX Name: The name of the PBX on this device. With this name a PBX is associated to a node. The field 'Name' (not Long Name) of a PBX Node object relates to this name.<br />
<br />
;DNS: DNS Name of the PBX. If configured this will be used for myPBX redirects if a client tries to register at a PBX on which the user is not configured.<br />
<br />
;Unknown Registrations: If this checkmark is set, the PBX accepts 'unknown' registrations. This means registrations with no matching object configured. If from an endpoint registered in this way a number of an object is dialed, which has no registration active and no 'HW-ID' configured the name used for the registration is configured as 'HW-ID' of this object. This is an easy way to deploy large numbers of phones.<br />
<br />
:'''With PBX Pwd only:''' If checked only registrations with a PBX authentication or a verified certificate in case of H.323/TLS are accepted. In this case either "PBX Pwd" or "TLS only" is set in a device generated.<br />
<br />
;Reverse Proxy Addresses: Up to 8 addresses (no DNS names allowed) can be entered in this field, separated by comma. Registrations from one of these addresses are assumed to be routed through a Reverse Proxy. To the address '/<certificate name>' can be added to also check the TLS certificate of the Reverse Proxy. If the reverse proxy certificate is to be verified, the certificate or the issuer of the reverse proxy certificate must be trusted in the PBX.<br />
; Assume TLS: If the ''Assume TLS'' checkmark is set, it is assumed, that the reverse Proxy did a successful check of the TLS certificate against the registration name.<br />
<br />
;Media relay endpoint/Firewall public IP: For media relay endpoints (e.g. third party SIP phones) the public address of the firewall of the PBX network can be configured. This address is signaled in the SDP to received RTP from the phone. The firewall should have configured a port forwarding to the PBX or the TURN, for the RTP range of the PBX or TURN.<br />
<br />
;Media relay endpoint/TURN: If this checkmark is set, the PBX allocates TURN endpoints for calls to or from media relay endpoints, which registerd thru the reverse proxy (e.g. third party SIP phones)<br />
<br />
;IP address for App Platform<br />
: The ip address of an App Platform can be configured here, with a DNS name used for it. If myApps uses a host to access the PBX different from the configured DNS name of the PBX, the hostname in any App url, which matches the configured AP DNS, is replaced by the AP IP. This way it is possible to configure a PBX with DNS names and access it with myApps when the DNS is not yet set up. If the checkmark ''Operation without DNS'' is set as well any matching DNS name is replaced also when an App service requests the URL of another App service (Example: Users requesting the URL of Devices for provisioning) and in the URL sent to the App services itself (Example: Devices uses this URL to construct the URL set at devices for the Devices registration).<br />
<br />
: Note that this mode is intended to be able to run the PBX using DNS names while the DNS is not yet in place. Once the DNS is up and running, neither the DNS name of the AP nor its IP address should be configured here.<br />
<br />
;Music On Hold URL: A URL for the Music On Hold. This file is read by the PBX using HTTP and sent to a held endpoint via RTP. The format of this URL is<br />
<br />
:'''<nowiki>http://<addr>/<file>.$coder?coder=g711a,g711u,g722,g723,g729,opus-nb,opus-wb&repeat=true</nowiki>'''. <addr> is the IP address of the http server, no dns name is allowed here. <file> is the filename. $coder will be replaced by the actual coder used.<br />
<br />
:Parameters: ''coder=g729,g711a,g711u,g723,opus-nb,opus-wb'' is the list of available coders. Only these coders must be specified for which a corresponding file exists. ''repeat=true'' should be specified in order to loop the file endlessly. ''random=true'' can be used to start the music on hold on a random point (this will work only if the URL is not local though).<br />
<br />
:By default the built-in Music-On-Hold is played (Pseudo URL: "MOH?coder=g729,g711a,g723&repeat=true"). You can also play a dial tone (Pseudo URL "TONE") or a ring-back tone (Pseudo URL "TONE?tone=ringback").<br />
<br />
:The maximum length of the URL is limited to 500 characters (bytes).<br />
<br />
:If you configure a wrong (or invalid) URL then you will have silence as MOH. To prevent this situation when the MoH for some specific context/user(see below) is missing and silence is played instead of any MoH, an additional parameter ''fallback=true'' is available. If the file provided in the URL is missing (HTTP Error 404 Not Found is delivered by the HTTP Server) and the parameter ''fallback'' is provided, the default MoH will be played instead of silence. To use a custom file as fallback MoH, instead of default MoH, any file name can be provided with the ''fallback'' parameter: e.g. ''fallback=other_filename''. The file other_filename.g7xx must be placed in the same folder as a file provided with URL. A special filename ''fallback=ringback'' can be used to generate a ringback tone (equivalent to ''TONE?tone=ringback'') instead to play an alternative file.<br />
<br />
:Within the URL %<id> can be used to put in some context information of the call. The information refers to the party which has put the receiving party on hold. For information about the receiving party itself, the id has to be preceded by '.' (e.g. '''.l''').<br />
<br />
:'''l''' Long Name<br />
:'''h''' Name (H.323 id)<br />
:'''n''' Number<br />
:'''N''' Node<br />
:'''P''' PBX<br />
:'''d''' Diverting Name<br />
:'''#d''' Diverting Number<br />
<br />
: See [[Howto:Dynamic_MOH]] for more details on how to use dynamic music on hold.<br />
<br />
;External Music On Hold: To offload the device from playing the Music on hold, the Music On Hold can be played by a separate device. This device can register with a name configured here. To retrieve the Music On Hold a call is sent to this device. For each held endpoint a call is sent.<br />
<br />
;Response Timeout: Global timeout (in seconds) after which any action for no response is taken (e.g. Call Forward on No Response). A timeout configured at any object overrides this value.<br />
<br />
;Dial Complete Timeout: Global timeout (in seconds) after which any action for incomplete dialed number is taken (e.g. incomplete destination at trunk object).<br />
<br />
;No. of Regs w/o Pwd: Number of registration without password authentication which are allowed per user. If 0 is configured no registration without password is possible.<br />
Pls. note that registrations from 127.0.0.1 w/o password will be accepted anyway<br />
<br />
;Security block time(s): Time for which a registration to a user is blocked after attempt with wrong password. Default is 20s. With a value of 0 this features is turned off.<br />
<br />
;Recall Timeout: A value configured here enables recall after transfer. If a call is transferred and not answered within this time, the call is sent back to the transferring endpoint.<br />
<br />
;Retries on busy (14s): Number of retries (Each attempt runs for 14 seconds) of blind transfer to a busy endpoint, before a recall back to the initiator is executed. During an attempt, the PBX waits to see if the target is free, and then delivers the call. The default value are 4-retries if the field is empty. If there is a 0 configured no further (in addition to the initial) will be executed. (This function depends on the ''Recall Timeout'' so the ''Recall Timeout'' must not be empty)<br />
<br />
;Max Call Duration (h): Number of hours until a call with media is disconnected automatically. Affects all calls with initialized media channels signalled via PBX.<br />
<br />
;Group Default Visibility: Defines additional visibility for active group members. These are added to the visibility rights derived from the [[{{NAMESPACE}}:PBX/Objects/Visibility | Visibility settings ]] in user or template definitions. Note that changes made here only take effect after a re-registration.<br />
<br />
;Presence with Alert: Enable presentation of presence on phone upon alert. Setting applies for all PBX users.<br />
<br />
;Enable External Transfer: Unless this checkbox is set any attempt to transfer an external call back to an external destination will result in disconnection of the call.<br />
<br />
;No CLIR on Internal calls: If checked numbers are displayed even if received with presentation restricted. When sending a call presentation restricted can still be set and should be honored by a public network.<br />
<br />
;Media Relay:<br />
:;Off: No Media Relay is done in the PBX <br />
:;On: All media traffic is routed through the PBX. With the '''No Media Relay if Addresses are identical or private''' checkmark, this is not done if the two call endpoints registration addresses are either private or equal (i.e. external endpoints behind the same NAT router). To identify an address as private the "Private Networks" configuration from ''IP4/General/Settings'' is used.<br />
:;Auto: Media traffic is routed through the PBX if calls are between private and public registration addresses but not for calls between private and private or public and public registration addresses. To identify an address as private the "Private Networks" configuration from ''IP4/General/Settings'' is used. In v11 this was the behaviour if ''RTP Proxy'' was ''on''.<br />
: Please note that when media relay is in effect for a call, '''video is not working'''. With ICE (available from v12r1), media relay in the PBX should be obsolete except for special applications. Check ''On'' or ''Auto'' only if you need to have this, since it creates CPU load on the PBX.<br />
<br />
;Generate CDRs: If this checkbox is set, the PBX generates CDRs for all calls. For details, refer to the [[{{NAMESPACE}}:Concept_Call_Detail_Record_CDR_PBX|CDR description article]].<br />
<br />
;Reverse Lookup URL<br />
: A String in the LDAP URL Format according [https://tools.ietf.org/html/rfc2255 RFC2255] which will be used by the PBX to make a lookup for all external numbers.<br />
: Number resolutions will be forwarded to internal applications like generated CDRs, Phone App etc..<br />
: Example: ''ldaps://ap.innovaphone.com/dc=entries?givenname,sn,company?sub?(metaSearchNumber=+%n)?bindname=innovaphone.com\contacts''<br />
:* dn: ''dc=entries''<br />
:* attributes: ''givenname,sn,company''<br />
:* scope: ''sub''<br />
:* filter: ''(metaSearchNumber=+%n)''<br />
:** %n is a placeholder for the given cgpn<br />
:* extension: ''bindname=innovaphone.com\contacts''<br />
:** The username for the authentication.<br />
:'''Variables'''<br />
:* %n - cgpn<br />
:* %u - h323 name of the current object<br />
: Also there is an additional field for the LDAP password<br />
: Examples are listed in the [[{{NAMESPACE}}:Concept_Number_Resolution_and_LDAP#PBX_Configuration|Concept Article]]<br />
<br />
;Route Root-Node External Calls to: Destination object (Long Name) of Root-Node external calls. This configuration option is available on the Master or Standby PBX only. Any call call which cannot be terminated inside the PBX is sent to this destination as long as neither the source nor the destination of the call can be associated with a node with a PBX configured. This object must be assigned to this PBX.<br />
:'''For calls from local PBX only''': If set on a master, calls from a slave are not sent to this destination but sent back to the slave where the call came from. On the slave the call is then sent to a destination configured with 'Route Root-Node External Calls to'.<br />
<br />
;Route PBX-Node External Calls to: Destination object (Long Name) of PBX-Node external calls. Any call which cannot be terminated inside the PBX is sent to this destination as long as the source nor the destination of the call can be associated with the node of this PBX. If a call is sent from or to an object defined inside the node of this PBX or in a node hierarchically below the node of this PBX the call is associated to the node of this PBX. This object must be assigned to this PBX, that is, it has to register to this PBX.<br />
<br />
;Route Internal Calls to: Destination object (Long Name) to which any call is sent for which a PBX internal destination was found, except for those calls that originated from that object. This can be used to apply special routing on PBX internal calls.<br />
<br />
;Escape Dialtone from: The PBX object (Long Name) to which a call is made to get a dialtone if a dialtone is configured for the escape of a node. As above, this object must be assigned to this PBX.<br />
<br />
;Prefix for Intl/Ntl/Subscriber/Area-Code/Country-Code: Prefixes to be used to map International, National and Subscriber numbers.<br />
:* '''International Prefix''' (<code>000</code> in Germany).<br />
:* '''National Prefix''' (<code>00</code> in Germany).<br />
:* '''Subscriber''' (in Germany, <code>0</code> is a commonly used trunk line access code).<br />
::The SubscriberID is used for hotkey-actions within myAPPs-launcher. By use of node-objects, adjust the Subscriber Prefix in the respective [[{{NAMESPACE}}:PBX/Objects/Node#Number_Mapping_.28International.2C_National.2C_Subscriber_Prefix.29|node object number mapping]].<br />
::For myPBX-launcher hotkeys, refer to the [[{{NAMESPACE}}:Phone/User/Directories#Dialing_location|dialing location settings]].<br />
<br />
:* '''Area-Code''' (in Germany for example, the town Mannheim has area code <code>621</code>).<br />
:* '''Country-Code''' (for Germany, <code>49</code> would be used)<br />
<br />
: These settings resemble the same settings found in the [[{{NAMESPACE}}:PBX/Objects/Node | Node ]] and [[{{NAMESPACE}}:PBX/Objects/PBX | PBX ]] object. However, they apply to the ''root'' node instead (and should be consistent through all PBXs in a multi-PBX system).<br />
<br />
;Adjust LDAP results for e.164: Add a "+" prefix to an PBX-internal LDAP-contact-search result. This is used for 3rd-party- and DECT-phones which are not able to benefit from the Dialing Location settings in case of LDAP-search in an E.164-setup. See also [[{{NAMESPACE}}:Concept_Number_Adjustments_%28Dialing_Location%29#PBX_database_used_as_LDAP_database|concept description]].<br />
<br />
;Tones: The tones scheme to be used for PBX generated dialtones. This applies to dialtones generated for node prefixes, ringback on transfer and some more.<br />
<br />
=== Slave PBX ===<br />
<br />
If the PBX is operated in Slave mode, then the Slave PBX section is displayed<br />
<br />
;Registration: The VOIP protocol used for the registration to the master. Possible choices are H.323, H.323/TCP or H.323/TLS.<br />
<br />
;Master: The IP address of the PBX master<br />
<br />
;License Only: If set, the PBX obtains license from master, but acts as master in all other respects.<br />
<br />
;Alternate Master: The IP address of an alternative PBX master (standby, if available)<br />
<br />
;Password: The password to be used for registration at the Master as configured in the corresponding PBX Object (The length of the password is limited to 16 characters)<br />
<br />
;Master GK-ID: The System Name/Gatekeeper ID of the PBX Master were will register (Optional, usually used for DynPBX).<br />
<br />
;Replication: This parameter allows you to select the replication style for the slave PBX: either ''All'' or ''Local'' (only users that need to be known in this PBX). For the replication process the [[Reference9:PBX/Config/Security|PBX Password]] is used which have to be the same password on all PBXes in the system.<br />
<br />
;dyn PBX ID: This parameter allows to set replication from a specific DynPBX configured on the Master Device.<br />
<br />
;Use local static User DB: A dynPBX has also this checkmark. The database of the main PBX is used, but be careful due to the increased memory usage. The dynPBX will create its own PBX datastructure which allocates memory.<br />
<br />
;Route Master calls if no Master to: If the master is not available, master calls are sent to this destination. Destination has to be an object with active registration.<br />
<br />
;Max Calls to Master/No Reroute: This parameter can be used to limit the calls to the master. If a call is sent to the master and there are already calls to/from the master equal to or exceeding this value, the call is rejected if '''No Reroute''' is set or is handled as if the master was not available otherwise.<br />
<br />
;License Limits: Here we can set limit of licensing for this Slave PBX for Port, Mobility, Operator and Softwarephone.<br />
<br />
For complete replication from master to slave, check also password in [[Reference9:Administration/PBX/Security]]<br />
<br />
=== Standby PBX ===<br />
<br />
If the PBX is operated in Standby mode, then the Standby PBX section is displayed<br />
<br />
;Master: The IP address of the PBX master<br />
<br />
;Replicate from Master: Turns on full replication from the master PBX<br />
<br />
;use TLS: Use LDAPS (TLS) instead of LDAP (TCP).<br />
<br />
For complete replication from master to slave or standby, check also password in [[Reference9:Administration/PBX/Security]]<br />
<br />
== License Status ==<br />
<br />
=== Licenses ===<br />
<br />
A list of all installed PBX license with their current usage is displayed here.<br />
<br />
<br />
;Count: The total number of installed licenses of this type.<br />
<br />
;Usage: The total usage of this license type<br />
<br />
;Local: The usage of this license on this PBX<br />
<br />
;Slaves: The usage of this license on PBX's registered to this PBX.<br />
<br />
see [[Reference10:Licenses]] for a description of the licenses.<br />
<br />
=== Registrations ===<br />
<br />
The current number of registrations and the limit as defined by the base license is displayed here. Because this limit is defined by the base license it includes any registrations because of standby as well, which require no registration license.</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference14r1:PBX/Config/General&diff=70826Reference14r1:PBX/Config/General2024-02-13T10:02:21Z<p>Ckl: /* Common */</p>
<hr />
<div>== Configuration ==<br />
<br />
=== Common ===<br />
<br />
;PBX Mode: The PBX operating mode<br />
:* '''Off''' - The PBX is disabled. After enabling the PBX a browser refresh is needed to activate additional PBX webpages.<br />
:* '''Master''' - The PBX on this device acts as Master. Within a multisite installation exactly one PBX must be configured as Master.<br />
:* '''Slave''' - The PBX on this device acts as Slave. Within a multisite installation several PBXes can be configured as Slave.<br />
:* '''Standby''' - The PBX on this device acts as Standby for the Master. As long as the master is available, this PBX is not active, but just monitors the Master. If the Master is not available this PBX is active.<br />
:* '''Standby-Slave''' - The PBX on this device acts as Standby for a Slave. As long as the slave is available, this PBX is not active, but just monitors the slave. If the slave is not available this PBX is active.<br />
<br />
;System Name: The system Name. On all PBX within a multisite installation the same System Name must be configured. For H.323 endpoints this name is the gatekeeper identifier, for SIP endpoints it is the server name.<br />
<br />
;Use as Domain: Uses the ''System Name'' as domain name, together with the name field in the user object the PBX constructs the email address (used for sending emails out ox myPBX). This mechanism is also used for Federation, to federate with other domains.<br />
<br />
;PBX Name: The name of the PBX on this device. With this name a PBX is associated to a node. The field 'Name' (not Long Name) of a PBX Node object relates to this name.<br />
<br />
;DNS: DNS Name of the PBX. If configured this will be used for myPBX redirects if a client tries to register at a PBX on which the user is not configured.<br />
<br />
;Unknown Registrations: If this checkmark is set, the PBX accepts 'unknown' registrations. This means registrations with no matching object configured. If from an endpoint registered in this way a number of an object is dialed, which has no registration active and no 'HW-ID' configured the name used for the registration is configured as 'HW-ID' of this object. This is an easy way to deploy large numbers of phones.<br />
<br />
:'''With PBX Pwd only:''' If checked only registrations with a PBX authentication or a verified certificate in case of H.323/TLS are accepted. In this case either "PBX Pwd" or "TLS only" is set in a device generated.<br />
<br />
;Reverse Proxy Addresses: Up to 8 addresses (no DNS names allowed) can be entered in this field, separated by comma. Registrations from one of these addresses are assumed to be routed through a Reverse Proxy. To the address '/<certificate name>' can be added to also check the TLS certificate of the Reverse Proxy. If the reverse proxy certificate is to be verified, the certificate or the issuer of the reverse proxy certificate must be trusted in the PBX.<br />
; Assume TLS: If the ''Assume TLS'' checkmark is set, it is assumed, that the reverse Proxy did a successful check of the TLS certificate against the registration name.<br />
<br />
;Media relay endpoint/Firewall public IP: For media relay endpoints (e.g. third party SIP phones) the public address of the firewall of the PBX network can be configured. This address is signaled in the SDP to received RTP from the phone. The firewall should have configured a port forwarding to the PBX or the TURN, for the RTP range of the PBX or TURN.<br />
<br />
;Media relay endpoint/TURN: If this checkmark is set, the PBX allocates TURN endpoints for calls to or from media relay endpoints, which registerd thru the reverse proxy (e.g. third party SIP phones)<br />
<br />
;IP address for App Platform<br />
: The ip address of an App Platform can be configured here, with a DNS name used for it. If myApps uses a host to access the PBX different from the configured DNS name of the PBX, the hostname in any App url, which matches the configured AP DNS, is replaced by the AP IP. This way it is possible to configure a PBX with DNS names and access it with myApps when the DNS is not yet set up. If the checkmark ''Operation without DNS'' is set as well any matching DNS name is replaced also when an App service requests the URL of another App service (Example: Users requesting the URL of Devices for provisioning) and in the URL sent to the App services itself (Example: Devices uses this URL to construct the URL set at devices for the Devices registration).<br />
<br />
: Note that this mode is intended to be able to run the PBX using DNS names while the DNS is not yet in place. Once the DNS is up and running, neither the DNS name of the AP nor its IP address should be configured here.<br />
<br />
;Music On Hold URL: A URL for the Music On Hold. This file is read by the PBX using HTTP and sent to a held endpoint via RTP. The format of this URL is<br />
<br />
:'''<nowiki>http://<addr>/<file>.$coder?coder=g711a,g711u,g722,g723,g729,opus-nb,opus-wb&repeat=true</nowiki>'''. <addr> is the IP address of the http server, no dns name is allowed here. <file> is the filename. $coder will be replaced by the actual coder used.<br />
<br />
:Parameters: ''coder=g729,g711a,g711u,g723,opus-nb,opus-wb'' is the list of available coders. Only these coders must be specified for which a corresponding file exists. ''repeat=true'' should be specified in order to loop the file endlessly. ''random=true'' can be used to start the music on hold on a random point (this will work only if the URL is not local though).<br />
<br />
:By default the built-in Music-On-Hold is played (Pseudo URL: "MOH?coder=g729,g711a,g723&repeat=true"). You can also play a dial tone (Pseudo URL "TONE") or a ring-back tone (Pseudo URL "TONE?tone=ringback").<br />
<br />
:The maximum length of the URL is limited to 500 characters (bytes).<br />
<br />
:If you configure a wrong (or invalid) URL then you will have silence as MOH. To prevent this situation when the MoH for some specific context/user(see below) is missing and silence is played instead of any MoH, an additional parameter ''fallback=true'' is available. If the file provided in the URL is missing (HTTP Error 404 Not Found is delivered by the HTTP Server) and the parameter ''fallback'' is provided, the default MoH will be played instead of silence. To use a custom file as fallback MoH, instead of default MoH, any file name can be provided with the ''fallback'' parameter: e.g. ''fallback=other_filename''. The file other_filename.g7xx must be placed in the same folder as a file provided with URL. A special filename ''fallback=ringback'' can be used to generate a ringback tone (equivalent to ''TONE?tone=ringback'') instead to play an alternative file.<br />
<br />
:Within the URL %<id> can be used to put in some context information of the call. The information refers to the party which has put the receiving party on hold. For information about the receiving party itself, the id has to be preceded by '.' (e.g. '''.l''').<br />
<br />
:'''l''' Long Name<br />
:'''h''' Name (H.323 id)<br />
:'''n''' Number<br />
:'''N''' Node<br />
:'''P''' PBX<br />
:'''d''' Diverting Name<br />
:'''#d''' Diverting Number<br />
<br />
: See [[Howto:Dynamic_MOH]] for more details on how to use dynamic music on hold.<br />
<br />
;External Music On Hold: To offload the device from playing the Music on hold, the Music On Hold can be played by a separate device. This device can register with a name configured here. To retrieve the Music On Hold a call is sent to this device. For each held endpoint a call is sent.<br />
<br />
;Response Timeout: Global timeout (in seconds) after which any action for no response is taken (e.g. Call Forward on No Response). A timeout configured at any object overrides this value.<br />
<br />
;Dial Complete Timeout: Global timeout (in seconds) after which any action for incomplete dialed number is taken (e.g. incomplete destination at trunk object).<br />
<br />
;No. of Regs w/o Pwd: Number of registration without password authentication which are allowed per user. If 0 is configured no registration without password is possible.<br />
Pls. note that registrations from 127.0.0.1 w/o password will be accepted anyway<br />
<br />
;Security block time(s): Time for which a registration to a user is blocked after attempt with wrong password. Default is 20s. With a value of 0 this features is turned off.<br />
<br />
;Recall Timeout: A value configured here enables recall after transfer. If a call is transferred and not answered within this time, the call is sent back to the transferring endpoint.<br />
<br />
;Chat no Attachments: If checked, no file attachments are allowed in chats for any user<br />
<br />
;Retries on busy (14s): Number of retries (Each attempt runs for 14 seconds) of blind transfer to a busy endpoint, before a recall back to the initiator is executed. During an attempt, the PBX waits to see if the target is free, and then delivers the call. The default value are 4-retries if the field is empty. If there is a 0 configured no further (in addition to the initial) will be executed. (This function depends on the ''Recall Timeout'' so the ''Recall Timeout'' must not be empty)<br />
<br />
;Max Call Duration (h): Number of hours until a call with media is disconnected automatically. Affects all calls with initialized media channels signalled via PBX.<br />
<br />
;Group Default Visibility: Defines additional visibility for active group members. These are added to the visibility rights derived from the [[{{NAMESPACE}}:PBX/Objects/Visibility | Visibility settings ]] in user or template definitions. Note that changes made here only take effect after a re-registration.<br />
<br />
;Presence with Alert: Enable presentation of presence on phone upon alert. Setting applies for all PBX users.<br />
<br />
;Enable External Transfer: Unless this checkbox is set any attempt to transfer an external call back to an external destination will result in disconnection of the call.<br />
<br />
;No CLIR on Internal calls: If checked numbers are displayed even if received with presentation restricted. When sending a call presentation restricted can still be set and should be honored by a public network.<br />
<br />
;Media Relay:<br />
:;Off: No Media Relay is done in the PBX <br />
:;On: All media traffic is routed through the PBX. With the '''No Media Relay if Addresses are identical or private''' checkmark, this is not done if the two call endpoints registration addresses are either private or equal (i.e. external endpoints behind the same NAT router). To identify an address as private the "Private Networks" configuration from ''IP4/General/Settings'' is used.<br />
:;Auto: Media traffic is routed through the PBX if calls are between private and public registration addresses but not for calls between private and private or public and public registration addresses. To identify an address as private the "Private Networks" configuration from ''IP4/General/Settings'' is used. In v11 this was the behaviour if ''RTP Proxy'' was ''on''.<br />
: Please note that when media relay is in effect for a call, '''video is not working'''. With ICE (available from v12r1), media relay in the PBX should be obsolete except for special applications. Check ''On'' or ''Auto'' only if you need to have this, since it creates CPU load on the PBX.<br />
<br />
;Generate CDRs: If this checkbox is set, the PBX generates CDRs for all calls. For details, refer to the [[{{NAMESPACE}}:Concept_Call_Detail_Record_CDR_PBX|CDR description article]].<br />
<br />
;Reverse Lookup URL<br />
: A String in the LDAP URL Format according [https://tools.ietf.org/html/rfc2255 RFC2255] which will be used by the PBX to make a lookup for all external numbers.<br />
: Number resolutions will be forwarded to internal applications like generated CDRs, Phone App etc..<br />
: Example: ''ldaps://ap.innovaphone.com/dc=entries?givenname,sn,company?sub?(metaSearchNumber=+%n)?bindname=innovaphone.com\contacts''<br />
:* dn: ''dc=entries''<br />
:* attributes: ''givenname,sn,company''<br />
:* scope: ''sub''<br />
:* filter: ''(metaSearchNumber=+%n)''<br />
:** %n is a placeholder for the given cgpn<br />
:* extension: ''bindname=innovaphone.com\contacts''<br />
:** The username for the authentication.<br />
:'''Variables'''<br />
:* %n - cgpn<br />
:* %u - h323 name of the current object<br />
: Also there is an additional field for the LDAP password<br />
: Examples are listed in the [[{{NAMESPACE}}:Concept_Number_Resolution_and_LDAP#PBX_Configuration|Concept Article]]<br />
<br />
;Logo URL<br />
:The URL to a customized logo image that will be displayed on the phones. The image size should be 220x150px or less, RGB color and a deep of color 24Bit or less. Leave empty to keep the standard logo. The file format should be PNG or JPEG.<br />
:Username and password or filekey cannot be stored in the link. Save the file to a location with anonymous access. Or save the file in Files, in a folder with username and password sharing. Store the link with user name and password in HTTP/Client on the phone.<br />
:You can distribute this to all phones via Devices/Device Configuration/Expert Configuration.<br />
<br />
;Route Root-Node External Calls to: Destination object (Long Name) of Root-Node external calls. This configuration option is available on the Master or Standby PBX only. Any call call which cannot be terminated inside the PBX is sent to this destination as long as neither the source nor the destination of the call can be associated with a node with a PBX configured. This object must be assigned to this PBX.<br />
:'''For calls from local PBX only''': If set on a master, calls from a slave are not sent to this destination but sent back to the slave where the call came from. On the slave the call is then sent to a destination configured with 'Route Root-Node External Calls to'.<br />
<br />
;Route PBX-Node External Calls to: Destination object (Long Name) of PBX-Node external calls. Any call which cannot be terminated inside the PBX is sent to this destination as long as the source nor the destination of the call can be associated with the node of this PBX. If a call is sent from or to an object defined inside the node of this PBX or in a node hierarchically below the node of this PBX the call is associated to the node of this PBX. This object must be assigned to this PBX, that is, it has to register to this PBX.<br />
<br />
;Route Internal Calls to: Destination object (Long Name) to which any call is sent for which a PBX internal destination was found, except for those calls that originated from that object. This can be used to apply special routing on PBX internal calls.<br />
<br />
;Escape Dialtone from: The PBX object (Long Name) to which a call is made to get a dialtone if a dialtone is configured for the escape of a node. As above, this object must be assigned to this PBX.<br />
<br />
;Prefix for Intl/Ntl/Subscriber/Area-Code/Country-Code: Prefixes to be used to map International, National and Subscriber numbers.<br />
:* '''International Prefix''' (<code>000</code> in Germany).<br />
:* '''National Prefix''' (<code>00</code> in Germany).<br />
:* '''Subscriber''' (in Germany, <code>0</code> is a commonly used trunk line access code).<br />
::The SubscriberID is used for hotkey-actions within myAPPs-launcher. By use of node-objects, adjust the Subscriber Prefix in the respective [[{{NAMESPACE}}:PBX/Objects/Node#Number_Mapping_.28International.2C_National.2C_Subscriber_Prefix.29|node object number mapping]].<br />
::For myPBX-launcher hotkeys, refer to the [[{{NAMESPACE}}:Phone/User/Directories#Dialing_location|dialing location settings]].<br />
<br />
:* '''Area-Code''' (in Germany for example, the town Mannheim has area code <code>621</code>).<br />
:* '''Country-Code''' (for Germany, <code>49</code> would be used)<br />
<br />
: These settings resemble the same settings found in the [[{{NAMESPACE}}:PBX/Objects/Node | Node ]] and [[{{NAMESPACE}}:PBX/Objects/PBX | PBX ]] object. However, they apply to the ''root'' node instead (and should be consistent through all PBXs in a multi-PBX system).<br />
<br />
;Adjust LDAP results for e.164: Add a "+" prefix to an PBX-internal LDAP-contact-search result. This is used for 3rd-party- and DECT-phones which are not able to benefit from the Dialing Location settings in case of LDAP-search in an E.164-setup. See also [[{{NAMESPACE}}:Concept_Number_Adjustments_%28Dialing_Location%29#PBX_database_used_as_LDAP_database|concept description]].<br />
<br />
;Tones: The tones scheme to be used for PBX generated dialtones. This applies to dialtones generated for node prefixes, ringback on transfer and some more.<br />
<br />
=== Slave PBX ===<br />
<br />
If the PBX is operated in Slave mode, then the Slave PBX section is displayed<br />
<br />
;Registration: The VOIP protocol used for the registration to the master. Possible choices are H.323, H.323/TCP or H.323/TLS.<br />
<br />
;Master: The IP address of the PBX master<br />
<br />
;License Only: If set, the PBX obtains license from master, but acts as master in all other respects.<br />
<br />
;Alternate Master: The IP address of an alternative PBX master (standby, if available)<br />
<br />
;Password: The password to be used for registration at the Master as configured in the corresponding PBX Object (The length of the password is limited to 16 characters)<br />
<br />
;Master GK-ID: The System Name/Gatekeeper ID of the PBX Master were will register (Optional, usually used for DynPBX).<br />
<br />
;Replication: This parameter allows you to select the replication style for the slave PBX: either ''All'' or ''Local'' (only users that need to be known in this PBX). For the replication process the [[Reference9:PBX/Config/Security|PBX Password]] is used which have to be the same password on all PBXes in the system.<br />
<br />
;dyn PBX ID: This parameter allows to set replication from a specific DynPBX configured on the Master Device.<br />
<br />
;Use local static User DB: A dynPBX has also this checkmark. The database of the main PBX is used, but be careful due to the increased memory usage. The dynPBX will create its own PBX datastructure which allocates memory.<br />
<br />
;Route Master calls if no Master to: If the master is not available, master calls are sent to this destination. Destination has to be an object with active registration.<br />
<br />
;Max Calls to Master/No Reroute: This parameter can be used to limit the calls to the master. If a call is sent to the master and there are already calls to/from the master equal to or exceeding this value, the call is rejected if '''No Reroute''' is set or is handled as if the master was not available otherwise.<br />
<br />
;License Limits: Here we can set limit of licensing for this Slave PBX for Port, Mobility, Operator and Softwarephone.<br />
<br />
For complete replication from master to slave, check also password in [[Reference9:Administration/PBX/Security]]<br />
<br />
=== Standby PBX ===<br />
<br />
If the PBX is operated in Standby mode, then the Standby PBX section is displayed<br />
<br />
;Master: The IP address of the PBX master<br />
<br />
;Replicate from Master: Turns on full replication from the master PBX<br />
<br />
;use TLS: Use LDAPS (TLS) instead of LDAP (TCP).<br />
<br />
For complete replication from master to slave or standby, check also password in [[Reference9:Administration/PBX/Security]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference8:Release_Notes_TAPI&diff=70818Reference8:Release Notes TAPI2024-02-12T09:46:15Z<p>Ckl: </p>
<hr />
<div>These release notes describe the V8 (that is, [[Reference8:SOAP_API | pbx800.wsdl]] based) TAPI driver implementation.<br/><br />
<br/><br />
<br />
Please see ''[[Reference:What_are_the_Release_Notes_Documents?|the disclaimer]]'' before using the information presented here!<br />
<br />
__NOEDITSECTION__<br />
{{#invoke-url: http://wiki.innovaphone.com/extensions/InvokeUrlFunction/projectListWebsocket.php?project=TAPI%20V8}}<br />
<br />
[[Category:Release Notes|TAPI]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:PHP_based_Update_Server_V2&diff=70797Howto:PHP based Update Server V22024-02-07T14:52:39Z<p>Ckl: /* queries/query/applies */</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
* web server running PHP 5 (e.g. Linux Application Platform)<br />
<br />
All Versions prior to 13r1 (for 13r1 and later, see [[Reference13r1:Concept App Service Devices]] instead). <br />
<br />
By default, the [[Reference10:Concept_Update_Server | Update Manager]] mechanism reads a file that corresponds to the device type (e.g. <code>update-ip222.htm</code>). While this makes sense (update scripts may vary by device type), it is sometimes tedious, as you have to edit a huge amount of files which typically are (at least partly) identical.<br />
<br />
Here is a PHP script that can be used as an ''Update Server'' that allows you to simplify the handling of update scripts. It also includes a mechanism that makes sure that all devices always have the same firmware installed as a given ''master device'' has. Finally, it implements a straight forward configuration backup scheme.<br />
<br />
This article describes version 2 of this update server (build 2006 and up). The previous version is described in [[Howto:PHP_based_Update_Server]]. The enhancements are<br />
* Status user interface showing all known devices<br />
* Ability to roll out custom device certificates<br />
* Ability to provide configuration files to MTLS-authenticated devices only (e.g. in order to keep certain configuration settings secure)<br />
* Hide configuration files from public read access<br />
<br />
==More Information==<br />
=== Requirements ===<br />
The update server script requires a web server with working PHP 5.3 or higher platform. It has been tested with Apache and ligHTTPD (on a [[Reference10:Concept Linux Application Platform | Linux Application Platform]]). On IIS, neither the certificate roll-out nor the MTLS authentication or configuration backup works with IIS.<br />
<br />
The platform running the PHP scripts must have a valid time setting!<br />
<br />
=== Features ===<br />
The update server can<br />
* update all your devices with boot code and firmware that corresponds to the versions running on a reference device of your choice<br />
* save backups of your devices configuration. Backups are saved only if the configuration changed since the last backup<br />
* invoke your own update scripts depending on <br />
** various phases (e.g. you can have ''staging'' scripts which are only executed once and normal scripts which are executed in normal operation later on)<br />
** devices classes (e.g. you can have different scripts for phones and gateways)<br />
** environments (e.g. you can have scripts for your internal devices and others for devices in home offices)<br />
** the device serial number<br />
* roll out customer specific device certificates<br />
* roll out update scripts to devices only that have identified themselves with a valid device certificate (MTLS)<br />
* maintain a list of devices in your installation along with some vital information about each individual device<br />
<br />
=== Installation ===<br />
<br />
==== On the Linux Application Platform ====<br />
Here is how you would install it on the [[Reference10:Concept_Linux_Application_Platform|LAP]]. Some of the steps may not be necessary if you don't want to use all features.<br />
<br />
On the ''Linux Application Platform'', you would <br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files<br />
* create a new root directory underneath <code>/var/www/innovaphone/mtls</code>, e.g. <code>/var/www/innovaphone/mtls/update</code><br />
* download the complete file package of scripts and files [http://download.innovaphone.com/ice/wiki-src/#php-update-server here] <br />
* copy all files and directories in to this new directory<br />
** create your local config files. We will never overwrite these in further updates.<br />
***Rename <code>config/user-config-sample.xml</code> to <code>config/user-config.xml</code><br />
***Rename <code>scripts/all-all-all-sample.txt</code> to <code>scripts/all-all-all.txt</code><br />
* change owner and group of all files and directories to <code>www-data</code>, change mode to 0600 for all files and 0700 for all directories (see [[#Migrating_from_build_2000_an_newer | below]] for how to do this with WinSCP)<br />
<br />
* log in to the LAP's admin UI (if you have not yet changed it, the default credentials will be <code>admin/linux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]) and open its ''Administration/Web Server/Change web server properties and public access to the web/webdav'' configuration UI. Add the following paths to ''Public Web Paths'' (assuming your base directory is called <code>update</code>):<br />
** <code>/mtls/update</code><br />
** <code>/mtls/update/web</code><br />
** <code>/mtls/update/admin</code><br />
** <code>/mtls/update/fw/</code> (note the trailing slash!)<br />
: make sure that no other sub-directories of ''update'' are listed<br />
: make sure you have no trailing '/' in any of these paths (except ''fw/')<br />
: this will make sure the update server's admin user interface is not accessible to the public<br />
<br />
At this point, you should be able to access the update server's admin user interface, e.g. <code>http://update.yourcompany.com/mtls/update/admin/admin.php</code>. However, the only thing you see is a login page. Please note that your browser should ask you for a password for this page (unless you already entered it before). Cookies must be enabled for the login page to work properly. <br />
<br />
===== If you want to provide HTTPS Acess to the Update Server =====<br />
For HTTPS access to the update server, you may want to install your own server certificate under ''Administration/Certificates/Current server certificate''. However, this is not strictly required (you will experience some warning messages when using your browser to access the update server, however, calling devices will work just fine). <br />
<br />
===== If you want to setup MTLS-restricted Delivery of Update Scripts =====<br />
If you think you have sensitive information in your update scripts, you should make sure to deliver such scripts to your own verified devices only. This can be done by authenticating calling devices with ''mutual TLS'' (MTLS). In this case, the calling device must use HTTPS to retrieve the update script and present a trusted client certificate that identifies itself as the calling device (that is, has the device serial as the certificate's ''common name'' (CN)). <br />
<br />
For this feature, you need to ''Configure mutual TLS'' in the LAP's ''Administration/Web Server'' panel. Simply tick the ''Active'' check-mark and select an appropriate ''MTLS Port''. The port must not conflict with any other TCP port used on the LAP, so neither 80 nor 443 is a good choice. 444 is a good choice. Although it [http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=8 is assigned to the snpp protocol] by IANA, it is not used by the LAP (and probably rarely used anyway).<br />
<br />
If MTLS is already activated on your LAP, simply take note of the currently configured ''MTLS Port''.<br />
<br />
<br/><br />
Please note that MTLS access is ''not possible'' through a ''reverse proxy'' (RP). This is because the RP always will terminate the incoming TLS connection and establish its own to the target. Therefore, the client certificate presented to the target server is the RP's certificate, not the original clients certificate. In our context - where MTLS is used to verify the identity of the original caller - clients must not access the update server through an RP.<br />
<br />
You can either expose your update server directly to the internet (and carefully think through the security implications) or create specific TCP port forwarding towards the update server in your NAT-router/firewall. In this case, we recommend to use non-standard HTTP and HTTPS ports on the NAT router (cause this will already keep most of the HTTP port scanners out there in the internet from functioning).<br />
<br />
<br/><br />
Also, you will need the public key of all the CAs you will trust. These must be configured in to the web server so it can trust the certificates your devices will present to it. See [[#Enforcing_Trust]] for details.<br />
<br />
==== On an Apache Server running on the Windows Operating System ====<br />
The update server will run on Apache too. However, as we did not test this setup thoroughly, we recommend to use the LAP's LigHTTPD instead. <br />
<br />
* For hints on using MTLS on an Apache Web Server, see [[Reference10:Concept_Provisioning#Enforcing_mutual_TLS_on_Apache | Enforcing mutual TLS on Apache]]<br />
* For hints on getting the ''innovaphone device certificate authority'' public keys (which you may or may not need), see [[Reference10:Concept_Provisioning#How_to_get_inno-dev-ca-certificate.crt | How to get inno-dev-ca-certificate.crt ]]<br />
<br />
==== On Microsoft's IIS ====<br />
We do not recommend to use IIS as <br />
* it does not support PUT easily<br />
* it is not compatible with the innovaphone device's MTLS implementation<br />
<br />
===Update existing installation===<br />
<br />
* download the [http://download.innovaphone.com/ice/wiki-src/#php-update-server new sources]<br />
* copy all files and directories to your existing installation folder (overwrite existing files)<br />
* change owner and group of all files and directories to www-data, change mode to 0600 for all files and 0700 for all directories<br />
<br />
===Configuration===<br />
<br />
If you intend to deliver your update scripts with MTLS (that is, with HTTPS and mutual certificate check)<br />
* you will need to have the full name of your ''certificate Authority'' (CA) as it is noted as ''Common Name'' (CN) in your CA's certificate<br />
* also, you will need a PEM version of your CA's public key (a ''PEM version'' of a certificate is a text file that begins with a line like <code>-----BEGIN CERTIFICATE-----</code>)<br />
<br />
Also, you need to know the ''MTLS Port'' configured in your LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts| If you want to setup MTLS-restricted Delivery of Update Scripts ]] above).<br />
<br />
All tweakable parameters are set in <code>user-config.xml</code>. You may want to use an XML-capable editor such as notepad++, netbeans or Visual-Studio for editing. if your editor supports it, you benefit from the ''document type description'' (DTD) provided in <code>full-config.dtd</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="false" debugcerts="false" debugscript="false"><br />
</config><br />
</syntaxhighlight><br />
<br />
The attributes of the <code>config</code> tag control some aspects of debugging. For now, simply set all 3 to <code>"true"</code> instead of <code>"false"</code>. Do not forget to revert them back to <code>"false"</code> when everything runs smoothly, large log files will result if not. <br />
<br />
==== Securing Access ====<br />
To control access to the update server's data, you should set a login. This can be done in the ''master'' tag by specifying both ''user'' and ''password'':<br />
<br />
<syntaxhighlight lang="html5"><br />
<master ... user="myadmin" password="mysecret"/><br />
</syntaxhighlight><br />
Default username and password are admin/password.<br />
<br />
==== Delivering Firmware and Boot Code ====<br />
At this point, you can simulate a device by requesting an update script using an URL like <code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code> in your browser (please note that this URL should not ask you for a password). Most likely, your browser will wait a little while and then say something like:<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# cannot get PBX info: cannot access http://yourmasterdevice.youdomain.tld/CMD0/box_info.xml, reading cache<br />
# cannot get cached PBX info: cannot access cache/master-info.xml - exit<br />
<br />
This is because you did not yet specify the ''master device'' which always runs the reference firmware. <br />
<br />
The idea here is that you have one innovaphone device in your system where the firmware is always manually updated. All other devices shall follow this reference firmware. The update server will deliver the firmware that runs on the master device to calling devices. For this to work, you need to set the ''info'' attribute of the [[#master|''master'']] tag and leave everything else ''as is''. <br />
<br />
Remember that you do all your local configuration changes in <code>user-config.xml</code>.<br />
<br />
As ''master'' is a first level tag, you can add it directly underneath the opening ''<config>'' tag. The only thing we need to define right now is the path to read the firmware information from your master device. Let's say your reference device has the IP address <code>172.16.0.10</code>, you end up with <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true" debugcerts="true" debugscript="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml" user="myadmin" password="mysecret"/><br />
</config><br />
</syntaxhighlight><br />
(you could use a DNS name instead of the IP address of course). <br />
The ''info'' URL is used by the update server itself only, it is never used by devices requesting an update script.<br />
<br />
When you refresh the page that simulates a device requesting an update script, the output should now change to something like<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# current firmware (unknown) does not match required firmware (130178)<br />
...<br />
<br />
This is to say that your master device runs firmware 130178 and the calling device does not (actually, as the calling client is simulated by your browser, it does not transmit its current firmware to the update server so it is ''(unknown)'').<br />
<br />
In order to actually update the clients with firmware and boot code, the files must be made available to the calling client somehow. This is done by specifying an URL in the [[Reference10:Concept_Update_Server#Prot_command | prot ]] and [[Reference10:Concept_Update_Server#Boot_command | boot ]] commands sent to the calling device:<br />
<br />
...<br />
# current firmware (unknown) does not match required firmware (130178)<br />
mod cmd UP0 prot http://update.yourcompany.com/mtls/update/fw/130178/ ser 130178<br />
# current boot code (unknown) does not match required boot code (130112)<br />
mod cmd UP0 boot http://update.yourcompany.com/mtls/update/fw/130112/ ser 130112<br />
...<br />
<br />
By default, the URL generated points to a sub-directory of your update server called ''fw'': <code>http://update.yourcompany.com/mtls/update/fw/130178/</code>. Note that this default URL expects sub-directories underneath the ''fw'' directory which correspond to the firmware and boot code build number. The file structure thus would be like<br />
<br />
/var/www/innovaphone/mtls/update/fw<br />
/130178<br />
/ip232.bin<br />
/130112<br />
/boot232.bin<br />
<br />
Note that the URL ends with a slash (<code>/</code>). This instructs the calling device to append the appropriate file name itself when retrieving the file (see [[Reference10:Concept_Update_Server#Prot_command | the prot command ]] for details). <br />
<br />
However, you can also specify any other URL by setting the ''url'' attribute in the ''fwstorage'' tag of your <code>user-config.xml</code>. In this attribute, certain meta words will be replaced (see [[#fwstorage | the ''fwstorage'' tag ]] for details). The default setting for example is <code>fw/{build}/</code>.<br />
<br />
You can disable firmware and boot code updates altogether by setting the ''info'' attribute in the ''master'' tag to an empty value.<br />
<br />
==== Your own Update Script Files ====<br />
The update server will deliver update scripts to calling devices which are synthesized from a number of ''update script snippets'' which you define yourself. The idea is that many devices share parts of their configuration while other parts are different. This depends on<br />
* the device ''class'' (e.g. a phone or a gateway)<br />
: the device class is automatically derived from its model. In fact, a single device may be in multiple classes. For example, an IP232 is in these classes: ''phone, pre_opus_phone, phone_newui'' which basically says that its a phone and has no OPUS codec yet but a "new" user interface (as opposed to the old one found e.g. on an IP110). A number of useful classes are pre-defined, but you can add your own in <code>user-config.xml</code>.<br />
* the device's environment (e.g. ''home-office'', ''branch-office'')<br />
: These reflect the fact that devices may need different configuration if they are in different locations (or environments). The update server does not know about a device's environment, which is why you need to configure it in to the devices update URL if you need this. By default, there is a single environment defined called ''default'', but of course, you can add your own<br />
* phase<br />
: configuring devices may need multiple phases to go through. If more than one phase is defined, the device will go through all phases sequentially, which is why a ''phase'' has a numerical ''seq'' attribute. Phases are went through in order of this attribute. When the device has reached the last phase, it will stay there. By default, there is only one phase called ''update''<br />
<br />
You can define update script snippets for any combination of device, environment and phase. You do so simply by placing appropriate files in to the ''scripts'' directory on your update server.<br />
<br />
Let's have a closer look at the web page we used to simulate a device calling for an update script before (<code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code>). It will show you all the possible files it would deliver to the device:<br />
<br />
# possible files (from scripts):<br />
# scripts/update-all-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-all-default.txt does not exist<br />
# scripts/update-phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone-default.txt does not exist<br />
# scripts/update-pre_opus_phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-pre_opus_phone-default.txt does not exist<br />
# scripts/update-phone_newui-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
The update script snippet files need to have proper names to define when they should be delivered. As you can see, they all start with <code>update-</code> which means that they apply to devices which are in the ''update'' phase. As by default there is only a single phase defined, all possible file names start with <code>update-</code>. <br />
<br />
The next part here is either <code>phone-</code>, <code>pre_opus_phone-</code>, <code>phone_newui-</code> or <code>all-</code>. This is because the calling IP232 is in three classes, so all respective snippets will be delivered to it. ''all'' however is a wild-card. That is, such files will be delivered to all devices, no matter what class they are in. You may wonder why there was no ''all'' for the phase. This is because there is only a single phase defined. If there would be more, the ''all''-variation of the phase-part of the file name would be appear.<br />
<br />
The third part finally is either <code>default</code> or <code>00_90_33_00_00_00</code> in our case. ''default'' is the name of the only ''environment'' that is defined by default. ''00_90_33_00_00_00'' is the device's serial number which is treated as an implicitly defined environment name. This allows you to deliver certain snippets to a single device only.<br />
<br />
Now let's create an update script snippet that is delivered to all phones in the default environment when they are in the update phase. The file name (which looks like ''phase''<code>-</code>''class''<code>-</code>''environment''<code>.txt</code>) has to be <code>update-phone-default.txt</code> therefore. Just for an exercise, let us set the device name (as shown in the browser's title bar) to ''This is a Phone!''.<br />
The command to do so is <code>config add CMD0 /name This+is+a+Phone%21</code>, so your file may look like this<br />
<br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
(btw: did you observe that config line arguments need to be url-encoded?). Now create the file and upload it in to the ''scripts'' directory of you update server. When you now refresh the page which simulates the device calling for an update script, you will see something like this:<br />
<br />
...<br />
# newest script (scripts/update-phone-default.txt) 11s old<br />
# files being updated (scripts/update-phone-default.txt 11s old, waiting for at least 90s to expire)<br />
<br />
When you update multiple script snippets, it is often undesirable that a device retrieves an inconsistent state of the scripts you are working on (e.g. if you already have saved one but not yet the other). To avoid this, the update server will look at the files modification dates and if one of those that needs to be delivered to the device is younger than 90 seconds, it will not send any of them at all. <br />
<br />
So when you wait for the 90 seconds to pass and refresh the page again, you will see something like this:<br />
<br />
# firmware build info cache is current<br />
# phase: update, nextphase: , environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
...<br />
# scripts/update-phone-default.txt 4.1 mins<br />
...<br />
# scripts/update-phone-default.txt<br />
# { begin script 'scripts/update-phone-default.txt' <br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
<br />
# end script 'scripts/update-phone-default.txt' }<br />
<br />
# trigger reset if required<br />
config write<br />
config activate<br />
iresetn<br />
<br />
As you can see, your snippet has been delivered by the update script. However, the update server has also added some trailing commands which write back the config to the devices flash memory (<code>config write</code>), activate it (<code>config activate</code>) and trigger a reset if needed (<code>iresetn</code>).<br />
<br />
If there are multiple snippets available for a calling device, all of them are concatenated in the sequence shown in the header of the returned script (where the possible file names are listed)<br />
<br />
==== Device Status ====<br />
When you have a lot of devices which are served by the update server, you may want to have a list of these devices along with some useful information regarding each devices. <br />
<br />
You can enable this by defining the ''status'' tag in your <code>user-config.xml</code>. So open the file and add the tag right next to the ''master'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
/><br />
</config><br />
</syntaxhighlight><br />
<br />
When you have done so, please refresh the page that simulates your calling device and then open <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>. You will now see a device list (well, a list with a single device, the one you simulated using your browser). The list will show some information regarding the devices that requested an update script lately. For more options, see [[#status]].<br />
<br />
==== Device Backup ====<br />
It is generally useful to have recent backups of all of your device configurations. The update server supports that if you enable it by defining the ''backup'' tag in your <code>user-config.xml</code>. Simply put it right next to the ''master'' or ''status'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<backup<br />
dir="backup"<br />
/><br />
</syntaxhighlight><br />
<br />
If you have done so and then refresh the page that simulates your calling device, you will now see <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
mod cmd UP0 scfg http://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m<br />
<br />
instead of <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
# Backups not enabled in config<br />
<br />
When a client requests an update script the next time, this will initiate a configuration backup which is stored underneath the ''backup'' directory. For each device, a sub-directory is created and all backup files are stored therein. By default, the last 10 differing configuration backups are kept.<br />
<br />
==== Controlling the Time-frames when Snippets are delivered ====<br />
The update client built-in to innovaphone devices allows to restrict updates to certain time frames (e.g. only during the night). This can be controlled using the [[Reference10:Concept_Update_Server#Times_command | times ]] command.<br />
<br />
You can configure the update server to use the time commands by setting the ''allow'' and ''initial'' attributes in the ''times'' tag in your <code>user-config.xml</code>. <br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"/><br />
</syntaxhighlight><br />
<br />
If either the ''allow'' or ''initial'' attribute is present, the update script will contain a line like<br />
<br />
...<br />
# Restricted times<br />
mod cmd UP1 times /allow 22,23,0,1,2,3,4 /initial 1<br />
<br />
In the example above, all update script activity will occur only between 10pm and 5am (local device time). For more details, see [[Reference10:Concept_Update_Server#Times_command | Concept Update Server]]<br />
<br />
==== Using and Enforcing HTTPS ====<br />
Your devices can either use HTTP or HTTPS to access the update server. In normal operation, the update server will generate URLs (e.g. the URLs used to retrieve firmware or to backup configurations) for the same protocol. <br />
<br />
However, you can enforce use of HTTPS by setting the ''forcehttps'' attribute in the [[Howto:PHP_based_Update_Server_V2#times | ''times'' ]] tag to <code>true</code>. If so, a device calling in with HTTP will be re-configured to use HTTPS:<br />
<br />
...<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
<br />
# changed state query args: polling, phase<br />
# new url=https://update.yourcompany.com/mtls/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i<br />
...<br />
(note that if you are using a non-standard port for HTTPS, you must define it using the ''httpsport'' attribute).<br />
<br />
==== Delivering Custom Certificates ====<br />
innovaphone devices come with pre-defined, trustworthy device certificates. However, all ''soft'' devices (such as the softwarephone, myPBX for Android/iOS or the IPVA) do not. Also, in many scenarios customers run their own ''public key infrastructure'' (PKI) and request their own certificates to be used instead of the pre-defined. This is why there is a need to roll-out custom certificates to devices. <br />
<br />
The update server supports this task to an extend. It can<br />
* check if a calling device has a proper certificate<br />
* instruct a calling device without proper certificate to create a ''certificate signing request'' (CSR)<br />
* download the CSR to the update server for easy access by the administrator<br />
* upload a signed CSR to the device<br />
* upload the public key(s) of the CA in to the device's trust list<br />
<br />
In order to roll-out custom certificates with the update server, the administrator needs to<br />
* run his own PKI (a.k.a. ''certificate authority'' (CA))<br />
* monitor CSRs that appear in the update server's device list<br />
* approve the request by manually submitting it to his own CA<br />
* upload the signed CSR to the update server<br />
<br />
Also, all devices must run ''V12r1 SR8'' or newer before the new certificate can be uploaded. Devices with older firmware will be updated automatically (see [[#Delivering_Firmware_and_Boot_Code]] above). However, the new firmware must be ''V12r1 SR8'' or later. <br />
<br />
To learn how you can create proper device certificates with your own windows CA, see [[Howto:Creating custom Certificates using a Windows Certificate Authority]].<br />
<br />
By default, custom certificates are turned off and you will see a note like<br />
<br />
# certificates: either certificate handling or state tracking not enabled - not doing any certificate checking<br />
<br />
in the delivered update script.<br />
<br />
Custom certificates are turned on by adding a ''customcerts'' tag to your <code>user-config.xml</code>:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
The page that simulates your calling device will now include a line saying:<br />
<br />
# certificates: we dont know anything about the current certificate state - not doing any certificate checking <br />
<br />
In order to decide if or if not a calling device has a valid certificate, the device needs to send its current public key to the update server. This is done by enabling ''queries'' in your <code>user-config.xml</code>. ''Queries'' is a means to have the device submit certain information to the update server. In the update server's default configuration, two queries are defined but not enabled:<br />
<br />
* the query ''certificates'' sends the current certificate state <br />
* the query ''admin'' sends the current device name (as set in ''General/Admin'')<br />
<br />
To enable a query, you must specify the class a calling device must be in in order to execute the query. This is done by adding an ''applies'' tag for the respective query in your configuration:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
</query><br />
</queries><br />
...<br />
</syntaxhighlight><br />
<br />
This basically says that both queries should be executed by devices of just any class. Unless you enable queries, your update script will include lines such as:<br />
<br />
# no queries defined for any of callers classes: phone+pre_opus_phone+phone_newui<br />
<br />
Once you have enabled them, you will see something like<br />
<br />
...<br />
# query 'certificates'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=certificates ser nop /always mod%20cmd%20X509%20xml-info<br />
# query 'admin'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=admin ser nop /always mod%20cmd%20CMD0%20xml-info<br />
...<br />
<br />
You can display the data sent by a query in the device status display. To do so, you need to define one or more ''show'' tags as part of the respective ''query'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<query id="certificates"><br />
<applies>*</applies><br />
<!-- show device certificate CNs and Issuer CNs in status page --><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
...<br />
</syntaxhighlight><br />
<br />
Two steps however are still missing: <br />
<br />
* a) you need to tell the update server the names of all the CAs you consider trustworthy. This is done by listing their names in the ''CAname'' attribute of the ''customcerts'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2,innovaphone-INNO-DC-W2K8-Zertifizierungsserver"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
: The example shown defines that you will accept both of innovaphone's device certificate authorities and also your own CA called ''innovaphone-INNO-DC-W2K8-Zertifizierungsserver''. Devices with device certificates issued by one of these CAs will be left untouched. For all other devices (for example, any ''myPBX for Android/iOS'' device that has a self-signed certificate), the generation of a custom certificate will be initiated.<br />
<br />
* And b) you need to provide the public key of your CA (so it can be uploaded to the calling device's trust list). You need to place the DER (not PEM) encoded public key in to a file called <code>certs/CAkey-01.cer</code> on your update server (if you want to upload the whole certificate chain, put all of the public keys in the chain in to separate additional files called <code>certs/CAkey-02.cer</code> and so forth.<br />
<br />
For more details on how to approve certificate signing requests, see [[#Approving Certificate Signing Requests]] below.<br />
<br />
==== Enforcing Trust ====<br />
Update script snippets may include sensitive information which you do not want to disclose to the public. Even though you can force the use of HTTPS (see above), this still does not keep your data secure. After all, an attacker could simply request an update script from your update server using HTTPS. To secure your data, you need to make sure that snippets are only sent to devices which identify themselves using a trusted certificate with a correct ''common name'' (CN). This is done using ''mutual transport layer security'' (MTLS). Therefore, you need to configure MTLS in the LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts | If you want to setup MTLS-restricted Delivery of Update Scripts]] above). <br />
<br />
You can enable this by setting ''forcetrust'' to <code>true</code> and ''httpsport'' to the port configured as ''MTLS Port'' in your web server. This is done in the ''times'' tag of your <code>user-config.xml</code>.<br />
<syntaxhighlight lang="html5"><br />
<times <br />
...<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
</syntaxhighlight><br />
Please note that ''forcetrust'' does nothing unless ''forcehttps'' is also set!<br />
<br />
If so, the update server will deliver update script snippets only to clients which identify themselves with a proper certificate. For this to work, you will need to add the public key of your certificate authority to your web server's list of trusted certificates. <br />
<br />
When using the ''Linux Application Platform'' (LAP), you need to add the PEM-encoded public key of your certificate authority to the ''innovaphone-ca.pem'' file in <code>/home/root/ssl_cert</code>. When you have installed the LAP, this file will contain the PEM-encoded public keys of the 2 innovaphone device certificate authorities. You can simply add the public key of your CA to the end of this file:<br />
<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca2 public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...your-ca public key...<br />
-----END CERTIFICATE-----<br />
<br />
You will need to restart the LigHTTPD then (most easily done by restarting the entire LAP (''Diagnose/Reset'') or by issuing the command <code>/etc/rc2.d/S02lighttpd restart</code> from the linux root command prompt). Finally, you must set the ''forcetrust'' attribute. <br />
(Note that the ''innovaphone-ca.pem'' file may be overwritten when a new LAP version is installed. It is thus a good idea to keep a copy and check it after an upgrade).<br />
<br />
When ''forcetrust'' is effective and the calling device does not use HTTPS, it will be reconfigured to do so:<br />
<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
...<br />
# new url=https://update.yourcompany.com:444/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i&env=default<br />
<br />
If the calling device uses HTTPS but sends no certificate, you will see a message like<br />
<br />
# cannot verify CN with HTTPS: SSL_CLIENT_S_DN_CN not present - fix web server configuration or use MTLS-enabled port!<br />
<br />
This is probably because it is using a non-MTLS enabled port for HTTPS (e.g. the standard port 443) although MTLS is configured for a different port. <br />
<br />
If the calling device uses HTTPS and sends a certificate but the CN does not match the serial number of the device, you will see a message such as<br />
<br />
# Device claims to be 'IP232-30-00-af' but identifies as 'CKL-CELSIUS-W10.innovaphone.sifi' by TLS<br />
<br />
If the calling device uses HTTPS and sends a certificate but the certificate is not trusted because its issuer is not listed in ''innovaphone-ca.pem'' (see above), the connection will be refused <br />
<br />
In all such cases, no snippet will be delivered. If the certificate is good, you will see a message like <br />
<br />
# good certificate(CN='IP232-30-00-af')<br />
<br />
followed by the appropriate snippets.<br />
<br />
==== Using multiple Phases ====<br />
When you configure multiple phases, all phases up to the final phase are stepped through and when all associated update script snippets for all phases are done, the device will ultimately stay in the final phase. This can be used e.g. to implement update script snippets which are executed once only when the device is initialized. Settings made in all but the last phase can later be overridden by the user or an administrator. The settings done in the update script settings for the final phase however are re-executed whenever one of your update script files for this phase changes.<br />
The process of deploying some initial settings is often called ''staging''.<br />
<br />
To enable staging, you therefore create a new phase that comes before the default phase (which is ''update''). Phases are sorted numerical by an attribute called ''seq''. As the default phase ''update'' is defined with ''seq=200'', your new staging phase must be defined with a lower seq value:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
...<br />
</syntaxhighlight><br />
<br />
When you define such a phase, there will be new possible update script snippet file names: <br />
<br />
# phase: staging, nextphase: update, environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
# scripts/all-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-all-default.txt does not exist<br />
# scripts/all-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone-default.txt does not exist<br />
# scripts/all-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-pre_opus_phone-default.txt does not exist<br />
# scripts/all-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone_newui-default.txt does not exist<br />
# scripts/staging-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-all-default.txt does not exist<br />
# scripts/staging-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone-default.txt does not exist<br />
# scripts/staging-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-pre_opus_phone-default.txt does not exist<br />
# scripts/staging-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone_newui-default.txt does not exist<br />
<br />
First of all, there are new names for this particular phase. Furthermore, as we now have multiple phases, the new wild-card name ''all'' is possible. <br />
<br />
An example for a staging script snippet might be a file called <code>staging-all-all.txt</code>: <br />
<br />
# change admin password<br />
config add CMD0 /user admin,my-admin-password<br />
config activate<br />
config rem CMD0 /user<br />
<br />
This will set the admin password to <code>my-admin-password</code> during staging (it should be obvious that such staging should only be done in combination with [[#Enforcing_Trust|Enforcing Trust]], see above).<br />
<br />
=== A complete <code>user-config.xml</code> Sample ===<br />
Here is a complete sample of a configuration file. <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
missing="1000"<br />
/><br />
<backup<br />
dir="backup"<br />
/><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2"<br />
CSRsan-dns-1="{name}.company.com"<br />
CSRsan-dns-2="{hwid}.company.com"<br />
CSRsan-dns-3="{rdns}"<br />
CSRsan-ip-1="{realip}"<br />
/><br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
<environments><br />
<environment id="intranet"/><br />
<environment id='sifi'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='berlin'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='verona'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='homeoffice'/><br />
<environment id='mobile'/><br />
</environments<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
</queries><br />
</config><br />
</syntaxhighlight><br />
<br />
=== Operation ===<br />
<br />
==== Configuring and Debugging a real Device ====<br />
To configure your device for use with the update server, you simply set <code>http://update.yourcompany.com/mtls/update/update.php</code> as ''Command File URL'' in ''Services/Update'' (you can optionally add a <code>?env=envname1,envname2</code> query argument if you want to set one or more specific environments for your device). The update server will re-configure the device later on so that it uses all the required query arguments.<br />
<br />
Generally, there are the following methods to set the ''Command File URL''<br />
* configure it manually using the admin GUI<br />
* provide it to the device using DHCP (this will however not work for the softwarephone or ''myPBX for Android/iOS'')<br />
* use the innovaphone provisioning service (see [[Reference10:Concept_Provisioning|Reference10:Concept Provisioning]] for details)<br />
<br />
Once the ''Command File URL'' is set on the device, you can debug what the update client in the device does, using the normal trace mechanism. For this, you will want to open the ''Debug'' page (<code>http://x.x.x.x/debug.xml</code>) and set the ''Update/Polling'' and ''Update/Execution'' check-marks. When the update client queries the update server, you will see lines like <br />
<br />
0:0826:465:5 - upd_poll: state IDLE -> RECV<br />
0:0826:465:7 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:466:0 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:695:0 - upd_poll: state=RECV sent()<br />
0:0826:752:0 - upd_poll: status 200 headercomplete=1 contentlength=0<br />
0:0826:754:0 - upd_poll: recv_data(2199)<br />
0:0826:754:1 - upd_poll: recv_data(0) EOF<br />
0:0826:754:1 - upd_poll: GET EOF - state=RECV http-status=200 length=2199<br />
0:0826:754:1 - upd_poll: do commands<br />
0:0826:754:1 - upd_poll: state RECV -> EXEC<br />
<br />
in the trace. Commands included in the update script will look like<br />
<br />
0:0826:754:5 - script::get_line: >line(mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m)<br />
0:0826:754:5 - upd_poll: pass 'mod cmd UP0 /sync scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m')<br />
<br />
Finally, you do not need to wait for the next time the device decides to contact the update server. Instead, you can force this by sending an <code>http://</code>''your-device-ip<code>/!mod cmd UP1 poll</code> to the device.<br />
<br />
==== The Device Status Update Page ====<br />
If status tracking is enabled (see [[#Device_Status | Device Status]] above), the update server will keep a list of devices it knows of. You can see this list by calling <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>.<br />
<br />
By default, the list will not be updated unless you refresh the page. However, if you set the ''refresh'' attribute in the ''status'' tag in your ''user-config.xml'' file to <code>60</code>, all entries in this list will be refreshed every 60 seconds (however, the list itself will not be reloaded, so to see new devices, you need to refresh the entire page). Devices that have not been seen for more than 7 days are shown in a different colour. Devices that have not been seen for longer than 90 days will be silently removed from the list.<br />
<br />
===== Filtering =====<br />
From build 2011, you can filter the devices shown using the ''device filter'' field in the ''Device'' column header. The filter string is matched against ip-address, serial number, type, name, phase, class, environment, firmware and bootcode. Also, you can filter by using the keywords ''present'' and ''missing'' (based on the ''missing'' attribute of the ''status'' tag in your configuration file). If one of these attributes match your filter expression, the device is shown.<br />
<br />
A filter expression must match a complete ''word''. For example, if you filter by <code>16</code> this would match the ip-address <code>172.</code>16<code>.0.20</code> but it would not match <code>192.</code>16<code>8.0.1</code>.<br />
<br />
If you specify multiple filter expressions (separated by white space), only devices which match all of the expressions will be shown. For example, if you filter by <code>172.16. ip222</code> this might match all IP222 in your 172.16.*.* network. Filter expressions are case insensitive.<br />
<br />
==== Approving Certificate Signing Requests ====<br />
When you use custom certificate roll-out, you will see a column ''Certificates'' in the device status list, showing the devices certificate status. <br />
<br />
[[Image:PHP_based_Update_Server_V2_Device_Status.png]]<br />
<br />
If a CSR has been created on the device, this will be available for download in the ''Files'' section of the ''Info'' column. You can submit the CSR to your CA and then upload the signed request (using the ''Upload'' button in the ''Files'' section of the ''Info'' column). The signed request will eventually be uploaded to the device then. On the device itself, the CSR is shown in ''General/Certificates''.<br />
<br />
[[Image:PHP_based_Update_Server_V2_CSR.png]]<br />
<br />
<br />
==== Certificate Errors ====<br />
When a signed certificate request uploaded to the device can not be installed on the device, you will see a message like <code>Certificate upload error - certificate handling stopped</code> in the ''Certificates'' columns for the device. In this case, any further processing will be stopped. Most likely, the reason is that you uploaded the wrong file as signed certificate request (either not a signed certificate at all or a signed request for another device).<br />
<br />
In this case<br />
* remove any certificate request from the device<br />
* remove the <code>X509/REQUESTERROR</code> from the device configuration (i.e. <code>vars del X509/REQUESTERROR</code>)<br />
: these 2 steps can be easily done by resetting the device to factory defaults and then set the ''Update URL'' again<br />
* remove any stored files for the device on the update server (shown in the ''Certificates'' column)<br />
<br />
The normal process will start all over again then.<br />
<br />
=== Migrating old PHP Update Server Installations ===<br />
==== Migrating from build 2000 and newer ====<br />
* Copy all files and folders, '''except''' the ''scripts'' and ''config'' folder, from the source to your destination<br />
* make sure all files and directories have correct owner (''www-data''), group (''www-data'') and mode (''read''/''write'' plus ''execute'' for directories)<br />
: for example, in WinSCP you can use the ''Properties (F9)'' dialogue on the installations root folder:<br />
: [[Image:PHP based Update Server V2-WinSCP-Properties.png]]<br />
* open the ''StatusPage'' and make sure you refresh all cached files in your browser (depending on your browser, this may happen with Ctrl-R or Ctrl-F5)<br />
<br />
==== Migrating from build 1011 and older ====<br />
To upgrade from the [http://download.innovaphone.com/ice/wiki-src/index.php?urloffset=php-update-server%2F&name=php-update-server+%28all+available+builds%29&reverselevel=0&maxbuilds=999&root=c%3A%2Finetpub%2Fwwwroot%2Fdownload%2Fice%2Fdownload%2Fp%2Fwiki-src%2Fphp-update-server%2F1010+%28PHP+based+Update+Server+final%29%2F.. old version (builds up to 1011)] of the PHP based update server (as described in [[Howto:PHP based Update Server]]), do the following<br />
<br />
* diff your <code>config.xml</code> to the one originally shipped (you can download it at [http://download.innovaphone.com/ice/download/p/wiki-src/php-update-server/1011%20(PHP%20based%20Update%20Server%20final)/php-update-server-1011.zip download.innovaphone.com/ice/wiki-src])<br />
: take note of all the changes you made to it<br />
* install the new version of the update server to a different URL<br />
* configure it according to your needs by modifying <code>user-config.xml</code>. Do not modify the new <code>config.xml</code>!<br />
** apply all modifications you did to your old <code>config.xml</code> to your new <code>user-config.xml</code><br />
** if you have used a custom setting for fwstorage/@url, you need to change it a bit. Change for example <code>url="http://myfwserver.mycompany.com</code> to <code>http://myfwserver.mycompany.com/{build}/</code> (note the trailing slash!)<br />
* copy all your update script snippet files from the old server (these are all the .txt files in the old server's root directory) to the <code>scripts</code> directory of your new server<br />
* copy the complete <code>fw</code> tree from the old server to the <code>fw</code> directory of your new server<br />
* thoroughly test your new installation with some test devices<br />
* when satisfied, edit <code>update-migrate.php</code> and change the code as described in the comment inside the file<br />
* copy <code>update-migrate.php</code> from the new installation to the old installation<br />
* on one of the devices which are currently served by the old update server, change the ''Command File URL'' so that it points to <code>update-migrate.php</code> instead of <code>update.php</code>. Do not change anything else in the URL. So if it currently is set to e.g. <br />
: <code>https://172.16.0.90/update/update.php?type=#t&env=sifi&polling=0&phase=update</code>, change it to<br />
: <code>https://172.16.0.90/update/update-migrate.php?type=#t&env=sifi&polling=0&phase=update</code><br />
* verify that this device properly switches to your new installation<br />
** the ''Command File URL'' is changed so that it points to the new installation<br />
** the device shows up in the device status page of your new server<br />
<br />
* rename <code>update.php</code> to <code>update-old.php</code> in your old update server<br />
* rename <code>update-migrate.php</code> to <code>update.php</code> in your old update server<br />
* verify that all of your devices start showing up in the new server's status page<br />
<br />
=== Complete Parameter Reference ===<br />
Note: attributes are marked with an ''at'' (<code>@</code>) prefix. So ''master/@info'' refers to the ''info'' attribute in the ''master'' tag.<br />
<br />
The following tags and attributes are available in the <code>user-config.xml</code> file, their default values are configured in <code>config.xml</code> (which you should not modify):<br />
<br />
==== master ====<br />
Defines the reference device where the firmware and boot code information is taken from.<br />
; master/@info : the URL to the device info data. Set it to an URL like <code>http://</code>''your-reference-device''<code>/CMD0/box_info.xml</code>. Please note that this device must not have the ''Password protect all HTTP pages'' set in ''Services/HTTP/Server''. If you use the literal <code>none</code>, the master device will not be queried<br />
; master/@expire : the firmware info cache lifetime. The firmware information is cached in a file (to make sure this information is present even if the reference device is off-line). The cache will not be refreshed before the ''expire'' time (in seconds) is expired<br />
; master/@cache : the cache file name. The web server must be able to write this file (see [[#Installation | ''Installation'' ]] above)<br />
; master/@user : if non-empty, this is the user name required to access the update server's web ui<br />
; master/@password : the password<br />
<br />
From build 2009, you can control the firmware and bootcode version to use on update:<br />
; master/@firmware : if set, it may be one of <br />
:: <code>master</code> (default if not set) : the firmware version is derived from the master device<br />
:: <code>none</code> : the firmware update is generally disabled<br />
:: ''build-number'' : the firmware is set to a certain ''build-number'' regardless of the firmware running on the master device<br />
; master/@bootcode: if set, it may be one of <br />
:: <code>master</code> (default if not set) : the bootcode version is derived from the master device<br />
:: <code>firmware</code> : the bootcode version is set to the firmware version. This may be useful if the master device has no bootcode (e.g. the ipva)<br />
:: <code>none</code> : the bootcode update is generally disabled<br />
:: ''build-number'' : the bootcode is set to a certain ''build-number'' regardless of the bootcode running on the master device<br />
<br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"<br />
/><br />
</syntaxhighlight><br />
<br />
==== master/applies ====<br />
Available from build 2009.<br />
<br />
Boot code and firmware updates are only executed by devices which match all of the given <applies> conditions. A condition is met if the device belongs to the class that is given as tag content. If the ''env'' attribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; master/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in <br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"><br />
<!-- applied to phones in berlin only --><br />
<applies>phone</applies><br />
<applies env="">berlin</applies><br />
</master><br />
</syntaxhighlight><br />
<br />
==== fwstorage ====<br />
Defines from where the device will retrieve the firmware files for updates.<br />
; fwstorage/@url : defines the URL to retrieve the files from. In this URL, the following meta words will be replaced as follows:<br />
:* <code>{build}</code> - the requested firmware or boot-code <br />
:* <code>{model}</code> - the devices type (e.g. <code>IP232</code>). This is derived from the ''type'' query argument used in the update URL which is set to the value <code>#t</code> which in turn is expanded to the [[Reference10:Concept_Update_Server#Scfg_command|''device type'']] of the device requesting the update script<br />
:* <code>{filetype}</code> - the type of the required file, either <code>boot</code> or <code>prot</code><br />
:* <code>{filename}</code> - (from build 2014) the boot code or firmware filename for the device in lower case. Required if you use the LAP as firmware storage and firmware files are falsely requested in uppercase letters (as URLs on the LAP are case sensitive and if the files are requested with upper case names, the original files with lowercase name are not found). This also allows you to use alternate firmware file names (such as e.g. <code>ip110-sip.bin</code>). See the ''filenames'' tag<br />
Note that if the ''url'' ends with a slash (<code>/</code>), the calling device will automatically [[Reference10:Concept_Update_Server#Prot_command|append the name of the requested file]]. In this case, the file system the URL points to must therefore contain sub-directories for each firmware build which contain the corresponding firmware builds (e.g, ''fw/12345/ip232.bin''). If ''url'' is not an URL (as is the case for the default value <code>fw/{build}/</code>), it is treated as a sub-directory underneath the update server's root directory<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="fw/{build}/"/><br />
</syntaxhighlight><br />
<br />
===== Loading firmware files from innovaphone.com =====<br />
You can also load firmware from the innovaphone.com web site. To do so, set ''url'' like so:<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="http://webbuild.innovaphone.com/{build}/"/><br />
</syntaxhighlight><br />
<br />
Please note that ''this is a voluntary service, no guarantee of availability, no service level agreement''.<br />
<br />
[[User:Ckl|ckl]] 15:40, 21 February 2023 (CET) This service has been discontinued. You may consider using <code>https://store.innovaphone.com/release/download/{build}/</code> instead.<br />
<br />
==== backup ====<br />
Defines where backup files are kept.<br />
; backup/@dir : the directory underneath the script directory where backups are stored<br />
; backup/@nbackups : the number of different backup files kept<br />
; backup/@scfg : the target URL for the scfg command. If this is not an url, it is interpreted relative to the script directory URL. You can add more options for the ''scfg'' command, like in <code>scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m ser hourly /force 1"</code> (this example will make sure backups are attempted only once per hour, so as to reduce load on the server).<br />
<br />
<syntaxhighlight lang="html5"><br />
<backup <br />
dir="backup"<br />
nbackups="10"<br />
scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m"<br />
/><br />
</syntaxhighlight><br />
<br />
==== status ====<br />
Defines details regarding the state kept for a device requesting an update script.<br />
; status/@dir : the directory the status files are kept in (e.g. <code>status</code>). Used as the name for a sub-directory underneath your install directory on your web server. You must make sure that files in this directory cannot be read by anyone without proper authentication, as such files may contain sensitive information.<br />
; status/@expire : if set and not empty, devices which have not requested an update script will be removed from the inventory after ''n'' seconds. For example, ''expire="7776000"'' will forget about devices after 90 days with no contact<br />
; status/@missing : devices are shown in a different colour (indicating that they are ''missing'') after ''n'' seconds. For example, ''missing="604800"'' will flag devices as missing after 7 days with no contact<br />
; status/@refresh : if set and not empty and larger than zero, the admin user interface showing the known devices will refresh the status of all shown devices each ''n'' seconds<br />
; status/@logkeep : number of seconds log messages for individual devices will be kept<br />
; status/usehttpsdevlinks : if set to true, links to devices in the status page are created using the https:// scheme<br />
<syntaxhighlight lang="html5"><br />
<status <br />
dir="status" <br />
expire="7776000" <br />
missing="200" <br />
refresh="10"<br />
logkeep="90"<br />
usehttpsdevlinks="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== times ====<br />
Defines details regarding the delivery of update scripts to requesting devices. <br />
<br />
; times/@dir : the directory the update scripts are stored in. For security reasons, you may want to make sure that files in this directory cannot be read without proper authentication.<br />
<br />
These attributes define the arguments of the [[Reference10:Concept_Update_Server#Times_command | times command]]. If neither ''allow'' nor ''initial'' is set, no ''times'' command is used (that is, the update scripts will be processed at all times).<br />
; times/@allow : the hours to be used in the ''times'' command (as in <code>mod cmd UP1 times /allow </code>''hours'')<br />
; times/@initial <br />
: the minutes to be used in the ''times'' command (as in <code>mod cmd UP1 times /initial </code>''minutes''). If you want to use initial staging of virgin devices ''forcestaging'' must be <code>true</code>.<br />
<br />
Delivered update scripts can include a [[Reference10:Concept_Update_Server#Check_command | check command ]] if the ''check'' attribute is set to <code>true</code>. . In this case, the devices will executed the scripts again only when you have edited/changed them.<br />
; times/@check : if set to true, update scripts will be executed once only (unless their contents change)<br />
<br />
When editing a number of update scripts, it is often not desirable if one changed script is already executed before another is changed too. When the ''grace'' attribute is set to a positive value, no script at all is delivered to requesting devices unless ''n'' seconds have expired since the last edit. For example, setting ''grace'' to 90 gives you one and a half minute to finish all your edits. <br />
<br />
; times/@grace : defines the number of seconds that must expire before update script changes take effect<br />
<br />
The update server works in either ''fast'' or ''normal'' mode. In ''fast'' mode, update scripts have to be requested more frequently, for example to step through multiple staging phases faster. This is enforced by modifying the calling device's ''Interval'' setting in [[Reference11r1:Services/Update | ''Services/Update '']] and setting it to 1 in ''fast'' mode.<br />
<br />
; times/@interval : polling interval in minutes for normal operation (that is, when all staging is done)<br />
; times/@polling : when you are using multiple ''phases'' (e.g. ''staging'' and ''update''), this defines the number of seconds to wait before the device reads the scripts for the next phase (see [[Reference10:Concept_Update_Server#Provision_command | provision command]]) (please do not modify the default value)<br />
<br />
In some installations it may be desired to deliver update scripts with HTTPS only or even only to calling devices which have identified themselves with a proper TLS certificate. <br />
<br />
; times/@forcehttps : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS. If the device is ready to receive an update script and still calls in with HTTP only, its ''Command File URL'' will be re-configured to use HTTPS automatically.<br />
; times/@httpsport : if you use a non-standard port for HTTPS access to update scripts, it can be defined here<br />
; times/@httpsurlmod : if your HTTPS URL differs from the HTTP URL, you can specifiy a ''modifier''. It has the form <code>!</code>''pattern''<code>!</code>''replacement''<code>!</code> where ''pattern'' is a [http://docs.php.net/manual/en/pcre.pattern.php PHP PCRE pattern]. For example, <code>!mtls/!!i</code> will remove the string <code>mtls/</code> from the URL used by the device to create the HTTPS URL to use. The delimiter (in this case "!") can be changed according to your own wishes, the first character defines the delimiter. The "i" at the end ignores uppercase.<br />
<br />
Note: If you want to change the hostname of your URL, you have to add the Port 444 to your hostname. The code should then look like this: <code>!hostname1:444/mtls/!hostname2:444/!i</code>.<br />
; times/@forcetrust : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS and a valid and trusted client certificate that identifies itself as the devices it claims to be<br />
; times/@forcestaging : before build 2009, the restrictions imposed by the times/@allow settings affected all update ''phases''. This however makes staging cumbersome, as you usually want to restrict normal configuration changes to off-working-hours. If it is <code>true</code>, the restriction will be applied only to the last phase (that is, the ''staging'' phases will execute immediately). Also, the final phase will be executed once by staged devices. <br />
: Since build 2021 the standard value was changed to <code>true</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
dir="scripts" <br />
allow="" <br />
initial="" <br />
check="true" <br />
polling="5" <br />
interval="15" <br />
grace="90" <br />
forcehttps="true" <br />
httpsport="444" <br />
forcetrust="false" <br />
httpsurlmod="!mtls/!!i"<br />
forcestaging="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== phases ==== <br />
Defines the scripting phases. In many installations, there are initializations which should be performed once only. This is known as the ''staging'' phase. Once this is done, the devices continue with the execution of the normal update scripts (this phase is known as ''update'' by default). In the (unlikely) event that you want more or less phases, you can add/remove ''phase'' tags.<br />
==== phases/phase ====<br />
; phases/phase/@id : the name for the phase<br />
; phases/phase/@seq: the sequential number for the phase. Phases are stepped-through in the numeric order defined by their ''seq'' attribute. <br />
<br />
By virtue of the ''seq'' attribute, you can insert phases in to the set of phases defined by default, which is<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<!-- sequence is important! --><br />
<phase id="staging" seq="100"/><br />
<phase id="update" seq="200"/><br />
</phases><br />
</syntaxhighlight><br />
For example,<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<phase id="phase" seq="150"></phase><br />
</phases><br />
</syntaxhighlight><br />
will insert a custom phase ''myphase'' as second phase.<br />
<br />
==== environments ====<br />
Defines the distinguished environments. Devices can have zero or more ''environments'' associated. The environments a device is considered to be in must be passed as <code>?env=</code>''name1,name2,..'' URL query arg in the update url. <br />
<br />
Update scripts for all environments listed will be applied.<br />
First the environment scripts itself, then all implied environments in the same order as the ''implies'' tags are listed in the config. <br />
<br />
The default config file defines the environments ''intern'' and ''homeoffice'' but you can define your own if you like.<br />
<br />
==== environments/environment ====<br />
; environments/environment/@id : the name for the environment<br />
<br />
==== environments/environment/implies ====<br />
Each ''environment'' may have a number of sub-tags of type ''implies''. It contains the ''id'' of another ''environment''. If a device is in an environment with an ''implies'' sub-tag, it is assumed that it is in the implied environment also. <br />
<syntaxhighlight lang="html5"><br />
<environments><br />
<environment id='hq'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='intranet'/><br />
<environment id='homeoffice'/><br />
</environments><br />
</syntaxhighlight><br />
The <code>implies</code> tag handles no recursion (therefore, in the above example, if ''intranet'' would have an implies, a device in environment ''hq'' would not inherit this implication. Instead, you must list it explicitly for the ''hq'' environment).<br />
<br />
If multiple environments will be used they will be applied also in the same order as the ''environment'' tags are listed in the config.<br />
<br />
;There are some special things to keep in mind when adding multiple environments in the ''env'' attribute and they use the ''implies'' tag in the config.<br />
:- The first environment specified in the arg ''env'' will be processed as expected with all children.<br />
:- Recursive ''implies'' from an environment added via ''implies'' is not performed. (No children's children)<br />
:- Only the first environment which is specified in the ''env'' arg with implies is processed, all additional values in the arg ''env'' will be ignored.<br />
:- If the environment in the arg ''env'' it is not the first value, then it must also use the ''implies'' config to implies itself. Otherwise only child are processed.<br />
<br />
==== classes ====<br />
Defines the available device classes. Devices of different classes (e.g. phones and gateways) can have different update scripts. The device class is derived from the <code>#t</code> (device type) query arg of the update script URL (which is why you must configure it in the update URL). By default, the classes ''phone'', ''opus_phone'', ''pre_opus_phone'', ''phone_oldui'', ''phone_newui'', ''mobile'', ''gw'', ''fxogw'', ''fxsgw'', ''brigw'', ''prigw'' are recognized (newer versions may include more and/or updated class definitions). Also, any device is considered to be member of a class that has the device type as class-name. For example, an IP112 is in a class called ''ip112''.<br />
<br />
A given device can be in multiple classes and will execute all update scripts available for these classes.<br />
<br />
==== classes/class ====<br />
Each ''class'' has a number of sub-tags of type ''model''. It contains the value replaced for the <code>#t</code> query arg. The models listed belong to the class.<br />
; classes/class/@id : the name of the class<br />
<br />
<syntaxhighlight lang="html5"><br />
<classes><br />
<class id="gw"><br />
<model>ip0010</model><br />
...<br />
</class><br />
<class id="phone"><br />
<model>ip110</model><br />
<model>ip232</model><br />
...<br />
</class><br />
<class id="phone_oldui"><br />
<model>ip110</model><br />
...<br />
</class><br />
<class id="phone_newui"><br />
<model>ip232</model><br />
...<br />
</class><br />
</classes><br />
</syntaxhighlight><br />
<br />
==== nobootdev ====<br />
Device types listed here do not receive boot code updates.<br />
===== nobootdev/model =====<br />
Each model tag defines a device type that shall not receive boot code updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nobootdev> <br />
<model>ipva</model><br />
<model>ipvadec</model><br />
<model>swphone</model><br />
<model>mypbxa</model><br />
<model>mypbxi</model><br />
</nobootdev><br />
</syntaxhighlight><br />
<br />
==== nofirmdev ====<br />
Device types listed here do not receive firmware updates.<br />
===== nofirmdev/model =====<br />
Each model tag defines a device type that shall not receive firmware updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nofirmdev> <br />
<model>swphone</model><br />
<model>mypbxi</model><br />
</nofirmdev><br />
</syntaxhighlight><br />
<br />
==== filenames ====<br />
Available from build 2014. Allows you to define alternate firmware and boocode names for use with the <code>{filename}</code> replacement (see the ''fwstorage'' tag), if required.<br />
<br />
===== filenames/model =====<br />
Each model defines a set of alternative file names for a certain device type.<br />
; filenames/model/@id : the (lowercase) device type identfier (e.g. <code>ip112</code>). If this matches the device type of the calling device, the alternate file names are replaced<br />
; filenames/model/@prot: the alternate firmware file name<br />
; filenames/model/@boot: the alternate boot file name<br />
<syntaxhighlight lang="html5"><br />
<filenames><br />
<model id="mypbxa" prot="mypbx.apk"/><br />
</filenames><br />
</syntaxhighlight><br />
<br />
defines the firmware file name <code>mypbx.apk</code> for the device type <code>mypbxa</code>.<br />
<br />
==== stdargs ====<br />
Better don't touch :-)<br />
<br />
==== customcerts ====<br />
innovaphone hardware devices come with a device specific, trust-able ''device certificate''. However, in many situations it is desirable to roll-out customer-created ''custom certificates'' to all devices. The update server can do this. This will replace the shipped devices certificates both on hardware devices (where all such certificates are derived from innovaphone's certificate authority) as well as on soft devices which run with a self.-signed certificate by default (such as the software-phone or ''myPBX for Android/iOS'').<br />
<br />
; customcerts/@dir : the name of the directory underneath your installation directory which stores device certificates. you should make sure that this directory cannot be accessed without proper authentication<br />
; customcerts/@CAkeys : a file name pattern (e.g. <code>CAkey*</code>). All files in ''customcerts/@dir'' which match the pattern must contain DER or PEM encoded public keys which form the ''certificate chain'' of your CA (usually it is only one and the public key of your CA). One key per file. The files, when sorted by name, must contain the CA key itself in the last file (any intermediate certificates, if any, in the other files in correct order)<br />
; customcerts/@CAtype : must be <code>manual</code> currently<br />
; customcerts/@CAname : a comma-separated list of CA names. Devices presenting a certificate signed by one of these CAs will be considered as having a valid certificate. Otherwise, they wil be instructed to create a ''certificate signing request'' (CSR) for subsequent submission to your CA<br />
; customcerts/@CAnamesep : defines the name separator for ''customcerts/@CAname''. The default is '<code>,</code>' and you must change it to a different value if one of your CAs includes a comma in its name<br />
; customcerts/@renew : if set and not 0, certificates are replaced by new ones ''n'' days before they expire<br />
; customcerts/@CAwildcard : normally, the update server will accept a certificate only if its CN matches the device's serial number. However, sometimes, so-called ''wildcard certificates'' such as <code>*.innovaphone.com</code> are used on a device (e.g. on a PBX or on a ''reverse proxy''). If set, the update server will also accept a certificate that contains a CN which equals the value of ''customcerts/@CAwildcard''. Note that the CN must match literally. For example, if ''''customcerts/@CAwildcard'' is set to <code>*.innovaphone.com</code> a CN like <code>host.innovaphone.com</code> is not accepted<br />
<br />
<br />
When the update server determines that a device calls in with an un-trusted or expired certificate, it will have it create a signing-request for a new certificate. The requested certificate properties can be defined:<br />
<br />
; customcerts/@CSRkey : the key size, e.g. <code>2048-bit</code>. Note that we do not recommend keys larger than 2048 bit (see [[Reference7:Certificate_management#Certificate_Key_Length_and_CPU_Usage]] for details)<br />
; customcerts/@CSRsignature : the signature algorithm, e.g. <code>SH256</code><br />
; customcerts/@CSRdn-cn : the CN (''common name'') part used for the DN<br />
; customcerts/@CSRdn-ou : the OU (''organizational unit'') part used for the DN<br />
; customcerts/@CSRdn-o : the O (''organization'') part used for the DN<br />
; customcerts/@CSRdn-l : the L (''locality'') part used for the DN<br />
; customcerts/@CSRdn-st : the ST (''state or province'') part used for the DN<br />
; customcerts/@CSRdn-c : the C (''country'') part used for the DN (note: ''CSRdn'' for many CAs, must be a 2-letter ISO country code)<br />
; customcerts/@CSRsan-dns-1 : the first of up to three DNS names <br />
; customcerts/@CSRsan-ip-1 : the first of up to two IP addresses<br />
<br />
Within the set of CSR attributes, the following meta-words will be replaced:<br />
<br />
* <code>{realip}</code> : the real IP address of the device as reported in the <code>ip=#i</code> query argument<br />
* <code>{ip}</code> : the IP address of the device as seen by the update server<br />
* <code>{proxy}</code> : the IP address of the reverse proxy the device used to reach the update server<br />
* <code>{sn}</code> : the serial number of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>009033030df0</code>)<br />
* <code>{hwid}</code> : the hardware-id of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>IP1200-03-0d-f0</code>)<br />
* <code>{name}</code> : the ''Device Name'' (set in ''General/Admin'') of the device<br />
* <code>{rdns}</code> : the DNS name found in a reverse DNS lookup for the real-ip address reported by the device (see ''{realip}'') above<br />
<br />
<syntaxhighlight lang="html5"><br />
<customcerts <br />
dir="certs" <br />
CAkeys="CAkey*" <br />
CAtype="manual" <br />
CAname="innovaphone-INNO-DC-W2K8-Zertifizierungsserver" <br />
CAnamesep="," <br />
CAwildcard="*.innovaphone.com"<br />
CSRkey="2048-bit" <br />
CSRsignature="SH256" <br />
CSRdn-cn="{hwid}" <br />
CSRdn-ou="" <br />
CSRdn-o="" <br />
CSRdn-l="" <br />
CSRdn-st="" <br />
CSRdn-c="" <br />
CSRsan-dns-1="{name}.company.com" <br />
CSRsan-dns-2="{hwid}.company.com" <br />
CSRsan-dns-3="{rdns}" <br />
CSRsan-ip-1="{realip}" <br />
CSRsan-ip-2="" <br />
renew="90" /><br />
</syntaxhighlight><br />
<br />
==== queries ====<br />
If queries are defined and applicable for the calling device, it will be instructed to send certain information to the update server which will be stored in the device status file kept on the server. Each piece of such information is defined using a separate ''query'' tag.<br />
<br />
; queries/@scfg : defines the URL used by the device to send the information. Normally not changed from the default<br />
<br />
==== queries/query ====<br />
Defines one piece of information to be submitted to the update server.<br />
<br />
; queries/query/@id : a unique name for the query<br />
; queries/query/@title : a title for this piece of information shown in the device status<br />
<br />
<syntaxhighlight lang="html5">.<br />
<!-- retrieve and show some details from IP4/General/STUN page --><br />
<query id="media" title="Media"><br />
<cmd>mod cmd MEDIA xml-info</cmd><br />
<applies>*</applies><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
<show title="Server">concat('STUN: ', /state/queries/media/info/@stun, ' TURN: ', /state/queries/media/info/@turn, ' (', /state/queries/media/info/@turn-user, ')')</show><br />
</query><br />
<br />
<!-- show primary phone registration --><br />
<query id="registration" title="Registration"><br />
<cmd>mod cmd PHONE/USER phone-regs /cmd phone-regs</cmd><br />
<applies>phone</applies><br />
<show title="State: User/Number">concat(/state/queries/registration/info/reg[@id = "0"]/@state, ': ' , /state/queries/registration/info/reg[@id = "0"]/@h323, '/', /state/queries/registration/info/reg[@id = "0"]/@e164)</show><br />
<show title="PBX/ID">concat(/state/queries/registration/info/reg[@id = "0"]/@gk-addr, '/', /state/queries/registration/info/reg[@id = "0"]/@gk-id)</show><br />
</query><br />
</syntaxhighlight><br />
<br />
==== queries/query/cmd ====<br />
The content of this tag defines the command to be executed on the device which outputs the requested information. These are ''mod cmd''s typically.<br />
<br />
<syntaxhighlight lang="html5"><br />
<!-- get info for custom certificates --><br />
<cmd>mod cmd X509 xml-info</cmd><br />
</syntaxhighlight><br />
<br />
See [[Howto:Effect_arbitrary_Configuration_Changes_using_a_HTTP_Command_Line_Client_or_from_an_Update#Using_the_Mechanism_for_Device_Status_Queries ]] for details on how to find out which commands to use.<br />
<br />
==== queries/query/applies ====<br />
Defined queries are only executed by devices which belong to the class that is given as tag content. If the ''env'' atribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; queries/query/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in (unfortunately, you can not combine both)<br />
<syntaxhighlight lang="html5"><br />
<!-- applied to phones only --><br />
<applies>phone</applies><br />
</syntaxhighlight><br />
<br />
==== queries/query/show====<br />
If one or more ''show'' tags are defined for the ''query'' tag, the data will be shown in the device status page. The values shown are defined by the tag content which is an XPath expression selecting the data from the device state. The XPath expression always begins with <code>/state/queries/</code>''query-id'' (as defined by the ''id'' attribute in the query tag). <br />
<br />
; queries/query/show/@title : used as title when the values are displayed in the status page<br />
<br />
<syntaxhighlight lang="html5"><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
</syntaxhighlight><br />
<br />
== Download ==<br />
*[http://download.innovaphone.com/ice/wiki-src#php-update-server http://download.innovaphone.com/ice/wiki-src/] - download the complete file package of scripts and files described in this article<br />
: you need to use build 2000 or higher. Older versions are described in [[Howto:PHP_based_Update_Server]]<br />
<br />
== Hints for writing your update snippets ==<br />
Writing update scripts is largely undocumented and sometimes tricky. Here are some general guidelines.<br />
<br />
=== Using <code>config add/del</code> ===<br />
Many setting are done using <code>config change</code> commands. So these are very useful in update scripts too.<br />
<br />
The straight forward method to find out which commands you need is as follows:<br />
* get a device of the type in question and do factory reset<br />
* save the configuration to a file<br />
* do the desired settings using the normal web admin UI<br />
* save the resulting configuration to another file<br />
* compare the saved files<br />
<br />
Usually, you want to set only specific parts of the configuration line. For example, assume you want to set the default coder to ''OPUS-WB''. Your saved configuration line might look like<br />
<br />
config change PHONE SIG /prot SH323 /gk-addr pbx.innovaphone.com /gk-id innovaphone.com /tones 0 /lcoder OPUS-WB,20, /coder OPUS-WB,20,k1<br />
<br />
To set only the coder settings, you would set the relevant options with the <code>config add</code> command as follows:<br />
<br />
config add PHONE SIG /lcoder OPUS-WB,20,<br />
config add PHONE SIG /coder OPUS-WB,20,k1<br />
<br />
You can also remove specific options, as in <br />
<br />
config rem PHONE SIG /tones<br />
<br />
which would remove the <code>/tones 0</code> from the configuration line for <code>PHONE SIG</code>. <br />
<br />
Note that the PHP update server will add the<br />
<br />
config write<br />
config activate <br />
iresetn<br />
<br />
at the end of the delivered script automatically, so you do not need to do it yourself. In fact, it is not recommended to have any reset command in your snippets.<br />
<br />
==== <code>config change</code> Lines with associated Passwords ====<br />
Sometimes, there is a password associated with certain configurations. Consider the STUN/TURN configuration: <br />
<br />
config change MEDIA /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd 2 /nat-detect 61<br />
<br />
The TURN password, being sensitive, needs to be encrypted in your configuration. This is why it is stored as a ''VAR'', which provides encryption support (see below):<br />
<br />
vars create MEDIA/TURN-PWD pc ....<br />
<br />
Unfortunately, the relationship between an option and an associated VAR needs to be guessed. You can be sure that the module name in the <code>config change</code> matches the modules name in the <code>vars create</code> command. However, the variable name must be guessed. <br />
<br />
You can also set the password in your script using the <code>vars create</code><!-- , but this does make sense only in staging (that is pre-update-phase) scripts (see below for a discussion why). If you need to do it in a script snippet for the update phase, you should -->, however, you may also consider using a <code>mod cmd</code> (see below) such as e.g.<br />
<br />
mod cmd MEDIA form /cmd form /del %2Fstun-slow /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd my-turn-pw /nat-detect 61<br />
<br />
=== Using <code>vars create</code> ===<br />
<br />
Sometimes it makes sense to use <code>vars create</code> commands. The syntax is<br />
<br />
<code> vars create </code>''<name> <flags> <value>''<br />
<br />
A ''<name>'' is a module name followed by a variable name, optionally followed by an index. For example, a phone will store the currently selected main registration as <code>vars create PHONE/ACTIVE-USER p </code>''<index>''. So the module is <code>PHONE</code> and the variable name is <code>ACTIVE-USER</code>. When the variable is multi-valued, an index is added to the name. For example, the registration settings for all but the first registration are stored as indexed variable, as in <code>vars create PHONE/USER-REG/00001 p </code>''<settings>'', where <code>00001</code> is the index (registrations count from 0 in this case).<br />
<br />
There are a number of flags which can be combined, the most prominent used in update scripts are:<br />
; p : permanent. The var is stored in flash memory (you will almost always use this flag in update scripts)<br />
; c : crypted. The var value needs to be encrypted and the ''<value>'' given is crypted<br />
; x : crypted, plain value. The var needs to be encrypted but the ''<value>'' is given as plain (that is, un-encrypted) text. This allows you to set an encrypted variable without encrypting the desired value<br />
; b : binary. The var value is binary. Its ''<value>'' is given as a bin2hex string<br />
<br />
Please note that using <code>vars create</code> will ''always'' set the internal ''reset needed condition'' in the device (regardless which ''<value>'' is set). This means that a subsequent <code>resetn</code> or <code>iresetn</code> will always trigger a reset. So if you use it, make sure that you use the ''times/@check'' ([[#times | see above]]) to protect your script from re-executing the snippet (and thus rebooting the device) all the time. <br />
<!--<br />
When you use a <code>mod cmd UP1 check ...</code> command in your script (see times/@check [[#times | above]]), this will create an endless loop if you use <code>vars create</code> followed by one of the <code>resetn</code> commands in the code guarded by the <code>check</code> command. This code will never be fully executed, as the ''reset needed'' condition will always be set and the reset will always happen. You can use it in ''pre-update-phase'' scripts (that is, during the staging process) as this is not protected by a <code>check</code> command.<br />
--><br />
<br />
=== Using <code>mod cmd</code> ===<br />
See [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]] for a discussion of how to use <code>mod cmd</code> in update scripts.<br />
<br />
== Writing your own Dynamic Scripting Code ==<br />
NB: this feature is available from build 2020.<br />
<br />
By default, the update server delivers update scripts which are composed from a number of static files (so-called ''snippets''). Sometimes it makes sense however, to create such snippets dynamically (e.g. based on a database lookup). Here is how to do this.<br />
<br />
Dynamic scripting is implemented by a user-supplied custom <code>class CustomUpdateSnippet</code>. The code for this class must be placed in to the file <code>config/scripting.class.php</code>. As a starter, sample class code is available in <code>config/scripting.class-sample.php</code>.<br />
<br />
<syntaxhighlight lang="php"><br />
<?php<br />
<br />
/**<br />
* to provide your own runtime generated snippets, rename this file to 'scripting.class.php' and <br />
* implement the member functions<br />
* $property will have one member called <br />
*/<br />
<br />
class CustomUpdateSnippet extends UpdateSnippet {<br />
<br />
/**<br />
* snippet to deliver after standard text snippets<br />
* @return array of string<br />
*/<br />
public function getPostSnippet() {<br />
return parent::getPostSnippet();<br />
}<br />
<br />
/**<br />
* snippet to deliver before standard text snippets<br />
* @return array of strings<br />
*/<br />
public function getPreSnippet() {<br />
return parent::getPreSnippet();<br />
}<br />
<br />
/** <br />
* do we want to suppress the standard text snippets?<br />
* @return boolean<br />
*/<br />
public function sendStandardSnippets() {<br />
return parent::sendStandardSnippets();<br />
}<br />
}<br />
?><br />
</syntaxhighlight><br />
<br />
The sample code overrides of the classes member functions just call the base classes which do nothing at all. So if you just copy the sample code into <code>scripting.class.php</code>, nothing will change. However, if <code>getPreSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''before'' the standard snippets are sent. Likewise, if <code>getPostSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''after'' the standard snippets are sent. If <code>sendStandardSnippets()</code> returns false, the standard snippets will not be sent.<br />
<br />
To determine the dynamic snippets to send, the user-provided <code>class CustomUpdateSnippet</code> has access to the members of its base <code>class UpdateSnippet</code>, namely the <code>var $statexml</code> member. This member contains a <code>SimpleXMLElement</code> object with all state information available for the calling device.<br />
<br />
Here is a sample state found in <code>$statexml</code> (as output by the <code>dump()</code> member function): <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0"?><br />
<state seen="1522065435" requested="130774bootcode" phase="update"><br />
<device sn="00-90-33-3e-0c-57" type="IP112" classes="phone, opus_phone, phone_newui, hstrace, ip112" ip="127.0.0.1" rp="false" phase="update"<br />
environments="sifi, 172net" certstat="either certificate handling or state tracking not enabled - not doing any certificate checking" hwid="IP222-46-5c-77" realip="172.16.80.241" requestDownloaded="false"/><br />
<firmware requested="130986" requested-at="1522065435"/><br />
<bootcode requested-at="1522065435"/><br />
<config filename="scripts/all-all-all.txt" delivered="1522065426" version="1486559970"/><br />
</state><br />
</syntaxhighlight><br />
<br />
The most interesting elements in this XML are probably the attributes of the <code>state</code> and the <code>device</code> tags.<br />
<br />
== Hints for using custom SSL Certificates ==<br />
<br />
Using your own SSL certificates can be useful depending on the scenario.<br />
<br />
We have two methods to accomplish this:<br />
<br />
=== Standard scenario as described in this wiki article ===<br />
The idea is that each certificate request is checked manually, edited and the UpdateServer is provided with the new device certificate.<br />
<br />
=== Special scenario with automated certificate renewal ===<br />
The idea is that every new/unknown (untrusted) device connects to the staging server. Here the device is released by manual checking/authorization by getting its first certificate. All further extensions can happen automatically afterwards.<br />
<br />
'''Important: All important settings concerning file permissions, access and co must be used from the above article and are only skipped in this part!'''<br />
<br />
We will create two Update-Server instances with the following configuration.<br />
<br />
# Staging Server (HTTPS/443)<br />
#*Put the sources in <code>/var/www/innovaphone/update</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]].<br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!update/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code| above]]<br />
#* '''Important''': Do not submit any further customer specific device configurations in the staging Server<br />
# Final MTLS / 444 update server<br />
#*Put the sources in <code>/var/www/innovaphone/mts</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]] with <code>renew = "x"</code><br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!mtls/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code | above]]<br />
#*Store all customer specific device configurations<br />
<br />
<br />
The meaning of the two different instances is:<br />
*The staging server <code>https://[ip]/update/admin/admin.php</code> will generate the provisioning URLs for new devices.<br />
*All new/unknown (untrusted) devices are only in the staging server.<br />
**Here you can check the devices and provide them a valid certificate after check/authorization.<br />
*With a valid certificate, the UpdateURL is automatically changed to the MTLS instance.<br />
*As soon as a device connects via MTLS we trust this device and we can use an automatic extension of the certificates.<br />
** Waiting CSR requests are in the configured folder ''certs'' in the notation <code>"[mac]-request.p10.pem"</code><br />
** If you operate your own CA, feel free to automate the reissue of the certificates via a sheduled Cronjob<br />
*** Help for Linux: [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
*** Help for Windows: [[Howto:Creating_custom_Certificates_using_a_Windows_Certificate_Authority]]<br />
** If a signed certificate with the format <code>"[mac]-signedrequest.cer"</code> is placed in the certs folder, the certificate is automatically updated on the device.<br />
<br />
== Optional Configurations on the Linux Application Platform ==<br />
===Support for more Connections===<br />
By default the LAP's web server lighttpd allows for 512 concurrent connections with 1024 open file descriptors. When you serve a huge number of devices with the update server, then this might be too low. You will see some devices not being able to contact the update server for a while. This should not be a real issue as the devices will retry. However, updating all devices may take long due to this.<br />
<br />
In the lighttpd log (in <code>/var/log/lighttpd</code> on the LAP), you will see entries like this:<br />
<br />
2017-10-16 13:45:18: (network_linux_sendfile.c.140) open failed: Too many open files <br />
2017-10-16 13:45:18: (mod_fastcgi.c.3075) write failed: Too many open files 24 <br />
2017-10-16 13:45:18: (server.c.1434) [note] sockets disabled, out-of-fds <br />
2017-10-16 16:23:25: (response.c.634) file not found ... or so: Too many open files /mtls/updatev2/web/innovaphone_logo_claim_fisch.png -><br />
2017-10-16 17:57:45: (server.c.1432) [note] sockets disabled, connection limit reached <br />
<br />
If you experience such issues, proceed as follows:<br />
<br />
* connect to the LAP with SFTP (e.g. using WinSCP)<br />
* change directory to <code>/etc/lighttpd</code><br />
* edit <code>lighttpd.conf</code><br />
* search for the 2 lines which set <code>server.max-fds</code> and <code>server.max-connections</code><br />
* replace the standard values. We recommend to set the number of file descriptors to 4 times the number of requests when using the update server, e.g. <br />
: old<br />
:: server.max-fds = 1024<br />
:: server.max-connections = 512<br />
: new<br />
:: server.max-fds = 8000<br />
:: server.max-connections = 2000<br />
* save the file<br />
* restart linux (''Diagnostics/Reset/Reboot'')<br />
<br />
Be sure to closely monitor the ''Diagnostics/Status'' page for a while after such configuration. <br />
<br />
Also, when you update the LAP, you will have to re-do the changes (as always when you change something ''under the hood'').<br />
<br />
=== Debug files rotation based on logrotate ===<br />
If you have to debug during operation and a lot of devices access the PHP Update Server V2, you have to keep track of the debug files or automatically limit them. For this you can use logrotate on the innovaphone Linux AP. With logrotate you can apply time-based or size-based rules when files are packed and when they are deleted.<br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files. <br />
* Under the path <code>/etc/logrotate.d</code> create a file e.g. <code>updateserver</code>.<br />
<br />
<code>/etc/logrotate.d/updateserver</code><br />
<br />
In this file now enter the following content and save it.<br />
<br />
<code><br />
/var/www/innovaphone/mtls/update/debug/*.txt {<br />
size 5M<br />
missingok<br />
rotate 2<br />
compress<br />
delaycompress<br />
notifempty<br />
create 644 www-data www-data<br />
}<br />
</code><br />
<br />
This will include all *.txt files from the debug directory of the PHP Update Server V2 in the logrotate process of the operating system.<br />
<br />
; size 5M : means every 5MB the file is rotated or zipped<br />
; size 5k : means every 5kB the file is rotated or zipped<br />
Alternatively, you can also rotate the file based on time<br />
; daily : means that every day the file is rotated or zipped<br />
; weekly : means that the file is rotated or zipped daily<br />
<br />
; missingok : if a debug file does not exist, it will be ignored<br />
; rotate n : files are removed before the last ones are deleted.<br />
; compress : compresses the old debug files<br />
; delaycompress : compresses the debug data only after it has been moved once<br />
; notifempty : empty log files are not rotated<br />
; create 644 www-data www-data : creates a new, empty debug file with appropriate permissions<br />
<br />
== Update Server and Reverse Proxy ==<br />
It is possible to implement access to the update server through a ''reverse proxy'' (RP). However, you have to keep some issues in mind:<br />
* ''Mutual Transport Layer Security'' (MTLS) is not possible<br />
: MTLS requires a direct TLS connection between the two parties. An RP however would terminate the TLS connection and entertain two independent connections to the parties. This breaks MTLS. If you want to use MTLS and serve external clients, you must therefore provide a direct access to the update server (that is an external IP address either directly or through port forwarding)<br />
: see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts]] for details<br />
* When using the RP, you can choose to forward encrypted traffic (HTTPS) arriving from external to the update server using HTTP (unencrypted). The update server however eventually rewrites the update URL in the calling devices configuration. In order to do this, it determines the type of the internal connection. So if the connection from the RP to the update server is using HTTP, it would create a ''http://...'' URL. Otherwise it would create an ''https://..'' URL (also, it would be using the same port). This way, if the RP forwards requests to the update server with HTTP, the synthesized new URL for the calling client would use HTTP too, even though the original request was sent with HTTPS. This may or may not be what you want (as all of your clients would end up using non-encrypted traffic). Even worse, it might not work at all, if the RP does not accept HTTP for example.<br />
<br />
== Related Articles ==<br />
* [[Reference10:Concept_Update_Server]]<br />
* [[Reference12r1:DHCP_client]]<br />
* [[Reference10:Concept_Linux_Application_Platform]]<br />
* [[Reference10:Concept_Provisioning]]<br />
* [[Howto:PHP_based_Update_Server]] (old version)<br />
* [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
* [[Howto:Creating custom Certificates using a Windows Certificate Authority]]<br />
* [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]]<br />
<br />
<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:PHP_based_Update_Server_V2&diff=70796Howto:PHP based Update Server V22024-02-07T14:46:31Z<p>Ckl: /* The Device Status Update Page */</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
* web server running PHP 5 (e.g. Linux Application Platform)<br />
<br />
All Versions prior to 13r1 (for 13r1 and later, see [[Reference13r1:Concept App Service Devices]] instead). <br />
<br />
By default, the [[Reference10:Concept_Update_Server | Update Manager]] mechanism reads a file that corresponds to the device type (e.g. <code>update-ip222.htm</code>). While this makes sense (update scripts may vary by device type), it is sometimes tedious, as you have to edit a huge amount of files which typically are (at least partly) identical.<br />
<br />
Here is a PHP script that can be used as an ''Update Server'' that allows you to simplify the handling of update scripts. It also includes a mechanism that makes sure that all devices always have the same firmware installed as a given ''master device'' has. Finally, it implements a straight forward configuration backup scheme.<br />
<br />
This article describes version 2 of this update server (build 2006 and up). The previous version is described in [[Howto:PHP_based_Update_Server]]. The enhancements are<br />
* Status user interface showing all known devices<br />
* Ability to roll out custom device certificates<br />
* Ability to provide configuration files to MTLS-authenticated devices only (e.g. in order to keep certain configuration settings secure)<br />
* Hide configuration files from public read access<br />
<br />
==More Information==<br />
=== Requirements ===<br />
The update server script requires a web server with working PHP 5.3 or higher platform. It has been tested with Apache and ligHTTPD (on a [[Reference10:Concept Linux Application Platform | Linux Application Platform]]). On IIS, neither the certificate roll-out nor the MTLS authentication or configuration backup works with IIS.<br />
<br />
The platform running the PHP scripts must have a valid time setting!<br />
<br />
=== Features ===<br />
The update server can<br />
* update all your devices with boot code and firmware that corresponds to the versions running on a reference device of your choice<br />
* save backups of your devices configuration. Backups are saved only if the configuration changed since the last backup<br />
* invoke your own update scripts depending on <br />
** various phases (e.g. you can have ''staging'' scripts which are only executed once and normal scripts which are executed in normal operation later on)<br />
** devices classes (e.g. you can have different scripts for phones and gateways)<br />
** environments (e.g. you can have scripts for your internal devices and others for devices in home offices)<br />
** the device serial number<br />
* roll out customer specific device certificates<br />
* roll out update scripts to devices only that have identified themselves with a valid device certificate (MTLS)<br />
* maintain a list of devices in your installation along with some vital information about each individual device<br />
<br />
=== Installation ===<br />
<br />
==== On the Linux Application Platform ====<br />
Here is how you would install it on the [[Reference10:Concept_Linux_Application_Platform|LAP]]. Some of the steps may not be necessary if you don't want to use all features.<br />
<br />
On the ''Linux Application Platform'', you would <br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files<br />
* create a new root directory underneath <code>/var/www/innovaphone/mtls</code>, e.g. <code>/var/www/innovaphone/mtls/update</code><br />
* download the complete file package of scripts and files [http://download.innovaphone.com/ice/wiki-src/#php-update-server here] <br />
* copy all files and directories in to this new directory<br />
** create your local config files. We will never overwrite these in further updates.<br />
***Rename <code>config/user-config-sample.xml</code> to <code>config/user-config.xml</code><br />
***Rename <code>scripts/all-all-all-sample.txt</code> to <code>scripts/all-all-all.txt</code><br />
* change owner and group of all files and directories to <code>www-data</code>, change mode to 0600 for all files and 0700 for all directories (see [[#Migrating_from_build_2000_an_newer | below]] for how to do this with WinSCP)<br />
<br />
* log in to the LAP's admin UI (if you have not yet changed it, the default credentials will be <code>admin/linux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]) and open its ''Administration/Web Server/Change web server properties and public access to the web/webdav'' configuration UI. Add the following paths to ''Public Web Paths'' (assuming your base directory is called <code>update</code>):<br />
** <code>/mtls/update</code><br />
** <code>/mtls/update/web</code><br />
** <code>/mtls/update/admin</code><br />
** <code>/mtls/update/fw/</code> (note the trailing slash!)<br />
: make sure that no other sub-directories of ''update'' are listed<br />
: make sure you have no trailing '/' in any of these paths (except ''fw/')<br />
: this will make sure the update server's admin user interface is not accessible to the public<br />
<br />
At this point, you should be able to access the update server's admin user interface, e.g. <code>http://update.yourcompany.com/mtls/update/admin/admin.php</code>. However, the only thing you see is a login page. Please note that your browser should ask you for a password for this page (unless you already entered it before). Cookies must be enabled for the login page to work properly. <br />
<br />
===== If you want to provide HTTPS Acess to the Update Server =====<br />
For HTTPS access to the update server, you may want to install your own server certificate under ''Administration/Certificates/Current server certificate''. However, this is not strictly required (you will experience some warning messages when using your browser to access the update server, however, calling devices will work just fine). <br />
<br />
===== If you want to setup MTLS-restricted Delivery of Update Scripts =====<br />
If you think you have sensitive information in your update scripts, you should make sure to deliver such scripts to your own verified devices only. This can be done by authenticating calling devices with ''mutual TLS'' (MTLS). In this case, the calling device must use HTTPS to retrieve the update script and present a trusted client certificate that identifies itself as the calling device (that is, has the device serial as the certificate's ''common name'' (CN)). <br />
<br />
For this feature, you need to ''Configure mutual TLS'' in the LAP's ''Administration/Web Server'' panel. Simply tick the ''Active'' check-mark and select an appropriate ''MTLS Port''. The port must not conflict with any other TCP port used on the LAP, so neither 80 nor 443 is a good choice. 444 is a good choice. Although it [http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=8 is assigned to the snpp protocol] by IANA, it is not used by the LAP (and probably rarely used anyway).<br />
<br />
If MTLS is already activated on your LAP, simply take note of the currently configured ''MTLS Port''.<br />
<br />
<br/><br />
Please note that MTLS access is ''not possible'' through a ''reverse proxy'' (RP). This is because the RP always will terminate the incoming TLS connection and establish its own to the target. Therefore, the client certificate presented to the target server is the RP's certificate, not the original clients certificate. In our context - where MTLS is used to verify the identity of the original caller - clients must not access the update server through an RP.<br />
<br />
You can either expose your update server directly to the internet (and carefully think through the security implications) or create specific TCP port forwarding towards the update server in your NAT-router/firewall. In this case, we recommend to use non-standard HTTP and HTTPS ports on the NAT router (cause this will already keep most of the HTTP port scanners out there in the internet from functioning).<br />
<br />
<br/><br />
Also, you will need the public key of all the CAs you will trust. These must be configured in to the web server so it can trust the certificates your devices will present to it. See [[#Enforcing_Trust]] for details.<br />
<br />
==== On an Apache Server running on the Windows Operating System ====<br />
The update server will run on Apache too. However, as we did not test this setup thoroughly, we recommend to use the LAP's LigHTTPD instead. <br />
<br />
* For hints on using MTLS on an Apache Web Server, see [[Reference10:Concept_Provisioning#Enforcing_mutual_TLS_on_Apache | Enforcing mutual TLS on Apache]]<br />
* For hints on getting the ''innovaphone device certificate authority'' public keys (which you may or may not need), see [[Reference10:Concept_Provisioning#How_to_get_inno-dev-ca-certificate.crt | How to get inno-dev-ca-certificate.crt ]]<br />
<br />
==== On Microsoft's IIS ====<br />
We do not recommend to use IIS as <br />
* it does not support PUT easily<br />
* it is not compatible with the innovaphone device's MTLS implementation<br />
<br />
===Update existing installation===<br />
<br />
* download the [http://download.innovaphone.com/ice/wiki-src/#php-update-server new sources]<br />
* copy all files and directories to your existing installation folder (overwrite existing files)<br />
* change owner and group of all files and directories to www-data, change mode to 0600 for all files and 0700 for all directories<br />
<br />
===Configuration===<br />
<br />
If you intend to deliver your update scripts with MTLS (that is, with HTTPS and mutual certificate check)<br />
* you will need to have the full name of your ''certificate Authority'' (CA) as it is noted as ''Common Name'' (CN) in your CA's certificate<br />
* also, you will need a PEM version of your CA's public key (a ''PEM version'' of a certificate is a text file that begins with a line like <code>-----BEGIN CERTIFICATE-----</code>)<br />
<br />
Also, you need to know the ''MTLS Port'' configured in your LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts| If you want to setup MTLS-restricted Delivery of Update Scripts ]] above).<br />
<br />
All tweakable parameters are set in <code>user-config.xml</code>. You may want to use an XML-capable editor such as notepad++, netbeans or Visual-Studio for editing. if your editor supports it, you benefit from the ''document type description'' (DTD) provided in <code>full-config.dtd</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="false" debugcerts="false" debugscript="false"><br />
</config><br />
</syntaxhighlight><br />
<br />
The attributes of the <code>config</code> tag control some aspects of debugging. For now, simply set all 3 to <code>"true"</code> instead of <code>"false"</code>. Do not forget to revert them back to <code>"false"</code> when everything runs smoothly, large log files will result if not. <br />
<br />
==== Securing Access ====<br />
To control access to the update server's data, you should set a login. This can be done in the ''master'' tag by specifying both ''user'' and ''password'':<br />
<br />
<syntaxhighlight lang="html5"><br />
<master ... user="myadmin" password="mysecret"/><br />
</syntaxhighlight><br />
Default username and password are admin/password.<br />
<br />
==== Delivering Firmware and Boot Code ====<br />
At this point, you can simulate a device by requesting an update script using an URL like <code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code> in your browser (please note that this URL should not ask you for a password). Most likely, your browser will wait a little while and then say something like:<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# cannot get PBX info: cannot access http://yourmasterdevice.youdomain.tld/CMD0/box_info.xml, reading cache<br />
# cannot get cached PBX info: cannot access cache/master-info.xml - exit<br />
<br />
This is because you did not yet specify the ''master device'' which always runs the reference firmware. <br />
<br />
The idea here is that you have one innovaphone device in your system where the firmware is always manually updated. All other devices shall follow this reference firmware. The update server will deliver the firmware that runs on the master device to calling devices. For this to work, you need to set the ''info'' attribute of the [[#master|''master'']] tag and leave everything else ''as is''. <br />
<br />
Remember that you do all your local configuration changes in <code>user-config.xml</code>.<br />
<br />
As ''master'' is a first level tag, you can add it directly underneath the opening ''<config>'' tag. The only thing we need to define right now is the path to read the firmware information from your master device. Let's say your reference device has the IP address <code>172.16.0.10</code>, you end up with <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true" debugcerts="true" debugscript="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml" user="myadmin" password="mysecret"/><br />
</config><br />
</syntaxhighlight><br />
(you could use a DNS name instead of the IP address of course). <br />
The ''info'' URL is used by the update server itself only, it is never used by devices requesting an update script.<br />
<br />
When you refresh the page that simulates a device requesting an update script, the output should now change to something like<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# current firmware (unknown) does not match required firmware (130178)<br />
...<br />
<br />
This is to say that your master device runs firmware 130178 and the calling device does not (actually, as the calling client is simulated by your browser, it does not transmit its current firmware to the update server so it is ''(unknown)'').<br />
<br />
In order to actually update the clients with firmware and boot code, the files must be made available to the calling client somehow. This is done by specifying an URL in the [[Reference10:Concept_Update_Server#Prot_command | prot ]] and [[Reference10:Concept_Update_Server#Boot_command | boot ]] commands sent to the calling device:<br />
<br />
...<br />
# current firmware (unknown) does not match required firmware (130178)<br />
mod cmd UP0 prot http://update.yourcompany.com/mtls/update/fw/130178/ ser 130178<br />
# current boot code (unknown) does not match required boot code (130112)<br />
mod cmd UP0 boot http://update.yourcompany.com/mtls/update/fw/130112/ ser 130112<br />
...<br />
<br />
By default, the URL generated points to a sub-directory of your update server called ''fw'': <code>http://update.yourcompany.com/mtls/update/fw/130178/</code>. Note that this default URL expects sub-directories underneath the ''fw'' directory which correspond to the firmware and boot code build number. The file structure thus would be like<br />
<br />
/var/www/innovaphone/mtls/update/fw<br />
/130178<br />
/ip232.bin<br />
/130112<br />
/boot232.bin<br />
<br />
Note that the URL ends with a slash (<code>/</code>). This instructs the calling device to append the appropriate file name itself when retrieving the file (see [[Reference10:Concept_Update_Server#Prot_command | the prot command ]] for details). <br />
<br />
However, you can also specify any other URL by setting the ''url'' attribute in the ''fwstorage'' tag of your <code>user-config.xml</code>. In this attribute, certain meta words will be replaced (see [[#fwstorage | the ''fwstorage'' tag ]] for details). The default setting for example is <code>fw/{build}/</code>.<br />
<br />
You can disable firmware and boot code updates altogether by setting the ''info'' attribute in the ''master'' tag to an empty value.<br />
<br />
==== Your own Update Script Files ====<br />
The update server will deliver update scripts to calling devices which are synthesized from a number of ''update script snippets'' which you define yourself. The idea is that many devices share parts of their configuration while other parts are different. This depends on<br />
* the device ''class'' (e.g. a phone or a gateway)<br />
: the device class is automatically derived from its model. In fact, a single device may be in multiple classes. For example, an IP232 is in these classes: ''phone, pre_opus_phone, phone_newui'' which basically says that its a phone and has no OPUS codec yet but a "new" user interface (as opposed to the old one found e.g. on an IP110). A number of useful classes are pre-defined, but you can add your own in <code>user-config.xml</code>.<br />
* the device's environment (e.g. ''home-office'', ''branch-office'')<br />
: These reflect the fact that devices may need different configuration if they are in different locations (or environments). The update server does not know about a device's environment, which is why you need to configure it in to the devices update URL if you need this. By default, there is a single environment defined called ''default'', but of course, you can add your own<br />
* phase<br />
: configuring devices may need multiple phases to go through. If more than one phase is defined, the device will go through all phases sequentially, which is why a ''phase'' has a numerical ''seq'' attribute. Phases are went through in order of this attribute. When the device has reached the last phase, it will stay there. By default, there is only one phase called ''update''<br />
<br />
You can define update script snippets for any combination of device, environment and phase. You do so simply by placing appropriate files in to the ''scripts'' directory on your update server.<br />
<br />
Let's have a closer look at the web page we used to simulate a device calling for an update script before (<code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code>). It will show you all the possible files it would deliver to the device:<br />
<br />
# possible files (from scripts):<br />
# scripts/update-all-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-all-default.txt does not exist<br />
# scripts/update-phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone-default.txt does not exist<br />
# scripts/update-pre_opus_phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-pre_opus_phone-default.txt does not exist<br />
# scripts/update-phone_newui-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
The update script snippet files need to have proper names to define when they should be delivered. As you can see, they all start with <code>update-</code> which means that they apply to devices which are in the ''update'' phase. As by default there is only a single phase defined, all possible file names start with <code>update-</code>. <br />
<br />
The next part here is either <code>phone-</code>, <code>pre_opus_phone-</code>, <code>phone_newui-</code> or <code>all-</code>. This is because the calling IP232 is in three classes, so all respective snippets will be delivered to it. ''all'' however is a wild-card. That is, such files will be delivered to all devices, no matter what class they are in. You may wonder why there was no ''all'' for the phase. This is because there is only a single phase defined. If there would be more, the ''all''-variation of the phase-part of the file name would be appear.<br />
<br />
The third part finally is either <code>default</code> or <code>00_90_33_00_00_00</code> in our case. ''default'' is the name of the only ''environment'' that is defined by default. ''00_90_33_00_00_00'' is the device's serial number which is treated as an implicitly defined environment name. This allows you to deliver certain snippets to a single device only.<br />
<br />
Now let's create an update script snippet that is delivered to all phones in the default environment when they are in the update phase. The file name (which looks like ''phase''<code>-</code>''class''<code>-</code>''environment''<code>.txt</code>) has to be <code>update-phone-default.txt</code> therefore. Just for an exercise, let us set the device name (as shown in the browser's title bar) to ''This is a Phone!''.<br />
The command to do so is <code>config add CMD0 /name This+is+a+Phone%21</code>, so your file may look like this<br />
<br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
(btw: did you observe that config line arguments need to be url-encoded?). Now create the file and upload it in to the ''scripts'' directory of you update server. When you now refresh the page which simulates the device calling for an update script, you will see something like this:<br />
<br />
...<br />
# newest script (scripts/update-phone-default.txt) 11s old<br />
# files being updated (scripts/update-phone-default.txt 11s old, waiting for at least 90s to expire)<br />
<br />
When you update multiple script snippets, it is often undesirable that a device retrieves an inconsistent state of the scripts you are working on (e.g. if you already have saved one but not yet the other). To avoid this, the update server will look at the files modification dates and if one of those that needs to be delivered to the device is younger than 90 seconds, it will not send any of them at all. <br />
<br />
So when you wait for the 90 seconds to pass and refresh the page again, you will see something like this:<br />
<br />
# firmware build info cache is current<br />
# phase: update, nextphase: , environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
...<br />
# scripts/update-phone-default.txt 4.1 mins<br />
...<br />
# scripts/update-phone-default.txt<br />
# { begin script 'scripts/update-phone-default.txt' <br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
<br />
# end script 'scripts/update-phone-default.txt' }<br />
<br />
# trigger reset if required<br />
config write<br />
config activate<br />
iresetn<br />
<br />
As you can see, your snippet has been delivered by the update script. However, the update server has also added some trailing commands which write back the config to the devices flash memory (<code>config write</code>), activate it (<code>config activate</code>) and trigger a reset if needed (<code>iresetn</code>).<br />
<br />
If there are multiple snippets available for a calling device, all of them are concatenated in the sequence shown in the header of the returned script (where the possible file names are listed)<br />
<br />
==== Device Status ====<br />
When you have a lot of devices which are served by the update server, you may want to have a list of these devices along with some useful information regarding each devices. <br />
<br />
You can enable this by defining the ''status'' tag in your <code>user-config.xml</code>. So open the file and add the tag right next to the ''master'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
/><br />
</config><br />
</syntaxhighlight><br />
<br />
When you have done so, please refresh the page that simulates your calling device and then open <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>. You will now see a device list (well, a list with a single device, the one you simulated using your browser). The list will show some information regarding the devices that requested an update script lately. For more options, see [[#status]].<br />
<br />
==== Device Backup ====<br />
It is generally useful to have recent backups of all of your device configurations. The update server supports that if you enable it by defining the ''backup'' tag in your <code>user-config.xml</code>. Simply put it right next to the ''master'' or ''status'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<backup<br />
dir="backup"<br />
/><br />
</syntaxhighlight><br />
<br />
If you have done so and then refresh the page that simulates your calling device, you will now see <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
mod cmd UP0 scfg http://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m<br />
<br />
instead of <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
# Backups not enabled in config<br />
<br />
When a client requests an update script the next time, this will initiate a configuration backup which is stored underneath the ''backup'' directory. For each device, a sub-directory is created and all backup files are stored therein. By default, the last 10 differing configuration backups are kept.<br />
<br />
==== Controlling the Time-frames when Snippets are delivered ====<br />
The update client built-in to innovaphone devices allows to restrict updates to certain time frames (e.g. only during the night). This can be controlled using the [[Reference10:Concept_Update_Server#Times_command | times ]] command.<br />
<br />
You can configure the update server to use the time commands by setting the ''allow'' and ''initial'' attributes in the ''times'' tag in your <code>user-config.xml</code>. <br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"/><br />
</syntaxhighlight><br />
<br />
If either the ''allow'' or ''initial'' attribute is present, the update script will contain a line like<br />
<br />
...<br />
# Restricted times<br />
mod cmd UP1 times /allow 22,23,0,1,2,3,4 /initial 1<br />
<br />
In the example above, all update script activity will occur only between 10pm and 5am (local device time). For more details, see [[Reference10:Concept_Update_Server#Times_command | Concept Update Server]]<br />
<br />
==== Using and Enforcing HTTPS ====<br />
Your devices can either use HTTP or HTTPS to access the update server. In normal operation, the update server will generate URLs (e.g. the URLs used to retrieve firmware or to backup configurations) for the same protocol. <br />
<br />
However, you can enforce use of HTTPS by setting the ''forcehttps'' attribute in the [[Howto:PHP_based_Update_Server_V2#times | ''times'' ]] tag to <code>true</code>. If so, a device calling in with HTTP will be re-configured to use HTTPS:<br />
<br />
...<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
<br />
# changed state query args: polling, phase<br />
# new url=https://update.yourcompany.com/mtls/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i<br />
...<br />
(note that if you are using a non-standard port for HTTPS, you must define it using the ''httpsport'' attribute).<br />
<br />
==== Delivering Custom Certificates ====<br />
innovaphone devices come with pre-defined, trustworthy device certificates. However, all ''soft'' devices (such as the softwarephone, myPBX for Android/iOS or the IPVA) do not. Also, in many scenarios customers run their own ''public key infrastructure'' (PKI) and request their own certificates to be used instead of the pre-defined. This is why there is a need to roll-out custom certificates to devices. <br />
<br />
The update server supports this task to an extend. It can<br />
* check if a calling device has a proper certificate<br />
* instruct a calling device without proper certificate to create a ''certificate signing request'' (CSR)<br />
* download the CSR to the update server for easy access by the administrator<br />
* upload a signed CSR to the device<br />
* upload the public key(s) of the CA in to the device's trust list<br />
<br />
In order to roll-out custom certificates with the update server, the administrator needs to<br />
* run his own PKI (a.k.a. ''certificate authority'' (CA))<br />
* monitor CSRs that appear in the update server's device list<br />
* approve the request by manually submitting it to his own CA<br />
* upload the signed CSR to the update server<br />
<br />
Also, all devices must run ''V12r1 SR8'' or newer before the new certificate can be uploaded. Devices with older firmware will be updated automatically (see [[#Delivering_Firmware_and_Boot_Code]] above). However, the new firmware must be ''V12r1 SR8'' or later. <br />
<br />
To learn how you can create proper device certificates with your own windows CA, see [[Howto:Creating custom Certificates using a Windows Certificate Authority]].<br />
<br />
By default, custom certificates are turned off and you will see a note like<br />
<br />
# certificates: either certificate handling or state tracking not enabled - not doing any certificate checking<br />
<br />
in the delivered update script.<br />
<br />
Custom certificates are turned on by adding a ''customcerts'' tag to your <code>user-config.xml</code>:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
The page that simulates your calling device will now include a line saying:<br />
<br />
# certificates: we dont know anything about the current certificate state - not doing any certificate checking <br />
<br />
In order to decide if or if not a calling device has a valid certificate, the device needs to send its current public key to the update server. This is done by enabling ''queries'' in your <code>user-config.xml</code>. ''Queries'' is a means to have the device submit certain information to the update server. In the update server's default configuration, two queries are defined but not enabled:<br />
<br />
* the query ''certificates'' sends the current certificate state <br />
* the query ''admin'' sends the current device name (as set in ''General/Admin'')<br />
<br />
To enable a query, you must specify the class a calling device must be in in order to execute the query. This is done by adding an ''applies'' tag for the respective query in your configuration:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
</query><br />
</queries><br />
...<br />
</syntaxhighlight><br />
<br />
This basically says that both queries should be executed by devices of just any class. Unless you enable queries, your update script will include lines such as:<br />
<br />
# no queries defined for any of callers classes: phone+pre_opus_phone+phone_newui<br />
<br />
Once you have enabled them, you will see something like<br />
<br />
...<br />
# query 'certificates'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=certificates ser nop /always mod%20cmd%20X509%20xml-info<br />
# query 'admin'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=admin ser nop /always mod%20cmd%20CMD0%20xml-info<br />
...<br />
<br />
You can display the data sent by a query in the device status display. To do so, you need to define one or more ''show'' tags as part of the respective ''query'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<query id="certificates"><br />
<applies>*</applies><br />
<!-- show device certificate CNs and Issuer CNs in status page --><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
...<br />
</syntaxhighlight><br />
<br />
Two steps however are still missing: <br />
<br />
* a) you need to tell the update server the names of all the CAs you consider trustworthy. This is done by listing their names in the ''CAname'' attribute of the ''customcerts'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2,innovaphone-INNO-DC-W2K8-Zertifizierungsserver"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
: The example shown defines that you will accept both of innovaphone's device certificate authorities and also your own CA called ''innovaphone-INNO-DC-W2K8-Zertifizierungsserver''. Devices with device certificates issued by one of these CAs will be left untouched. For all other devices (for example, any ''myPBX for Android/iOS'' device that has a self-signed certificate), the generation of a custom certificate will be initiated.<br />
<br />
* And b) you need to provide the public key of your CA (so it can be uploaded to the calling device's trust list). You need to place the DER (not PEM) encoded public key in to a file called <code>certs/CAkey-01.cer</code> on your update server (if you want to upload the whole certificate chain, put all of the public keys in the chain in to separate additional files called <code>certs/CAkey-02.cer</code> and so forth.<br />
<br />
For more details on how to approve certificate signing requests, see [[#Approving Certificate Signing Requests]] below.<br />
<br />
==== Enforcing Trust ====<br />
Update script snippets may include sensitive information which you do not want to disclose to the public. Even though you can force the use of HTTPS (see above), this still does not keep your data secure. After all, an attacker could simply request an update script from your update server using HTTPS. To secure your data, you need to make sure that snippets are only sent to devices which identify themselves using a trusted certificate with a correct ''common name'' (CN). This is done using ''mutual transport layer security'' (MTLS). Therefore, you need to configure MTLS in the LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts | If you want to setup MTLS-restricted Delivery of Update Scripts]] above). <br />
<br />
You can enable this by setting ''forcetrust'' to <code>true</code> and ''httpsport'' to the port configured as ''MTLS Port'' in your web server. This is done in the ''times'' tag of your <code>user-config.xml</code>.<br />
<syntaxhighlight lang="html5"><br />
<times <br />
...<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
</syntaxhighlight><br />
Please note that ''forcetrust'' does nothing unless ''forcehttps'' is also set!<br />
<br />
If so, the update server will deliver update script snippets only to clients which identify themselves with a proper certificate. For this to work, you will need to add the public key of your certificate authority to your web server's list of trusted certificates. <br />
<br />
When using the ''Linux Application Platform'' (LAP), you need to add the PEM-encoded public key of your certificate authority to the ''innovaphone-ca.pem'' file in <code>/home/root/ssl_cert</code>. When you have installed the LAP, this file will contain the PEM-encoded public keys of the 2 innovaphone device certificate authorities. You can simply add the public key of your CA to the end of this file:<br />
<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca2 public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...your-ca public key...<br />
-----END CERTIFICATE-----<br />
<br />
You will need to restart the LigHTTPD then (most easily done by restarting the entire LAP (''Diagnose/Reset'') or by issuing the command <code>/etc/rc2.d/S02lighttpd restart</code> from the linux root command prompt). Finally, you must set the ''forcetrust'' attribute. <br />
(Note that the ''innovaphone-ca.pem'' file may be overwritten when a new LAP version is installed. It is thus a good idea to keep a copy and check it after an upgrade).<br />
<br />
When ''forcetrust'' is effective and the calling device does not use HTTPS, it will be reconfigured to do so:<br />
<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
...<br />
# new url=https://update.yourcompany.com:444/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i&env=default<br />
<br />
If the calling device uses HTTPS but sends no certificate, you will see a message like<br />
<br />
# cannot verify CN with HTTPS: SSL_CLIENT_S_DN_CN not present - fix web server configuration or use MTLS-enabled port!<br />
<br />
This is probably because it is using a non-MTLS enabled port for HTTPS (e.g. the standard port 443) although MTLS is configured for a different port. <br />
<br />
If the calling device uses HTTPS and sends a certificate but the CN does not match the serial number of the device, you will see a message such as<br />
<br />
# Device claims to be 'IP232-30-00-af' but identifies as 'CKL-CELSIUS-W10.innovaphone.sifi' by TLS<br />
<br />
If the calling device uses HTTPS and sends a certificate but the certificate is not trusted because its issuer is not listed in ''innovaphone-ca.pem'' (see above), the connection will be refused <br />
<br />
In all such cases, no snippet will be delivered. If the certificate is good, you will see a message like <br />
<br />
# good certificate(CN='IP232-30-00-af')<br />
<br />
followed by the appropriate snippets.<br />
<br />
==== Using multiple Phases ====<br />
When you configure multiple phases, all phases up to the final phase are stepped through and when all associated update script snippets for all phases are done, the device will ultimately stay in the final phase. This can be used e.g. to implement update script snippets which are executed once only when the device is initialized. Settings made in all but the last phase can later be overridden by the user or an administrator. The settings done in the update script settings for the final phase however are re-executed whenever one of your update script files for this phase changes.<br />
The process of deploying some initial settings is often called ''staging''.<br />
<br />
To enable staging, you therefore create a new phase that comes before the default phase (which is ''update''). Phases are sorted numerical by an attribute called ''seq''. As the default phase ''update'' is defined with ''seq=200'', your new staging phase must be defined with a lower seq value:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
...<br />
</syntaxhighlight><br />
<br />
When you define such a phase, there will be new possible update script snippet file names: <br />
<br />
# phase: staging, nextphase: update, environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
# scripts/all-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-all-default.txt does not exist<br />
# scripts/all-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone-default.txt does not exist<br />
# scripts/all-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-pre_opus_phone-default.txt does not exist<br />
# scripts/all-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone_newui-default.txt does not exist<br />
# scripts/staging-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-all-default.txt does not exist<br />
# scripts/staging-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone-default.txt does not exist<br />
# scripts/staging-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-pre_opus_phone-default.txt does not exist<br />
# scripts/staging-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone_newui-default.txt does not exist<br />
<br />
First of all, there are new names for this particular phase. Furthermore, as we now have multiple phases, the new wild-card name ''all'' is possible. <br />
<br />
An example for a staging script snippet might be a file called <code>staging-all-all.txt</code>: <br />
<br />
# change admin password<br />
config add CMD0 /user admin,my-admin-password<br />
config activate<br />
config rem CMD0 /user<br />
<br />
This will set the admin password to <code>my-admin-password</code> during staging (it should be obvious that such staging should only be done in combination with [[#Enforcing_Trust|Enforcing Trust]], see above).<br />
<br />
=== A complete <code>user-config.xml</code> Sample ===<br />
Here is a complete sample of a configuration file. <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
missing="1000"<br />
/><br />
<backup<br />
dir="backup"<br />
/><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2"<br />
CSRsan-dns-1="{name}.company.com"<br />
CSRsan-dns-2="{hwid}.company.com"<br />
CSRsan-dns-3="{rdns}"<br />
CSRsan-ip-1="{realip}"<br />
/><br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
<environments><br />
<environment id="intranet"/><br />
<environment id='sifi'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='berlin'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='verona'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='homeoffice'/><br />
<environment id='mobile'/><br />
</environments<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
</queries><br />
</config><br />
</syntaxhighlight><br />
<br />
=== Operation ===<br />
<br />
==== Configuring and Debugging a real Device ====<br />
To configure your device for use with the update server, you simply set <code>http://update.yourcompany.com/mtls/update/update.php</code> as ''Command File URL'' in ''Services/Update'' (you can optionally add a <code>?env=envname1,envname2</code> query argument if you want to set one or more specific environments for your device). The update server will re-configure the device later on so that it uses all the required query arguments.<br />
<br />
Generally, there are the following methods to set the ''Command File URL''<br />
* configure it manually using the admin GUI<br />
* provide it to the device using DHCP (this will however not work for the softwarephone or ''myPBX for Android/iOS'')<br />
* use the innovaphone provisioning service (see [[Reference10:Concept_Provisioning|Reference10:Concept Provisioning]] for details)<br />
<br />
Once the ''Command File URL'' is set on the device, you can debug what the update client in the device does, using the normal trace mechanism. For this, you will want to open the ''Debug'' page (<code>http://x.x.x.x/debug.xml</code>) and set the ''Update/Polling'' and ''Update/Execution'' check-marks. When the update client queries the update server, you will see lines like <br />
<br />
0:0826:465:5 - upd_poll: state IDLE -> RECV<br />
0:0826:465:7 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:466:0 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:695:0 - upd_poll: state=RECV sent()<br />
0:0826:752:0 - upd_poll: status 200 headercomplete=1 contentlength=0<br />
0:0826:754:0 - upd_poll: recv_data(2199)<br />
0:0826:754:1 - upd_poll: recv_data(0) EOF<br />
0:0826:754:1 - upd_poll: GET EOF - state=RECV http-status=200 length=2199<br />
0:0826:754:1 - upd_poll: do commands<br />
0:0826:754:1 - upd_poll: state RECV -> EXEC<br />
<br />
in the trace. Commands included in the update script will look like<br />
<br />
0:0826:754:5 - script::get_line: >line(mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m)<br />
0:0826:754:5 - upd_poll: pass 'mod cmd UP0 /sync scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m')<br />
<br />
Finally, you do not need to wait for the next time the device decides to contact the update server. Instead, you can force this by sending an <code>http://</code>''your-device-ip<code>/!mod cmd UP1 poll</code> to the device.<br />
<br />
==== The Device Status Update Page ====<br />
If status tracking is enabled (see [[#Device_Status | Device Status]] above), the update server will keep a list of devices it knows of. You can see this list by calling <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>.<br />
<br />
By default, the list will not be updated unless you refresh the page. However, if you set the ''refresh'' attribute in the ''status'' tag in your ''user-config.xml'' file to <code>60</code>, all entries in this list will be refreshed every 60 seconds (however, the list itself will not be reloaded, so to see new devices, you need to refresh the entire page). Devices that have not been seen for more than 7 days are shown in a different colour. Devices that have not been seen for longer than 90 days will be silently removed from the list.<br />
<br />
===== Filtering =====<br />
From build 2011, you can filter the devices shown using the ''device filter'' field in the ''Device'' column header. The filter string is matched against ip-address, serial number, type, name, phase, class, environment, firmware and bootcode. Also, you can filter by using the keywords ''present'' and ''missing'' (based on the ''missing'' attribute of the ''status'' tag in your configuration file). If one of these attributes match your filter expression, the device is shown.<br />
<br />
A filter expression must match a complete ''word''. For example, if you filter by <code>16</code> this would match the ip-address <code>172.</code>16<code>.0.20</code> but it would not match <code>192.</code>16<code>8.0.1</code>.<br />
<br />
If you specify multiple filter expressions (separated by white space), only devices which match all of the expressions will be shown. For example, if you filter by <code>172.16. ip222</code> this might match all IP222 in your 172.16.*.* network. Filter expressions are case insensitive.<br />
<br />
==== Approving Certificate Signing Requests ====<br />
When you use custom certificate roll-out, you will see a column ''Certificates'' in the device status list, showing the devices certificate status. <br />
<br />
[[Image:PHP_based_Update_Server_V2_Device_Status.png]]<br />
<br />
If a CSR has been created on the device, this will be available for download in the ''Files'' section of the ''Info'' column. You can submit the CSR to your CA and then upload the signed request (using the ''Upload'' button in the ''Files'' section of the ''Info'' column). The signed request will eventually be uploaded to the device then. On the device itself, the CSR is shown in ''General/Certificates''.<br />
<br />
[[Image:PHP_based_Update_Server_V2_CSR.png]]<br />
<br />
<br />
==== Certificate Errors ====<br />
When a signed certificate request uploaded to the device can not be installed on the device, you will see a message like <code>Certificate upload error - certificate handling stopped</code> in the ''Certificates'' columns for the device. In this case, any further processing will be stopped. Most likely, the reason is that you uploaded the wrong file as signed certificate request (either not a signed certificate at all or a signed request for another device).<br />
<br />
In this case<br />
* remove any certificate request from the device<br />
* remove the <code>X509/REQUESTERROR</code> from the device configuration (i.e. <code>vars del X509/REQUESTERROR</code>)<br />
: these 2 steps can be easily done by resetting the device to factory defaults and then set the ''Update URL'' again<br />
* remove any stored files for the device on the update server (shown in the ''Certificates'' column)<br />
<br />
The normal process will start all over again then.<br />
<br />
=== Migrating old PHP Update Server Installations ===<br />
==== Migrating from build 2000 and newer ====<br />
* Copy all files and folders, '''except''' the ''scripts'' and ''config'' folder, from the source to your destination<br />
* make sure all files and directories have correct owner (''www-data''), group (''www-data'') and mode (''read''/''write'' plus ''execute'' for directories)<br />
: for example, in WinSCP you can use the ''Properties (F9)'' dialogue on the installations root folder:<br />
: [[Image:PHP based Update Server V2-WinSCP-Properties.png]]<br />
* open the ''StatusPage'' and make sure you refresh all cached files in your browser (depending on your browser, this may happen with Ctrl-R or Ctrl-F5)<br />
<br />
==== Migrating from build 1011 and older ====<br />
To upgrade from the [http://download.innovaphone.com/ice/wiki-src/index.php?urloffset=php-update-server%2F&name=php-update-server+%28all+available+builds%29&reverselevel=0&maxbuilds=999&root=c%3A%2Finetpub%2Fwwwroot%2Fdownload%2Fice%2Fdownload%2Fp%2Fwiki-src%2Fphp-update-server%2F1010+%28PHP+based+Update+Server+final%29%2F.. old version (builds up to 1011)] of the PHP based update server (as described in [[Howto:PHP based Update Server]]), do the following<br />
<br />
* diff your <code>config.xml</code> to the one originally shipped (you can download it at [http://download.innovaphone.com/ice/download/p/wiki-src/php-update-server/1011%20(PHP%20based%20Update%20Server%20final)/php-update-server-1011.zip download.innovaphone.com/ice/wiki-src])<br />
: take note of all the changes you made to it<br />
* install the new version of the update server to a different URL<br />
* configure it according to your needs by modifying <code>user-config.xml</code>. Do not modify the new <code>config.xml</code>!<br />
** apply all modifications you did to your old <code>config.xml</code> to your new <code>user-config.xml</code><br />
** if you have used a custom setting for fwstorage/@url, you need to change it a bit. Change for example <code>url="http://myfwserver.mycompany.com</code> to <code>http://myfwserver.mycompany.com/{build}/</code> (note the trailing slash!)<br />
* copy all your update script snippet files from the old server (these are all the .txt files in the old server's root directory) to the <code>scripts</code> directory of your new server<br />
* copy the complete <code>fw</code> tree from the old server to the <code>fw</code> directory of your new server<br />
* thoroughly test your new installation with some test devices<br />
* when satisfied, edit <code>update-migrate.php</code> and change the code as described in the comment inside the file<br />
* copy <code>update-migrate.php</code> from the new installation to the old installation<br />
* on one of the devices which are currently served by the old update server, change the ''Command File URL'' so that it points to <code>update-migrate.php</code> instead of <code>update.php</code>. Do not change anything else in the URL. So if it currently is set to e.g. <br />
: <code>https://172.16.0.90/update/update.php?type=#t&env=sifi&polling=0&phase=update</code>, change it to<br />
: <code>https://172.16.0.90/update/update-migrate.php?type=#t&env=sifi&polling=0&phase=update</code><br />
* verify that this device properly switches to your new installation<br />
** the ''Command File URL'' is changed so that it points to the new installation<br />
** the device shows up in the device status page of your new server<br />
<br />
* rename <code>update.php</code> to <code>update-old.php</code> in your old update server<br />
* rename <code>update-migrate.php</code> to <code>update.php</code> in your old update server<br />
* verify that all of your devices start showing up in the new server's status page<br />
<br />
=== Complete Parameter Reference ===<br />
Note: attributes are marked with an ''at'' (<code>@</code>) prefix. So ''master/@info'' refers to the ''info'' attribute in the ''master'' tag.<br />
<br />
The following tags and attributes are available in the <code>user-config.xml</code> file, their default values are configured in <code>config.xml</code> (which you should not modify):<br />
<br />
==== master ====<br />
Defines the reference device where the firmware and boot code information is taken from.<br />
; master/@info : the URL to the device info data. Set it to an URL like <code>http://</code>''your-reference-device''<code>/CMD0/box_info.xml</code>. Please note that this device must not have the ''Password protect all HTTP pages'' set in ''Services/HTTP/Server''. If you use the literal <code>none</code>, the master device will not be queried<br />
; master/@expire : the firmware info cache lifetime. The firmware information is cached in a file (to make sure this information is present even if the reference device is off-line). The cache will not be refreshed before the ''expire'' time (in seconds) is expired<br />
; master/@cache : the cache file name. The web server must be able to write this file (see [[#Installation | ''Installation'' ]] above)<br />
; master/@user : if non-empty, this is the user name required to access the update server's web ui<br />
; master/@password : the password<br />
<br />
From build 2009, you can control the firmware and bootcode version to use on update:<br />
; master/@firmware : if set, it may be one of <br />
:: <code>master</code> (default if not set) : the firmware version is derived from the master device<br />
:: <code>none</code> : the firmware update is generally disabled<br />
:: ''build-number'' : the firmware is set to a certain ''build-number'' regardless of the firmware running on the master device<br />
; master/@bootcode: if set, it may be one of <br />
:: <code>master</code> (default if not set) : the bootcode version is derived from the master device<br />
:: <code>firmware</code> : the bootcode version is set to the firmware version. This may be useful if the master device has no bootcode (e.g. the ipva)<br />
:: <code>none</code> : the bootcode update is generally disabled<br />
:: ''build-number'' : the bootcode is set to a certain ''build-number'' regardless of the bootcode running on the master device<br />
<br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"<br />
/><br />
</syntaxhighlight><br />
<br />
==== master/applies ====<br />
Available from build 2009.<br />
<br />
Boot code and firmware updates are only executed by devices which match all of the given <applies> conditions. A condition is met if the device belongs to the class that is given as tag content. If the ''env'' attribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; master/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in <br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"><br />
<!-- applied to phones in berlin only --><br />
<applies>phone</applies><br />
<applies env="">berlin</applies><br />
</master><br />
</syntaxhighlight><br />
<br />
==== fwstorage ====<br />
Defines from where the device will retrieve the firmware files for updates.<br />
; fwstorage/@url : defines the URL to retrieve the files from. In this URL, the following meta words will be replaced as follows:<br />
:* <code>{build}</code> - the requested firmware or boot-code <br />
:* <code>{model}</code> - the devices type (e.g. <code>IP232</code>). This is derived from the ''type'' query argument used in the update URL which is set to the value <code>#t</code> which in turn is expanded to the [[Reference10:Concept_Update_Server#Scfg_command|''device type'']] of the device requesting the update script<br />
:* <code>{filetype}</code> - the type of the required file, either <code>boot</code> or <code>prot</code><br />
:* <code>{filename}</code> - (from build 2014) the boot code or firmware filename for the device in lower case. Required if you use the LAP as firmware storage and firmware files are falsely requested in uppercase letters (as URLs on the LAP are case sensitive and if the files are requested with upper case names, the original files with lowercase name are not found). This also allows you to use alternate firmware file names (such as e.g. <code>ip110-sip.bin</code>). See the ''filenames'' tag<br />
Note that if the ''url'' ends with a slash (<code>/</code>), the calling device will automatically [[Reference10:Concept_Update_Server#Prot_command|append the name of the requested file]]. In this case, the file system the URL points to must therefore contain sub-directories for each firmware build which contain the corresponding firmware builds (e.g, ''fw/12345/ip232.bin''). If ''url'' is not an URL (as is the case for the default value <code>fw/{build}/</code>), it is treated as a sub-directory underneath the update server's root directory<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="fw/{build}/"/><br />
</syntaxhighlight><br />
<br />
===== Loading firmware files from innovaphone.com =====<br />
You can also load firmware from the innovaphone.com web site. To do so, set ''url'' like so:<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="http://webbuild.innovaphone.com/{build}/"/><br />
</syntaxhighlight><br />
<br />
Please note that ''this is a voluntary service, no guarantee of availability, no service level agreement''.<br />
<br />
[[User:Ckl|ckl]] 15:40, 21 February 2023 (CET) This service has been discontinued. You may consider using <code>https://store.innovaphone.com/release/download/{build}/</code> instead.<br />
<br />
==== backup ====<br />
Defines where backup files are kept.<br />
; backup/@dir : the directory underneath the script directory where backups are stored<br />
; backup/@nbackups : the number of different backup files kept<br />
; backup/@scfg : the target URL for the scfg command. If this is not an url, it is interpreted relative to the script directory URL. You can add more options for the ''scfg'' command, like in <code>scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m ser hourly /force 1"</code> (this example will make sure backups are attempted only once per hour, so as to reduce load on the server).<br />
<br />
<syntaxhighlight lang="html5"><br />
<backup <br />
dir="backup"<br />
nbackups="10"<br />
scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m"<br />
/><br />
</syntaxhighlight><br />
<br />
==== status ====<br />
Defines details regarding the state kept for a device requesting an update script.<br />
; status/@dir : the directory the status files are kept in (e.g. <code>status</code>). Used as the name for a sub-directory underneath your install directory on your web server. You must make sure that files in this directory cannot be read by anyone without proper authentication, as such files may contain sensitive information.<br />
; status/@expire : if set and not empty, devices which have not requested an update script will be removed from the inventory after ''n'' seconds. For example, ''expire="7776000"'' will forget about devices after 90 days with no contact<br />
; status/@missing : devices are shown in a different colour (indicating that they are ''missing'') after ''n'' seconds. For example, ''missing="604800"'' will flag devices as missing after 7 days with no contact<br />
; status/@refresh : if set and not empty and larger than zero, the admin user interface showing the known devices will refresh the status of all shown devices each ''n'' seconds<br />
; status/@logkeep : number of seconds log messages for individual devices will be kept<br />
; status/usehttpsdevlinks : if set to true, links to devices in the status page are created using the https:// scheme<br />
<syntaxhighlight lang="html5"><br />
<status <br />
dir="status" <br />
expire="7776000" <br />
missing="200" <br />
refresh="10"<br />
logkeep="90"<br />
usehttpsdevlinks="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== times ====<br />
Defines details regarding the delivery of update scripts to requesting devices. <br />
<br />
; times/@dir : the directory the update scripts are stored in. For security reasons, you may want to make sure that files in this directory cannot be read without proper authentication.<br />
<br />
These attributes define the arguments of the [[Reference10:Concept_Update_Server#Times_command | times command]]. If neither ''allow'' nor ''initial'' is set, no ''times'' command is used (that is, the update scripts will be processed at all times).<br />
; times/@allow : the hours to be used in the ''times'' command (as in <code>mod cmd UP1 times /allow </code>''hours'')<br />
; times/@initial <br />
: the minutes to be used in the ''times'' command (as in <code>mod cmd UP1 times /initial </code>''minutes''). If you want to use initial staging of virgin devices ''forcestaging'' must be <code>true</code>.<br />
<br />
Delivered update scripts can include a [[Reference10:Concept_Update_Server#Check_command | check command ]] if the ''check'' attribute is set to <code>true</code>. . In this case, the devices will executed the scripts again only when you have edited/changed them.<br />
; times/@check : if set to true, update scripts will be executed once only (unless their contents change)<br />
<br />
When editing a number of update scripts, it is often not desirable if one changed script is already executed before another is changed too. When the ''grace'' attribute is set to a positive value, no script at all is delivered to requesting devices unless ''n'' seconds have expired since the last edit. For example, setting ''grace'' to 90 gives you one and a half minute to finish all your edits. <br />
<br />
; times/@grace : defines the number of seconds that must expire before update script changes take effect<br />
<br />
The update server works in either ''fast'' or ''normal'' mode. In ''fast'' mode, update scripts have to be requested more frequently, for example to step through multiple staging phases faster. This is enforced by modifying the calling device's ''Interval'' setting in [[Reference11r1:Services/Update | ''Services/Update '']] and setting it to 1 in ''fast'' mode.<br />
<br />
; times/@interval : polling interval in minutes for normal operation (that is, when all staging is done)<br />
; times/@polling : when you are using multiple ''phases'' (e.g. ''staging'' and ''update''), this defines the number of seconds to wait before the device reads the scripts for the next phase (see [[Reference10:Concept_Update_Server#Provision_command | provision command]]) (please do not modify the default value)<br />
<br />
In some installations it may be desired to deliver update scripts with HTTPS only or even only to calling devices which have identified themselves with a proper TLS certificate. <br />
<br />
; times/@forcehttps : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS. If the device is ready to receive an update script and still calls in with HTTP only, its ''Command File URL'' will be re-configured to use HTTPS automatically.<br />
; times/@httpsport : if you use a non-standard port for HTTPS access to update scripts, it can be defined here<br />
; times/@httpsurlmod : if your HTTPS URL differs from the HTTP URL, you can specifiy a ''modifier''. It has the form <code>!</code>''pattern''<code>!</code>''replacement''<code>!</code> where ''pattern'' is a [http://docs.php.net/manual/en/pcre.pattern.php PHP PCRE pattern]. For example, <code>!mtls/!!i</code> will remove the string <code>mtls/</code> from the URL used by the device to create the HTTPS URL to use. The delimiter (in this case "!") can be changed according to your own wishes, the first character defines the delimiter. The "i" at the end ignores uppercase.<br />
<br />
Note: If you want to change the hostname of your URL, you have to add the Port 444 to your hostname. The code should then look like this: <code>!hostname1:444/mtls/!hostname2:444/!i</code>.<br />
; times/@forcetrust : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS and a valid and trusted client certificate that identifies itself as the devices it claims to be<br />
; times/@forcestaging : before build 2009, the restrictions imposed by the times/@allow settings affected all update ''phases''. This however makes staging cumbersome, as you usually want to restrict normal configuration changes to off-working-hours. If it is <code>true</code>, the restriction will be applied only to the last phase (that is, the ''staging'' phases will execute immediately). Also, the final phase will be executed once by staged devices. <br />
: Since build 2021 the standard value was changed to <code>true</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
dir="scripts" <br />
allow="" <br />
initial="" <br />
check="true" <br />
polling="5" <br />
interval="15" <br />
grace="90" <br />
forcehttps="true" <br />
httpsport="444" <br />
forcetrust="false" <br />
httpsurlmod="!mtls/!!i"<br />
forcestaging="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== phases ==== <br />
Defines the scripting phases. In many installations, there are initializations which should be performed once only. This is known as the ''staging'' phase. Once this is done, the devices continue with the execution of the normal update scripts (this phase is known as ''update'' by default). In the (unlikely) event that you want more or less phases, you can add/remove ''phase'' tags.<br />
==== phases/phase ====<br />
; phases/phase/@id : the name for the phase<br />
; phases/phase/@seq: the sequential number for the phase. Phases are stepped-through in the numeric order defined by their ''seq'' attribute. <br />
<br />
By virtue of the ''seq'' attribute, you can insert phases in to the set of phases defined by default, which is<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<!-- sequence is important! --><br />
<phase id="staging" seq="100"/><br />
<phase id="update" seq="200"/><br />
</phases><br />
</syntaxhighlight><br />
For example,<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<phase id="phase" seq="150"></phase><br />
</phases><br />
</syntaxhighlight><br />
will insert a custom phase ''myphase'' as second phase.<br />
<br />
==== environments ====<br />
Defines the distinguished environments. Devices can have zero or more ''environments'' associated. The environments a device is considered to be in must be passed as <code>?env=</code>''name1,name2,..'' URL query arg in the update url. <br />
<br />
Update scripts for all environments listed will be applied.<br />
First the environment scripts itself, then all implied environments in the same order as the ''implies'' tags are listed in the config. <br />
<br />
The default config file defines the environments ''intern'' and ''homeoffice'' but you can define your own if you like.<br />
<br />
==== environments/environment ====<br />
; environments/environment/@id : the name for the environment<br />
<br />
==== environments/environment/implies ====<br />
Each ''environment'' may have a number of sub-tags of type ''implies''. It contains the ''id'' of another ''environment''. If a device is in an environment with an ''implies'' sub-tag, it is assumed that it is in the implied environment also. <br />
<syntaxhighlight lang="html5"><br />
<environments><br />
<environment id='hq'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='intranet'/><br />
<environment id='homeoffice'/><br />
</environments><br />
</syntaxhighlight><br />
The <code>implies</code> tag handles no recursion (therefore, in the above example, if ''intranet'' would have an implies, a device in environment ''hq'' would not inherit this implication. Instead, you must list it explicitly for the ''hq'' environment).<br />
<br />
If multiple environments will be used they will be applied also in the same order as the ''environment'' tags are listed in the config.<br />
<br />
;There are some special things to keep in mind when adding multiple environments in the ''env'' attribute and they use the ''implies'' tag in the config.<br />
:- The first environment specified in the arg ''env'' will be processed as expected with all children.<br />
:- Recursive ''implies'' from an environment added via ''implies'' is not performed. (No children's children)<br />
:- Only the first environment which is specified in the ''env'' arg with implies is processed, all additional values in the arg ''env'' will be ignored.<br />
:- If the environment in the arg ''env'' it is not the first value, then it must also use the ''implies'' config to implies itself. Otherwise only child are processed.<br />
<br />
==== classes ====<br />
Defines the available device classes. Devices of different classes (e.g. phones and gateways) can have different update scripts. The device class is derived from the <code>#t</code> (device type) query arg of the update script URL (which is why you must configure it in the update URL). By default, the classes ''phone'', ''opus_phone'', ''pre_opus_phone'', ''phone_oldui'', ''phone_newui'', ''mobile'', ''gw'', ''fxogw'', ''fxsgw'', ''brigw'', ''prigw'' are recognized (newer versions may include more and/or updated class definitions). Also, any device is considered to be member of a class that has the device type as class-name. For example, an IP112 is in a class called ''ip112''.<br />
<br />
A given device can be in multiple classes and will execute all update scripts available for these classes.<br />
<br />
==== classes/class ====<br />
Each ''class'' has a number of sub-tags of type ''model''. It contains the value replaced for the <code>#t</code> query arg. The models listed belong to the class.<br />
; classes/class/@id : the name of the class<br />
<br />
<syntaxhighlight lang="html5"><br />
<classes><br />
<class id="gw"><br />
<model>ip0010</model><br />
...<br />
</class><br />
<class id="phone"><br />
<model>ip110</model><br />
<model>ip232</model><br />
...<br />
</class><br />
<class id="phone_oldui"><br />
<model>ip110</model><br />
...<br />
</class><br />
<class id="phone_newui"><br />
<model>ip232</model><br />
...<br />
</class><br />
</classes><br />
</syntaxhighlight><br />
<br />
==== nobootdev ====<br />
Device types listed here do not receive boot code updates.<br />
===== nobootdev/model =====<br />
Each model tag defines a device type that shall not receive boot code updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nobootdev> <br />
<model>ipva</model><br />
<model>ipvadec</model><br />
<model>swphone</model><br />
<model>mypbxa</model><br />
<model>mypbxi</model><br />
</nobootdev><br />
</syntaxhighlight><br />
<br />
==== nofirmdev ====<br />
Device types listed here do not receive firmware updates.<br />
===== nofirmdev/model =====<br />
Each model tag defines a device type that shall not receive firmware updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nofirmdev> <br />
<model>swphone</model><br />
<model>mypbxi</model><br />
</nofirmdev><br />
</syntaxhighlight><br />
<br />
==== filenames ====<br />
Available from build 2014. Allows you to define alternate firmware and boocode names for use with the <code>{filename}</code> replacement (see the ''fwstorage'' tag), if required.<br />
<br />
===== filenames/model =====<br />
Each model defines a set of alternative file names for a certain device type.<br />
; filenames/model/@id : the (lowercase) device type identfier (e.g. <code>ip112</code>). If this matches the device type of the calling device, the alternate file names are replaced<br />
; filenames/model/@prot: the alternate firmware file name<br />
; filenames/model/@boot: the alternate boot file name<br />
<syntaxhighlight lang="html5"><br />
<filenames><br />
<model id="mypbxa" prot="mypbx.apk"/><br />
</filenames><br />
</syntaxhighlight><br />
<br />
defines the firmware file name <code>mypbx.apk</code> for the device type <code>mypbxa</code>.<br />
<br />
==== stdargs ====<br />
Better don't touch :-)<br />
<br />
==== customcerts ====<br />
innovaphone hardware devices come with a device specific, trust-able ''device certificate''. However, in many situations it is desirable to roll-out customer-created ''custom certificates'' to all devices. The update server can do this. This will replace the shipped devices certificates both on hardware devices (where all such certificates are derived from innovaphone's certificate authority) as well as on soft devices which run with a self.-signed certificate by default (such as the software-phone or ''myPBX for Android/iOS'').<br />
<br />
; customcerts/@dir : the name of the directory underneath your installation directory which stores device certificates. you should make sure that this directory cannot be accessed without proper authentication<br />
; customcerts/@CAkeys : a file name pattern (e.g. <code>CAkey*</code>). All files in ''customcerts/@dir'' which match the pattern must contain DER or PEM encoded public keys which form the ''certificate chain'' of your CA (usually it is only one and the public key of your CA). One key per file. The files, when sorted by name, must contain the CA key itself in the last file (any intermediate certificates, if any, in the other files in correct order)<br />
; customcerts/@CAtype : must be <code>manual</code> currently<br />
; customcerts/@CAname : a comma-separated list of CA names. Devices presenting a certificate signed by one of these CAs will be considered as having a valid certificate. Otherwise, they wil be instructed to create a ''certificate signing request'' (CSR) for subsequent submission to your CA<br />
; customcerts/@CAnamesep : defines the name separator for ''customcerts/@CAname''. The default is '<code>,</code>' and you must change it to a different value if one of your CAs includes a comma in its name<br />
; customcerts/@renew : if set and not 0, certificates are replaced by new ones ''n'' days before they expire<br />
; customcerts/@CAwildcard : normally, the update server will accept a certificate only if its CN matches the device's serial number. However, sometimes, so-called ''wildcard certificates'' such as <code>*.innovaphone.com</code> are used on a device (e.g. on a PBX or on a ''reverse proxy''). If set, the update server will also accept a certificate that contains a CN which equals the value of ''customcerts/@CAwildcard''. Note that the CN must match literally. For example, if ''''customcerts/@CAwildcard'' is set to <code>*.innovaphone.com</code> a CN like <code>host.innovaphone.com</code> is not accepted<br />
<br />
<br />
When the update server determines that a device calls in with an un-trusted or expired certificate, it will have it create a signing-request for a new certificate. The requested certificate properties can be defined:<br />
<br />
; customcerts/@CSRkey : the key size, e.g. <code>2048-bit</code>. Note that we do not recommend keys larger than 2048 bit (see [[Reference7:Certificate_management#Certificate_Key_Length_and_CPU_Usage]] for details)<br />
; customcerts/@CSRsignature : the signature algorithm, e.g. <code>SH256</code><br />
; customcerts/@CSRdn-cn : the CN (''common name'') part used for the DN<br />
; customcerts/@CSRdn-ou : the OU (''organizational unit'') part used for the DN<br />
; customcerts/@CSRdn-o : the O (''organization'') part used for the DN<br />
; customcerts/@CSRdn-l : the L (''locality'') part used for the DN<br />
; customcerts/@CSRdn-st : the ST (''state or province'') part used for the DN<br />
; customcerts/@CSRdn-c : the C (''country'') part used for the DN (note: ''CSRdn'' for many CAs, must be a 2-letter ISO country code)<br />
; customcerts/@CSRsan-dns-1 : the first of up to three DNS names <br />
; customcerts/@CSRsan-ip-1 : the first of up to two IP addresses<br />
<br />
Within the set of CSR attributes, the following meta-words will be replaced:<br />
<br />
* <code>{realip}</code> : the real IP address of the device as reported in the <code>ip=#i</code> query argument<br />
* <code>{ip}</code> : the IP address of the device as seen by the update server<br />
* <code>{proxy}</code> : the IP address of the reverse proxy the device used to reach the update server<br />
* <code>{sn}</code> : the serial number of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>009033030df0</code>)<br />
* <code>{hwid}</code> : the hardware-id of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>IP1200-03-0d-f0</code>)<br />
* <code>{name}</code> : the ''Device Name'' (set in ''General/Admin'') of the device<br />
* <code>{rdns}</code> : the DNS name found in a reverse DNS lookup for the real-ip address reported by the device (see ''{realip}'') above<br />
<br />
<syntaxhighlight lang="html5"><br />
<customcerts <br />
dir="certs" <br />
CAkeys="CAkey*" <br />
CAtype="manual" <br />
CAname="innovaphone-INNO-DC-W2K8-Zertifizierungsserver" <br />
CAnamesep="," <br />
CAwildcard="*.innovaphone.com"<br />
CSRkey="2048-bit" <br />
CSRsignature="SH256" <br />
CSRdn-cn="{hwid}" <br />
CSRdn-ou="" <br />
CSRdn-o="" <br />
CSRdn-l="" <br />
CSRdn-st="" <br />
CSRdn-c="" <br />
CSRsan-dns-1="{name}.company.com" <br />
CSRsan-dns-2="{hwid}.company.com" <br />
CSRsan-dns-3="{rdns}" <br />
CSRsan-ip-1="{realip}" <br />
CSRsan-ip-2="" <br />
renew="90" /><br />
</syntaxhighlight><br />
<br />
==== queries ====<br />
If queries are defined and applicable for the calling device, it will be instructed to send certain information to the update server which will be stored in the device status file kept on the server. Each piece of such information is defined using a separate ''query'' tag.<br />
<br />
; queries/@scfg : defines the URL used by the device to send the information. Normally not changed from the default<br />
<br />
==== queries/query ====<br />
Defines one piece of information to be submitted to the update server.<br />
<br />
; queries/query/@id : a unique name for the query<br />
; queries/query/@title : a title for this piece of information shown in the device status<br />
<br />
<syntaxhighlight lang="html5">.<br />
<!-- retrieve and show some details from IP4/General/STUN page --><br />
<query id="media" title="Media"><br />
<cmd>mod cmd MEDIA xml-info</cmd><br />
<applies>*</applies><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
<show title="Server">concat('STUN: ', /state/queries/media/info/@stun, ' TURN: ', /state/queries/media/info/@turn, ' (', /state/queries/media/info/@turn-user, ')')</show><br />
</query><br />
<br />
<!-- show primary phone registration --><br />
<query id="registration" title="Registration"><br />
<cmd>mod cmd PHONE/USER phone-regs /cmd phone-regs</cmd><br />
<applies>phone</applies><br />
<show title="State: User/Number">concat(/state/queries/registration/info/reg[@id = "0"]/@state, ': ' , /state/queries/registration/info/reg[@id = "0"]/@h323, '/', /state/queries/registration/info/reg[@id = "0"]/@e164)</show><br />
<show title="PBX/ID">concat(/state/queries/registration/info/reg[@id = "0"]/@gk-addr, '/', /state/queries/registration/info/reg[@id = "0"]/@gk-id)</show><br />
</query><br />
</syntaxhighlight><br />
<br />
==== queries/query/cmd ====<br />
The content of this tag defines the command to be executed on the device which outputs the requested information. These are ''mod cmd''s typically.<br />
<br />
<syntaxhighlight lang="html5"><br />
<!-- get info for custom certificates --><br />
<cmd>mod cmd X509 xml-info</cmd><br />
</syntaxhighlight><br />
<br />
See [[Howto:Effect_arbitrary_Configuration_Changes_using_a_HTTP_Command_Line_Client_or_from_an_Update#Using_the_Mechanism_for_Device_Status_Queries ]] for details on how to find out which commands to use.<br />
<br />
==== queries/query/applies ====<br />
Defined queries are only executed by devices which belong to the class that is given as tag content. If the ''env'' atribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; queries/query/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in (unfortunately, you can not combine bith)<br />
<syntaxhighlight lang="html5"><br />
<!-- applied to phones only --><br />
<applies>phone</applies><br />
</syntaxhighlight><br />
<br />
==== queries/query/show====<br />
If one or more ''show'' tags are defined for the ''query'' tag, the data will be shown in the device status page. The values shown are defined by the tag content which is an XPath expression selecting the data from the device state. The XPath expression always begins with <code>/state/queries/</code>''query-id'' (as defined by the ''id'' attribute in the query tag). <br />
<br />
; queries/query/show/@title : used as title when the values are displayed in the status page<br />
<br />
<syntaxhighlight lang="html5"><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
</syntaxhighlight><br />
<br />
== Download ==<br />
*[http://download.innovaphone.com/ice/wiki-src#php-update-server http://download.innovaphone.com/ice/wiki-src/] - download the complete file package of scripts and files described in this article<br />
: you need to use build 2000 or higher. Older versions are described in [[Howto:PHP_based_Update_Server]]<br />
<br />
== Hints for writing your update snippets ==<br />
Writing update scripts is largely undocumented and sometimes tricky. Here are some general guidelines.<br />
<br />
=== Using <code>config add/del</code> ===<br />
Many setting are done using <code>config change</code> commands. So these are very useful in update scripts too.<br />
<br />
The straight forward method to find out which commands you need is as follows:<br />
* get a device of the type in question and do factory reset<br />
* save the configuration to a file<br />
* do the desired settings using the normal web admin UI<br />
* save the resulting configuration to another file<br />
* compare the saved files<br />
<br />
Usually, you want to set only specific parts of the configuration line. For example, assume you want to set the default coder to ''OPUS-WB''. Your saved configuration line might look like<br />
<br />
config change PHONE SIG /prot SH323 /gk-addr pbx.innovaphone.com /gk-id innovaphone.com /tones 0 /lcoder OPUS-WB,20, /coder OPUS-WB,20,k1<br />
<br />
To set only the coder settings, you would set the relevant options with the <code>config add</code> command as follows:<br />
<br />
config add PHONE SIG /lcoder OPUS-WB,20,<br />
config add PHONE SIG /coder OPUS-WB,20,k1<br />
<br />
You can also remove specific options, as in <br />
<br />
config rem PHONE SIG /tones<br />
<br />
which would remove the <code>/tones 0</code> from the configuration line for <code>PHONE SIG</code>. <br />
<br />
Note that the PHP update server will add the<br />
<br />
config write<br />
config activate <br />
iresetn<br />
<br />
at the end of the delivered script automatically, so you do not need to do it yourself. In fact, it is not recommended to have any reset command in your snippets.<br />
<br />
==== <code>config change</code> Lines with associated Passwords ====<br />
Sometimes, there is a password associated with certain configurations. Consider the STUN/TURN configuration: <br />
<br />
config change MEDIA /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd 2 /nat-detect 61<br />
<br />
The TURN password, being sensitive, needs to be encrypted in your configuration. This is why it is stored as a ''VAR'', which provides encryption support (see below):<br />
<br />
vars create MEDIA/TURN-PWD pc ....<br />
<br />
Unfortunately, the relationship between an option and an associated VAR needs to be guessed. You can be sure that the module name in the <code>config change</code> matches the modules name in the <code>vars create</code> command. However, the variable name must be guessed. <br />
<br />
You can also set the password in your script using the <code>vars create</code><!-- , but this does make sense only in staging (that is pre-update-phase) scripts (see below for a discussion why). If you need to do it in a script snippet for the update phase, you should -->, however, you may also consider using a <code>mod cmd</code> (see below) such as e.g.<br />
<br />
mod cmd MEDIA form /cmd form /del %2Fstun-slow /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd my-turn-pw /nat-detect 61<br />
<br />
=== Using <code>vars create</code> ===<br />
<br />
Sometimes it makes sense to use <code>vars create</code> commands. The syntax is<br />
<br />
<code> vars create </code>''<name> <flags> <value>''<br />
<br />
A ''<name>'' is a module name followed by a variable name, optionally followed by an index. For example, a phone will store the currently selected main registration as <code>vars create PHONE/ACTIVE-USER p </code>''<index>''. So the module is <code>PHONE</code> and the variable name is <code>ACTIVE-USER</code>. When the variable is multi-valued, an index is added to the name. For example, the registration settings for all but the first registration are stored as indexed variable, as in <code>vars create PHONE/USER-REG/00001 p </code>''<settings>'', where <code>00001</code> is the index (registrations count from 0 in this case).<br />
<br />
There are a number of flags which can be combined, the most prominent used in update scripts are:<br />
; p : permanent. The var is stored in flash memory (you will almost always use this flag in update scripts)<br />
; c : crypted. The var value needs to be encrypted and the ''<value>'' given is crypted<br />
; x : crypted, plain value. The var needs to be encrypted but the ''<value>'' is given as plain (that is, un-encrypted) text. This allows you to set an encrypted variable without encrypting the desired value<br />
; b : binary. The var value is binary. Its ''<value>'' is given as a bin2hex string<br />
<br />
Please note that using <code>vars create</code> will ''always'' set the internal ''reset needed condition'' in the device (regardless which ''<value>'' is set). This means that a subsequent <code>resetn</code> or <code>iresetn</code> will always trigger a reset. So if you use it, make sure that you use the ''times/@check'' ([[#times | see above]]) to protect your script from re-executing the snippet (and thus rebooting the device) all the time. <br />
<!--<br />
When you use a <code>mod cmd UP1 check ...</code> command in your script (see times/@check [[#times | above]]), this will create an endless loop if you use <code>vars create</code> followed by one of the <code>resetn</code> commands in the code guarded by the <code>check</code> command. This code will never be fully executed, as the ''reset needed'' condition will always be set and the reset will always happen. You can use it in ''pre-update-phase'' scripts (that is, during the staging process) as this is not protected by a <code>check</code> command.<br />
--><br />
<br />
=== Using <code>mod cmd</code> ===<br />
See [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]] for a discussion of how to use <code>mod cmd</code> in update scripts.<br />
<br />
== Writing your own Dynamic Scripting Code ==<br />
NB: this feature is available from build 2020.<br />
<br />
By default, the update server delivers update scripts which are composed from a number of static files (so-called ''snippets''). Sometimes it makes sense however, to create such snippets dynamically (e.g. based on a database lookup). Here is how to do this.<br />
<br />
Dynamic scripting is implemented by a user-supplied custom <code>class CustomUpdateSnippet</code>. The code for this class must be placed in to the file <code>config/scripting.class.php</code>. As a starter, sample class code is available in <code>config/scripting.class-sample.php</code>.<br />
<br />
<syntaxhighlight lang="php"><br />
<?php<br />
<br />
/**<br />
* to provide your own runtime generated snippets, rename this file to 'scripting.class.php' and <br />
* implement the member functions<br />
* $property will have one member called <br />
*/<br />
<br />
class CustomUpdateSnippet extends UpdateSnippet {<br />
<br />
/**<br />
* snippet to deliver after standard text snippets<br />
* @return array of string<br />
*/<br />
public function getPostSnippet() {<br />
return parent::getPostSnippet();<br />
}<br />
<br />
/**<br />
* snippet to deliver before standard text snippets<br />
* @return array of strings<br />
*/<br />
public function getPreSnippet() {<br />
return parent::getPreSnippet();<br />
}<br />
<br />
/** <br />
* do we want to suppress the standard text snippets?<br />
* @return boolean<br />
*/<br />
public function sendStandardSnippets() {<br />
return parent::sendStandardSnippets();<br />
}<br />
}<br />
?><br />
</syntaxhighlight><br />
<br />
The sample code overrides of the classes member functions just call the base classes which do nothing at all. So if you just copy the sample code into <code>scripting.class.php</code>, nothing will change. However, if <code>getPreSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''before'' the standard snippets are sent. Likewise, if <code>getPostSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''after'' the standard snippets are sent. If <code>sendStandardSnippets()</code> returns false, the standard snippets will not be sent.<br />
<br />
To determine the dynamic snippets to send, the user-provided <code>class CustomUpdateSnippet</code> has access to the members of its base <code>class UpdateSnippet</code>, namely the <code>var $statexml</code> member. This member contains a <code>SimpleXMLElement</code> object with all state information available for the calling device.<br />
<br />
Here is a sample state found in <code>$statexml</code> (as output by the <code>dump()</code> member function): <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0"?><br />
<state seen="1522065435" requested="130774bootcode" phase="update"><br />
<device sn="00-90-33-3e-0c-57" type="IP112" classes="phone, opus_phone, phone_newui, hstrace, ip112" ip="127.0.0.1" rp="false" phase="update"<br />
environments="sifi, 172net" certstat="either certificate handling or state tracking not enabled - not doing any certificate checking" hwid="IP222-46-5c-77" realip="172.16.80.241" requestDownloaded="false"/><br />
<firmware requested="130986" requested-at="1522065435"/><br />
<bootcode requested-at="1522065435"/><br />
<config filename="scripts/all-all-all.txt" delivered="1522065426" version="1486559970"/><br />
</state><br />
</syntaxhighlight><br />
<br />
The most interesting elements in this XML are probably the attributes of the <code>state</code> and the <code>device</code> tags.<br />
<br />
== Hints for using custom SSL Certificates ==<br />
<br />
Using your own SSL certificates can be useful depending on the scenario.<br />
<br />
We have two methods to accomplish this:<br />
<br />
=== Standard scenario as described in this wiki article ===<br />
The idea is that each certificate request is checked manually, edited and the UpdateServer is provided with the new device certificate.<br />
<br />
=== Special scenario with automated certificate renewal ===<br />
The idea is that every new/unknown (untrusted) device connects to the staging server. Here the device is released by manual checking/authorization by getting its first certificate. All further extensions can happen automatically afterwards.<br />
<br />
'''Important: All important settings concerning file permissions, access and co must be used from the above article and are only skipped in this part!'''<br />
<br />
We will create two Update-Server instances with the following configuration.<br />
<br />
# Staging Server (HTTPS/443)<br />
#*Put the sources in <code>/var/www/innovaphone/update</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]].<br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!update/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code| above]]<br />
#* '''Important''': Do not submit any further customer specific device configurations in the staging Server<br />
# Final MTLS / 444 update server<br />
#*Put the sources in <code>/var/www/innovaphone/mts</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]] with <code>renew = "x"</code><br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!mtls/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code | above]]<br />
#*Store all customer specific device configurations<br />
<br />
<br />
The meaning of the two different instances is:<br />
*The staging server <code>https://[ip]/update/admin/admin.php</code> will generate the provisioning URLs for new devices.<br />
*All new/unknown (untrusted) devices are only in the staging server.<br />
**Here you can check the devices and provide them a valid certificate after check/authorization.<br />
*With a valid certificate, the UpdateURL is automatically changed to the MTLS instance.<br />
*As soon as a device connects via MTLS we trust this device and we can use an automatic extension of the certificates.<br />
** Waiting CSR requests are in the configured folder ''certs'' in the notation <code>"[mac]-request.p10.pem"</code><br />
** If you operate your own CA, feel free to automate the reissue of the certificates via a sheduled Cronjob<br />
*** Help for Linux: [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
*** Help for Windows: [[Howto:Creating_custom_Certificates_using_a_Windows_Certificate_Authority]]<br />
** If a signed certificate with the format <code>"[mac]-signedrequest.cer"</code> is placed in the certs folder, the certificate is automatically updated on the device.<br />
<br />
== Optional Configurations on the Linux Application Platform ==<br />
===Support for more Connections===<br />
By default the LAP's web server lighttpd allows for 512 concurrent connections with 1024 open file descriptors. When you serve a huge number of devices with the update server, then this might be too low. You will see some devices not being able to contact the update server for a while. This should not be a real issue as the devices will retry. However, updating all devices may take long due to this.<br />
<br />
In the lighttpd log (in <code>/var/log/lighttpd</code> on the LAP), you will see entries like this:<br />
<br />
2017-10-16 13:45:18: (network_linux_sendfile.c.140) open failed: Too many open files <br />
2017-10-16 13:45:18: (mod_fastcgi.c.3075) write failed: Too many open files 24 <br />
2017-10-16 13:45:18: (server.c.1434) [note] sockets disabled, out-of-fds <br />
2017-10-16 16:23:25: (response.c.634) file not found ... or so: Too many open files /mtls/updatev2/web/innovaphone_logo_claim_fisch.png -><br />
2017-10-16 17:57:45: (server.c.1432) [note] sockets disabled, connection limit reached <br />
<br />
If you experience such issues, proceed as follows:<br />
<br />
* connect to the LAP with SFTP (e.g. using WinSCP)<br />
* change directory to <code>/etc/lighttpd</code><br />
* edit <code>lighttpd.conf</code><br />
* search for the 2 lines which set <code>server.max-fds</code> and <code>server.max-connections</code><br />
* replace the standard values. We recommend to set the number of file descriptors to 4 times the number of requests when using the update server, e.g. <br />
: old<br />
:: server.max-fds = 1024<br />
:: server.max-connections = 512<br />
: new<br />
:: server.max-fds = 8000<br />
:: server.max-connections = 2000<br />
* save the file<br />
* restart linux (''Diagnostics/Reset/Reboot'')<br />
<br />
Be sure to closely monitor the ''Diagnostics/Status'' page for a while after such configuration. <br />
<br />
Also, when you update the LAP, you will have to re-do the changes (as always when you change something ''under the hood'').<br />
<br />
=== Debug files rotation based on logrotate ===<br />
If you have to debug during operation and a lot of devices access the PHP Update Server V2, you have to keep track of the debug files or automatically limit them. For this you can use logrotate on the innovaphone Linux AP. With logrotate you can apply time-based or size-based rules when files are packed and when they are deleted.<br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files. <br />
* Under the path <code>/etc/logrotate.d</code> create a file e.g. <code>updateserver</code>.<br />
<br />
<code>/etc/logrotate.d/updateserver</code><br />
<br />
In this file now enter the following content and save it.<br />
<br />
<code><br />
/var/www/innovaphone/mtls/update/debug/*.txt {<br />
size 5M<br />
missingok<br />
rotate 2<br />
compress<br />
delaycompress<br />
notifempty<br />
create 644 www-data www-data<br />
}<br />
</code><br />
<br />
This will include all *.txt files from the debug directory of the PHP Update Server V2 in the logrotate process of the operating system.<br />
<br />
; size 5M : means every 5MB the file is rotated or zipped<br />
; size 5k : means every 5kB the file is rotated or zipped<br />
Alternatively, you can also rotate the file based on time<br />
; daily : means that every day the file is rotated or zipped<br />
; weekly : means that the file is rotated or zipped daily<br />
<br />
; missingok : if a debug file does not exist, it will be ignored<br />
; rotate n : files are removed before the last ones are deleted.<br />
; compress : compresses the old debug files<br />
; delaycompress : compresses the debug data only after it has been moved once<br />
; notifempty : empty log files are not rotated<br />
; create 644 www-data www-data : creates a new, empty debug file with appropriate permissions<br />
<br />
== Update Server and Reverse Proxy ==<br />
It is possible to implement access to the update server through a ''reverse proxy'' (RP). However, you have to keep some issues in mind:<br />
* ''Mutual Transport Layer Security'' (MTLS) is not possible<br />
: MTLS requires a direct TLS connection between the two parties. An RP however would terminate the TLS connection and entertain two independent connections to the parties. This breaks MTLS. If you want to use MTLS and serve external clients, you must therefore provide a direct access to the update server (that is an external IP address either directly or through port forwarding)<br />
: see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts]] for details<br />
* When using the RP, you can choose to forward encrypted traffic (HTTPS) arriving from external to the update server using HTTP (unencrypted). The update server however eventually rewrites the update URL in the calling devices configuration. In order to do this, it determines the type of the internal connection. So if the connection from the RP to the update server is using HTTP, it would create a ''http://...'' URL. Otherwise it would create an ''https://..'' URL (also, it would be using the same port). This way, if the RP forwards requests to the update server with HTTP, the synthesized new URL for the calling client would use HTTP too, even though the original request was sent with HTTPS. This may or may not be what you want (as all of your clients would end up using non-encrypted traffic). Even worse, it might not work at all, if the RP does not accept HTTP for example.<br />
<br />
== Related Articles ==<br />
* [[Reference10:Concept_Update_Server]]<br />
* [[Reference12r1:DHCP_client]]<br />
* [[Reference10:Concept_Linux_Application_Platform]]<br />
* [[Reference10:Concept_Provisioning]]<br />
* [[Howto:PHP_based_Update_Server]] (old version)<br />
* [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
* [[Howto:Creating custom Certificates using a Windows Certificate Authority]]<br />
* [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]]<br />
<br />
<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:PHP_based_Update_Server_V2&diff=70795Howto:PHP based Update Server V22024-02-07T14:32:10Z<p>Ckl: /* Delivering Custom Certificates */</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
* web server running PHP 5 (e.g. Linux Application Platform)<br />
<br />
All Versions prior to 13r1 (for 13r1 and later, see [[Reference13r1:Concept App Service Devices]] instead). <br />
<br />
By default, the [[Reference10:Concept_Update_Server | Update Manager]] mechanism reads a file that corresponds to the device type (e.g. <code>update-ip222.htm</code>). While this makes sense (update scripts may vary by device type), it is sometimes tedious, as you have to edit a huge amount of files which typically are (at least partly) identical.<br />
<br />
Here is a PHP script that can be used as an ''Update Server'' that allows you to simplify the handling of update scripts. It also includes a mechanism that makes sure that all devices always have the same firmware installed as a given ''master device'' has. Finally, it implements a straight forward configuration backup scheme.<br />
<br />
This article describes version 2 of this update server (build 2006 and up). The previous version is described in [[Howto:PHP_based_Update_Server]]. The enhancements are<br />
* Status user interface showing all known devices<br />
* Ability to roll out custom device certificates<br />
* Ability to provide configuration files to MTLS-authenticated devices only (e.g. in order to keep certain configuration settings secure)<br />
* Hide configuration files from public read access<br />
<br />
==More Information==<br />
=== Requirements ===<br />
The update server script requires a web server with working PHP 5.3 or higher platform. It has been tested with Apache and ligHTTPD (on a [[Reference10:Concept Linux Application Platform | Linux Application Platform]]). On IIS, neither the certificate roll-out nor the MTLS authentication or configuration backup works with IIS.<br />
<br />
The platform running the PHP scripts must have a valid time setting!<br />
<br />
=== Features ===<br />
The update server can<br />
* update all your devices with boot code and firmware that corresponds to the versions running on a reference device of your choice<br />
* save backups of your devices configuration. Backups are saved only if the configuration changed since the last backup<br />
* invoke your own update scripts depending on <br />
** various phases (e.g. you can have ''staging'' scripts which are only executed once and normal scripts which are executed in normal operation later on)<br />
** devices classes (e.g. you can have different scripts for phones and gateways)<br />
** environments (e.g. you can have scripts for your internal devices and others for devices in home offices)<br />
** the device serial number<br />
* roll out customer specific device certificates<br />
* roll out update scripts to devices only that have identified themselves with a valid device certificate (MTLS)<br />
* maintain a list of devices in your installation along with some vital information about each individual device<br />
<br />
=== Installation ===<br />
<br />
==== On the Linux Application Platform ====<br />
Here is how you would install it on the [[Reference10:Concept_Linux_Application_Platform|LAP]]. Some of the steps may not be necessary if you don't want to use all features.<br />
<br />
On the ''Linux Application Platform'', you would <br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files<br />
* create a new root directory underneath <code>/var/www/innovaphone/mtls</code>, e.g. <code>/var/www/innovaphone/mtls/update</code><br />
* download the complete file package of scripts and files [http://download.innovaphone.com/ice/wiki-src/#php-update-server here] <br />
* copy all files and directories in to this new directory<br />
** create your local config files. We will never overwrite these in further updates.<br />
***Rename <code>config/user-config-sample.xml</code> to <code>config/user-config.xml</code><br />
***Rename <code>scripts/all-all-all-sample.txt</code> to <code>scripts/all-all-all.txt</code><br />
* change owner and group of all files and directories to <code>www-data</code>, change mode to 0600 for all files and 0700 for all directories (see [[#Migrating_from_build_2000_an_newer | below]] for how to do this with WinSCP)<br />
<br />
* log in to the LAP's admin UI (if you have not yet changed it, the default credentials will be <code>admin/linux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]) and open its ''Administration/Web Server/Change web server properties and public access to the web/webdav'' configuration UI. Add the following paths to ''Public Web Paths'' (assuming your base directory is called <code>update</code>):<br />
** <code>/mtls/update</code><br />
** <code>/mtls/update/web</code><br />
** <code>/mtls/update/admin</code><br />
** <code>/mtls/update/fw/</code> (note the trailing slash!)<br />
: make sure that no other sub-directories of ''update'' are listed<br />
: make sure you have no trailing '/' in any of these paths (except ''fw/')<br />
: this will make sure the update server's admin user interface is not accessible to the public<br />
<br />
At this point, you should be able to access the update server's admin user interface, e.g. <code>http://update.yourcompany.com/mtls/update/admin/admin.php</code>. However, the only thing you see is a login page. Please note that your browser should ask you for a password for this page (unless you already entered it before). Cookies must be enabled for the login page to work properly. <br />
<br />
===== If you want to provide HTTPS Acess to the Update Server =====<br />
For HTTPS access to the update server, you may want to install your own server certificate under ''Administration/Certificates/Current server certificate''. However, this is not strictly required (you will experience some warning messages when using your browser to access the update server, however, calling devices will work just fine). <br />
<br />
===== If you want to setup MTLS-restricted Delivery of Update Scripts =====<br />
If you think you have sensitive information in your update scripts, you should make sure to deliver such scripts to your own verified devices only. This can be done by authenticating calling devices with ''mutual TLS'' (MTLS). In this case, the calling device must use HTTPS to retrieve the update script and present a trusted client certificate that identifies itself as the calling device (that is, has the device serial as the certificate's ''common name'' (CN)). <br />
<br />
For this feature, you need to ''Configure mutual TLS'' in the LAP's ''Administration/Web Server'' panel. Simply tick the ''Active'' check-mark and select an appropriate ''MTLS Port''. The port must not conflict with any other TCP port used on the LAP, so neither 80 nor 443 is a good choice. 444 is a good choice. Although it [http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=8 is assigned to the snpp protocol] by IANA, it is not used by the LAP (and probably rarely used anyway).<br />
<br />
If MTLS is already activated on your LAP, simply take note of the currently configured ''MTLS Port''.<br />
<br />
<br/><br />
Please note that MTLS access is ''not possible'' through a ''reverse proxy'' (RP). This is because the RP always will terminate the incoming TLS connection and establish its own to the target. Therefore, the client certificate presented to the target server is the RP's certificate, not the original clients certificate. In our context - where MTLS is used to verify the identity of the original caller - clients must not access the update server through an RP.<br />
<br />
You can either expose your update server directly to the internet (and carefully think through the security implications) or create specific TCP port forwarding towards the update server in your NAT-router/firewall. In this case, we recommend to use non-standard HTTP and HTTPS ports on the NAT router (cause this will already keep most of the HTTP port scanners out there in the internet from functioning).<br />
<br />
<br/><br />
Also, you will need the public key of all the CAs you will trust. These must be configured in to the web server so it can trust the certificates your devices will present to it. See [[#Enforcing_Trust]] for details.<br />
<br />
==== On an Apache Server running on the Windows Operating System ====<br />
The update server will run on Apache too. However, as we did not test this setup thoroughly, we recommend to use the LAP's LigHTTPD instead. <br />
<br />
* For hints on using MTLS on an Apache Web Server, see [[Reference10:Concept_Provisioning#Enforcing_mutual_TLS_on_Apache | Enforcing mutual TLS on Apache]]<br />
* For hints on getting the ''innovaphone device certificate authority'' public keys (which you may or may not need), see [[Reference10:Concept_Provisioning#How_to_get_inno-dev-ca-certificate.crt | How to get inno-dev-ca-certificate.crt ]]<br />
<br />
==== On Microsoft's IIS ====<br />
We do not recommend to use IIS as <br />
* it does not support PUT easily<br />
* it is not compatible with the innovaphone device's MTLS implementation<br />
<br />
===Update existing installation===<br />
<br />
* download the [http://download.innovaphone.com/ice/wiki-src/#php-update-server new sources]<br />
* copy all files and directories to your existing installation folder (overwrite existing files)<br />
* change owner and group of all files and directories to www-data, change mode to 0600 for all files and 0700 for all directories<br />
<br />
===Configuration===<br />
<br />
If you intend to deliver your update scripts with MTLS (that is, with HTTPS and mutual certificate check)<br />
* you will need to have the full name of your ''certificate Authority'' (CA) as it is noted as ''Common Name'' (CN) in your CA's certificate<br />
* also, you will need a PEM version of your CA's public key (a ''PEM version'' of a certificate is a text file that begins with a line like <code>-----BEGIN CERTIFICATE-----</code>)<br />
<br />
Also, you need to know the ''MTLS Port'' configured in your LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts| If you want to setup MTLS-restricted Delivery of Update Scripts ]] above).<br />
<br />
All tweakable parameters are set in <code>user-config.xml</code>. You may want to use an XML-capable editor such as notepad++, netbeans or Visual-Studio for editing. if your editor supports it, you benefit from the ''document type description'' (DTD) provided in <code>full-config.dtd</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="false" debugcerts="false" debugscript="false"><br />
</config><br />
</syntaxhighlight><br />
<br />
The attributes of the <code>config</code> tag control some aspects of debugging. For now, simply set all 3 to <code>"true"</code> instead of <code>"false"</code>. Do not forget to revert them back to <code>"false"</code> when everything runs smoothly, large log files will result if not. <br />
<br />
==== Securing Access ====<br />
To control access to the update server's data, you should set a login. This can be done in the ''master'' tag by specifying both ''user'' and ''password'':<br />
<br />
<syntaxhighlight lang="html5"><br />
<master ... user="myadmin" password="mysecret"/><br />
</syntaxhighlight><br />
Default username and password are admin/password.<br />
<br />
==== Delivering Firmware and Boot Code ====<br />
At this point, you can simulate a device by requesting an update script using an URL like <code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code> in your browser (please note that this URL should not ask you for a password). Most likely, your browser will wait a little while and then say something like:<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# cannot get PBX info: cannot access http://yourmasterdevice.youdomain.tld/CMD0/box_info.xml, reading cache<br />
# cannot get cached PBX info: cannot access cache/master-info.xml - exit<br />
<br />
This is because you did not yet specify the ''master device'' which always runs the reference firmware. <br />
<br />
The idea here is that you have one innovaphone device in your system where the firmware is always manually updated. All other devices shall follow this reference firmware. The update server will deliver the firmware that runs on the master device to calling devices. For this to work, you need to set the ''info'' attribute of the [[#master|''master'']] tag and leave everything else ''as is''. <br />
<br />
Remember that you do all your local configuration changes in <code>user-config.xml</code>.<br />
<br />
As ''master'' is a first level tag, you can add it directly underneath the opening ''<config>'' tag. The only thing we need to define right now is the path to read the firmware information from your master device. Let's say your reference device has the IP address <code>172.16.0.10</code>, you end up with <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true" debugcerts="true" debugscript="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml" user="myadmin" password="mysecret"/><br />
</config><br />
</syntaxhighlight><br />
(you could use a DNS name instead of the IP address of course). <br />
The ''info'' URL is used by the update server itself only, it is never used by devices requesting an update script.<br />
<br />
When you refresh the page that simulates a device requesting an update script, the output should now change to something like<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# current firmware (unknown) does not match required firmware (130178)<br />
...<br />
<br />
This is to say that your master device runs firmware 130178 and the calling device does not (actually, as the calling client is simulated by your browser, it does not transmit its current firmware to the update server so it is ''(unknown)'').<br />
<br />
In order to actually update the clients with firmware and boot code, the files must be made available to the calling client somehow. This is done by specifying an URL in the [[Reference10:Concept_Update_Server#Prot_command | prot ]] and [[Reference10:Concept_Update_Server#Boot_command | boot ]] commands sent to the calling device:<br />
<br />
...<br />
# current firmware (unknown) does not match required firmware (130178)<br />
mod cmd UP0 prot http://update.yourcompany.com/mtls/update/fw/130178/ ser 130178<br />
# current boot code (unknown) does not match required boot code (130112)<br />
mod cmd UP0 boot http://update.yourcompany.com/mtls/update/fw/130112/ ser 130112<br />
...<br />
<br />
By default, the URL generated points to a sub-directory of your update server called ''fw'': <code>http://update.yourcompany.com/mtls/update/fw/130178/</code>. Note that this default URL expects sub-directories underneath the ''fw'' directory which correspond to the firmware and boot code build number. The file structure thus would be like<br />
<br />
/var/www/innovaphone/mtls/update/fw<br />
/130178<br />
/ip232.bin<br />
/130112<br />
/boot232.bin<br />
<br />
Note that the URL ends with a slash (<code>/</code>). This instructs the calling device to append the appropriate file name itself when retrieving the file (see [[Reference10:Concept_Update_Server#Prot_command | the prot command ]] for details). <br />
<br />
However, you can also specify any other URL by setting the ''url'' attribute in the ''fwstorage'' tag of your <code>user-config.xml</code>. In this attribute, certain meta words will be replaced (see [[#fwstorage | the ''fwstorage'' tag ]] for details). The default setting for example is <code>fw/{build}/</code>.<br />
<br />
You can disable firmware and boot code updates altogether by setting the ''info'' attribute in the ''master'' tag to an empty value.<br />
<br />
==== Your own Update Script Files ====<br />
The update server will deliver update scripts to calling devices which are synthesized from a number of ''update script snippets'' which you define yourself. The idea is that many devices share parts of their configuration while other parts are different. This depends on<br />
* the device ''class'' (e.g. a phone or a gateway)<br />
: the device class is automatically derived from its model. In fact, a single device may be in multiple classes. For example, an IP232 is in these classes: ''phone, pre_opus_phone, phone_newui'' which basically says that its a phone and has no OPUS codec yet but a "new" user interface (as opposed to the old one found e.g. on an IP110). A number of useful classes are pre-defined, but you can add your own in <code>user-config.xml</code>.<br />
* the device's environment (e.g. ''home-office'', ''branch-office'')<br />
: These reflect the fact that devices may need different configuration if they are in different locations (or environments). The update server does not know about a device's environment, which is why you need to configure it in to the devices update URL if you need this. By default, there is a single environment defined called ''default'', but of course, you can add your own<br />
* phase<br />
: configuring devices may need multiple phases to go through. If more than one phase is defined, the device will go through all phases sequentially, which is why a ''phase'' has a numerical ''seq'' attribute. Phases are went through in order of this attribute. When the device has reached the last phase, it will stay there. By default, there is only one phase called ''update''<br />
<br />
You can define update script snippets for any combination of device, environment and phase. You do so simply by placing appropriate files in to the ''scripts'' directory on your update server.<br />
<br />
Let's have a closer look at the web page we used to simulate a device calling for an update script before (<code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code>). It will show you all the possible files it would deliver to the device:<br />
<br />
# possible files (from scripts):<br />
# scripts/update-all-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-all-default.txt does not exist<br />
# scripts/update-phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone-default.txt does not exist<br />
# scripts/update-pre_opus_phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-pre_opus_phone-default.txt does not exist<br />
# scripts/update-phone_newui-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
The update script snippet files need to have proper names to define when they should be delivered. As you can see, they all start with <code>update-</code> which means that they apply to devices which are in the ''update'' phase. As by default there is only a single phase defined, all possible file names start with <code>update-</code>. <br />
<br />
The next part here is either <code>phone-</code>, <code>pre_opus_phone-</code>, <code>phone_newui-</code> or <code>all-</code>. This is because the calling IP232 is in three classes, so all respective snippets will be delivered to it. ''all'' however is a wild-card. That is, such files will be delivered to all devices, no matter what class they are in. You may wonder why there was no ''all'' for the phase. This is because there is only a single phase defined. If there would be more, the ''all''-variation of the phase-part of the file name would be appear.<br />
<br />
The third part finally is either <code>default</code> or <code>00_90_33_00_00_00</code> in our case. ''default'' is the name of the only ''environment'' that is defined by default. ''00_90_33_00_00_00'' is the device's serial number which is treated as an implicitly defined environment name. This allows you to deliver certain snippets to a single device only.<br />
<br />
Now let's create an update script snippet that is delivered to all phones in the default environment when they are in the update phase. The file name (which looks like ''phase''<code>-</code>''class''<code>-</code>''environment''<code>.txt</code>) has to be <code>update-phone-default.txt</code> therefore. Just for an exercise, let us set the device name (as shown in the browser's title bar) to ''This is a Phone!''.<br />
The command to do so is <code>config add CMD0 /name This+is+a+Phone%21</code>, so your file may look like this<br />
<br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
(btw: did you observe that config line arguments need to be url-encoded?). Now create the file and upload it in to the ''scripts'' directory of you update server. When you now refresh the page which simulates the device calling for an update script, you will see something like this:<br />
<br />
...<br />
# newest script (scripts/update-phone-default.txt) 11s old<br />
# files being updated (scripts/update-phone-default.txt 11s old, waiting for at least 90s to expire)<br />
<br />
When you update multiple script snippets, it is often undesirable that a device retrieves an inconsistent state of the scripts you are working on (e.g. if you already have saved one but not yet the other). To avoid this, the update server will look at the files modification dates and if one of those that needs to be delivered to the device is younger than 90 seconds, it will not send any of them at all. <br />
<br />
So when you wait for the 90 seconds to pass and refresh the page again, you will see something like this:<br />
<br />
# firmware build info cache is current<br />
# phase: update, nextphase: , environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
...<br />
# scripts/update-phone-default.txt 4.1 mins<br />
...<br />
# scripts/update-phone-default.txt<br />
# { begin script 'scripts/update-phone-default.txt' <br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
<br />
# end script 'scripts/update-phone-default.txt' }<br />
<br />
# trigger reset if required<br />
config write<br />
config activate<br />
iresetn<br />
<br />
As you can see, your snippet has been delivered by the update script. However, the update server has also added some trailing commands which write back the config to the devices flash memory (<code>config write</code>), activate it (<code>config activate</code>) and trigger a reset if needed (<code>iresetn</code>).<br />
<br />
If there are multiple snippets available for a calling device, all of them are concatenated in the sequence shown in the header of the returned script (where the possible file names are listed)<br />
<br />
==== Device Status ====<br />
When you have a lot of devices which are served by the update server, you may want to have a list of these devices along with some useful information regarding each devices. <br />
<br />
You can enable this by defining the ''status'' tag in your <code>user-config.xml</code>. So open the file and add the tag right next to the ''master'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
/><br />
</config><br />
</syntaxhighlight><br />
<br />
When you have done so, please refresh the page that simulates your calling device and then open <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>. You will now see a device list (well, a list with a single device, the one you simulated using your browser). The list will show some information regarding the devices that requested an update script lately. For more options, see [[#status]].<br />
<br />
==== Device Backup ====<br />
It is generally useful to have recent backups of all of your device configurations. The update server supports that if you enable it by defining the ''backup'' tag in your <code>user-config.xml</code>. Simply put it right next to the ''master'' or ''status'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<backup<br />
dir="backup"<br />
/><br />
</syntaxhighlight><br />
<br />
If you have done so and then refresh the page that simulates your calling device, you will now see <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
mod cmd UP0 scfg http://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m<br />
<br />
instead of <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
# Backups not enabled in config<br />
<br />
When a client requests an update script the next time, this will initiate a configuration backup which is stored underneath the ''backup'' directory. For each device, a sub-directory is created and all backup files are stored therein. By default, the last 10 differing configuration backups are kept.<br />
<br />
==== Controlling the Time-frames when Snippets are delivered ====<br />
The update client built-in to innovaphone devices allows to restrict updates to certain time frames (e.g. only during the night). This can be controlled using the [[Reference10:Concept_Update_Server#Times_command | times ]] command.<br />
<br />
You can configure the update server to use the time commands by setting the ''allow'' and ''initial'' attributes in the ''times'' tag in your <code>user-config.xml</code>. <br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"/><br />
</syntaxhighlight><br />
<br />
If either the ''allow'' or ''initial'' attribute is present, the update script will contain a line like<br />
<br />
...<br />
# Restricted times<br />
mod cmd UP1 times /allow 22,23,0,1,2,3,4 /initial 1<br />
<br />
In the example above, all update script activity will occur only between 10pm and 5am (local device time). For more details, see [[Reference10:Concept_Update_Server#Times_command | Concept Update Server]]<br />
<br />
==== Using and Enforcing HTTPS ====<br />
Your devices can either use HTTP or HTTPS to access the update server. In normal operation, the update server will generate URLs (e.g. the URLs used to retrieve firmware or to backup configurations) for the same protocol. <br />
<br />
However, you can enforce use of HTTPS by setting the ''forcehttps'' attribute in the [[Howto:PHP_based_Update_Server_V2#times | ''times'' ]] tag to <code>true</code>. If so, a device calling in with HTTP will be re-configured to use HTTPS:<br />
<br />
...<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
<br />
# changed state query args: polling, phase<br />
# new url=https://update.yourcompany.com/mtls/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i<br />
...<br />
(note that if you are using a non-standard port for HTTPS, you must define it using the ''httpsport'' attribute).<br />
<br />
==== Delivering Custom Certificates ====<br />
innovaphone devices come with pre-defined, trustworthy device certificates. However, all ''soft'' devices (such as the softwarephone, myPBX for Android/iOS or the IPVA) do not. Also, in many scenarios customers run their own ''public key infrastructure'' (PKI) and request their own certificates to be used instead of the pre-defined. This is why there is a need to roll-out custom certificates to devices. <br />
<br />
The update server supports this task to an extend. It can<br />
* check if a calling device has a proper certificate<br />
* instruct a calling device without proper certificate to create a ''certificate signing request'' (CSR)<br />
* download the CSR to the update server for easy access by the administrator<br />
* upload a signed CSR to the device<br />
* upload the public key(s) of the CA in to the device's trust list<br />
<br />
In order to roll-out custom certificates with the update server, the administrator needs to<br />
* run his own PKI (a.k.a. ''certificate authority'' (CA))<br />
* monitor CSRs that appear in the update server's device list<br />
* approve the request by manually submitting it to his own CA<br />
* upload the signed CSR to the update server<br />
<br />
Also, all devices must run ''V12r1 SR8'' or newer before the new certificate can be uploaded. Devices with older firmware will be updated automatically (see [[#Delivering_Firmware_and_Boot_Code]] above). However, the new firmware must be ''V12r1 SR8'' or later. <br />
<br />
To learn how you can create proper device certificates with your own windows CA, see [[Howto:Creating custom Certificates using a Windows Certificate Authority]].<br />
<br />
By default, custom certificates are turned off and you will see a note like<br />
<br />
# certificates: either certificate handling or state tracking not enabled - not doing any certificate checking<br />
<br />
in the delivered update script.<br />
<br />
Custom certificates are turned on by adding a ''customcerts'' tag to your <code>user-config.xml</code>:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
The page that simulates your calling device will now include a line saying:<br />
<br />
# certificates: we dont know anything about the current certificate state - not doing any certificate checking <br />
<br />
In order to decide if or if not a calling device has a valid certificate, the device needs to send its current public key to the update server. This is done by enabling ''queries'' in your <code>user-config.xml</code>. ''Queries'' is a means to have the device submit certain information to the update server. In the update server's default configuration, two queries are defined but not enabled:<br />
<br />
* the query ''certificates'' sends the current certificate state <br />
* the query ''admin'' sends the current device name (as set in ''General/Admin'')<br />
<br />
To enable a query, you must specify the class a calling device must be in in order to execute the query. This is done by adding an ''applies'' tag for the respective query in your configuration:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
</query><br />
</queries><br />
...<br />
</syntaxhighlight><br />
<br />
This basically says that both queries should be executed by devices of just any class. Unless you enable queries, your update script will include lines such as:<br />
<br />
# no queries defined for any of callers classes: phone+pre_opus_phone+phone_newui<br />
<br />
Once you have enabled them, you will see something like<br />
<br />
...<br />
# query 'certificates'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=certificates ser nop /always mod%20cmd%20X509%20xml-info<br />
# query 'admin'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=admin ser nop /always mod%20cmd%20CMD0%20xml-info<br />
...<br />
<br />
You can display the data sent by a query in the device status display. To do so, you need to define one or more ''show'' tags as part of the respective ''query'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<query id="certificates"><br />
<applies>*</applies><br />
<!-- show device certificate CNs and Issuer CNs in status page --><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
...<br />
</syntaxhighlight><br />
<br />
Two steps however are still missing: <br />
<br />
* a) you need to tell the update server the names of all the CAs you consider trustworthy. This is done by listing their names in the ''CAname'' attribute of the ''customcerts'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2,innovaphone-INNO-DC-W2K8-Zertifizierungsserver"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
: The example shown defines that you will accept both of innovaphone's device certificate authorities and also your own CA called ''innovaphone-INNO-DC-W2K8-Zertifizierungsserver''. Devices with device certificates issued by one of these CAs will be left untouched. For all other devices (for example, any ''myPBX for Android/iOS'' device that has a self-signed certificate), the generation of a custom certificate will be initiated.<br />
<br />
* And b) you need to provide the public key of your CA (so it can be uploaded to the calling device's trust list). You need to place the DER (not PEM) encoded public key in to a file called <code>certs/CAkey-01.cer</code> on your update server (if you want to upload the whole certificate chain, put all of the public keys in the chain in to separate additional files called <code>certs/CAkey-02.cer</code> and so forth.<br />
<br />
For more details on how to approve certificate signing requests, see [[#Approving Certificate Signing Requests]] below.<br />
<br />
==== Enforcing Trust ====<br />
Update script snippets may include sensitive information which you do not want to disclose to the public. Even though you can force the use of HTTPS (see above), this still does not keep your data secure. After all, an attacker could simply request an update script from your update server using HTTPS. To secure your data, you need to make sure that snippets are only sent to devices which identify themselves using a trusted certificate with a correct ''common name'' (CN). This is done using ''mutual transport layer security'' (MTLS). Therefore, you need to configure MTLS in the LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts | If you want to setup MTLS-restricted Delivery of Update Scripts]] above). <br />
<br />
You can enable this by setting ''forcetrust'' to <code>true</code> and ''httpsport'' to the port configured as ''MTLS Port'' in your web server. This is done in the ''times'' tag of your <code>user-config.xml</code>.<br />
<syntaxhighlight lang="html5"><br />
<times <br />
...<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
</syntaxhighlight><br />
Please note that ''forcetrust'' does nothing unless ''forcehttps'' is also set!<br />
<br />
If so, the update server will deliver update script snippets only to clients which identify themselves with a proper certificate. For this to work, you will need to add the public key of your certificate authority to your web server's list of trusted certificates. <br />
<br />
When using the ''Linux Application Platform'' (LAP), you need to add the PEM-encoded public key of your certificate authority to the ''innovaphone-ca.pem'' file in <code>/home/root/ssl_cert</code>. When you have installed the LAP, this file will contain the PEM-encoded public keys of the 2 innovaphone device certificate authorities. You can simply add the public key of your CA to the end of this file:<br />
<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca2 public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...your-ca public key...<br />
-----END CERTIFICATE-----<br />
<br />
You will need to restart the LigHTTPD then (most easily done by restarting the entire LAP (''Diagnose/Reset'') or by issuing the command <code>/etc/rc2.d/S02lighttpd restart</code> from the linux root command prompt). Finally, you must set the ''forcetrust'' attribute. <br />
(Note that the ''innovaphone-ca.pem'' file may be overwritten when a new LAP version is installed. It is thus a good idea to keep a copy and check it after an upgrade).<br />
<br />
When ''forcetrust'' is effective and the calling device does not use HTTPS, it will be reconfigured to do so:<br />
<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
...<br />
# new url=https://update.yourcompany.com:444/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i&env=default<br />
<br />
If the calling device uses HTTPS but sends no certificate, you will see a message like<br />
<br />
# cannot verify CN with HTTPS: SSL_CLIENT_S_DN_CN not present - fix web server configuration or use MTLS-enabled port!<br />
<br />
This is probably because it is using a non-MTLS enabled port for HTTPS (e.g. the standard port 443) although MTLS is configured for a different port. <br />
<br />
If the calling device uses HTTPS and sends a certificate but the CN does not match the serial number of the device, you will see a message such as<br />
<br />
# Device claims to be 'IP232-30-00-af' but identifies as 'CKL-CELSIUS-W10.innovaphone.sifi' by TLS<br />
<br />
If the calling device uses HTTPS and sends a certificate but the certificate is not trusted because its issuer is not listed in ''innovaphone-ca.pem'' (see above), the connection will be refused <br />
<br />
In all such cases, no snippet will be delivered. If the certificate is good, you will see a message like <br />
<br />
# good certificate(CN='IP232-30-00-af')<br />
<br />
followed by the appropriate snippets.<br />
<br />
==== Using multiple Phases ====<br />
When you configure multiple phases, all phases up to the final phase are stepped through and when all associated update script snippets for all phases are done, the device will ultimately stay in the final phase. This can be used e.g. to implement update script snippets which are executed once only when the device is initialized. Settings made in all but the last phase can later be overridden by the user or an administrator. The settings done in the update script settings for the final phase however are re-executed whenever one of your update script files for this phase changes.<br />
The process of deploying some initial settings is often called ''staging''.<br />
<br />
To enable staging, you therefore create a new phase that comes before the default phase (which is ''update''). Phases are sorted numerical by an attribute called ''seq''. As the default phase ''update'' is defined with ''seq=200'', your new staging phase must be defined with a lower seq value:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
...<br />
</syntaxhighlight><br />
<br />
When you define such a phase, there will be new possible update script snippet file names: <br />
<br />
# phase: staging, nextphase: update, environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
# scripts/all-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-all-default.txt does not exist<br />
# scripts/all-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone-default.txt does not exist<br />
# scripts/all-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-pre_opus_phone-default.txt does not exist<br />
# scripts/all-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone_newui-default.txt does not exist<br />
# scripts/staging-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-all-default.txt does not exist<br />
# scripts/staging-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone-default.txt does not exist<br />
# scripts/staging-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-pre_opus_phone-default.txt does not exist<br />
# scripts/staging-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone_newui-default.txt does not exist<br />
<br />
First of all, there are new names for this particular phase. Furthermore, as we now have multiple phases, the new wild-card name ''all'' is possible. <br />
<br />
An example for a staging script snippet might be a file called <code>staging-all-all.txt</code>: <br />
<br />
# change admin password<br />
config add CMD0 /user admin,my-admin-password<br />
config activate<br />
config rem CMD0 /user<br />
<br />
This will set the admin password to <code>my-admin-password</code> during staging (it should be obvious that such staging should only be done in combination with [[#Enforcing_Trust|Enforcing Trust]], see above).<br />
<br />
=== A complete <code>user-config.xml</code> Sample ===<br />
Here is a complete sample of a configuration file. <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
missing="1000"<br />
/><br />
<backup<br />
dir="backup"<br />
/><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2"<br />
CSRsan-dns-1="{name}.company.com"<br />
CSRsan-dns-2="{hwid}.company.com"<br />
CSRsan-dns-3="{rdns}"<br />
CSRsan-ip-1="{realip}"<br />
/><br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
<environments><br />
<environment id="intranet"/><br />
<environment id='sifi'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='berlin'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='verona'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='homeoffice'/><br />
<environment id='mobile'/><br />
</environments<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
</queries><br />
</config><br />
</syntaxhighlight><br />
<br />
=== Operation ===<br />
<br />
==== Configuring and Debugging a real Device ====<br />
To configure your device for use with the update server, you simply set <code>http://update.yourcompany.com/mtls/update/update.php</code> as ''Command File URL'' in ''Services/Update'' (you can optionally add a <code>?env=envname1,envname2</code> query argument if you want to set one or more specific environments for your device). The update server will re-configure the device later on so that it uses all the required query arguments.<br />
<br />
Generally, there are the following methods to set the ''Command File URL''<br />
* configure it manually using the admin GUI<br />
* provide it to the device using DHCP (this will however not work for the softwarephone or ''myPBX for Android/iOS'')<br />
* use the innovaphone provisioning service (see [[Reference10:Concept_Provisioning|Reference10:Concept Provisioning]] for details)<br />
<br />
Once the ''Command File URL'' is set on the device, you can debug what the update client in the device does, using the normal trace mechanism. For this, you will want to open the ''Debug'' page (<code>http://x.x.x.x/debug.xml</code>) and set the ''Update/Polling'' and ''Update/Execution'' check-marks. When the update client queries the update server, you will see lines like <br />
<br />
0:0826:465:5 - upd_poll: state IDLE -> RECV<br />
0:0826:465:7 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:466:0 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:695:0 - upd_poll: state=RECV sent()<br />
0:0826:752:0 - upd_poll: status 200 headercomplete=1 contentlength=0<br />
0:0826:754:0 - upd_poll: recv_data(2199)<br />
0:0826:754:1 - upd_poll: recv_data(0) EOF<br />
0:0826:754:1 - upd_poll: GET EOF - state=RECV http-status=200 length=2199<br />
0:0826:754:1 - upd_poll: do commands<br />
0:0826:754:1 - upd_poll: state RECV -> EXEC<br />
<br />
in the trace. Commands included in the update script will look like<br />
<br />
0:0826:754:5 - script::get_line: >line(mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m)<br />
0:0826:754:5 - upd_poll: pass 'mod cmd UP0 /sync scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m')<br />
<br />
Finally, you do not need to wait for the next time the device decides to contact the update server. Instead, you can force this by sending an <code>http://</code>''your-device-ip<code>/!mod cmd UP1 poll</code> to the device.<br />
<br />
==== The Device Status Update Page ====<br />
If status tracking is enabled (see [[#Device_Status | Device Status]] above), the update server will keep a list of devices it knows of. You can see this list by calling <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>.<br />
<br />
By default, the list will not be updated unless you refresh the page. However, if you set the ''refresh'' attribute in the ''status'' tag in your ''user-config.xml'' file to <code>60</code>, all entries in this list will be refreshed every 60 seconds (however, the list itself will not be reloaded, so to see new devices, your need to refresh the entire page). Devices that have not been seen for more than 7 days are shown in a different colour. Devices that have not been seen for longer than 90 days will be silently removed from the list.<br />
<br />
===== Filtering =====<br />
From build 2011, you can filter the devices shown using the ''device filter'' field in the ''Device'' column header. The filter string is matched against ip-address, serial number, type, name, phase, class, environment, firmware and bootcode. Also, you can filter by using the keywords ''present'' and ''missing'' (based on the ''missing'' attribute of the ''status'' tag in your configuration file). If one of these attributes match your filter expression, the device is shown.<br />
<br />
A filter expression must match a complete ''word''. For example, if you filter by <code>16</code> this would match the ip-address <code>172.</code>16<code>.0.20</code> but it would not match <code>192.</code>16<code>8.0.1</code>.<br />
<br />
If you specify multiple filter expressions (separated by white space), only devices which match all of the expressions will be shown. For example, if you filter by <code>172.16. ip222</code> this might match all IP222 in your 172.16.*.* network. Filter expressions are case insensitive.<br />
<br />
==== Approving Certificate Signing Requests ====<br />
When you use custom certificate roll-out, you will see a column ''Certificates'' in the device status list, showing the devices certificate status. <br />
<br />
[[Image:PHP_based_Update_Server_V2_Device_Status.png]]<br />
<br />
If a CSR has been created on the device, this will be available for download in the ''Files'' section of the ''Info'' column. You can submit the CSR to your CA and then upload the signed request (using the ''Upload'' button in the ''Files'' section of the ''Info'' column). The signed request will eventually be uploaded to the device then. On the device itself, the CSR is shown in ''General/Certificates''.<br />
<br />
[[Image:PHP_based_Update_Server_V2_CSR.png]]<br />
<br />
<br />
==== Certificate Errors ====<br />
When a signed certificate request uploaded to the device can not be installed on the device, you will see a message like <code>Certificate upload error - certificate handling stopped</code> in the ''Certificates'' columns for the device. In this case, any further processing will be stopped. Most likely, the reason is that you uploaded the wrong file as signed certificate request (either not a signed certificate at all or a signed request for another device).<br />
<br />
In this case<br />
* remove any certificate request from the device<br />
* remove the <code>X509/REQUESTERROR</code> from the device configuration (i.e. <code>vars del X509/REQUESTERROR</code>)<br />
: these 2 steps can be easily done by resetting the device to factory defaults and then set the ''Update URL'' again<br />
* remove any stored files for the device on the update server (shown in the ''Certificates'' column)<br />
<br />
The normal process will start all over again then.<br />
<br />
=== Migrating old PHP Update Server Installations ===<br />
==== Migrating from build 2000 and newer ====<br />
* Copy all files and folders, '''except''' the ''scripts'' and ''config'' folder, from the source to your destination<br />
* make sure all files and directories have correct owner (''www-data''), group (''www-data'') and mode (''read''/''write'' plus ''execute'' for directories)<br />
: for example, in WinSCP you can use the ''Properties (F9)'' dialogue on the installations root folder:<br />
: [[Image:PHP based Update Server V2-WinSCP-Properties.png]]<br />
* open the ''StatusPage'' and make sure you refresh all cached files in your browser (depending on your browser, this may happen with Ctrl-R or Ctrl-F5)<br />
<br />
==== Migrating from build 1011 and older ====<br />
To upgrade from the [http://download.innovaphone.com/ice/wiki-src/index.php?urloffset=php-update-server%2F&name=php-update-server+%28all+available+builds%29&reverselevel=0&maxbuilds=999&root=c%3A%2Finetpub%2Fwwwroot%2Fdownload%2Fice%2Fdownload%2Fp%2Fwiki-src%2Fphp-update-server%2F1010+%28PHP+based+Update+Server+final%29%2F.. old version (builds up to 1011)] of the PHP based update server (as described in [[Howto:PHP based Update Server]]), do the following<br />
<br />
* diff your <code>config.xml</code> to the one originally shipped (you can download it at [http://download.innovaphone.com/ice/download/p/wiki-src/php-update-server/1011%20(PHP%20based%20Update%20Server%20final)/php-update-server-1011.zip download.innovaphone.com/ice/wiki-src])<br />
: take note of all the changes you made to it<br />
* install the new version of the update server to a different URL<br />
* configure it according to your needs by modifying <code>user-config.xml</code>. Do not modify the new <code>config.xml</code>!<br />
** apply all modifications you did to your old <code>config.xml</code> to your new <code>user-config.xml</code><br />
** if you have used a custom setting for fwstorage/@url, you need to change it a bit. Change for example <code>url="http://myfwserver.mycompany.com</code> to <code>http://myfwserver.mycompany.com/{build}/</code> (note the trailing slash!)<br />
* copy all your update script snippet files from the old server (these are all the .txt files in the old server's root directory) to the <code>scripts</code> directory of your new server<br />
* copy the complete <code>fw</code> tree from the old server to the <code>fw</code> directory of your new server<br />
* thoroughly test your new installation with some test devices<br />
* when satisfied, edit <code>update-migrate.php</code> and change the code as described in the comment inside the file<br />
* copy <code>update-migrate.php</code> from the new installation to the old installation<br />
* on one of the devices which are currently served by the old update server, change the ''Command File URL'' so that it points to <code>update-migrate.php</code> instead of <code>update.php</code>. Do not change anything else in the URL. So if it currently is set to e.g. <br />
: <code>https://172.16.0.90/update/update.php?type=#t&env=sifi&polling=0&phase=update</code>, change it to<br />
: <code>https://172.16.0.90/update/update-migrate.php?type=#t&env=sifi&polling=0&phase=update</code><br />
* verify that this device properly switches to your new installation<br />
** the ''Command File URL'' is changed so that it points to the new installation<br />
** the device shows up in the device status page of your new server<br />
<br />
* rename <code>update.php</code> to <code>update-old.php</code> in your old update server<br />
* rename <code>update-migrate.php</code> to <code>update.php</code> in your old update server<br />
* verify that all of your devices start showing up in the new server's status page<br />
<br />
=== Complete Parameter Reference ===<br />
Note: attributes are marked with an ''at'' (<code>@</code>) prefix. So ''master/@info'' refers to the ''info'' attribute in the ''master'' tag.<br />
<br />
The following tags and attributes are available in the <code>user-config.xml</code> file, their default values are configured in <code>config.xml</code> (which you should not modify):<br />
<br />
==== master ====<br />
Defines the reference device where the firmware and boot code information is taken from.<br />
; master/@info : the URL to the device info data. Set it to an URL like <code>http://</code>''your-reference-device''<code>/CMD0/box_info.xml</code>. Please note that this device must not have the ''Password protect all HTTP pages'' set in ''Services/HTTP/Server''. If you use the literal <code>none</code>, the master device will not be queried<br />
; master/@expire : the firmware info cache lifetime. The firmware information is cached in a file (to make sure this information is present even if the reference device is off-line). The cache will not be refreshed before the ''expire'' time (in seconds) is expired<br />
; master/@cache : the cache file name. The web server must be able to write this file (see [[#Installation | ''Installation'' ]] above)<br />
; master/@user : if non-empty, this is the user name required to access the update server's web ui<br />
; master/@password : the password<br />
<br />
From build 2009, you can control the firmware and bootcode version to use on update:<br />
; master/@firmware : if set, it may be one of <br />
:: <code>master</code> (default if not set) : the firmware version is derived from the master device<br />
:: <code>none</code> : the firmware update is generally disabled<br />
:: ''build-number'' : the firmware is set to a certain ''build-number'' regardless of the firmware running on the master device<br />
; master/@bootcode: if set, it may be one of <br />
:: <code>master</code> (default if not set) : the bootcode version is derived from the master device<br />
:: <code>firmware</code> : the bootcode version is set to the firmware version. This may be useful if the master device has no bootcode (e.g. the ipva)<br />
:: <code>none</code> : the bootcode update is generally disabled<br />
:: ''build-number'' : the bootcode is set to a certain ''build-number'' regardless of the bootcode running on the master device<br />
<br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"<br />
/><br />
</syntaxhighlight><br />
<br />
==== master/applies ====<br />
Available from build 2009.<br />
<br />
Boot code and firmware updates are only executed by devices which match all of the given <applies> conditions. A condition is met if the device belongs to the class that is given as tag content. If the ''env'' attribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; master/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in <br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"><br />
<!-- applied to phones in berlin only --><br />
<applies>phone</applies><br />
<applies env="">berlin</applies><br />
</master><br />
</syntaxhighlight><br />
<br />
==== fwstorage ====<br />
Defines from where the device will retrieve the firmware files for updates.<br />
; fwstorage/@url : defines the URL to retrieve the files from. In this URL, the following meta words will be replaced as follows:<br />
:* <code>{build}</code> - the requested firmware or boot-code <br />
:* <code>{model}</code> - the devices type (e.g. <code>IP232</code>). This is derived from the ''type'' query argument used in the update URL which is set to the value <code>#t</code> which in turn is expanded to the [[Reference10:Concept_Update_Server#Scfg_command|''device type'']] of the device requesting the update script<br />
:* <code>{filetype}</code> - the type of the required file, either <code>boot</code> or <code>prot</code><br />
:* <code>{filename}</code> - (from build 2014) the boot code or firmware filename for the device in lower case. Required if you use the LAP as firmware storage and firmware files are falsely requested in uppercase letters (as URLs on the LAP are case sensitive and if the files are requested with upper case names, the original files with lowercase name are not found). This also allows you to use alternate firmware file names (such as e.g. <code>ip110-sip.bin</code>). See the ''filenames'' tag<br />
Note that if the ''url'' ends with a slash (<code>/</code>), the calling device will automatically [[Reference10:Concept_Update_Server#Prot_command|append the name of the requested file]]. In this case, the file system the URL points to must therefore contain sub-directories for each firmware build which contain the corresponding firmware builds (e.g, ''fw/12345/ip232.bin''). If ''url'' is not an URL (as is the case for the default value <code>fw/{build}/</code>), it is treated as a sub-directory underneath the update server's root directory<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="fw/{build}/"/><br />
</syntaxhighlight><br />
<br />
===== Loading firmware files from innovaphone.com =====<br />
You can also load firmware from the innovaphone.com web site. To do so, set ''url'' like so:<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="http://webbuild.innovaphone.com/{build}/"/><br />
</syntaxhighlight><br />
<br />
Please note that ''this is a voluntary service, no guarantee of availability, no service level agreement''.<br />
<br />
[[User:Ckl|ckl]] 15:40, 21 February 2023 (CET) This service has been discontinued. You may consider using <code>https://store.innovaphone.com/release/download/{build}/</code> instead.<br />
<br />
==== backup ====<br />
Defines where backup files are kept.<br />
; backup/@dir : the directory underneath the script directory where backups are stored<br />
; backup/@nbackups : the number of different backup files kept<br />
; backup/@scfg : the target URL for the scfg command. If this is not an url, it is interpreted relative to the script directory URL. You can add more options for the ''scfg'' command, like in <code>scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m ser hourly /force 1"</code> (this example will make sure backups are attempted only once per hour, so as to reduce load on the server).<br />
<br />
<syntaxhighlight lang="html5"><br />
<backup <br />
dir="backup"<br />
nbackups="10"<br />
scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m"<br />
/><br />
</syntaxhighlight><br />
<br />
==== status ====<br />
Defines details regarding the state kept for a device requesting an update script.<br />
; status/@dir : the directory the status files are kept in (e.g. <code>status</code>). Used as the name for a sub-directory underneath your install directory on your web server. You must make sure that files in this directory cannot be read by anyone without proper authentication, as such files may contain sensitive information.<br />
; status/@expire : if set and not empty, devices which have not requested an update script will be removed from the inventory after ''n'' seconds. For example, ''expire="7776000"'' will forget about devices after 90 days with no contact<br />
; status/@missing : devices are shown in a different colour (indicating that they are ''missing'') after ''n'' seconds. For example, ''missing="604800"'' will flag devices as missing after 7 days with no contact<br />
; status/@refresh : if set and not empty and larger than zero, the admin user interface showing the known devices will refresh the status of all shown devices each ''n'' seconds<br />
; status/@logkeep : number of seconds log messages for individual devices will be kept<br />
; status/usehttpsdevlinks : if set to true, links to devices in the status page are created using the https:// scheme<br />
<syntaxhighlight lang="html5"><br />
<status <br />
dir="status" <br />
expire="7776000" <br />
missing="200" <br />
refresh="10"<br />
logkeep="90"<br />
usehttpsdevlinks="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== times ====<br />
Defines details regarding the delivery of update scripts to requesting devices. <br />
<br />
; times/@dir : the directory the update scripts are stored in. For security reasons, you may want to make sure that files in this directory cannot be read without proper authentication.<br />
<br />
These attributes define the arguments of the [[Reference10:Concept_Update_Server#Times_command | times command]]. If neither ''allow'' nor ''initial'' is set, no ''times'' command is used (that is, the update scripts will be processed at all times).<br />
; times/@allow : the hours to be used in the ''times'' command (as in <code>mod cmd UP1 times /allow </code>''hours'')<br />
; times/@initial <br />
: the minutes to be used in the ''times'' command (as in <code>mod cmd UP1 times /initial </code>''minutes''). If you want to use initial staging of virgin devices ''forcestaging'' must be <code>true</code>.<br />
<br />
Delivered update scripts can include a [[Reference10:Concept_Update_Server#Check_command | check command ]] if the ''check'' attribute is set to <code>true</code>. . In this case, the devices will executed the scripts again only when you have edited/changed them.<br />
; times/@check : if set to true, update scripts will be executed once only (unless their contents change)<br />
<br />
When editing a number of update scripts, it is often not desirable if one changed script is already executed before another is changed too. When the ''grace'' attribute is set to a positive value, no script at all is delivered to requesting devices unless ''n'' seconds have expired since the last edit. For example, setting ''grace'' to 90 gives you one and a half minute to finish all your edits. <br />
<br />
; times/@grace : defines the number of seconds that must expire before update script changes take effect<br />
<br />
The update server works in either ''fast'' or ''normal'' mode. In ''fast'' mode, update scripts have to be requested more frequently, for example to step through multiple staging phases faster. This is enforced by modifying the calling device's ''Interval'' setting in [[Reference11r1:Services/Update | ''Services/Update '']] and setting it to 1 in ''fast'' mode.<br />
<br />
; times/@interval : polling interval in minutes for normal operation (that is, when all staging is done)<br />
; times/@polling : when you are using multiple ''phases'' (e.g. ''staging'' and ''update''), this defines the number of seconds to wait before the device reads the scripts for the next phase (see [[Reference10:Concept_Update_Server#Provision_command | provision command]]) (please do not modify the default value)<br />
<br />
In some installations it may be desired to deliver update scripts with HTTPS only or even only to calling devices which have identified themselves with a proper TLS certificate. <br />
<br />
; times/@forcehttps : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS. If the device is ready to receive an update script and still calls in with HTTP only, its ''Command File URL'' will be re-configured to use HTTPS automatically.<br />
; times/@httpsport : if you use a non-standard port for HTTPS access to update scripts, it can be defined here<br />
; times/@httpsurlmod : if your HTTPS URL differs from the HTTP URL, you can specifiy a ''modifier''. It has the form <code>!</code>''pattern''<code>!</code>''replacement''<code>!</code> where ''pattern'' is a [http://docs.php.net/manual/en/pcre.pattern.php PHP PCRE pattern]. For example, <code>!mtls/!!i</code> will remove the string <code>mtls/</code> from the URL used by the device to create the HTTPS URL to use. The delimiter (in this case "!") can be changed according to your own wishes, the first character defines the delimiter. The "i" at the end ignores uppercase.<br />
<br />
Note: If you want to change the hostname of your URL, you have to add the Port 444 to your hostname. The code should then look like this: <code>!hostname1:444/mtls/!hostname2:444/!i</code>.<br />
; times/@forcetrust : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS and a valid and trusted client certificate that identifies itself as the devices it claims to be<br />
; times/@forcestaging : before build 2009, the restrictions imposed by the times/@allow settings affected all update ''phases''. This however makes staging cumbersome, as you usually want to restrict normal configuration changes to off-working-hours. If it is <code>true</code>, the restriction will be applied only to the last phase (that is, the ''staging'' phases will execute immediately). Also, the final phase will be executed once by staged devices. <br />
: Since build 2021 the standard value was changed to <code>true</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
dir="scripts" <br />
allow="" <br />
initial="" <br />
check="true" <br />
polling="5" <br />
interval="15" <br />
grace="90" <br />
forcehttps="true" <br />
httpsport="444" <br />
forcetrust="false" <br />
httpsurlmod="!mtls/!!i"<br />
forcestaging="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== phases ==== <br />
Defines the scripting phases. In many installations, there are initializations which should be performed once only. This is known as the ''staging'' phase. Once this is done, the devices continue with the execution of the normal update scripts (this phase is known as ''update'' by default). In the (unlikely) event that you want more or less phases, you can add/remove ''phase'' tags.<br />
==== phases/phase ====<br />
; phases/phase/@id : the name for the phase<br />
; phases/phase/@seq: the sequential number for the phase. Phases are stepped-through in the numeric order defined by their ''seq'' attribute. <br />
<br />
By virtue of the ''seq'' attribute, you can insert phases in to the set of phases defined by default, which is<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<!-- sequence is important! --><br />
<phase id="staging" seq="100"/><br />
<phase id="update" seq="200"/><br />
</phases><br />
</syntaxhighlight><br />
For example,<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<phase id="phase" seq="150"></phase><br />
</phases><br />
</syntaxhighlight><br />
will insert a custom phase ''myphase'' as second phase.<br />
<br />
==== environments ====<br />
Defines the distinguished environments. Devices can have zero or more ''environments'' associated. The environments a device is considered to be in must be passed as <code>?env=</code>''name1,name2,..'' URL query arg in the update url. <br />
<br />
Update scripts for all environments listed will be applied.<br />
First the environment scripts itself, then all implied environments in the same order as the ''implies'' tags are listed in the config. <br />
<br />
The default config file defines the environments ''intern'' and ''homeoffice'' but you can define your own if you like.<br />
<br />
==== environments/environment ====<br />
; environments/environment/@id : the name for the environment<br />
<br />
==== environments/environment/implies ====<br />
Each ''environment'' may have a number of sub-tags of type ''implies''. It contains the ''id'' of another ''environment''. If a device is in an environment with an ''implies'' sub-tag, it is assumed that it is in the implied environment also. <br />
<syntaxhighlight lang="html5"><br />
<environments><br />
<environment id='hq'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='intranet'/><br />
<environment id='homeoffice'/><br />
</environments><br />
</syntaxhighlight><br />
The <code>implies</code> tag handles no recursion (therefore, in the above example, if ''intranet'' would have an implies, a device in environment ''hq'' would not inherit this implication. Instead, you must list it explicitly for the ''hq'' environment).<br />
<br />
If multiple environments will be used they will be applied also in the same order as the ''environment'' tags are listed in the config.<br />
<br />
;There are some special things to keep in mind when adding multiple environments in the ''env'' attribute and they use the ''implies'' tag in the config.<br />
:- The first environment specified in the arg ''env'' will be processed as expected with all children.<br />
:- Recursive ''implies'' from an environment added via ''implies'' is not performed. (No children's children)<br />
:- Only the first environment which is specified in the ''env'' arg with implies is processed, all additional values in the arg ''env'' will be ignored.<br />
:- If the environment in the arg ''env'' it is not the first value, then it must also use the ''implies'' config to implies itself. Otherwise only child are processed.<br />
<br />
==== classes ====<br />
Defines the available device classes. Devices of different classes (e.g. phones and gateways) can have different update scripts. The device class is derived from the <code>#t</code> (device type) query arg of the update script URL (which is why you must configure it in the update URL). By default, the classes ''phone'', ''opus_phone'', ''pre_opus_phone'', ''phone_oldui'', ''phone_newui'', ''mobile'', ''gw'', ''fxogw'', ''fxsgw'', ''brigw'', ''prigw'' are recognized (newer versions may include more and/or updated class definitions). Also, any device is considered to be member of a class that has the device type as class-name. For example, an IP112 is in a class called ''ip112''.<br />
<br />
A given device can be in multiple classes and will execute all update scripts available for these classes.<br />
<br />
==== classes/class ====<br />
Each ''class'' has a number of sub-tags of type ''model''. It contains the value replaced for the <code>#t</code> query arg. The models listed belong to the class.<br />
; classes/class/@id : the name of the class<br />
<br />
<syntaxhighlight lang="html5"><br />
<classes><br />
<class id="gw"><br />
<model>ip0010</model><br />
...<br />
</class><br />
<class id="phone"><br />
<model>ip110</model><br />
<model>ip232</model><br />
...<br />
</class><br />
<class id="phone_oldui"><br />
<model>ip110</model><br />
...<br />
</class><br />
<class id="phone_newui"><br />
<model>ip232</model><br />
...<br />
</class><br />
</classes><br />
</syntaxhighlight><br />
<br />
==== nobootdev ====<br />
Device types listed here do not receive boot code updates.<br />
===== nobootdev/model =====<br />
Each model tag defines a device type that shall not receive boot code updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nobootdev> <br />
<model>ipva</model><br />
<model>ipvadec</model><br />
<model>swphone</model><br />
<model>mypbxa</model><br />
<model>mypbxi</model><br />
</nobootdev><br />
</syntaxhighlight><br />
<br />
==== nofirmdev ====<br />
Device types listed here do not receive firmware updates.<br />
===== nofirmdev/model =====<br />
Each model tag defines a device type that shall not receive firmware updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nofirmdev> <br />
<model>swphone</model><br />
<model>mypbxi</model><br />
</nofirmdev><br />
</syntaxhighlight><br />
<br />
==== filenames ====<br />
Available from build 2014. Allows you to define alternate firmware and boocode names for use with the <code>{filename}</code> replacement (see the ''fwstorage'' tag), if required.<br />
<br />
===== filenames/model =====<br />
Each model defines a set of alternative file names for a certain device type.<br />
; filenames/model/@id : the (lowercase) device type identfier (e.g. <code>ip112</code>). If this matches the device type of the calling device, the alternate file names are replaced<br />
; filenames/model/@prot: the alternate firmware file name<br />
; filenames/model/@boot: the alternate boot file name<br />
<syntaxhighlight lang="html5"><br />
<filenames><br />
<model id="mypbxa" prot="mypbx.apk"/><br />
</filenames><br />
</syntaxhighlight><br />
<br />
defines the firmware file name <code>mypbx.apk</code> for the device type <code>mypbxa</code>.<br />
<br />
==== stdargs ====<br />
Better don't touch :-)<br />
<br />
==== customcerts ====<br />
innovaphone hardware devices come with a device specific, trust-able ''device certificate''. However, in many situations it is desirable to roll-out customer-created ''custom certificates'' to all devices. The update server can do this. This will replace the shipped devices certificates both on hardware devices (where all such certificates are derived from innovaphone's certificate authority) as well as on soft devices which run with a self.-signed certificate by default (such as the software-phone or ''myPBX for Android/iOS'').<br />
<br />
; customcerts/@dir : the name of the directory underneath your installation directory which stores device certificates. you should make sure that this directory cannot be accessed without proper authentication<br />
; customcerts/@CAkeys : a file name pattern (e.g. <code>CAkey*</code>). All files in ''customcerts/@dir'' which match the pattern must contain DER or PEM encoded public keys which form the ''certificate chain'' of your CA (usually it is only one and the public key of your CA). One key per file. The files, when sorted by name, must contain the CA key itself in the last file (any intermediate certificates, if any, in the other files in correct order)<br />
; customcerts/@CAtype : must be <code>manual</code> currently<br />
; customcerts/@CAname : a comma-separated list of CA names. Devices presenting a certificate signed by one of these CAs will be considered as having a valid certificate. Otherwise, they wil be instructed to create a ''certificate signing request'' (CSR) for subsequent submission to your CA<br />
; customcerts/@CAnamesep : defines the name separator for ''customcerts/@CAname''. The default is '<code>,</code>' and you must change it to a different value if one of your CAs includes a comma in its name<br />
; customcerts/@renew : if set and not 0, certificates are replaced by new ones ''n'' days before they expire<br />
; customcerts/@CAwildcard : normally, the update server will accept a certificate only if its CN matches the device's serial number. However, sometimes, so-called ''wildcard certificates'' such as <code>*.innovaphone.com</code> are used on a device (e.g. on a PBX or on a ''reverse proxy''). If set, the update server will also accept a certificate that contains a CN which equals the value of ''customcerts/@CAwildcard''. Note that the CN must match literally. For example, if ''''customcerts/@CAwildcard'' is set to <code>*.innovaphone.com</code> a CN like <code>host.innovaphone.com</code> is not accepted<br />
<br />
<br />
When the update server determines that a device calls in with an un-trusted or expired certificate, it will have it create a signing-request for a new certificate. The requested certificate properties can be defined:<br />
<br />
; customcerts/@CSRkey : the key size, e.g. <code>2048-bit</code>. Note that we do not recommend keys larger than 2048 bit (see [[Reference7:Certificate_management#Certificate_Key_Length_and_CPU_Usage]] for details)<br />
; customcerts/@CSRsignature : the signature algorithm, e.g. <code>SH256</code><br />
; customcerts/@CSRdn-cn : the CN (''common name'') part used for the DN<br />
; customcerts/@CSRdn-ou : the OU (''organizational unit'') part used for the DN<br />
; customcerts/@CSRdn-o : the O (''organization'') part used for the DN<br />
; customcerts/@CSRdn-l : the L (''locality'') part used for the DN<br />
; customcerts/@CSRdn-st : the ST (''state or province'') part used for the DN<br />
; customcerts/@CSRdn-c : the C (''country'') part used for the DN (note: ''CSRdn'' for many CAs, must be a 2-letter ISO country code)<br />
; customcerts/@CSRsan-dns-1 : the first of up to three DNS names <br />
; customcerts/@CSRsan-ip-1 : the first of up to two IP addresses<br />
<br />
Within the set of CSR attributes, the following meta-words will be replaced:<br />
<br />
* <code>{realip}</code> : the real IP address of the device as reported in the <code>ip=#i</code> query argument<br />
* <code>{ip}</code> : the IP address of the device as seen by the update server<br />
* <code>{proxy}</code> : the IP address of the reverse proxy the device used to reach the update server<br />
* <code>{sn}</code> : the serial number of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>009033030df0</code>)<br />
* <code>{hwid}</code> : the hardware-id of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>IP1200-03-0d-f0</code>)<br />
* <code>{name}</code> : the ''Device Name'' (set in ''General/Admin'') of the device<br />
* <code>{rdns}</code> : the DNS name found in a reverse DNS lookup for the real-ip address reported by the device (see ''{realip}'') above<br />
<br />
<syntaxhighlight lang="html5"><br />
<customcerts <br />
dir="certs" <br />
CAkeys="CAkey*" <br />
CAtype="manual" <br />
CAname="innovaphone-INNO-DC-W2K8-Zertifizierungsserver" <br />
CAnamesep="," <br />
CAwildcard="*.innovaphone.com"<br />
CSRkey="2048-bit" <br />
CSRsignature="SH256" <br />
CSRdn-cn="{hwid}" <br />
CSRdn-ou="" <br />
CSRdn-o="" <br />
CSRdn-l="" <br />
CSRdn-st="" <br />
CSRdn-c="" <br />
CSRsan-dns-1="{name}.company.com" <br />
CSRsan-dns-2="{hwid}.company.com" <br />
CSRsan-dns-3="{rdns}" <br />
CSRsan-ip-1="{realip}" <br />
CSRsan-ip-2="" <br />
renew="90" /><br />
</syntaxhighlight><br />
<br />
==== queries ====<br />
If queries are defined and applicable for the calling device, it will be instructed to send certain information to the update server which will be stored in the device status file kept on the server. Each piece of such information is defined using a separate ''query'' tag.<br />
<br />
; queries/@scfg : defines the URL used by the device to send the information. Normally not changed from the default<br />
<br />
==== queries/query ====<br />
Defines one piece of information to be submitted to the update server.<br />
<br />
; queries/query/@id : a unique name for the query<br />
; queries/query/@title : a title for this piece of information shown in the device status<br />
<br />
<syntaxhighlight lang="html5">.<br />
<!-- retrieve and show some details from IP4/General/STUN page --><br />
<query id="media" title="Media"><br />
<cmd>mod cmd MEDIA xml-info</cmd><br />
<applies>*</applies><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
<show title="Server">concat('STUN: ', /state/queries/media/info/@stun, ' TURN: ', /state/queries/media/info/@turn, ' (', /state/queries/media/info/@turn-user, ')')</show><br />
</query><br />
<br />
<!-- show primary phone registration --><br />
<query id="registration" title="Registration"><br />
<cmd>mod cmd PHONE/USER phone-regs /cmd phone-regs</cmd><br />
<applies>phone</applies><br />
<show title="State: User/Number">concat(/state/queries/registration/info/reg[@id = "0"]/@state, ': ' , /state/queries/registration/info/reg[@id = "0"]/@h323, '/', /state/queries/registration/info/reg[@id = "0"]/@e164)</show><br />
<show title="PBX/ID">concat(/state/queries/registration/info/reg[@id = "0"]/@gk-addr, '/', /state/queries/registration/info/reg[@id = "0"]/@gk-id)</show><br />
</query><br />
</syntaxhighlight><br />
<br />
==== queries/query/cmd ====<br />
The content of this tag defines the command to be executed on the device which outputs the requested information. These are ''mod cmd''s typically.<br />
<br />
<syntaxhighlight lang="html5"><br />
<!-- get info for custom certificates --><br />
<cmd>mod cmd X509 xml-info</cmd><br />
</syntaxhighlight><br />
<br />
See [[Howto:Effect_arbitrary_Configuration_Changes_using_a_HTTP_Command_Line_Client_or_from_an_Update#Using_the_Mechanism_for_Device_Status_Queries ]] for details on how to find out which commands to use.<br />
<br />
==== queries/query/applies ====<br />
Defined queries are only executed by devices which belong to the class that is given as tag content. If the ''env'' atribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; queries/query/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in (unfortunately, you can not combine bith)<br />
<syntaxhighlight lang="html5"><br />
<!-- applied to phones only --><br />
<applies>phone</applies><br />
</syntaxhighlight><br />
<br />
==== queries/query/show====<br />
If one or more ''show'' tags are defined for the ''query'' tag, the data will be shown in the device status page. The values shown are defined by the tag content which is an XPath expression selecting the data from the device state. The XPath expression always begins with <code>/state/queries/</code>''query-id'' (as defined by the ''id'' attribute in the query tag). <br />
<br />
; queries/query/show/@title : used as title when the values are displayed in the status page<br />
<br />
<syntaxhighlight lang="html5"><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
</syntaxhighlight><br />
<br />
== Download ==<br />
*[http://download.innovaphone.com/ice/wiki-src#php-update-server http://download.innovaphone.com/ice/wiki-src/] - download the complete file package of scripts and files described in this article<br />
: you need to use build 2000 or higher. Older versions are described in [[Howto:PHP_based_Update_Server]]<br />
<br />
== Hints for writing your update snippets ==<br />
Writing update scripts is largely undocumented and sometimes tricky. Here are some general guidelines.<br />
<br />
=== Using <code>config add/del</code> ===<br />
Many setting are done using <code>config change</code> commands. So these are very useful in update scripts too.<br />
<br />
The straight forward method to find out which commands you need is as follows:<br />
* get a device of the type in question and do factory reset<br />
* save the configuration to a file<br />
* do the desired settings using the normal web admin UI<br />
* save the resulting configuration to another file<br />
* compare the saved files<br />
<br />
Usually, you want to set only specific parts of the configuration line. For example, assume you want to set the default coder to ''OPUS-WB''. Your saved configuration line might look like<br />
<br />
config change PHONE SIG /prot SH323 /gk-addr pbx.innovaphone.com /gk-id innovaphone.com /tones 0 /lcoder OPUS-WB,20, /coder OPUS-WB,20,k1<br />
<br />
To set only the coder settings, you would set the relevant options with the <code>config add</code> command as follows:<br />
<br />
config add PHONE SIG /lcoder OPUS-WB,20,<br />
config add PHONE SIG /coder OPUS-WB,20,k1<br />
<br />
You can also remove specific options, as in <br />
<br />
config rem PHONE SIG /tones<br />
<br />
which would remove the <code>/tones 0</code> from the configuration line for <code>PHONE SIG</code>. <br />
<br />
Note that the PHP update server will add the<br />
<br />
config write<br />
config activate <br />
iresetn<br />
<br />
at the end of the delivered script automatically, so you do not need to do it yourself. In fact, it is not recommended to have any reset command in your snippets.<br />
<br />
==== <code>config change</code> Lines with associated Passwords ====<br />
Sometimes, there is a password associated with certain configurations. Consider the STUN/TURN configuration: <br />
<br />
config change MEDIA /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd 2 /nat-detect 61<br />
<br />
The TURN password, being sensitive, needs to be encrypted in your configuration. This is why it is stored as a ''VAR'', which provides encryption support (see below):<br />
<br />
vars create MEDIA/TURN-PWD pc ....<br />
<br />
Unfortunately, the relationship between an option and an associated VAR needs to be guessed. You can be sure that the module name in the <code>config change</code> matches the modules name in the <code>vars create</code> command. However, the variable name must be guessed. <br />
<br />
You can also set the password in your script using the <code>vars create</code><!-- , but this does make sense only in staging (that is pre-update-phase) scripts (see below for a discussion why). If you need to do it in a script snippet for the update phase, you should -->, however, you may also consider using a <code>mod cmd</code> (see below) such as e.g.<br />
<br />
mod cmd MEDIA form /cmd form /del %2Fstun-slow /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd my-turn-pw /nat-detect 61<br />
<br />
=== Using <code>vars create</code> ===<br />
<br />
Sometimes it makes sense to use <code>vars create</code> commands. The syntax is<br />
<br />
<code> vars create </code>''<name> <flags> <value>''<br />
<br />
A ''<name>'' is a module name followed by a variable name, optionally followed by an index. For example, a phone will store the currently selected main registration as <code>vars create PHONE/ACTIVE-USER p </code>''<index>''. So the module is <code>PHONE</code> and the variable name is <code>ACTIVE-USER</code>. When the variable is multi-valued, an index is added to the name. For example, the registration settings for all but the first registration are stored as indexed variable, as in <code>vars create PHONE/USER-REG/00001 p </code>''<settings>'', where <code>00001</code> is the index (registrations count from 0 in this case).<br />
<br />
There are a number of flags which can be combined, the most prominent used in update scripts are:<br />
; p : permanent. The var is stored in flash memory (you will almost always use this flag in update scripts)<br />
; c : crypted. The var value needs to be encrypted and the ''<value>'' given is crypted<br />
; x : crypted, plain value. The var needs to be encrypted but the ''<value>'' is given as plain (that is, un-encrypted) text. This allows you to set an encrypted variable without encrypting the desired value<br />
; b : binary. The var value is binary. Its ''<value>'' is given as a bin2hex string<br />
<br />
Please note that using <code>vars create</code> will ''always'' set the internal ''reset needed condition'' in the device (regardless which ''<value>'' is set). This means that a subsequent <code>resetn</code> or <code>iresetn</code> will always trigger a reset. So if you use it, make sure that you use the ''times/@check'' ([[#times | see above]]) to protect your script from re-executing the snippet (and thus rebooting the device) all the time. <br />
<!--<br />
When you use a <code>mod cmd UP1 check ...</code> command in your script (see times/@check [[#times | above]]), this will create an endless loop if you use <code>vars create</code> followed by one of the <code>resetn</code> commands in the code guarded by the <code>check</code> command. This code will never be fully executed, as the ''reset needed'' condition will always be set and the reset will always happen. You can use it in ''pre-update-phase'' scripts (that is, during the staging process) as this is not protected by a <code>check</code> command.<br />
--><br />
<br />
=== Using <code>mod cmd</code> ===<br />
See [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]] for a discussion of how to use <code>mod cmd</code> in update scripts.<br />
<br />
== Writing your own Dynamic Scripting Code ==<br />
NB: this feature is available from build 2020.<br />
<br />
By default, the update server delivers update scripts which are composed from a number of static files (so-called ''snippets''). Sometimes it makes sense however, to create such snippets dynamically (e.g. based on a database lookup). Here is how to do this.<br />
<br />
Dynamic scripting is implemented by a user-supplied custom <code>class CustomUpdateSnippet</code>. The code for this class must be placed in to the file <code>config/scripting.class.php</code>. As a starter, sample class code is available in <code>config/scripting.class-sample.php</code>.<br />
<br />
<syntaxhighlight lang="php"><br />
<?php<br />
<br />
/**<br />
* to provide your own runtime generated snippets, rename this file to 'scripting.class.php' and <br />
* implement the member functions<br />
* $property will have one member called <br />
*/<br />
<br />
class CustomUpdateSnippet extends UpdateSnippet {<br />
<br />
/**<br />
* snippet to deliver after standard text snippets<br />
* @return array of string<br />
*/<br />
public function getPostSnippet() {<br />
return parent::getPostSnippet();<br />
}<br />
<br />
/**<br />
* snippet to deliver before standard text snippets<br />
* @return array of strings<br />
*/<br />
public function getPreSnippet() {<br />
return parent::getPreSnippet();<br />
}<br />
<br />
/** <br />
* do we want to suppress the standard text snippets?<br />
* @return boolean<br />
*/<br />
public function sendStandardSnippets() {<br />
return parent::sendStandardSnippets();<br />
}<br />
}<br />
?><br />
</syntaxhighlight><br />
<br />
The sample code overrides of the classes member functions just call the base classes which do nothing at all. So if you just copy the sample code into <code>scripting.class.php</code>, nothing will change. However, if <code>getPreSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''before'' the standard snippets are sent. Likewise, if <code>getPostSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''after'' the standard snippets are sent. If <code>sendStandardSnippets()</code> returns false, the standard snippets will not be sent.<br />
<br />
To determine the dynamic snippets to send, the user-provided <code>class CustomUpdateSnippet</code> has access to the members of its base <code>class UpdateSnippet</code>, namely the <code>var $statexml</code> member. This member contains a <code>SimpleXMLElement</code> object with all state information available for the calling device.<br />
<br />
Here is a sample state found in <code>$statexml</code> (as output by the <code>dump()</code> member function): <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0"?><br />
<state seen="1522065435" requested="130774bootcode" phase="update"><br />
<device sn="00-90-33-3e-0c-57" type="IP112" classes="phone, opus_phone, phone_newui, hstrace, ip112" ip="127.0.0.1" rp="false" phase="update"<br />
environments="sifi, 172net" certstat="either certificate handling or state tracking not enabled - not doing any certificate checking" hwid="IP222-46-5c-77" realip="172.16.80.241" requestDownloaded="false"/><br />
<firmware requested="130986" requested-at="1522065435"/><br />
<bootcode requested-at="1522065435"/><br />
<config filename="scripts/all-all-all.txt" delivered="1522065426" version="1486559970"/><br />
</state><br />
</syntaxhighlight><br />
<br />
The most interesting elements in this XML are probably the attributes of the <code>state</code> and the <code>device</code> tags.<br />
<br />
== Hints for using custom SSL Certificates ==<br />
<br />
Using your own SSL certificates can be useful depending on the scenario.<br />
<br />
We have two methods to accomplish this:<br />
<br />
=== Standard scenario as described in this wiki article ===<br />
The idea is that each certificate request is checked manually, edited and the UpdateServer is provided with the new device certificate.<br />
<br />
=== Special scenario with automated certificate renewal ===<br />
The idea is that every new/unknown (untrusted) device connects to the staging server. Here the device is released by manual checking/authorization by getting its first certificate. All further extensions can happen automatically afterwards.<br />
<br />
'''Important: All important settings concerning file permissions, access and co must be used from the above article and are only skipped in this part!'''<br />
<br />
We will create two Update-Server instances with the following configuration.<br />
<br />
# Staging Server (HTTPS/443)<br />
#*Put the sources in <code>/var/www/innovaphone/update</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]].<br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!update/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code| above]]<br />
#* '''Important''': Do not submit any further customer specific device configurations in the staging Server<br />
# Final MTLS / 444 update server<br />
#*Put the sources in <code>/var/www/innovaphone/mts</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]] with <code>renew = "x"</code><br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!mtls/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code | above]]<br />
#*Store all customer specific device configurations<br />
<br />
<br />
The meaning of the two different instances is:<br />
*The staging server <code>https://[ip]/update/admin/admin.php</code> will generate the provisioning URLs for new devices.<br />
*All new/unknown (untrusted) devices are only in the staging server.<br />
**Here you can check the devices and provide them a valid certificate after check/authorization.<br />
*With a valid certificate, the UpdateURL is automatically changed to the MTLS instance.<br />
*As soon as a device connects via MTLS we trust this device and we can use an automatic extension of the certificates.<br />
** Waiting CSR requests are in the configured folder ''certs'' in the notation <code>"[mac]-request.p10.pem"</code><br />
** If you operate your own CA, feel free to automate the reissue of the certificates via a sheduled Cronjob<br />
*** Help for Linux: [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
*** Help for Windows: [[Howto:Creating_custom_Certificates_using_a_Windows_Certificate_Authority]]<br />
** If a signed certificate with the format <code>"[mac]-signedrequest.cer"</code> is placed in the certs folder, the certificate is automatically updated on the device.<br />
<br />
== Optional Configurations on the Linux Application Platform ==<br />
===Support for more Connections===<br />
By default the LAP's web server lighttpd allows for 512 concurrent connections with 1024 open file descriptors. When you serve a huge number of devices with the update server, then this might be too low. You will see some devices not being able to contact the update server for a while. This should not be a real issue as the devices will retry. However, updating all devices may take long due to this.<br />
<br />
In the lighttpd log (in <code>/var/log/lighttpd</code> on the LAP), you will see entries like this:<br />
<br />
2017-10-16 13:45:18: (network_linux_sendfile.c.140) open failed: Too many open files <br />
2017-10-16 13:45:18: (mod_fastcgi.c.3075) write failed: Too many open files 24 <br />
2017-10-16 13:45:18: (server.c.1434) [note] sockets disabled, out-of-fds <br />
2017-10-16 16:23:25: (response.c.634) file not found ... or so: Too many open files /mtls/updatev2/web/innovaphone_logo_claim_fisch.png -><br />
2017-10-16 17:57:45: (server.c.1432) [note] sockets disabled, connection limit reached <br />
<br />
If you experience such issues, proceed as follows:<br />
<br />
* connect to the LAP with SFTP (e.g. using WinSCP)<br />
* change directory to <code>/etc/lighttpd</code><br />
* edit <code>lighttpd.conf</code><br />
* search for the 2 lines which set <code>server.max-fds</code> and <code>server.max-connections</code><br />
* replace the standard values. We recommend to set the number of file descriptors to 4 times the number of requests when using the update server, e.g. <br />
: old<br />
:: server.max-fds = 1024<br />
:: server.max-connections = 512<br />
: new<br />
:: server.max-fds = 8000<br />
:: server.max-connections = 2000<br />
* save the file<br />
* restart linux (''Diagnostics/Reset/Reboot'')<br />
<br />
Be sure to closely monitor the ''Diagnostics/Status'' page for a while after such configuration. <br />
<br />
Also, when you update the LAP, you will have to re-do the changes (as always when you change something ''under the hood'').<br />
<br />
=== Debug files rotation based on logrotate ===<br />
If you have to debug during operation and a lot of devices access the PHP Update Server V2, you have to keep track of the debug files or automatically limit them. For this you can use logrotate on the innovaphone Linux AP. With logrotate you can apply time-based or size-based rules when files are packed and when they are deleted.<br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files. <br />
* Under the path <code>/etc/logrotate.d</code> create a file e.g. <code>updateserver</code>.<br />
<br />
<code>/etc/logrotate.d/updateserver</code><br />
<br />
In this file now enter the following content and save it.<br />
<br />
<code><br />
/var/www/innovaphone/mtls/update/debug/*.txt {<br />
size 5M<br />
missingok<br />
rotate 2<br />
compress<br />
delaycompress<br />
notifempty<br />
create 644 www-data www-data<br />
}<br />
</code><br />
<br />
This will include all *.txt files from the debug directory of the PHP Update Server V2 in the logrotate process of the operating system.<br />
<br />
; size 5M : means every 5MB the file is rotated or zipped<br />
; size 5k : means every 5kB the file is rotated or zipped<br />
Alternatively, you can also rotate the file based on time<br />
; daily : means that every day the file is rotated or zipped<br />
; weekly : means that the file is rotated or zipped daily<br />
<br />
; missingok : if a debug file does not exist, it will be ignored<br />
; rotate n : files are removed before the last ones are deleted.<br />
; compress : compresses the old debug files<br />
; delaycompress : compresses the debug data only after it has been moved once<br />
; notifempty : empty log files are not rotated<br />
; create 644 www-data www-data : creates a new, empty debug file with appropriate permissions<br />
<br />
== Update Server and Reverse Proxy ==<br />
It is possible to implement access to the update server through a ''reverse proxy'' (RP). However, you have to keep some issues in mind:<br />
* ''Mutual Transport Layer Security'' (MTLS) is not possible<br />
: MTLS requires a direct TLS connection between the two parties. An RP however would terminate the TLS connection and entertain two independent connections to the parties. This breaks MTLS. If you want to use MTLS and serve external clients, you must therefore provide a direct access to the update server (that is an external IP address either directly or through port forwarding)<br />
: see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts]] for details<br />
* When using the RP, you can choose to forward encrypted traffic (HTTPS) arriving from external to the update server using HTTP (unencrypted). The update server however eventually rewrites the update URL in the calling devices configuration. In order to do this, it determines the type of the internal connection. So if the connection from the RP to the update server is using HTTP, it would create a ''http://...'' URL. Otherwise it would create an ''https://..'' URL (also, it would be using the same port). This way, if the RP forwards requests to the update server with HTTP, the synthesized new URL for the calling client would use HTTP too, even though the original request was sent with HTTPS. This may or may not be what you want (as all of your clients would end up using non-encrypted traffic). Even worse, it might not work at all, if the RP does not accept HTTP for example.<br />
<br />
== Related Articles ==<br />
* [[Reference10:Concept_Update_Server]]<br />
* [[Reference12r1:DHCP_client]]<br />
* [[Reference10:Concept_Linux_Application_Platform]]<br />
* [[Reference10:Concept_Provisioning]]<br />
* [[Howto:PHP_based_Update_Server]] (old version)<br />
* [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
* [[Howto:Creating custom Certificates using a Windows Certificate Authority]]<br />
* [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]]<br />
<br />
<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference10:Concept_Provisioning&diff=70784Reference10:Concept Provisioning2024-02-05T19:05:52Z<p>Ckl: /* Securing the Configuration Data sent */</p>
<hr />
<div>Starting with version 10, innovaphone devices connect automatically with <code>http://config.innovaphone.com/init</code> during commissioning or after resetting to the initial state. This process serves to simplify provisioning.<br />
<br />
In the future, this mechanism can be used by innovaphone partners to significantly reduce configuration work. You can set up an individual update server that contains pre-prepared configurations for innovaphone devices. These can then be assigned to a specific project in my.innovaphone. A corresponding update URL is all that is stored for this purpose. The allocated update server applies to new devices that are assigned via the MAC address to this project in my.innovaphone. <br />
<br />
If an innovaphone device then connects with <code>http://config.innovaphone.com/init</code>, an enquiry is made in my.innovaphone to see whether an update server is available for the MAC address. If this is the case, the device can connect automatically to it via the update URL to retrieve the local configuration - fully automated, without further intervention by the administrator. <br />
<br />
A connection is always established automatically to <code>http://config.innovaphone.com/init</code>. If you would like to avoid this procedure, the innovaphone device must be booted in an environment without a public internet connection.<br />
<br />
The following information is transmitted to innovaphone upon connection with <code>http://config.innovaphone.com/init</code>: The version and serial number of the device and the public IP address. This information is managed in accordance with innovaphone's data protection policy.<br />
<br />
[[Image:config-provider-scheme.png]]<br />
<br />
=== Implementation ===<br />
From version 10, the default <code>UP1</code> config file line is <code>config change UP1 /url http://config.innovaphone.com/init /poll 1</code> for all devices. If this setting becomes effective (that is, if it is not changed before the first poll and no overriding setting is deployed to the device via DHCP), the device will try to read an update script from the URL given. This URL points towards a server that is hosted by innovaphone. If the device in question has a valid and working DNS setting and the device has access to the internet, the server will be queried and return an update script which is applied to the device. <code>http://config.innovaphone.com/init</code> is said to be the ''config redirection service''.<br />
<br />
Under standard circumstances, the script returned will simply remove the update URL from the configuration after a while. <br />
<br />
However, if the device<br />
# is known to my.innovaphone.com<br />
# is assigned to a my.innovaphone project<br />
# this project has an ''URL'' configured in the project properties<br />
the script returned will change the update URL to the ''URL'' defined for the project. This new URL is considered an update server for the device in question.<br />
<br />
It is up to the server specified in the ''URL'' set (known as ''config server'') how to proceed further. This mechanism can be used by partners to implement 3rd party provisioning schemes. innovaphone may or may not offer such a service ([[User:Ckl|ckl]] 16:29, 8 July 2013 (CEST) as of today, innovaphone does not).<br />
<br />
The redirection process is logged in to a database at innovaphone. This is to be able to maintain and debug this service and also detect possible fraud and misuse.<br />
<br />
From July 2018, this mechanism is also used for the discovery of devices after a ''reset to factory defaults''. See [[#Discovering Devices]] below. <br />
<br />
=== Security Implications ===<br />
A device polling the ''config redirection service'' will send a request along with certain data to <code>http://config.innovaphone.com/init</code> (for details about the data sent see below). To avoid this process, users must start the device in a network environment that has no internet access. The normal configuration UI can then be used to remove the UP1 configuration settings. Please note that this must be done whenever the device has been set to factory defaults! Another option is to start the device in a network environment where an [[Reference:DHCP_client#Supported_Options|update URL is deployed via DHCP]]. <br />
<br />
If the device is redirected, certain further information is sent to the device from the ''config redirection service''. This includes at least the new URL of the final ''config server''. Although the ''config redirection service'' can be instructed to contact the ''config server'' via HTTPS, it should be clear that the information sent from the ''config redirection service'' to the device is sent in clear. Also, the device does not need to authenticate against the ''config redirection service'' (which would defeat the whole purpose). This implies that everybody can retrieve this information, just by pretending to be the device with said serial number. You can avoid this by not specifying an ''URL'' in my.innovaphone.com.<br />
<br />
==== Securing the Configuration Data sent ====<br />
The ''config server'' needs to be careful whom to send configuration data. Generally, to secure the data, it ''must be sent securely to the device it is intended for only''. <br />
<br />
So 2 things must be done:<br />
* the data must be delivered via HTTPS, so nobody can sniff it on the wire<br />
* mutual TLS must be enforced and the calling devices certificate must be verified, so that config files are delivered to the intended target device only<br />
<br />
Let us assume the ''config server'' is implemented as a PHP web server. The following code can be used as a starter then:<br />
<syntaxhighlight php><br />
<?php<br />
/*<br />
* this interface is for secure provisioning. It demo's sample code that provides a config file in a secure manner. This in particular means:<br />
* - delivered via HTTPS<br />
* - config files are delivered to the intended target device only<br />
*<br />
* This makes sure no configuration data can be accessed by an attacker unless the attacker has access to the calling devices certificate (which<br />
* is practically the same as having access to the device itself)<br />
*<br />
* For this to work, mutual TLS is used between the device and the web server. The PHP code then has access to the (trustable) certificate<br />
* information presented by the client and can then tailor the information provided accordingly.<br />
*<br />
* there are two versions of negotiating mutual TLS. One of them upgrades the one-way TLS only after establishing the HTTP command. This mode<br />
* is not supported by the innovaphone http client (as of v10sr2).<br />
* - Tests have shown that IIS is using this mode and will thus not work.<br />
* - Apache however does support the other mode and will thus work.<br />
* - in lighttpd, we seem to have access to SSL_CLIENT_S_DN_C, SSL_CLIENT_S_DN_CN, SSL_CLIENT_S_DN_O only. We can not check against $goodCA thus<br />
* and we must synthesize SSL_CLIENT_S_DN. Also, we must configure so the web server so that it enforces client certificates and ionly trusts the<br />
* innovaphone root CA<br />
*/<br />
<br />
$goodCA = array(<br />
'/C=DE/O=innovaphone/OU=innovaphone Device Certification Authority',<br />
'/C=DE/O=innovaphone/OU=innovaphone Device Certification Authority 2',<br />
);<br />
$goodDeviceDNPrefix = '/C=DE/O=innovaphone/CN=';<br />
<br />
$useLightTPD = false; // set this to true if you use lighttpd AND have configured it correctly<br />
if ($useLightTPD) {<br />
$_SERVER['SSL_CLIENT_S_DN'] = "/C={$_SERVER['SSL_CLIENT_S_DN_C']}/O={$_SERVER['SSL_CLIENT_S_DN_O']}/CN={$_SERVER['SSL_CLIENT_S_DN_CN']}";<br />
$_SERVER['SSL_CLIENT_VERIFY'] = 'SUCCESS'; // make sure this is asserted in lighttpd.conf (using ssl.verifyclient.enforce);<br />
$_SERVER['SSL_CLIENT_I_DN'] = $goodCA[0];<br />
}<br />
<br />
// dump TLS info<br />
print "# <pre>";<br />
foreach (<br />
array(<br />
"HTTPS", "SSL_CLIENT_S_DN", "SSL_CLIENT_S_DN_CN", "SSL_CLIENT_I_DN", "SSL_CLIENT_VERIFY", "SSL_SERVER_S_DN"<br />
)<br />
as $var) {<br />
print "# $var: '" . (empty($_SERVER[$var]) ? "<not-set>" : $_SERVER[$var]) . "'\n";<br />
}<br />
<br />
/*<br />
* the dump above will yield something like<br />
# HTTPS: 'on'<br />
# SSL_CLIENT_S_DN: '/C=DE/O=innovaphone/CN=IP1202-35-fe-4d'<br />
# SSL_CLIENT_S_DN_CN: 'IP1202-35-fe-4d'<br />
# SSL_CLIENT_I_DN: '/C=DE/O=innovaphone/OU=innovaphone Device Certification Authority'<br />
# SSL_CLIENT_VERIFY: 'SUCCESS'<br />
# SSL_SERVER_S_DN: '/C=cert-country/ST=cert-state/L=cert-locality/O=cert-o/OU=cert-ou/CN=cert-cn'<br />
*<br />
* the provisioning server needs to verify<br />
* - HTTPS is on<br />
* - SSL_CLIENT_VERIFY is 'SUCCESS' so certificate validation was OK<br />
* - SSL_CLIENT_I_DN is '/C=DE/O=innovaphone/OU=innovaphone Device Certification Authority', so it is a trusted innovaphone certificate<br />
* - SSL_CLIENT_S_DN starts with '/C=DE/O=innovaphone/CN=' so it is an innovaphone device certificate<br />
*<br />
* if all this is true, then it has to look at SSL_CLIENT_S_DN_CN. This is the requesting device and we must only hand out information that belongs to this device.<br />
*/<br />
<br />
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') die("# https required\n");<br />
if (empty($_SERVER['SSL_CLIENT_VERIFY']) || $_SERVER['SSL_CLIENT_VERIFY'] != 'SUCCESS') die("# certificates not verified\n");<br />
if (empty($_SERVER['SSL_CLIENT_I_DN']) || !in_array($_SERVER['SSL_CLIENT_I_DN'], $goodCA)) die("# wrong CA\n");<br />
if (empty($_SERVER['SSL_CLIENT_S_DN']) || substr($_SERVER['SSL_CLIENT_S_DN'], 0, strlen($goodDeviceDNPrefix)) != $goodDeviceDNPrefix) die("# not an innovaphone device\n");<br />
<br />
// get serial<br />
$cn = empty($_SERVER['SSL_CLIENT_S_DN_CN']) ? "??-??" : $_SERVER['SSL_CLIENT_S_DN_CN'];<br />
<br />
// some early certificates where broken, fix here:<br />
$brokenCN = array(<br />
"IP222s" => "IP222",<br />
"IP232s" => "IP232",<br />
"IP240a" => "IP240",<br />
"IP200a" => "IP200",<br />
);<br />
$cnparts = explode("-", $cn);<br />
<br />
foreach ($brokenCN as $broken => $fix) {<br />
if ($cnparts[0] == $broken) {<br />
$cnparts[0] = $fix;<br />
$cn = implode("-", $cnparts);<br />
break;<br />
}<br />
}<br />
<br />
// now get and return relevant configuration data<br />
print "# update script for {$cn}\n";<br />
?><br />
</syntaxhighlight><br />
<br />
Please note that there was an issue when an innovaphone client attempts to retrieve an update script with MTLS. This has been fixed with v10sr3.<br />
<br />
===== Enforcing mutual TLS on Apache =====<br />
Here are the relevant snippets you have to add to your httpd.conf to setup Apache for mutual TLS:<br />
<br />
# make sure you listen for HTTPS<br />
Listen 443 https<br />
<br />
# ssl must be loaded<br />
LoadModule ssl_module modules/mod_ssl.so<br />
<br />
# configure SSL <br />
<IfModule ssl_module><br />
SSLRandomSeed startup builtin<br />
SSLRandomSeed connect builtin<br />
<br />
# require client verification<br />
SSLVerifyClient require<br />
<br />
# the web servers own certificate <br />
# must be something that is trusted by the calling device<br />
SSLCertificateFile "C:/Program Files (x86)/Apache Software Foundation/Apache2.2/certs/your-certificate.pem"<br />
<br />
# the CA we trust - innovaphone device certification authority only!<br />
SSLCACertificateFile "C:/Program Files (x86)/Apache Software Foundation/Apache2.2/certs/inno-dev-ca-certificate.crt"<br />
<br />
# make certificate data available to PHP programs<br />
SSLOptions +StdEnvVars +ExportCertData<br />
<br />
# switch on SSL engine for all virtual hosts on port 443<br />
<VirtualHost _default_:443><br />
SSLEngine on<br />
#...<br />
</VirtualHost><br />
<br />
</IfModule><br />
<br />
===== Enforcing mutual TLS on LightTPD=====<br />
Here are the relevant snippets you have to add to your lighttpd.conf to setup LightTPD for mutual TLS:<br />
<br />
# the server must be ssl-capable!<br />
<br />
## modules to load<br />
# we need setenv <br />
server.modules = (<br />
# ...,<br />
"mod_setenv",<br />
# ...<br />
)<br />
<br />
#### SSL engine<br />
$SERVER["socket"] == ":443" {<br />
ssl.engine = "enable"<br />
ssl.pemfile = "C:/Program Files (x86)/Apache Software Foundation/Apache2.2/certs/your-certificate.pem"<br />
ssl.ca-file = "C:/Program Files (x86)/Apache Software Foundation/Apache2.2/certs/inno-dev-ca-certificate.crt"<br />
# allow client side certificates<br />
ssl.verifyclient.activate = "enable"<br />
# enforce client side certificates<br />
ssl.verifyclient.enforce = "enable" <br />
# tell PHP we have HTTPS<br />
setenv.add-environment = ( "HTTPS" => "on" )<br />
}<br />
<br />
On the Linux Application Platform, you can do this as described in [[Reference10:Concept_Linux_Application_Platform#Configure_mutual_TLS | Configure mutual TLS ]].<br />
<br />
===== Enforcing mutual TLS on IIS =====<br />
On Microsoft's IIS, we did not succeed to enforce MTLS, unfortunately.<br />
<br />
===== How to get <code>inno-dev-ca-certificate.crt</code> =====<br />
The file <code>inno-dev-ca-certificate.crt</code> in the above HTTP server configuration snippets is a PEM encoded version of innovaphone's ''device certifcation authority'' certificate (public key only, of course). You can get this from any innovaphone device that already trusts another innovaphone device by downloading it from the ''PEM'' link under ''General/Certificates/Trust list''.<br />
<br />
[[Image:Concept Provisioning get-inno-ca.png]]<br />
<br />
The other certificate file used in the snippets (called <code>your-certificate.pem</code> there) is actually the full blown certificate you want to use for your provisioning server. Make sure that the calling devices trust this certificate (e.g. by setting it as ''Trust'' in the my.innovaphone project configuration)!<br />
<br />
====== New innovaphone device CA from July 2015 ======<br />
From July 2015 on, we are using a new device certification autority, which is called <code>/C=DE/O=innovaphone/OU=innovaphone Device Certification Authority 2</code>. In your provisioning service, you must accept device certificates from both CAs. The ''inno-dev-ca-certificate.crt'' file used previously should be replaced by the ''inno-dev-ca-all-certificate.crt'' file which includes the concatenated public keys of both CA.<br />
<br />
Here is its content:<br />
<pre><br />
-----BEGIN CERTIFICATE-----<br />
MIIDlzCCAn+gAwIBAgIBADANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGDAJERTEU<br />
MBIGA1UECgwLaW5ub3ZhcGhvbmUxMzAxBgNVBAsMKmlubm92YXBob25lIERldmlj<br />
ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wODAxMDEwMDAwMDBaFw00ODAx<br />
MDEwMDAwMDBaMFgxCzAJBgNVBAYMAkRFMRQwEgYDVQQKDAtpbm5vdmFwaG9uZTEz<br />
MDEGA1UECwwqaW5ub3ZhcGhvbmUgRGV2aWNlIENlcnRpZmljYXRpb24gQXV0aG9y<br />
aXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAur7epPiIOkN6vIbt<br />
46eH1AzIskP9HvyL63VgG3eJ+oqZRw4xn13FHzDBpnMZw1XW01byAlbYum+w5QTo<br />
QDdZ6NTxe17TCduIXvCAE5MyvTyzF0NuAJ9EZe4UUELoE1whFkYM+xSLvh6uqv4p<br />
TOFMtTBUtIUJ4g2tTgQ7UdxNdgLnXDzb9O0ZedVNydYhqNIPYgI5L6SLzy5Kflbz<br />
mT9eMFEYQSNRlrvAowN5LqjsyA8CFY8rY9G2yZUHoB4TOK4zdhxrQW/mHotWT8hL<br />
eEhAoK9/EBkQckyk/M21K78U4GtoMxYOFAjmH6SaqhnyGOe+03F/VCV6QZbNmzaR<br />
HR2uoQIDAQABo2wwajASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB<br />
BjAiBgNVHSMBAQAEGDAWgBQSom116MPX3gNCCyTsncz1BTBUWjAgBgNVHQ4BAQAE<br />
FgQUEqJtdejD194DQgsk7J3M9QUwVFowDQYJKoZIhvcNAQEFBQADggEBACvQUbBc<br />
AhMsPEuFG/omAHw0O36h7nfSDK+i9/LfsESvjCmQSgMmep7we7/SBIqHTDrvc+e9<br />
MkoIqkBjzO2kpfOMt09i4inff4mKs971tm0lX4I8J+5RO2I/RHdQMbHBgprZYOmQ<br />
Nk6uV2b87LQHh0WU4EE/IctxHQwZ2xq9n3i7io8ODvaJsgEuIe2rpmTi8RJ0MuzV<br />
bNaQqNnmZT2bqgPVUIrIVyEOCHYEY59ZvwuNq9oOpUqqzuIy2N2dJ41ODDE/ivd2<br />
LiUFap5vD+TndeutfMCodLdUR9MsdMCZ/N9QoZ6q4KS4JV/Hc7kwKBw1W6HbQfyh<br />
Ig6j9ACxEGOFRDY=<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
MIIDmzCCAoOgAwIBAgIBADANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJERTEU<br />
MBIGA1UEChMLaW5ub3ZhcGhvbmUxNTAzBgNVBAsTLGlubm92YXBob25lIERldmlj<br />
ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTE1MDYyNTAwMDAwMFoXDTQ4<br />
MDEwMTAwMDAwMFowWjELMAkGA1UEBhMCREUxFDASBgNVBAoTC2lubm92YXBob25l<br />
MTUwMwYDVQQLEyxpbm5vdmFwaG9uZSBEZXZpY2UgQ2VydGlmaWNhdGlvbiBBdXRo<br />
b3JpdHkgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9bexBENv6a<br />
uu8k+9Yz1C2Uf1awmPGFlQpyDfCullm8MQKTtcju4yyI6ZvB3O/b6q3Twh44gcN1<br />
CIh3buUyOuc7lOFnil00H8i/qgQLBCt4JRt3y72ZlPJGvuQnDGlTKqfBOuuNh02g<br />
QFVyJk02g1EJHDhcY86m3imZ6YFbxxDw9XDx6BDw0CfqGBptmUUaG/1qejhSW+il<br />
dSPBMGvYnlyyLwYw++wpMa9/h3GFXph+JJ70i5plLPXYLB2ak9HjGJXM+NhC9KXp<br />
qV+YDPdpPZXWg+CpzQRGOzk5hz7NuAgyHkOILsQpftAiq9k1vHy8z6ByKAnW3Jc6<br />
3bSAkrzriAkCAwEAAaNsMGowEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E<br />
BAMCAQYwIgYDVR0jAQEABBgwFoAUUm9Jo7Xo8yT34tARvFVe0VHks0UwIAYDVR0O<br />
AQEABBYEFFJvSaO16PMk9+LQEbxVXtFR5LNFMA0GCSqGSIb3DQEBCwUAA4IBAQCc<br />
WLWShSLEH4of84A5oxTW2kHMzvsYgG4EmgtE0iSf1xfueHoP7g2bjPwNcp57aWTD<br />
fUzw3sn7wFICK+7b0pDRHZYkC9gmc6mWZ9I16Vm8W/SqvB75DSUfuk/Lrcs7TjrV<br />
q+J2mQXgyrREngq1CXUBCCf6+W/CZbxq0d5TdlEz9TDNjdd1E1G3CDpZiVE4AFlm<br />
uiUi8clkGES/bh5gNcZ+/vfb0pylUjbO84tBYc4fqw7WFPTvEXZ9KHa09uI9dHPN<br />
SSr3jLua7BbhRQALK2M+DFVWFNGFcxTEUfOSMjQbXnRL6mdtDScYCvvunRJlQB81<br />
S0HWEZ+eG+I6lGJk+d8R<br />
-----END CERTIFICATE-----<br />
</pre><br />
<br />
=== Configuration ===<br />
To enable the config redirection server scheme for a particular device, you first need to add the device to your account in my.innovaphone.com. You then assign it to a project. For the project, you set the ''URL'' and ''Trust'' (optional) property. The ''URL'' property must be set to a valid URL which targets a config server. If the URL uses HTTPS, the ''Trust'' must be set additionally to the public key used by the target ''config server''. <br />
<br />
You may obtain the value for the ''Trust'' property by ''trusting'' your target ''config server'''s certificate on any innovaphone device (see [[Reference10:General/Certificates]]). You then need to copy the ''value part'' of the corresponding <code>vars create X509/TRUSTED/nnnnn</code> variable (available via [[Reference10:Maintenance/Diagnostics/Config-Show]]). <br />
<br />
Example:<br />
<br />
vars create X509/TRUSTED/00000 pb 308203973082027fa0030...<br />
<br />
you set ''Trust'' to <br />
<br />
*:308203973082027fa0030...<br />
<br />
=== Protocol Details ===<br />
This information is subject to change!<br />
<br />
All redirection activities done to the device will be written to the update clients ''CFG'' variable, shown here<br />
<br />
'''Current Update Serials'''<br />
{|<br />
|-<br />
! Name<br />
! Value<br />
! Date<br />
|-<br />
| PROT<br />
| 100850<br />
| 08.07.2013-14:17:50<br />
|-<br />
|-<br />
| ...<br />
|<br />
|<br />
|-<br />
| CFG<br />
| '''redirected to <nowiki>https://1.2.3.4/DRIVE/CF0/update/staging.txt</nowiki>'''<br />
|<br />
|}<br />
<br />
==== Script sent to the device in response to the default URL ====<br />
This script will instruct the device to poll again with a number of query args<br />
<pre><br />
# innovaphone std device redirector<br />
# no device info present - rewriting poll url to https://config.innovaphone.com/init/redirect.php?mac=#m&hwid=#h&firm=#F&boot=#B&type=#t<br />
config change UP1 /provision 5 /trace /no-dhcp /url https%3A%2F%2Fconfig.innovaphone.com%2Finit%2Fredirect.php%3Fmac%3D%23m%26hwid%3D%23h%26firm%3D%23F%26boot%3D%23B%26type%3D%23t<br />
# activate changes<br />
config write<br />
config activate<br />
iresetn<br />
</pre><br />
<br />
==== Script sent to the device if it is unknown to my.innovaphone ====<br />
<pre><br />
# innovaphone std device redirector<br />
# unknown mac 00-90-33-30-00-bf<br />
config rem UP1 /provision<br />
config rem UP1 /poll<br />
config rem UP1 /url<br />
config rem UP1 /no-dhcp<br />
config rem UP1 /trace<br />
# activate changes<br />
config write<br />
config activate<br />
iresetn<br />
</pre><br />
Note that from July 2018, this script will not be sent until 18 hours after the first call. During these 18 hours, the service will first return a script that modifies the current query arguments so that it includes a <code>nostdprv=</code>''unix-time'' argument (''unix-time'' is the time the device first called). The device then keeps polling the site every minute using this ''nostdprv'' argument. After the 18 hours, the script shown above is returned to remove the update URL. The device therefore stops calling the site. <br />
<br />
To disable this mechanism, you can remove the device's ''Command File URL'' in ''Services/Update'' (either using the admin UI or with <code>config change UP1</code> or the script shown above).<br />
<br />
This mechanism is now used for [[#Discovering Devices]] and for [[Reference13r1:Concept_Provisioning | v13 provisioning]].<br />
<br />
==== Script sent to the device if a config server is known ====<br />
<pre><br />
# innovaphone std device redirector<br />
# redirect<br />
# redirected to https://1.2.3.4/DRIVE/CF0/update/staging.txt<br />
vars create UPDATE/CFG p redirected+to+https://1.2.3.4/DRIVE/CF0/update/staging.txt<br />
config change UP1 /provision 5 /trace /no-dhcp /url https%3A%2F%2F1.2.3.4%2FDRIVE%2FCF0%2Fupdate%2Fstaging.txt<br />
# activate changes<br />
config write<br />
config activate<br />
iresetn<br />
</pre><br />
<br />
If there is a ''Trust'' value configured for the project, it will be passed to the device in 2 additional lines<br />
<br />
# trust<br />
vars create X509/TRUSTED/00099 pb 308205953082037da0030201020201003...<br />
<br />
==== Optional further information sent to the device ====<br />
The ''config redirection service'' can report a number of data to the calling device. This data is stored in the update clients persistent data with [[Reference10:Concept_Update_Server#Setvar_command | setvar commands ]] such as <code>mod cmd UP1 setvar r-type IP232</code>. This is done however only if the project configuration in my.innovaphone is setup accordingly. To do so, you need to prefix the ''Trust'' property of the project in my.innovaphone.com with a list of property names, separated from the trust with a colon (e.g. <code>type,ip:</code>''trust-value''). To request all of the available properties, use <code>*:</code> as property list.<br />
<br />
Here is the current list of properties that can be requested (as of [[User:Ckl|ckl]] 16:29, 8 July 2013 (CEST)): <br />
<br />
{| class="wikitable" border="1"<br />
|+<br />
| property || sample value || description<br />
|-<br />
| mac || 00-90-33-30-00-af || device serial number <br />
|-<br />
| boot || 100850 || device boot code version<br />
|-<br />
| hwid || 501 || device hardware-id<br />
|-<br />
| type || IP232 || device type<br />
|-<br />
| prj-id || 10801 || project-id in my.innovaphone<br />
|-<br />
| prj-name || Kuenkel00001 || project name in my.innovaphone<br />
|-<br />
| prj-comment || ckl's+Privat-VM || project comment in my.innovaphone<br />
|-<br />
| prj-trust || 308205953082037da0030201 ... || config server public key (see above)<br />
|-<br />
| cust-name || Hosting Company|| customer name in my.innovaphone of the device's project <br />
|-<br />
| cust-id || 1527 || customer id in my.innovaphone of the device's project <br />
|-<br />
| ip || 172.16.10.45 || calling ip<br />
|-<br />
| prj-urlroot || <nowiki>https://1.2.3.4/DRIVE/CF0/update</nowiki> || config server's URL with last component stripped<br />
|-<br />
| redirected || 2013-07-08.14:05:14 || date and time stamp of redirection<br />
|}<br />
<br />
=== List of possible Messages sent to Client Device === <br />
; no device info present - rewriting poll url : update URL query arguments did not include device identification. Query args are appended and update URL is rewritten.<br />
; mac not given : incomplete device identification was given in update URL query args. Polling is turned off.<br />
; unknown mac : serial number unknown to our database. Polling is turned off.<br />
; mac in more than one project with configured update URL : there are ambiguous config server specifications set in my.innovaphone. Polling is turned off.<br />
; nothing to redirect for : there is no config server defined for this device. Polling is turned off.<br />
; firmware/boot code xxx too old - must be at least yyy : the calling device has an old firmware running which cannot be upgraded safely. Polling is turned off.<br />
; firmware/boot code xxx needs upgrade - upgrading to yyy from path : firmware is too old but can be upgraded. An appropriate upgrade command is sent to the device.<br />
; redirected to : the device has been redirected to the specified config server<br />
<br />
<br />
= Software Phone =<br />
Please note that the SoftwarePhone only supports the <code>http://config.innovaphone.com/init</code> scheme from V10 build 101100. Furthermore, as the SoftwarePhone by nature does not have a built-in ''device certificate'', it cannot be trusted automatically by the provisioning server. <br />
<br />
= Discovering Devices =<br />
As outlined above, we do log devices which contact <code>config.innovaphone.com/init</code> in a database. From July 2018, we provide a ''discovery service'' for such devices based on this. You can use the web site [http://config.innovaphone.com config.innovaphone.com] to see a list of your own devices. This may greatly simplify the process of accessing a device which has been reset to factory defaults recently. <br />
<br />
[[Image:Provisioning_Device_Discovery.png]]<br />
<br />
You can also access this information programmatically by calling [http://config.innovaphone.com/mydevice.json config.innovaphone.com/mydevice.json]:<br />
<br />
<code><br />
[{"id":600515,"mac":"00-90-33-41-03-57","text":"grace period 172.16.108.2","time":1530631511,"customerid":0,"projectid":0,"ip":"145.253.157.7","devattrs":{"mac":"00-90-33-41-03-57","hwid":"IP811-41-03-57","firm":"13A127","boot":"113454","type":"IP811","provisioning-code":"","localip":"172.16.108.2","nostdprv":"1530631285","ver":"13r1 dvl IP811[13.A127], Bootcode[113454], Hardware[300] ","_allIPs":["145.253.157.7","145.253.157.20"]},"localip":"172.16.108.2"}]<br />
</code><br />
= Related Articles =<br />
; [[Reference10:Concept_Update_Server]] : description of the update server, especially the [[Reference10:Concept_Update_Server#Setvar_command | setvar]], [[Reference10:Concept_Update_Server#Replace_command | replace ]] and [[ Reference10:Concept_Update_Server#Eval_command | eval ]] commands.<br />
<br />
; [[Reference:My_Innovaphone#External_interface]] : description of the programmatic interface to add device serial numbers to a my.innovaphone project</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference14r1:Concept_App_Service_Devices&diff=70696Reference14r1:Concept App Service Devices2024-01-22T10:55:54Z<p>Ckl: /* Certificates configuration */</p>
<hr />
<div>[[Category:Concept|Apps]]<br />
<br />
The App Service Devices is an App Service which can be installed on an innovaphone App Platform. It is used to administrate the innovaphone devices which belong to the whole installation.<br />
<br />
== Applies To == <br />
<br />
* innovaphone PBX from version 13r1<br />
<br />
== Apps ==<br />
<br />
=== Devices App (innovaphone-devices) ===<br />
This is the standard UI App for Devices.<br />
<br />
Parameters:<br />
<br />
;Websocket: to publish the com.innovaphone.devicesui-API, which can be used to link directly to devices (which is done e.g. inside the Events app)<br />
<br />
=== Devices API (innovaphone-devices-api) ===<br />
This is a hidden App, which provides the Devices API (com.innovaphone.devices). This API can be used to find and communicate with devices which are registered to the Devices App.<br />
<br />
Parameters:<br />
<br />
;Hidden: DevicesApi must be a hidden App<br />
;Websocket: to get the URL of the Devices App itself which is used for provisioning<br />
<br />
== PBX Manager Plugins ==<br />
<br />
=== Devices ===<br />
<br />
With the Devices plugin App objects can be created, edited and deleted for Devices and the Devices API on the PBX.<br />
<br />
== Concepts ==<br />
All innovaphone hard phones and gateways starting from version 13r1 can establish a registration inside the Devices App and therefor administrative tasks can be performed through the App.<br />
<br />
=== Domains ===<br />
<br />
In a hosted environment, it might be needed to add multiple domains to the Devices App.<br><br />
After the initial installation of Devices through Install, you have administrative rights and can add more domains. <br />
<br />
==== Administrative rights ====<br />
You have administrative rights if the configured PBX App object for Devices uses the password of the instance which is configured in the Manager App and the PBX uses the same domain as this instance.<br />
<br />
==== Domain local rights ====<br />
You have just rights to a certain domain if the configured PBX App object for Devices uses the domain password which can be configured in Devices for each domain and the PBX uses the same domain name as configured in Devices.<br />
<br />
==== Assign rights to other domains ====<br />
Domains can gain access to other domains by configuring these access rights in the Devices App.<br />
<br />
==== Deploy passwords and access rights ====<br />
During install, a domain is created inside the Devices App. There is a checkmark Deploy the domain password on all devices which is set by default and which is the same as the administrative password.<br />
If you change the domain password, all passwords on all administrative devices and app platforms which are connected to this domain will be also changed.<br/><br />
Since SR25: devices which are auto provisioned or provisioned through the Profile/Users Admin App will get a random password instead of the domain password.<br />
If you add a phone device manually to a domain inside the Devices App, this phone device will also get a random password. Other device types added manually or devices added with a provisioning code which has been created within the Devices App directly, will get the domain password instead.<br/><br />
<br/><br />
This will also set the manager password and the SSH passwords of App Platforms.<br/><br />
<br/><br />
The clear text random passwords can be requested and viewed in the settings of a device in the Devices App.<br />
<br />
Attention: If you untick the '''Deploy administrative devices passwords''' checkmark, the password won't be deployed anymore, but also not reconfigured to the default password!<br />
<br />
Attention: After each reconnect of a client, the password will be deployed again.<br />
This means, if the checkmark is set and you set another password somewhere else (e.g. under General::Admin),<br />
this password will be just valid until the next reboot or reconnect to the Devices App!<br />
<br />
;Create new random passwords<br />
You can rollout new random passwords by unticking the '''Deploy administrative devices passwords''' checkmark and ticking it again.<br/><br />
<br />
;Devices with multiple domains (Cloud)<br />
In a hosted environment, you can use the Devices App with multiple domains. The corresponding instance of Devices has a configured domain and an instance password set inside the manager.<br><br />
<br />
The hosting PBX has a domain inside Devices with a specific password.<br><br />
If this domain name matches the instance domain of the Devices instance and the password matches either the instance password or the domain password, you will be logged in as admin and have access to all domains.<br><br />
<br />
Each newly created domain has its own password inside Devices. If you create an App Object inside a PBX with this domain and the password of this domain (inside Devices), you will just have access to this single domain.<br><br />
<br />
Devices also has the possibility to grant access to other domains for a specific domain.<br />
<br />
=== Device connections ===<br />
The Devices registration is done through a websocket connection to the webserver of the App Platform and the Devices instance (standard HTTP/s port).<br><br />
The URL used can be configured manually under [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:General/Devices_Registration Devices Registration] or through a new provisioning mechanism.<br><br />
<br><br />
A binary protocol is used to transfer information between the device and the devices App. The MAC address is the unique key to identify a device.<br><br />
<br><br />
If a device is added to a domain in Devices, the device gets a unique random password. Without such a password, a device shows up as '''unassigned''' in the Devices app.<br />
This is also the case after a long reset or when the device lost its configuration.<br><br />
<br><br />
In such a case, the device has to be manually added again to a domain or it has to be provisioned again.<br />
<br />
If a device is not assigned to a domain or provisioning category, it won't receive any configurations.<br />
<br />
==== Devices Registrations ====<br />
See [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:General/Devices_Registration Devices Registration]<br />
<br />
==== Second Devices Registration URL ====<br />
<br />
The firmware has a second SYSCLIENT2 module, which can be configured to point to a different Devices instance and the AP Manager also has a second Devices Registration URL.<br><br />
<br><br />
If you use this configuration option, you should take care and consider:<br />
<br />
* if the Devices domain from the second configuration provides a password for all devices, the password is ignored, as it would lead to inconsistend passwords otherwise (from 13r1 SR12 on)<br />
* the Devices domain from the second configuration should not provide Devices Configurations or Update Jobs as these might collide with configurations from the first Devices instance!<br />
<br />
Generally, only one of the two ''Devices'' App instances connected to the device must apply changes to the device.<br />
<br />
We strongly discourage the usage of this second configuration option due to unexpected behaviour if incorrectly used!<br />
Never use the second URL if you use software or hardware rental!<br />
<br />
==== Device information ====<br />
<br />
The following data is transferred:<br />
* MAC address<br />
* device type (e.g. IPVA, AppPlatform, IP112)<br />
* IP addresses (IPv4, IPv6)<br />
* version (not searchable)<br />
<br />
You can also search for this data inside the devices tab in the App.<br />
<br />
<!--==== Device Advanced UI ====<br />
By default, Devices administrators and non admins can access the advanced UI of any device without limitations.<br/><br />
In certain scenerios (e.g. Cloud), you may want to restrict this access to just the PBX on gateways and IPVAs for non adminstrative users, which just have access to certain domains.<br/><br />
<br/><br />
In this case, you can set the corresponding checkmark in the device settings tab of the specific device.<br/><br />
If the flag is set, just the PBX tab of the advanced UI is accessible for non administrative Devices users.<br />
<br />
Technically this means, that every HTTP request is checked if it starts with PBX0 and if not, the request is rejected with 401 unauthorized.<br />
<br />
If the device can be accessed directly without Devices, you may need a reverse proxy in between to filter HTTP requests.<br />
--><br />
=== Provisioning ===<br />
Devices can be added by provisioning (both phones and gateways). <br />
The standard online provisioning looks like this:<br />
* a user or administrator creates a provisioning code in the Devices or Users App (this stores the Devices URL on the provisioning server side)<br />
* a device with 13r1 or newer is connected to the network after a long reset<br />
* the provisioning code has to be entered within one hour (after one hour, the Update URL isn't polled anymore)<br />
* the device sends this code to config.innovaphone.com and retrieves the Devices URL<br />
* the device connects to the Devices URL and is added to the domain where the code has been created<br />
<br />
There is also an [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:Concept_Offline_Provisioning offline provisioning mode].<br><br />
There is also an [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:Concept_Provisioning#Automatic_provisioning automatic provisioning mode].<br />
<br />
Note: provisioning is cancelled if the device already belongs to another domain in the same Devices instance! This prevents the unauthorized move of a device between different domains.<br />
<br />
==== Devices Registration URL ====<br />
<br />
The Devices Registration URL which is set through the provisioning process, always starts with '''wss'''. This is not dependent anymore on the App URL which is configured inside the PBX Devices App object.<br />
<br />
=== Updates ===<br />
The Devices App can be used to rollout updates.<br><br />
<br />
==== JSON files ====<br />
The update files have to be acccessible on a webserver without authentication and two json files are used to describe the used firmware and apps.<br><br />
You can either use the standard innovaphone App Store for these files or your own local webserver.<br><br />
<br />
==== Update jobs ====<br />
You can configure several update jobs with different categories to organize your updates.<br><br />
Update jobs are always sequentially processed and inside one update job, just '''20''' devices are updated at the same time.<br />
<br />
==== Device update check after the update job has been already executed====<br />
If a device goes online, the newest suitable update job is searched for this device (depending on the categories).<br><br />
If an update job is found, the update is just performed, if there has been '''no''' successfull update for this device in this job before and the version does not match (simply string compare).<br />
<br />
====Update errors====<br />
If an update fails, the Devices App shows an error. You may have to enable further tracing on the updated device itself to find out the reason of the failure.<br><br />
<br><br />
* on a gateway/phone add a trace flag to the UP1 and HTTPCLIENT0 module and take a look at the events which may already tell you the reason<br />
* on an App Platform, enable the App and HTTP Client trace flag on the manager<br />
<br />
An update job is retried '''two''' times if updates inside this job failed. The retry is done '''ten''' minutes after the update job finished previously.<br />
<br />
====innovaphone myApps====<br />
The path to the App Store and the used innovaphone myApps version is updated to the version of the gateway on gateways with an enabled PBX after a successfull update.<br><br />
This configuration can be found [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:PBX/Config/myApps here]<br />
<br />
Inside the configuration on an update job, the flag "Do not update myApps launcher software" disables the update of the innovaphone myApps version inside PBXes.<br />
<br />
=== Backups ===<br />
The Devices App can be used to backup the configuration and data of Apps and devices.<br><br />
<br />
====Webserver requirements====<br />
Backups are stored on a webserver with or without Digest/Basic authentication and with the '''webdav PUT''' method.<br />
<br />
====Backup jobs====<br />
Backup jobs are executed sequentially. Inside one backup job, just '''20''' backups are performed at the same time.<br />
<br />
====Phones or gateways====<br />
The configuration file is stored. Therefor the Devices App tells the device to store the configuration with a !mod cmd UP0 /sync prot URL.<br />
<br />
====Apps on an App Platform====<br />
The databases of all existing instances and the manager are stored.<br><br />
Each installed App Service can have multiple instances and each App instance has its own database.<br><br />
A database contains '''both''' configuration and data of an App instance! <br><br />
The manager database contains the webserver certificate and further manager related configuration settings.<br />
<br />
The Devices App establishes a websocket connection to the manager and tells the manager where to store the backups.<br><br />
The manager on the backuped App Platform itself performs the HTTP requests to store the files.<br />
<br />
====Backup errors====<br />
If a backup fails, the Devices App shows an error. You may have to enable further tracing on the backuped device itself to find out the reason of the failure.<br />
<br />
* on a gateway/phone add a trace flag to the UP1 and HTTPCLIENT0 <br />
* on an App Platform, enable the App and HTTP Client trace flag on the manager<br />
<br />
=== Restore ===<br />
====Phones or gateways====<br />
Just as always under [http://wiki.innovaphone.com/index.php?title={{NAMESPACE}}:Administration/Upload/Config Upload Config].<br />
<br />
====Apps====<br />
The App Service itself has to be installed on the App Platform prior restoring of a single instance.<br><br />
The restoring is done in the Manager App on the App Platform itself.<br><br />
The intance settings, the configuration and data are restored in a single process.<br />
<br />
=== Device Configurations ===<br />
<br />
You can define several device configurations. These configurations are either applied to all devices inside this domain or to selected categories.<br/><br />
Configurations are applied on creation and on every reconnect of a matching device.<br />
<br />
==== Transfer checkmarks ====<br />
<br />
Some configuration options have a specific checkmark to enable the transfer of the option.<br/><br />
<br/><br />
* Checkmark disabled:<br />
** configuration option field(s) are disabled <br />
** option values are '''not''' transferred at all<br />
* Checkmark enabled:<br />
** configuration option field(s) are enabled<br />
** option values are transferred, even if field values are empty<br />
<br />
==== Expert configuration ====<br />
<br />
The expert configuration can be used to configure different settings which are not available inside the other device configurations.<br/><br />
You can use the standard syntax of an update server script (see [[ {{NAMESPACE}}:Concept_Update_Server | Concept Update Server ]] for a general overview and the section on [[Howto:PHP_based_Update_Server_V2#Hints_for_writing_your_update_snippets | Hints for writing your update snippets ]] (note that the remainder of this article relates to the now deprecated old mechanism, so please disregard the rest)).<br/><br />
<br/><br />
Some hints:<br />
* the expert configuration is just executed once after a device restarted and on change of the expert configuration itself<br />
* the expert configuration is executed after all other device configuration types, so that you can override changes<br />
* to make configuration changes effective, you may also need to issue a final ''config write'', ''config activate'' and ''iresetn'' command (see the [[ {{NAMESPACE}}:Concept_Update_Server#Check_command | ''check'' command]] for details)<br />
* some commands shouldn't be used inside the script:<br />
** [[ {{NAMESPACE}}:Concept_Update_Server#Times_command |''times'']]: since the expert configuration is executed only once after a reboot of the device and when changes are made, it does not make sense to use the ''times'' command<br />
** [[ {{NAMESPACE}}:Concept_Update_Server#Prot_command |''prot'']]|[[ {{NAMESPACE}}:Concept_Update_Server#Boot_command |''boot'']]: use an update job instead<br />
** [[ {{NAMESPACE}}:Concept_Update_Server#Scfg_command |''scfg'']]: use a backup job instead<br />
** [[Howto:PHP_based_Update_Server_V2#Using_vars_create|''vars create'']]: this cmd will always raise the ''reset needed condition'' and the aforementioned ''iresetn'' command would always execute therefore. As a result, the devices would enter a boot loop. Be sure to avoid this using an appropriate [[ {{NAMESPACE}}:Concept_Update_Server#Check_command |''check'' command ]] and update its ''<serial>'' properly whenever the expert configuration is changed<br />
<br />
==== Certificates configuration ====<br />
You can use this configuration to rollout certificates to the trust list of your devices.<br/><br />
<br />
* Manual upload certificates.<br />
* Configure up to five URLs which are polled every 24 hours. They must return files with PEM formatted public keys (one or more) which are then rolled out.<br />
* Configure https://download.innovaphone.com/certificates/innovaphone.pem to always have the innovaphone public keys in your trust list (e.g. used for our push service).<br />
* Configure https://download.innovaphone.com/certificates/ca.pem to always have the innovaphone Device Certification Authority certificates in your trust list.<br />
* Configure https://download.innovaphone.com/certificates/ca-unverified.pem to always have the innovaphone Unverified Device Certification Authority certificates in your trust list.<br />
** The Unverified CA is used for non hardware devices, e.g. IPVAs, which are not shipped with an official innovaphone Device Certification Authority certificate, as innovaphone has no control over the serial number here.<br />
<br />
Using this configuration, the trust list can only be managed through ''Devices'' because it is cleared before each rollout.<br />
<br />
=== Software rental ===<br />
Regarding the Software Rental program and the Payment Method, please refer to:<br />
* [[{{NAMESPACE}}:Concept_Software_Rental|Concept Software Rental]]<br />
<br />
<br />
Software rental can be done for innovaphone Cloud installations or for software, which operates on the customer premisis. This could eventually be customer owned hardware or a privat virtual machine, managed by the customer.<br />
<br />
==== Hardware licenses ====<br />
Hardware licenses have to be bound on the specific device in my.innovaphone itself and currently can't be handled within the Devices App itself. So you also need to download the licenses from my.innovaphone and upload them on the device with the already known methods.<br />
<br />
===== Hardware licenses with software rental =====<br />
If your device has software rental licenses, you can bind hardware licenses within my.innovaphone in the software rental project itself.<br />
<br />
===== Hardware licenses without software rental =====<br />
If your device does not have software rental licenses, you should bind hardware licenses in a separate non rental project in my.innovaphone.<br />
<br />
==== innovaphone Cloud ====<br />
Inside the innovaphone Cloud, you just have to upload your activation keys with iSCs to add licenses to one or more gateways.<br><br />
<br />
==== Email expiry notifications ====<br />
You will receive an email notification if the rental of a project expires. This notification will be sent '''seven''' weeks before the expiry and then once a week until the rental expires.<br><br />
You can configure one or more email recipients in the domain settings.<br><br />
If no email address is configured, the email address of the logged in user account is used.<br />
<br />
==== History ====<br />
You can download the history in the Devices App. You'll get a CSV file (semicolon separated). The date and number format depends on the selected language in the UI.<br><br />
There is also an API available for automated downloas, which is described [[ {{NAMESPACE}}:Concept_App_Service_Devices#API_to_download_rental_history|here ]].<br />
<br />
====Own installation====<br />
Inside your own installation, you have to register a new my.innovaphone account or you can use an existing account inside the Devices App.<br><br />
One domain inside Devices belongs to one project inside your my.innovaphone company, so you can handle multiple domains with one my.innovaphone account.<br />
<br />
====Technical aspects====<br />
A new rental expiration date is calculated on each license or balance change in the domain.<br><br />
So after each change new licenses with a new date are transfered to the gateways and also stored in the Devices App itself.<br><br />
If the rental expires, the gateway reboots and the licenses are not available anymore.<br><br />
The licenses are also transfered after each reconnect of a gateway to the Devices App.<br><br />
<br><br />
For license and balance changes, the Devices App must be online and have access to my.innovaphone.com!<br />
<br />
====Usage====<br />
You have to add (use the + symbol) a PBX and select all licenses you want to rent.<br><br />
Use the '''apply''' button to really bind these licenses. Without usage of the apply button, you can just see a precalculation of the iSCs/month and the rental end date, but nothing is really charged from your balance.<br />
<br />
===Change of IP address/DNS name in PBX object===<br />
If the IP address or DNS name inside the PBX object of the Devices App changes, all currently connected clients get a new Devices Registration URL and also all clients, which connect afterwards with the old host name.<br />
<br />
== Appendix ==<br />
=== Sample firmware.json ===<br />
<code type="javascript"><br />
{<br />
"devices": [<br />
{ "id": "IP0010", "versions": [ "13r1" ] },<br />
{ "id": "IP0011", "versions": [ "13r1" ] },<br />
{ "id": "IP101", "versions": [ "13r1" ] },<br />
{ "id": "IP102", "versions": [ "13r1" ] },<br />
{ "id": "IP1060", "versions": [ "13r1" ] },<br />
{ "id": "IP110", "versions": [ "13r1" ] },<br />
{ "id": "IP110A", "versions": [ "13r1" ] },<br />
{ "id": "IP111", "versions": [ "13r1" ] },<br />
{ "id": "IP112", "versions": [ "13r1" ] },<br />
{ "id": "IP1130", "versions": [ "13r1" ] },<br />
{ "id": "IP1260", "versions": [ "13r1" ] },<br />
{ "id": "IP150", "versions": [ "13r1" ] },<br />
{ "id": "IP2000", "versions": [ "13r1" ] },<br />
{ "id": "IP200A", "versions": [ "13r1" ] },<br />
{ "id": "IP22", "versions": [ "13r1" ] },<br />
{ "id": "IP222", "versions": [ "13r1" ] },<br />
{ "id": "IP230", "versions": [ "13r1" ] },<br />
{ "id": "IP232", "versions": [ "13r1" ] },<br />
{ "id": "IP24", "versions": [ "13r1" ] },<br />
{ "id": "IP240", "versions": [ "13r1" ] },<br />
{ "id": "IP241", "versions": [ "13r1" ] },<br />
{ "id": "IP29", "versions": [ "13r1" ] },<br />
{ "id": "IP3010", "versions": [ "13r1" ] },<br />
{ "id": "IP3011", "versions": [ "13r1" ] },<br />
{ "id": "IP302", "versions": [ "13r1" ] },<br />
{ "id": "IP305", "versions": [ "13r1" ] },<br />
{ "id": "IP311", "versions": [ "13r1" ] },<br />
{ "id": "IP411", "versions": [ "13r1" ] },<br />
{ "id": "IP6000", "versions": [ "13r1" ] },<br />
{ "id": "IP6010", "versions": [ "13r1" ] },<br />
{ "id": "IP800", "versions": [ "13r1" ] },<br />
{ "id": "IP810", "versions": [ "13r1" ] },<br />
{ "id": "IP811", "versions": [ "13r1" ] },<br />
{ "id": "IPVA", "versions": [ "13r1" ] }<br />
],<br />
"versions": [<br />
{ "id": "13r1", "build": "131705", "text": "13r1 dvl [131705]", "wiki": "" }<br />
]<br />
}<br />
</code><br />
<br />
=== Sample apps.json ===<br />
<code type="javascript"><br />
{<br />
"apps": [<br />
{<br />
"id": "apidemo",<br />
"folder": "apidemo",<br />
"binary": "apidemo",<br />
"versions": [<br />
{ "id": "13r1", "build": "131705", "label": "dvl" }<br />
],<br />
"manufacturer": "innovaphone",<br />
"title": "Apidemo",<br />
"description": "The Apidemo"<br />
},<br />
{<br />
"id": "appstore",<br />
"folder": "appstore",<br />
"binary": "appstore",<br />
"versions": [<br />
{ "id": "13r1", "build": "131705", "label": "dvl" }<br />
],<br />
"manufacturer": "innovaphone",<br />
"title": "AppStore",<br />
"description": "AppStore"<br />
}<br />
]<br />
}<br />
</code><br />
<br />
=== Sample software.json ===<br />
<code type="javascript"><br />
{<br />
"software": [<br />
{<br />
"id": "myappsandroid",<br />
"folder": "myappsandroid",<br />
"binary": "myapps.apk",<br />
"versions": [<br />
{ "id": "13r1", "build": "131705", "label": "dvl" }<br />
],<br />
"manufacturer": "innovaphone",<br />
"title": "myApps Android",<br />
"description": "myApps for Android"<br />
},<br />
{<br />
"id": "myappswindows",<br />
"folder": "myappswindows",<br />
"binary": "myAppsSetup.msi",<br />
"versions": [<br />
{ "id": "13r1", "build": "131705", "label": "dvl" }<br />
],<br />
"manufacturer": "innovaphone",<br />
"title": "myApps Windows",<br />
"description": "myApps for Windows"<br />
}<br />
]<br />
}<br />
</code><br />
<br />
=== API to download rental history ===<br />
<br />
You can download the history as a UTF-8 CSV file with simple '''HTTP GET''' requests with '''digest''' authentication.<br><br />
The CSV file uses the semicolon as delimiter.<br/><br />
<br><br />
The URL to download the history is e.g.:<br><br />
'''<code>https://mydomain.com/mydomain.com/devices/csvapi?...</code>'''<br />
<br />
You can find this path by editing the instance in the AP manager.<br />
In the listed paths, you'll find something which ends with csvapi<br />
<br />
In the innovaphone Cloud environment, the URL can be retrieved by taking a look at the Devices App object inside the cloud PBX and replacing the ending '''innovaphone-devices''' with '''csvapi''' inside the App URL, so it looks e.g. like this:<br />
<br />
https://cloud-apps0.innovaphone.com/cloud.innovaphone.com/devices/csvapi<br />
<br />
====CSV columns====<br />
Most columns should be self explaining, but the column '''invoice reference''' refers to a value which you can configure manually in the domain settings in the Devices App!<br />
<br />
====Digest authentication====<br />
You can login with two different username/password combinations, while the domain is the digest username:<br />
<br />
* If you use the domain and password of your Devices App instance inside the AP Manager, you have automatically access to all domains.<br><br />
* If you use the domain name of any domain as digest username and the configured domain password, you'll have access to this domain and all domains to which this domain has access to<br />
<br />
====Query parameters====<br />
The following query parameters can be used (don't forget to URL encode the parameter values, if neccessary!):<br />
* all: if '''1''' or '''true''', the history is queried for all domains where the digest user has access to<br />
* domain: required, if ''all'' is not set<br />
* lang: the output language of the CSV file, which also controls date and number formats, e.g. '''en''', '''de''', ...<br />
* tz: the timezone for dates, e.g. '''Europe/Berlin'''<br />
* type: two types are available:<br />
** '''history''': history which contains changes<br />
** '''overview''': a monthly overview with the iSC costs at the first of the last months<br />
* from: unix timestamp (UTC) in milliseconds, ignored for type=overview<br />
* to: unix timestamp (UTC) in milliseconds, ignored for type=overview<br />
<br />
====Example requests====<br />
* <code>https://mydomain.com/mydomain.com/devices/csvapi?domain=mydomain.com&type=overview&lang=en&tz=Europe%2FBerlin</code><br />
* <code>https://mydomain.com/mydomain.com/devices/csvapi?domain=mydomain.com&type=history&lang=en&tz=Europe%2FBerlin&from=1601903431000&to=1601903385000</code><br />
* <code>https://mydomain.com/mydomain.com/devices/csvapi?all=1&type=overview&lang=en&tz=Europe%2FBerlin</code><br />
<br />
== Known issues ==<br />
<br />
===SoftwarePhone===<br />
The Devices App is not meant to be used with the windows SoftwarPhone standalone installation.<br />
<br />
== Related Articles == <br />
* [[Howto:Software_Rental]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:How_to_Reset_IPXXX&diff=70350Howto:How to Reset IPXXX2023-12-18T08:39:41Z<p>Ckl: /* More Information */</p>
<hr />
<div>==Summary==<br />
Different Reset Options of IPXXX<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* All innovaphone products with the latest boot code.<br />
<br />
<!-- Keywords: factory default, led behaviour, tftp mode, clear config, gwload--><br />
==More Information==<br />
Different options resetting innovaphone devices.<br />
<br />
* bringing to Factory default<br />
* bringing to TFTP mode for GWLoad<br />
* clearing config<br />
* explaining the LED behavior<br />
<br />
Additional Information for the following table, TFTP-Mode or TFTP-Mode + FACTORY RESET for the phones IP110, IP150, IP200, IP202, IP230, IP240<br />
<br />
Seperating the phone from the power, press the Key/Button, keep on pressing the Key/Button, plug in the PSU or ethernet cable (in case of PoE), watch the status of the device (table: visual display), release the Key/Button after time shown in the table. If you want to reanimate this devices now with GWLoad, do not powercycle the device again. If the factory reset was successful the device is now in the defined TFTP-Mode and ready for GWLoad. If you just wanted to factory reset the device you can do a powercycle for sure.<br />
<br />
If the factory reset was not successful please contact rma@innovaphone.com<br />
<br />
or look at [[Howto:Get Access to Gateways if the Assistant doesn't boot the Device]].<br />
<br />
<br />
{| border=1<br />
!Device (visual display)<br />
!Reset Button/Key<br />
!Button pressed<br />
!Clear Config <br />
Firmware<br />
Boot code update<br />
!Pressing Button to TFTP without deleting config in seconds ca.<br />
!Pressing Button to factory reset in seconds ca.<br />
!GWLoad- / TFTP-Mode<br />
!Firmware starting<br />
!Firmware ready<br />
!Firmware Error<br />
|----<br />
|IP21<br />
(all LED's)<br />
|Button<br />
|ready: <5s blink, >5s constant flash<br />
<br />
line1 + line2 + door + aux constant flash / link + act off<br />
<br />
with plugged in ethernet cable 100M + link: <3s off, >3s normal operating state<br />
|...<br />
<br />
ready fast blink, line1 + line2 + door + aux off link + 100M normal operating<br />
<br />
|3<br />
|25<br />
|all LED’s are constant flashing<br />
<br />
link is blinking<br />
|ready + line1/2 + door + aux + 100M (if link)<br />
<br />
permanent for ca. 5s<br />
<br />
link blinking<br />
|ready + 100M (if link)<br />
<br />
link blinking<br />
|<br />
|----<br />
|IP400<br />
(all LED’s)<br />
|Button<br />
|PPP+Tel.1+Tel.2: <5s blink, [5;8] constant flash, >8 ready + link + act constant flash<br />
<br />
without ethernet cable plugged in: <8s off, 8s one blink, >8s off<br />
|<br />
|3<br />
|25<br />
|ready + link + act flashing constant<br />
|<8 ready + PPP + tel1 + tel2 constant flash,<br />
<br />
[3;10] link + act. uncoordinated flashing<br />
<br />
>10s ready + link (if link) constant act. blinking<br />
|ready + 100M (if link)<br />
<br />
link blinking<br />
|<br />
|----<br />
|IP3000<br />
(all LED’s)<br />
|Button<br />
|<6s PRI1+PRI2+S/T+ready blink, [6;10] PRI1 + PRI2 + S/T + ready constant flash,<br />
<br />
act + speed + link off<br />
<br />
>10s PRI1 + PRI2 + S/T constant flash, ready + act + speed + link off<br />
<br />
with ethernet cable plugged in: act + speed + link normal working state<br />
|fast blink (green)<br />
|3<br />
|25<br />
|all LED’s flashing permanent<br />
<br />
ready off<br />
<br />
link blinking<br />
|>2 link + speed constant flashing, act blinking<br />
<br />
<5s PRI1 + PRI2 + S/T constant flash<br />
<br />
>5s ready constant flash, (if link) link + speed constant flashing, act blinking<br />
|ready constant flash, (if link) link + speed constant flashing, act blinking<br />
|blinking (green)<br />
|----<br />
|IP800/IP6000/IPx010<br />
(red/green/orange ready LED )<br />
|Button<br />
|<6 slow gn blink<br />
<br />
[6;10] constant red<br />
<br />
[10;13] fast gn blink<br />
<br />
<13 constant orange<br />
|fast blink (green)<br />
|3<br />
|15 (IP800)<br />
<br />
15-20 (IP6000/IPx010)<br />
|constant orange<br />
|red<br />
|green<br />
|blinking (red)<br />
|----<br />
|IP1200<br />
|Button<br />
|<2s alarm red constant flash, ready off<br />
<br />
[2;7] alarm off, ready slow blink green<br />
<br />
[7;13] alarm off, ready fast blink green<br />
<br />
>13s alarm off, ready 2 Hz blink green<br />
|fast blink<br />
|3<br />
|10<br />
|slow flash ca. 2 Hz<br />
|<2s alarm red constant flash<br />
<br />
ready green constant flash<br />
<br />
power orange constant flash<br />
<br />
[2;4] alarm off, ready off, power orange<br />
<br />
>4s alarm off, ready constant green flash, power constant green flash<br />
|ready constant green flash and power constant orange flash<br />
|alarm (red)<br />
|----<br />
|IP1202/IP1203<br />
|Button<br />
|<3s LED off<br />
<br />
[3;10] LED blink fast blue 1 sec<br />
<br />
>10s LED blink fast blue 1 sec,<br />
than blink slow blue until factory reset completed,<br />
than constant yellow/amber<br />
|fast blink in blue<br />
|3<br />
|10<br />
|constant yellow/amber<br />
|LED red until firmware lstarted<br />
<br />
LED blink fast red until ethernet link up<br />
<br />
LED blink fast blue until Air Sync established<br />
<br />
LED constant blue if Air Sync OK<br />
<br />
|LED constant blue<br />
|LED constant red<br />
|----<br />
|IP200/IP202<br />
(red handset)<br />
|alt key<br />
|slow blink<br />
|slow blink<br />
|3<br />
|5<br />
|constant flash<br />
|slow blink<br />
|off<br />
|constant flash<br />
|----<br />
|IP200A/IP210<br />
(red handset)<br />
|alt key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|15<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking <br />
|----<br />
|IP110<br />
(LED’s in F1 key)<br />
|save key<br />
|slow blink<br />
|F1 off<br />
|3<br />
|10 - 15<br />
|slow flash<br />
ca. 2 Hz<br />
|constant F1 flash<br />
|off<br />
|blinking <br />
|----<br />
|IP101<br />
(MWI LED)<br />
|backspace key<br />
|press key when power cycle, hold ca 10 sec till led flashing. Power cycle again when slow blink<br />
|<br />
|<br />
|<br />
|<br />
|<br />
|<br />
| <br />
|----<br />
|IP102<br />
(MWI LED)<br />
|backspace key<br />
|press key when power cycle, hold ca 10 sec till led flashing. Power cycle again when slow blink<br />
|<br />
|<br />
|<br />
|<br />
|<br />
|<br />
| <br />
|----<br />
|IP111<br />
(MWI LED)<br />
|home key<br />
|press key when power cycle, hold ca 10 sec till led flashing. Power cycle again when slow blink<br />
|<br />
|<br />
|<br />
|<br />
|<br />
|<br />
| <br />
|----<br />
|IP112<br />
(MWI LED)<br />
|home key<br />
|press key when power cycle, hold ca 10 sec till led flashing. Power cycle again when slow blink<br />
|<br />
|<br />
|<br />
|<br />
|<br />
|<br />
| <br />
|----<br />
|IP230<br />
(menu key)<br />
|menu key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|10<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking <br />
|----<br />
|IP240<br />
(menu key)<br />
|menu key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|10<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking <br />
|----<br />
|IP241<br />
(menu key)<br />
|alt key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|10<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking<br />
|----<br />
|IP222<br />
(MWI LED)<br />
|ESC key or Home key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|15<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking<br />
|----<br />
|IP232<br />
(MWI LED)<br />
|ESC key or Home key<br />
|slow blink<br />
|fast blink<br />
|3<br />
|15<br />
|slow flash<br />
ca. 2 Hz<br />
|on<br />
|off<br />
|blinking <br />
<br />
|}<br />
<br />
== Related Articles ==<br />
* [[Howto:How to use gwload]]<br />
* [[Howto:How_to_Reset_IPXXX_(V10...)]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference:Device_Health_Check&diff=70078Reference:Device Health Check2023-12-06T12:28:09Z<p>Ckl: /* Monitoring what's going on */</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
<br />
<!-- Keywords: debugging speicherbedarf speichernutzung speicherfehler performance counter trap --><br />
<br />
==More Information==<br />
<br />
This article advises how to perform a quick ''health check'' on an innovaphone device. This may serve as guidance for routine checking of a PBX systems state as well as to start debugging when a device malfunctions. <br />
<br />
<br />
===Overview===<br />
<br />
To perform a device health check, the following steps are recommended<br />
<br />
* Inspect the device alarm table<br />
* Inspect the device event list<br />
* Inspect performance counters<br />
* Check volatile memory (RAM) usage<br />
* Check CPU usage<br />
* Check persistent memory (Flash) usage<br />
* Check CF card usage<br />
* Examine system log<br />
* Check for efficient PBX configuration<br />
<br />
Depending on the device type, some of the steps may or may not apply.<br />
<br />
=== The Alarm Table ===<br />
The systems alarm table ([[Reference13r3:Maintenance/Diagnostics/Alarms|Maintenance/Diagnostics/Alarms]]) should always be empty. If there are entries in it, examine them carefully and fix the problem so that the alarms disappears. The reason for this is pretty simple: if the alarm table is filled with entries that you already have checked but considered ''acceptable'', then it will not take long until a severe problem will hide itself in between the harmless entries. So make it a habit that your alarm table is empty always.<br />
<br />
The alarms have a ''details page'' available in the ''Code'' columns. These sometimes show useful further information. Also, many of the error codes have a dedicated wiki help page, available through the details page ''Help'' button.<br />
<br />
=== The Event List ===<br />
The systems [[Reference8:Administration/Diagnostics/Events|events list]] available under ''Administration/Diagnostics/Events'' may contain a fair amount of entry types. As opposed to the alarm table, where the entry is removed when the problem is fixed, entries in the event list are not removed (except the list exceeds its allowable size in which case the oldest entries will be removed). As a result, your event list will likely rarely be empty. You need to work through the list and analyse each entry to determine if or if not it is still relevant. To avoid analysing entries again and again, you can clear the whole list when finished, or (from V8 on), you can declare it as ''already taken care of'' using the ''Mark'' button in the detail page (which will render the entries ''Code'' column in green instead of red).<br />
<br />
As for alarms, it is important to take care of problems which frequently create entries in the event list, as otherwise you will most likely overlook severe problems in a crowded event list.<br />
<br />
=== The Calls Screen ===<br />
One important issue is consistently good voice quality. In a life system, you can do a quick check on voice quality simply by looking at the PBX's [[Reference:Administration/PBX/Calls | Administration/PBX/Calls ]] screen. <br />
<br />
<pre><br />
Number Name Protocol Media Dir Number Name Protocol Media Uptime State <br />
13 abc H323EFC:TRANSIT G711A (2,0,0) x >> 001730 H323EFC:TRANSIT G711A (2,0,0) x 0d 0h 29m 31s Connected SRTP<br />
0017200 H323EFC:TRANSIT G711A (46,5,3) >> 37 cba H323EFC:TRANSIT G711A (39,8,29) 0d 0h 7m 2s Connected <br />
</pre><br />
<br />
Have an eye on the values within round parentheses in the ''Media'' column, most notably the third ''packet loss'' value. These should be low, more than 5/minute for ''packet loss'' e.g. is a good reason to be worried (so in our example, the second call is still acceptable but on the lower end of satisfaction). More information about the call screen can be found in [[Howto:How_to_read_the_call_screen | How to read the call screen]].<br />
<br />
<br />
==== Recent Call Data ====<br />
While the calls screen is a good tool to see how your calls feel like right now, they do not allow you to review data for terminated calls. A rule of thumb however says that everything is fine as long as you look at it, but gets worse as soon as your end customers are left alone with the system. A nice tool to get at past data easily is the internal [[ Reference7:Administration/Gateway/CDR0 | Administration/Gateway/CDR0 ]] CDR logging. You can save call detail records on your local CF card (using the <code>LOCAL</code> ''Log Server Type'' which will save them to <code>/DRIVE/CF0/log/CDR*.txt</code>. You can open these files with any text editor and although they look a bit strange, you can search straight for entries like <code>&xcoder=G711A,30(0,10,0)&rcoder=G711A,30(0,0,0)</code>. The values in parentheses have the same meaning as in the PBX's calls screen.<br />
<br />
=== The Performance Counters ===<br />
The systems [[Reference13r2:Maintenance/Diagnostics/Counters|performance counters ]] available under ''Maintenance/Diagnostics/Counters'' are another valuable tool to determine its health status. These are graphs which show the status of certain resource over the last 24 hours (8 hours are shown, you can scroll through time using the arrow buttons). Each individual value on the x-axis is a 2-minutes average. Lets have look at the resource counters currently available:<br />
<br />
==== CPU ====<br />
This counter shows the total CPU usage. A system that runs near to 100% all the time is a candidate for unpleasant behaviour, obviously. However, running on 100% simply says that things are getting slow. It does not necessarily say that things don't work. But as a rule of life, demand is always increasing, not decreasing, so there are two options in such a situation: <br />
* be prepared for an upgrade<br />
* eliminate the reason for excessive CPU usage<br />
We will discuss in a later section of this article how to determine who the CPU hog is.<br />
<br />
We generally recommend to make sure CPU load is not more than two thirds in average. This of course is just a rule of thumb. You may well run with a system with higher load or you may experience problems with a system with lower load. Still, if your system continuously runs with more than 2/3 of its CPU power, you should ask yourself if this is expected behaviour. <br />
<br />
Also, watch out for unusual peaks. When you have a system that has a healthy looking CPU load graph, see if there are perhaps thin peaks. These would indicate short time frames with high load. They do not look impressive in the graph, still they may create severely bad user experience. Suppose for example there is a CPU usage pattern which creates 100% CPU load for a minute every once in a while. This would look like a comfortable 50% value in the 2-minutes graph slice. The end user though may experience service outages, signalling time-outs, frustrating response times etc. during this period of time. Such patterns usually look like a sharp ''needle'' in the graph and you should pay attention if you observe such.<br />
<br />
==== CPU-R ====<br />
This counter shows the amount of ''reserved CPU'' time. As opposed to the CPU counter, this is not CPU load that actually is used, but CPU performance which needs to be reserved for real-time application. Sending/receiving RTP data is considered real-time, for example. As opposed to, say HTTP access, where missing CPU performance merely makes things running slower, missing CPU performance for transmission of RTP data results in voice drop-outs and thus is considered a failure. This is why calls are rejected right away when no CPU performance can be reserved. When your CPU-R counter runs near to 100% often, then your system is overloaded and needs to be upgraded. Note that real-time apps do not suffer from 100% CPU values though, as the innovaphone operating system features a very efficient real-time prioritization.<br />
<br />
You can take a look over the used mips. With this command you get the exact information of the used mips in this moment. You can also use the CPU-R counter and move the cursor over the diagram. This method is a little bit inexact. This command is much faster than the CPU-R counter. The counter has a 5 Sec delay.<br />
<pre><br />
ip-PBX/!mod cmd CPU mips-usage<br />
</pre><br />
<br />
<br />
==== MEM ====<br />
This counter shows the total volatile memory (RAM) usage. Memory usage is in fact more critical than the CPU usage, as a system with low or no CPU resources left will still run, albeit slowly. A system with no memory resources will stop working and re-boot instead! It is thus crucial to have an eye on memory usage. You may observe that the memory usage graph usually is flat, that is, the value never decreases and rarely increases, especially once the device ran for a while after a re-boot. This is because the memory allocation strategy eventually claims memory for a specific purpose if needed, but never de-allocates it further on. Instead, objects allocated but no longer used are marked as ''free'' and re-used when an object of the same type is needed later on. The memory allocation graph shows all allocated objects, including those which are used and those which are currently free (as they are not available for use by objects of other types).<br />
<br />
If memory usage grows steadily, there is most likely a memory leak somehow. That is, a function in the device allocates objects and thus claims memory but fails to mark those objects as free when done. If this happens, each function invocation will result in lost memory and the only way to recover from this situation is to re-boot the system. We will discuss how to track down memory hogs in a later section.<br />
<br />
==== PRIx / BRIx ====<br />
These counters show the number of B-channels used on the respective ISDN interfaces. Its interpretation should be fairly straight forward.<br />
<br />
=== Finding a CPU Hog ===<br />
In the event that you observe excessive CPU load, you need to find out which function actually consumes it. Once you have done so, you can either try to re-configure the system so that the causing function is used less, or you can add an extra device and move this function to the new device. For example, if you find that your PBX runs out of CPU resources because of heavy load on the system's CF-card, you may want to move the CF card to a different device which has more CPU cycles left.<br />
<br />
Unfortunately, you cannot determine which ''function'' takes your CPU cycles away easily. However, you can determine which ''module'' does. In our previous example, you won't be able to determine that it's excessive config save operations on the CF card that produces the load, but you will be able to determine that its the CF card that does.<br />
<br />
A ''module'' is a piece of code in the devices firmware that performs a certain task. You can think of it as a ''thread'' in an operating system such as Windows or Linux. ''Modules'' are named and you can retrieve a list of modules currently running on your device using the <code>!mod</code> command. Assuming your device has the IP address 172.16.0.10, you would open [http://172.16.0.10/!mod http://172.16.0.10/!mod] with your browser. You will receive a list of modules such as this one:<br />
<br />
<br />
[[Image:Debugging_check-list_1.png]]<br />
<br />
<br />
In this table, the first column (modules) and the second column (ticks) are of interest. The second column shows the number of ticks used by the module since the counters were reset the last time. The system never resets the counters on its own. They will be reset if you add the <code>clr</code> option to the <code>mod</code> command ([http://172.16.0.10/!mod+clr http://172.16.0.10/!mod clr]). A ''tick'' by the way corresponds to some clock-tick found in the devices CPU (platform specific), so be prepared for large values. <br />
<br />
For easier analysis, you may want to import this table into your favourite spread sheet application. In Microsoft's Excel (c) for example, you would highlight the whole table, copy it in to your copy&paste buffer, then use ''Inhalte einfügen / Paste Content'' with ''Quelle: Text / Source: Text'' so you get each column in a separate Excel column (you may need to use the ''Import Assistant'' then, later V8 and V9 firmware builds output a more excel friendly table format). After some cleanup (remove unused columns), your spread sheet should then look something like this: <br />
<br />
<br />
[[Image:Device Health Check - mod-cmd-excel-1.png | Excel Import]]<br />
<br />
You can then add a column (column D in our example) which relates the number of ticks used by a module to the total number of ticks used (<code>=B96/($D$98)</code>). This is the relative load imposed by a certain module. Finally, sort the table by this column and you get a list of CPU hog candidates:<br />
<br />
<br />
[[Image:Device Health Check - mod-cmd-excel-2.png | Excel Analysis]]<br />
<br />
<br />
Unfortunately, there is no list of ''which module has which name'', but a look at the module name will give a good indication of what it is. The typical scenario is that IP0 (the IP stack), ETHx (the ethernet drivers) and H323 (the H.323 signalling stack) will be on top. If the device is a gateway too, the DSP drivers (AC-DSPx) and SRTP encryption drivers (MV78X00_CRYPT) will be in the top 10 too. In our example, you can see that the compact flash driver (CFLASH) is also prominent. Compared to that, the PBX itself - accounting for 2% of the cycles consumed - is a side-note only in our example. <br />
<br />
Be aware that these figures are counted from the last counter reset. To identify the source of a CPU resource problem, you would wait for the situation to happen (i.e. you would wait for a time the CPU usage is high) and then do the math. However, you should start the analysis by resetting the counters (<code>!mod clr</code>), then wait a significant amount of time (half a minute may be a good starter), then get the stats (<code>!mod</code>) and analyse. If you fail to reset the counters, your picture will be distorted as it is influenced by previous (and probably unknown) activity.<br />
<br />
=== Finding a DRAM Memory Hog ===<br />
<br />
The strategy to find a dram memory hog is pretty much similar to the one to find the CPU hog. However, rather than using the <code>!mod</code> command, you would use the <code>!mem</code> ([http://172.16.0.10/!mem http://172.16.0.10/!mem]) command. The output will look something like this (again, later builds have somewhat different layout, so excel import will be easier):<br />
<br />
<pre><br />
Total Memory Usage = 1061428<br />
name size used free usage<br />
client_gui_button 64 0 0 0<br />
client_gui_text 64 0 0 0<br />
client_gui_break 60 0 0 0<br />
client_gui_ruler 60 0 0 0<br />
client_gui_list_element 60 0 0 0<br />
client_gui_list 64 0 0 0<br />
client_gui_table_cell 60 0 0 0<br />
client_gui_table_row 60 0 0 0<br />
client_gui_table 60 0 0 0<br />
</pre><br />
<br />
The table has a line for each type of internal object (e.g. <code>client_gui_button</code>). The ''size'' column gives the size of a single instance in byte, the ''used'' and ''free'' columns the number of allocated objects which are still in use or free for re-use again, while ''usage'' is <code>size * (used + free)</code>. So clearly, the ''usage'' column is the amount of bytes what you are interested in. Again, we will do some spread-sheet math. <br />
<br />
<br />
[[Image:Device Health Check - mem-cmd-excel-1.png | Excel Analysis]]<br />
<br />
<br />
In our example, we relate the values in the ''usage'' column to the total allocatable memory size (column G) and the currently allocated memory (column F). The table is then sorted by column F, resulting in a list of memory using objects.<br />
<br />
As for the modules, there is no ''which object type has which name'' list, but looking at the object names will give some indication on what kind of object it is and which function it may be related to. The screen-shot was taken from an idle system, this is why column G is all zero. In a life system, the result may look more like this:<br />
<br />
<pre><br />
Name Size Used Free Sum % of allocated %of available<br />
cmd_exec 376 36 74 41360 1% 0%<br />
flashdir_item 120 418 0 50160 1% 0%<br />
pbx_connector 508 67 47 57912 2% 0%<br />
h323_call 552 67 46 62376 2% 0%<br />
gk_map 448 176 1 79296 2% 0%<br />
tcp_socket 408 150 176 133008 4% 0%<br />
pbx_user 584 270 0 157680 4% 0%<br />
packet 52 4997 488 285220 8% 0%<br />
http_request 5472 33 75 590976 17% 0%<br />
buffer 32 10437 8255 598144 17% 0%<br />
ac_491 122648 6 0 735888 21% 0%<br />
</pre><br />
<br />
As a rule of thumb, many systems tend to run out of CPU more likely than out of DRAM memory. <br />
<br />
<br />
''packet'' and ''buffer'' are special, as they do not relate to a specific function. Whenever the system needs temporary, short lived memory - e.g. to carry a network packet - it will allocate a ''packet'' that carries one or more ''buffers''. Peak network load thus will allocate a huge amount of ''packets'' and ''buffers'' which might not be used again but still consume memory. When the number of ''packets'' or ''buffers'' increases over time, this is most likely an indication of a memory leak. <br />
<br />
<br />
The bottom of the memory table includes a number of lines as follows:<br />
<br />
<pre><br />
allocated: 1466608<br />
free for malloc: 58580<br />
total malloc space: 3149356<br />
physcial memory: 8388608<br />
Total memory usage: 3090776<br />
Memory Load: 36%<br />
</pre><br />
<br />
The ''allocated:'' line is the number of allocated objects. That is, the sum of the ''usage'' column. Please note that this include all free objects of a specific type, which can be used for allocation of new objects of this type. <br />
<br />
The ''free for malloc:'' line is the amount of memory which can be allocated from. This will be used whenever an object is needed for which there is no previously allocated but now unused object space (as shown in the ''free'' column).<br />
<br />
The ''total malloc space:'' is the value of ''free for malloc:'' when the system has booted.<br />
<br />
The ''Total memory usage:'' is the memory already claimed from the malloc space (that is ''total malloc space:'' minus ''free for malloc''). It does ''not'' include static memory use such as decompressed firmware, stack, etc.<br />
<br />
The ''Memory Load:'' finally relates the ''Total memory usage:'' to the ''physical memory:''. Please note though that the box will run out of memory as soon as ''free for malloc'' goes down to zero! It might be useful thus to relate ''free for malloc:'' to ''total malloc space:'', which is exactly what the ''MEM'' counter in ''Administration/Diagnostics'' displays.<br />
<br />
==== Obtaining more Information about used Objects ====<br />
Sometimes, object types - even though their meaning is pretty clear - cannot be related to functions easily. For example, if you see an entry like<br />
<br />
<pre><br />
tcp_socket 376 15 64 29704<br />
</pre><br />
<br />
it tells you that a number of sockets are used, but not what for. To see details of an object, some object types support the <code>mem-info</code> method. You would look at the detail properties of the objects of a certain type using e.g. <code>http//172.16.0.10/!mem info tcp_socket</code> ([http://172.16.0.10/!mem+info+tcp_socket http//172.16.0.10/!mem info tcp_socket]). The result list<br />
<br />
<pre><br />
0(c0e09a0c): NAT_LISTEN: ports=0:0, state=0:INIT, ostate=5680, x-q=0, r-q=0<br />
1(c0e1073c): PCAP_SOCK_LISTEN: ports=2002:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
2(c0e17174): HTTP_LISTEN: ports=80:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
3(c0e173d8): HTTPS_LISTEN: ports=443:0, state=0:INIT, ostate=23130, x-q=0, r-q=0<br />
4(c0e181b8): T_USER: ports=23:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
5(c0e1fcd8): LSRV_SOCK_LISTEN: ports=389:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
6(c0e718f4): GK-LISTEN: ports=2049:0, state=0:INIT, ostate=-16053, x-q=0, r-q=0<br />
7(c0e71b54): GK-LISTEN: ports=2050:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
8(c0e71e3c): GK-LISTEN: ports=2051:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
9(c0e72310): GK-LISTEN: ports=2052:0, state=0:INIT, ostate=0, x-q=0, r-q=0<br />
10(c0e725f8): GK-LISTEN: ports=2053:0, state=0:INIT, ostate=1950, x-q=0, r-q=0<br />
11(c0e728e0): GK-LISTEN: ports=2054:0, state=0:INIT, ostate=-16148, x-q=0, r-q=0<br />
12(c0e72bc8): H323_LISTEN: ports=1720:0, state=0:INIT, ostate=-16027, x-q=0, r-q=0<br />
</pre><br />
<br />
may include some useful hints. This is particularly useful for the ''packet'' data structure, as it is used by all classes: [http://172.16.0.10/!mem+info+packet http//172.16.0.10/!mem info packet] indicates the packet creator module:<br />
<br />
<pre><br />
5717(c1d1d0b8): ETHIF,c0078b38 len=31<br />
5719(c18197fc): H323_OUT,c0078b38 len=31<br />
5725(c1cfb864): H323_ACCEPT,c0078b38 len=31<br />
5728(c1d04670): ETHIF,c0078b38 len=31<br />
5729(c1d56ac8): HTTP4_REQUEST,c00e228c len=113<br />
5730(c1f09518): HTTP_SOCKET,c0078b38 len=240<br />
</pre><br />
<br />
If there is a memory leak, the module causing it can easily be spotted.<br />
<br />
==== Monitoring what's going on ====<br />
The ''!mem info'' approach described above can also be used quite independently of memory leaks to get a real-time overview of what is going on in the system.<br />
<br />
Let us assume you need to know how many myApps clients are connected to the system<br />
* the first step would be to issue a <code>!mem</code> command to get the complete list of memory objects<br />
* now for the creative part: you need to guess the name of the object you are interested in. In our case, it is rather simple. We are looking for a client, so let's see which objects have an ''client'' as part of their name (and also have a value greater than 0 in the ''used'' column). What we find is<br />
<pre><br />
pbx_appclient_dialog_call 84 204 463 56028<br />
pbx_appclient_dialog 76 1596 189 135660<br />
pbx_appclient_presence 84 5050 425 459900<br />
pbx_appclient_standby_monitor 72 0 0 0<br />
pbx_appclient_login_context 44 0 2 88<br />
pbx_appclient_session 264 176 10 49104<br />
</pre><br />
: this looks promising, ''pbx_appclient_session'' could be our friend. So we try <cmd>!mem info pbx_appclient_session'' and here you go:<br />
<br />
155(84ce4e50): (Christoph Künkel) addr=172.16.68.10 secure=1 session=f64c7b8c57226001077f009033500068 ua=myApps for Windows deleting=0 closed=0<br />
<br />
Note though that <br />
* objects may or may not have the ''info'' method implemented. If not, you will see a useless list of memory addresses only<br />
* support for the method and format of its output may change with builds<br />
* all the !mem command variations are debug tools and not recommended for regular use (for example, you should not create a tool or app based on them)<br />
<br />
==== Buffers ====<br />
So far we have talked about ''objects'' in the operating system. However, there are also other users of dynamically allocated memory, the so-called ''buffers''. These are used for network packets for example. You can use <code>http://172.16.0.10/!buf</code> to get a list of used buffers.<br />
<br />
<pre><br />
pool used free<br />
0: 32 4129 2573 375312<br />
1: 64 319 652 85448<br />
2: 128 154 218 56544<br />
3: 256 188 85 76440<br />
4: 512 155 167 172592<br />
5: 1024 24 6 31440<br />
6: 2048 0 1 2072<br />
7: 4096 120 4 510880<br />
8: 8192 2 4 49296<br />
1360024<br />
</pre><br />
<br />
The second column shows the buffer size (buffers are always allocated in multiple-of-2 sizes). To see who has allocated the buffers, you can issue <code>http://172.16.0.10/!buf info size</code> where ''size'' is the one of the valid buffer sizes (32 to 8192). <br />
<br />
<pre><br />
http://172.16.0.10/!buf info 8192<br />
15c6e80: c=HTTP4_REQUEST(114988)<br />
15c4e60: c=HTTP_GET(19838c)<br />
15e8650: c=HTTP_GET(1983a0)<br />
</pre><br />
<br />
This example shows that 3 buffers of size 8192 are used and all are somehow related to HTTP GET requests.<br />
<br />
=== Finding a Flash Memory Hog ===<br />
Unfortunately, there is no counter graph for flash memory usage. Still, there is a statistics page as for CPU and DRAM memory. It is retrieved using the <code>!mod cmd FLASHMAN0 info</code> command ([http://172.16.0.10/!mod+cmd+FLASHMAN0+info http://172.16.3.63/!mod cmd FLASHMAN0 info]):<br />
<br />
<pre><br />
bottom 0xfe020000 base 0xfe020000 top 0xfffc0000 segsize 0x20000 segments 253<br />
LDAP - used 128k avail 92k owned 1152k (max 3200k)<br />
VARS - used 67k avail 95k owned 256k (max 256k)<br />
<br />
0 0xfe020000 free(0x00) owner MINI(0x09) magic 0x666d order 0x00020031 usage 0x00000001<br />
...<br />
7 0xfe100000 free(0x00) owner FIRM(0x08) magic 0x666d order 0x0002002f usage 0x00000001<br />
...<br />
226 0xffc60000 free(0xff) owner 255 (0xff) magic 0xffff order 0xffffffff usage 0xffffffff<br />
...<br />
230 0xffce0000 used(0x80) owner VARS(0x06) magic 0x666d order 0x00000008 usage 0x00000001<br />
231 0xffd00000 used(0x80) owner LDAP(0x04) magic 0x666d order 0x00000009 usage 0x00000001<br />
</pre><br />
<br />
This table shows all allocated flash memory segments. Users include:<br />
; MINI : the mini (rescue) firmware<br />
; FIRM : the real firmware<br />
; VARS : the storage for non-volatile configuration parameters<br />
; LDAP : the directory storage (holds the PBX object configuration as well as call lists and personal user directories)<br />
<br />
Each flash memory user has a fixed maximum quota. In our example, LDAP has set a maximum of 3200kB of which 1152kB are already claimed. In the current active flash segment (that is, the segment which is used to satisfy subsequent memory requests), 92kB are left for allocation. For the VARS flash user, all possible segments are claimed already (256kB of 256kB) and 95kB are left for allocation.<br />
<br />
==== Wasting Memory by deleting Objects ====<br />
When PBX objects are deleted, the GUID is kept in flash memory for a while with a note that the object has been deleted. This looks something like <code>mod cmd FLASHDIR0 add-item 101 (cn=object that will be deleted soon)(isDeleted=true)(guid;bin=3886C5ABE909D3119475009033290009)(usn=18243)</code>. Note that this sample entry still takes approximately 110 bytes flash memory. If you experience lack of flash memory and see lots of such entries, you might want to save the devices config, remove the lines with <code>(isDeleted=true)</code> and re-load the config to get rid of these entries. This typically happens when you turn on a new replication for a box that had a large PBX configuration before. These now obsolete PBX entries will be deleted from the configuration when the replication takes place.<br />
<br />
=== Finding a Compact Flash Hog ===<br />
Compact flash card space can easily be examined using the ''Info'' button available in [[ Reference7:Administration/Diagnostics/CF | Administration/Diagnostics/CF (up to V8) ]] or [[ Reference9:General/Compact-Flash/General | Reference9:General/Compact-Flash/General (from V9) ]]. The CF card's content can be examined using the ''Browse CF Content'' link on the same page.<br />
<br />
=== Using Syslog ===<br />
Many failures are listed in the ''Alarms'' and ''Events'' sections, as discussed above. However, some are not. It is a good habit therefore to take transcript of the systems log messages and browse through them in search of intermittent problems. This can easily be done by storing the syslog on the local CF card (as defined on [[ Reference8:Configuration/General/Logging | Configuration/General/Logging ]]). It is important to tick the check marks on [[ Reference8:Administration/Diagnostics/Logging | Administration/Diagnostics/Logging ]] in a sensible way in order not to flood the log with useless messages. As a starter, the following settings are recommended:<br />
<br />
{|<br />
| Category || recommended setting<br />
|-<br />
| TCP || off ||<br />
|-<br />
| PPP || off ||<br />
|-<br />
| PBX Calls || on ||<br />
|-<br />
| Gateway Calls || on ||<br />
|-<br />
| Gateway Routing || off ||<br />
|-<br />
| H.323 Registrations || on ||<br />
|-<br />
| SIP/UDP Registrations || off, unless you use SIP endpoints ||<br />
|-<br />
| SIP/TCP Registrations || off, unless you use SIP endpoints ||<br />
|-<br />
| SIP/TLS Registrations || off, unless you use SIP endpoints ||<br />
|-<br />
| H.323-NAT || off ||<br />
|-<br />
| Administration || off ||<br />
|-<br />
| TELx/PRIx/PPP || off, unless you use this interface ||<br />
|-<br />
|}<br />
Please note that ''Administration'' might be a recommended setting if documentation of changes is an issue to you.<br />
<br />
=== Resource sensitive PBX Configuration ===<br />
Very often, resource issues are caused by inappropriate PBX configuration. Although there are of course a huge number of possibilities to do it wrong, here are a few prominent and easy to avoid things to keep in mind:<br />
<br />
* do not use long identifiers. All PBX identifiers (user names, group names, ...) are stored in clear text in the PBX LDAP directory. Longer names use more flash space thus. If you deal with a 100 user PBX, you probably don't care. However, if you have 10.000 users structured in groups with a huge amount of members per group, calling a group <code>cti</code> instead of <code>Users with CTI Integration</code> does make a difference!<br />
* do not put objects in to groups they do not need to belong to. You may be tempted to add users to certain groups ''by default'', thinking that it will make your life easier later on if the group assignment is in fact needed. However, group assignments not only consume flash space in the PBX's LDAP directory, but also consume in-core memory during run-time. Even more, doing so will make it more probably to exceed the maximum size of a single group (which is 2000 members currently). Having more than the allowed maximum of group members in a single group will result in strange behaviour which is difficult to track down.<br />
* do not put objects as ''active'' members in to groups when they don't need to be active. ''Active'' group memberships consume additional DRAM memory as well as additional CPU cycles during run-time. <br />
* do not configure [[Reference7:Administration/PBX/Objects#Object_Properties | group indications ]] when they are not needed. Group indications will result in additional memory and CPU consumption as well as network traffic. When group indications are configured, information is distributed to the respective group members regardless of if it is used there or not. So if no group indication is required, don't send it! If it is required, use the smallest group available to send them to. Do not re-use a group for this purpose just because it exists if it includes more members than required for the group indications. Rather create a new group and restrict it to the members required. One particular bad habit is to put all users in a single group, have them all be ''active'' and configure this group as the group used for ''group indications'' for all users. While this has the beauty that PBX functions which require group indications will always work (which to configure correctly may be a bit tedious sometimes), but it has the downside that each call to any of these users suffers from increased CPU and network load with each added user!<br />
* do not create objects that aren't needed. Each PBX object consumes flash and DRAM memory, even if it is never used. <br />
* do not put objects in to the group used by the TAPI service provider which are never monitored anyway. All activity information for all users in the tapi group is conveyed by the PBX to the TAPI service provider. If it is never used, this is a waste of resources and adds load both to the PBX and to the machine running the TSP.<br />
<br />
=== Monitoring a multi-Device System ===<br />
If your system consists of more than one relevant device, you may want to configure Alarm, Event and Syslog forwarding (as configured in [[ Reference8:Configuration/General/Logging | Configuration/General/Logging ]]) so you have this information in a single place to review.<br />
<br />
=== Monitoring the Linux Application Platform (LAP) ===<br />
<br />
To see the LAP's health status, first have a look at the ''Diagnostics/Status'' page.<br />
<br />
==Related Articles==<br />
* [[Howto:Config_size_Limitations]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference12r1:Concept_Reverse_Proxy&diff=69784Reference12r1:Concept Reverse Proxy2023-11-20T20:20:04Z<p>Ckl: /* VARS used for Host definitions */</p>
<hr />
<div>[[Category:Concept|Reverse Proxy]]<br />
[[Category:Concept_Reverse_Proxy]]<br />
The Reverse Proxy is a software module, which is available on innovaphone gateways. It is designed to allow safe access to services of the innovaphone PBX from the public internet. To accomplish this the gateway must be accessible from the public internet either by NAT port forwarding, or directly. The reverse proxy forwards traffic to configurable destinations.<br />
The access to internal destinations can be limited in several ways and algorithms to detect attacks are implemented, which are used to put ip addresses into a blacklist.<br />
The reverse proxy supports H.323, SIP, HTTP and LDAP over TCP or TLS.<br />
<br />
== Configuration ==<br />
<br />
The reverse proxy only accepts any connections on the supported protocols, if the port numbers for these protocols are configured. The well-known port numbers or <br />
non standard port numbers can be used for these protocols.<br />
<br />
A timeout may be configured until which an entry in the blacklist is removed automatically.<br />
<br />
A threshold suspicious requests per minute can be used to tune detection of attacks.<br />
<br />
Forwarding to internal destinations is based on the addressed host. For this the received requests are analyzed and for each supported protocol different elements are used to identify the addressed host.<br />
<br />
== Basic Operation ==<br />
<br />
Connections are accepted on the configured port. The received protocol is analyzed to determine the addressed host and a internal connection is established to this host. If the incoming connection is TCP for the internal connection TCP is used as well, if the external connection is TLS, for the internal connection TLS is used. If only TCP or only TLS is configured for the internal connection it is used regardless if the incoming connection was TCP or TLS.<br />
<br />
If the Check Certificate checkmark is set, for the internal connection TLS is used only if the received certificate matches the user name within the protocol. This way a host receiving a request through the Reverse Proxy using TLS can assume that the connection was authenticated using a valid certificate, which matches the user.<br />
<br />
Access to a configured protocol may be limited to certain networks. This is done by configuring a list of networks in a addr:mask form.<br />
<br />
=== H.323 ===<br />
<br />
For H.323 registrations, the gatekeeper identifier received in GatekeeperRequest or RegistrationRequest messages is matched to the Name configured for the host. Only H.323 over H.225 (H.450-17) is supported.<br />
<br />
For calls without registration a destination in the for <user>@<domain> is expected. <domain> is matched to the Name configured for the host. This can be used for H.323 open federation.<br />
<br />
=== SIP ===<br />
<br />
For SIP registration the domain part of the FROM header of a REGISTER message is matched to the Name of the configured host.<br />
<br />
Also SIP INVITE messages are forwarded by the Reverse Proxy, if the FROM URI matches with the configured host.<br />
<br />
=== HTTP ===<br />
<br />
For HTTP requests the host header is matched to the Name of the configured host. If within a single TCP/TLS connection requests are sent to different hosts, the outgoing connections are terminated and for the request to the other host a new connection is established.<br />
<br />
The path which may be accessed can be restricted, by configuring the allowed path. If the pass is configured with a trailing '/' no access to folders inside this path is allowed.<br />
<br />
=== LDAP ===<br />
<br />
For LDAP the LDAP_BIND message is analysed. A user in the form <domain>\<user> is expected and the <domain> is matched to the domain part of the name of the configured host.<br />
<br />
== Attack detection ==<br />
<br />
Attacks are detected based on the frequency of unsuccessful requests. If more then a configured number of such requests are received within a sliding window of 1min the originating IP address is put into the black list.<br />
<br />
To provide some indication about current suspicious requests a list with the the 10 remote IP addresses with the highest number of requests is displayed.<br />
<br />
== Blacklist/Whitelist ==<br />
<br />
A blacklist/whitelist mechanism is used to block IP addresses or grant access to IP addresses regardless of attack detection. An entry into list can be configured with a timeout, so that it will be removed after the timeout automatically. Entries generated by the attack detection use the configured timeout.<br />
<br />
A blacklist entry can be easily changed to a whitelist entry by setting the whitelist checkmark.<br />
<br />
== Reverse Proxy with innovaphone PBX ==<br />
<br />
The innovaphone PBX has some features implemented especially for integration with the Reverse Proxy.<br />
<br />
=== Controlling Authentication ===<br />
<br />
On the innovaphone PBX up to 8 Reverse Proxies can be configured which are used to forward requests to the PBX. A Reverse Proxy is identified by its IP address. Optionally a certificate name can be configured for a Reverse Proxy, to verify that the connection really sent by the Reverse Proxy and not just relayed by some other equipment.<br />
<br />
Registrations through a Reverse Proxy are only accepted if the Reverse Proxy flag at the destination device is set. This way no unexpected registration is possible. When the Assume TLS checkmark is set, a registration received from the Reverse Proxy via TLS is assumed to be authenticated with a proper TLS certificate on the Reverse Proxy already. So no further authentication is required on the PBX. Such a registration is accepted also if TLS Only is set on the device.<br />
If a registration from the Reverse Proxy is received via TCP password authentication is required.<br />
<br />
=== Support for multiple Slave PBXs ===<br />
<br />
To address a specific Slave PBX through a Reverse Proxy, a GK-ID with the format <domain>/<location> can be used. A PBX ignores anything in the GK-ID after the '/'. This way in the Reverse Proxy for each slave PBX sharing all the same domain a separate host entry can be configured.<br />
<br />
On the phone this format can be used to address the correct destination PBX.<br />
<br />
If the registration redirect mechanism shall be used, so that all client can share the same configuration, normally the PBX redirects to the IP address of the correct slave. In this case this does not work since from the public internet the internal addresses are inaccessible, so DNS Names must be configured on all PBXs. In the external DNS these DNS names must all be resolved to the Remote Proxy. The PBX changes the redirect GK-ID to contain the new location in the above format.<br />
<br />
The physical location of the phone in case of redirection is the PBX to which the first registration was made. The PBX uses the <physical-location>@domain... format of the GK-ID on redirection.<br />
<br />
Physical location and registration PBX can be configured manually on the phone by using a GK-ID of <physical-location>@domain/<registration PBX><br />
<br />
=== Support for Standby PBXs ===<br />
<br />
To address different PBXs for the primary and the alternate registration different GK-IDs have to be used for the primary and alternate (standby) PBX in the form of <domain>/<location> with a different location for primary and alternate. This way in the reverse proxy different host entries can be configured to address the active and standby PBX.<br />
To configure this on innovaphone endpoints a GK-ID in the form <domain>/<primary>:<alternate> can be configured. For the primary registration <domain>/<primary> and for the alternate registration <domain>/<alternate> is then used.<br />
<br />
== Configuration Items used ==<br />
<br />
=== Command line arguments ===<br />
<br />
The reverse proxy uses the following command line arguments (the arguments found on the "config change REVERSE-PROXY" line:<br />
;/h323 <port>: The H.323/TCP listening port<br />
;/sip <port>: The SIP/TCP listening port<br />
;/ldap <port>: The LDAP listening port<br />
;/http <port>: The HTTP listening port<br />
;/h323s <port>: The H.323/TLS listening port<br />
;/sips <port>: The SIP/TLS listening port<br />
;/ldaps <port>: The LDAPS listening port<br />
;/https <port>: The HTTPS listening port<br />
;/expire <mins>: The blacklist expiration timeout in minutes<br />
;/threshold <num>: Number of suspicous requests per minute, which causes a blacklist entry<br />
;/public-nat-addr <ip-address>: The public address of the NAT router, which forwards the requests to the RP. Needed for SIP, so that the correct address for further requests can be provided<br />
;/log-http-rej: Log rejected HTTP requests<br />
;/log-http-fwd: Log forwarded HTTP requests<br />
;/log-ldap-rej: Log rejected LDAP requests<br />
;/log-ldap-fwd: Log forwarded LDAP requests<br />
;/log-sip-rej: Log rejected SIP requests<br />
;/log-sip-fwd: Log forwarded SIP requests<br />
;/log-h323-rej: Log rejected H.323 requests<br />
;/log-h323-fwd: Log forwarded H.323 requests<br />
<br />
=== VARS used for Host definitions ===<br />
<br />
The name of the VARS for host definitions is formed as follows:<br />
<br />
RPRXY/H<FNV-1a Hash of Hostname>/<sequence><br />
;FNV-1a Hash of Hostname: The hash is calculated as defined in [https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function FNV-1a Hash] (32-bit)<br />
;sequence: If multiple host names yield the same hash value, different sequence numbers are used to create uniqque names<br />
<br />
The vars create lines in the config file create these vars. Example:<br />
<br />
vars create RPRXY/HD93FC082/00000 ...<br />
<br />
The flags used for these VARS are pb (permanent, binary)<br />
<br />
The content of the VARS is binary, optimized for quick access to the relevant data.<br />
<br />
The general format is<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
word - offset H.323 mappings<br />
word - offset SIP mappings<br />
word - offset LDAP mappings<br />
word - offset HTTP mappings<br />
char [] - Hostname, variable length<br />
byte [] - Mappings<br />
}<br />
</syntaxhighlight><br />
;offsets: The offsets are a byte offset into the var content to where the mappings info is stored<br />
;hostname: A variable length zero terminated hostname<br />
;mappings: Start of the mappings. All mappings are on 4 bytes aligned offsets<br />
<br />
For the mappings the following structure is used:<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
IPaddr addr;<br />
word port;<br />
word ports;<br />
word nets;<br />
word flags;<br />
word next;<br />
word ext1;<br />
word ext2;<br />
word ext3;<br />
char path[];<br />
};<br />
</syntaxhighlight><br />
;addr: The destination IP address, as IPv6 address. If the destination is IPv4 the mapped IPv4 format is used (::ffff:<IPv4>)<br />
;port: The port used for TCP<br />
;ports: The port used for TLS<br />
;nets: Offset from the beginning of the mapping to a network filter list, if present. If no filter is defined 0 is set<br />
;flags: Flags defined for the mapping. The following values are used:<br />
:;MAP_CERT - 1: If set, the certificates of incoming TLS connections are checked<br />
:;MAP_APP - 2: App Login<br />
:;MAP_DEFAULT - 4: Default path for a given host<br />
;path: A zero terminated path<br />
;ext1, ext2, ext3: Fields reserved for extensions. With these fields, the mapping header is an even 32 bytes<br />
; next : is the offset in bytes from the beginning of this map to the beginning of the next one. It must be 0 for the last map for a host<br />
<br />
If nets is set the following structure is used at the indicated offset:<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
word count;<br />
word spare;<br />
struct {<br />
IPaddr addr;<br />
IPaddr mask;<br />
} net;<br />
}<br />
</syntaxhighlight><br />
;count: Number of networks in the list<br />
;spare: Used for 4 bytes alignment<br />
;net: Array of addr/mask pairs for the networks, as IPv6 addresses.<br />
<br />
=== VARS used for White/Black list entries ===<br />
<br />
RPRXY/I<FNV-1a Hash of address>/<sequence><br />
;FNV-1a Hash of the address: The hash is calculated as defined in [https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function FNV-1a Hash] (32-bit)<br />
;sequence: If multiple addresses yield the same hash value, different sequence numbers are used to create uniqque names<br />
<br />
The vars create lines in the config file create these vars. Example:<br />
<br />
vars create RPRXY/IC2D83483/00000 ...<br />
<br />
The flags used for these VARS are pb (permanent, binary)<br />
<br />
The content of the VARS is binary, optimized for quick access to the relevant data.<br />
<br />
The following structure is used for the content:<br />
<br />
struct {<br />
dword addr[4];<br />
byte white;<br />
byte fill[3];<br />
time_t date;<br />
word description_len;<br />
char description[128];<br />
};<br />
<br />
;addr: The address on the list<br />
;white: True if white list entry<br />
;fill: For alignment<br />
;date: Unix timestamp of entry into list<br />
;description_len: Length of description<br />
;description: Description text without terminating zero. A maximum length of 128 characters is stored.<br />
<br />
== Known Problems ==<br />
<br />
[[:Category:Problem_Reverse_Proxy|Known Problems]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference12r1:Concept_Reverse_Proxy&diff=69765Reference12r1:Concept Reverse Proxy2023-11-20T11:31:37Z<p>Ckl: /* VARS used for Host definitions */</p>
<hr />
<div>[[Category:Concept|Reverse Proxy]]<br />
[[Category:Concept_Reverse_Proxy]]<br />
The Reverse Proxy is a software module, which is available on innovaphone gateways. It is designed to allow safe access to services of the innovaphone PBX from the public internet. To accomplish this the gateway must be accessible from the public internet either by NAT port forwarding, or directly. The reverse proxy forwards traffic to configurable destinations.<br />
The access to internal destinations can be limited in several ways and algorithms to detect attacks are implemented, which are used to put ip addresses into a blacklist.<br />
The reverse proxy supports H.323, SIP, HTTP and LDAP over TCP or TLS.<br />
<br />
== Configuration ==<br />
<br />
The reverse proxy only accepts any connections on the supported protocols, if the port numbers for these protocols are configured. The well-known port numbers or <br />
non standard port numbers can be used for these protocols.<br />
<br />
A timeout may be configured until which an entry in the blacklist is removed automatically.<br />
<br />
A threshold suspicious requests per minute can be used to tune detection of attacks.<br />
<br />
Forwarding to internal destinations is based on the addressed host. For this the received requests are analyzed and for each supported protocol different elements are used to identify the addressed host.<br />
<br />
== Basic Operation ==<br />
<br />
Connections are accepted on the configured port. The received protocol is analyzed to determine the addressed host and a internal connection is established to this host. If the incoming connection is TCP for the internal connection TCP is used as well, if the external connection is TLS, for the internal connection TLS is used. If only TCP or only TLS is configured for the internal connection it is used regardless if the incoming connection was TCP or TLS.<br />
<br />
If the Check Certificate checkmark is set, for the internal connection TLS is used only if the received certificate matches the user name within the protocol. This way a host receiving a request through the Reverse Proxy using TLS can assume that the connection was authenticated using a valid certificate, which matches the user.<br />
<br />
Access to a configured protocol may be limited to certain networks. This is done by configuring a list of networks in a addr:mask form.<br />
<br />
=== H.323 ===<br />
<br />
For H.323 registrations, the gatekeeper identifier received in GatekeeperRequest or RegistrationRequest messages is matched to the Name configured for the host. Only H.323 over H.225 (H.450-17) is supported.<br />
<br />
For calls without registration a destination in the for <user>@<domain> is expected. <domain> is matched to the Name configured for the host. This can be used for H.323 open federation.<br />
<br />
=== SIP ===<br />
<br />
For SIP registration the domain part of the FROM header of a REGISTER message is matched to the Name of the configured host.<br />
<br />
Also SIP INVITE messages are forwarded by the Reverse Proxy, if the FROM URI matches with the configured host.<br />
<br />
=== HTTP ===<br />
<br />
For HTTP requests the host header is matched to the Name of the configured host. If within a single TCP/TLS connection requests are sent to different hosts, the outgoing connections are terminated and for the request to the other host a new connection is established.<br />
<br />
The path which may be accessed can be restricted, by configuring the allowed path. If the pass is configured with a trailing '/' no access to folders inside this path is allowed.<br />
<br />
=== LDAP ===<br />
<br />
For LDAP the LDAP_BIND message is analysed. A user in the form <domain>\<user> is expected and the <domain> is matched to the domain part of the name of the configured host.<br />
<br />
== Attack detection ==<br />
<br />
Attacks are detected based on the frequency of unsuccessful requests. If more then a configured number of such requests are received within a sliding window of 1min the originating IP address is put into the black list.<br />
<br />
To provide some indication about current suspicious requests a list with the the 10 remote IP addresses with the highest number of requests is displayed.<br />
<br />
== Blacklist/Whitelist ==<br />
<br />
A blacklist/whitelist mechanism is used to block IP addresses or grant access to IP addresses regardless of attack detection. An entry into list can be configured with a timeout, so that it will be removed after the timeout automatically. Entries generated by the attack detection use the configured timeout.<br />
<br />
A blacklist entry can be easily changed to a whitelist entry by setting the whitelist checkmark.<br />
<br />
== Reverse Proxy with innovaphone PBX ==<br />
<br />
The innovaphone PBX has some features implemented especially for integration with the Reverse Proxy.<br />
<br />
=== Controlling Authentication ===<br />
<br />
On the innovaphone PBX up to 8 Reverse Proxies can be configured which are used to forward requests to the PBX. A Reverse Proxy is identified by its IP address. Optionally a certificate name can be configured for a Reverse Proxy, to verify that the connection really sent by the Reverse Proxy and not just relayed by some other equipment.<br />
<br />
Registrations through a Reverse Proxy are only accepted if the Reverse Proxy flag at the destination device is set. This way no unexpected registration is possible. When the Assume TLS checkmark is set, a registration received from the Reverse Proxy via TLS is assumed to be authenticated with a proper TLS certificate on the Reverse Proxy already. So no further authentication is required on the PBX. Such a registration is accepted also if TLS Only is set on the device.<br />
If a registration from the Reverse Proxy is received via TCP password authentication is required.<br />
<br />
=== Support for multiple Slave PBXs ===<br />
<br />
To address a specific Slave PBX through a Reverse Proxy, a GK-ID with the format <domain>/<location> can be used. A PBX ignores anything in the GK-ID after the '/'. This way in the Reverse Proxy for each slave PBX sharing all the same domain a separate host entry can be configured.<br />
<br />
On the phone this format can be used to address the correct destination PBX.<br />
<br />
If the registration redirect mechanism shall be used, so that all client can share the same configuration, normally the PBX redirects to the IP address of the correct slave. In this case this does not work since from the public internet the internal addresses are inaccessible, so DNS Names must be configured on all PBXs. In the external DNS these DNS names must all be resolved to the Remote Proxy. The PBX changes the redirect GK-ID to contain the new location in the above format.<br />
<br />
The physical location of the phone in case of redirection is the PBX to which the first registration was made. The PBX uses the <physical-location>@domain... format of the GK-ID on redirection.<br />
<br />
Physical location and registration PBX can be configured manually on the phone by using a GK-ID of <physical-location>@domain/<registration PBX><br />
<br />
=== Support for Standby PBXs ===<br />
<br />
To address different PBXs for the primary and the alternate registration different GK-IDs have to be used for the primary and alternate (standby) PBX in the form of <domain>/<location> with a different location for primary and alternate. This way in the reverse proxy different host entries can be configured to address the active and standby PBX.<br />
To configure this on innovaphone endpoints a GK-ID in the form <domain>/<primary>:<alternate> can be configured. For the primary registration <domain>/<primary> and for the alternate registration <domain>/<alternate> is then used.<br />
<br />
== Configuration Items used ==<br />
<br />
=== Command line arguments ===<br />
<br />
The reverse proxy uses the following command line arguments (the arguments found on the "config change REVERSE-PROXY" line:<br />
;/h323 <port>: The H.323/TCP listening port<br />
;/sip <port>: The SIP/TCP listening port<br />
;/ldap <port>: The LDAP listening port<br />
;/http <port>: The HTTP listening port<br />
;/h323s <port>: The H.323/TLS listening port<br />
;/sips <port>: The SIP/TLS listening port<br />
;/ldaps <port>: The LDAPS listening port<br />
;/https <port>: The HTTPS listening port<br />
;/expire <mins>: The blacklist expiration timeout in minutes<br />
;/threshold <num>: Number of suspicous requests per minute, which causes a blacklist entry<br />
;/public-nat-addr <ip-address>: The public address of the NAT router, which forwards the requests to the RP. Needed for SIP, so that the correct address for further requests can be provided<br />
;/log-http-rej: Log rejected HTTP requests<br />
;/log-http-fwd: Log forwarded HTTP requests<br />
;/log-ldap-rej: Log rejected LDAP requests<br />
;/log-ldap-fwd: Log forwarded LDAP requests<br />
;/log-sip-rej: Log rejected SIP requests<br />
;/log-sip-fwd: Log forwarded SIP requests<br />
;/log-h323-rej: Log rejected H.323 requests<br />
;/log-h323-fwd: Log forwarded H.323 requests<br />
<br />
=== VARS used for Host definitions ===<br />
<br />
The name of the VARS for host definitions is formed as follows:<br />
<br />
RPRXY/H<FNV-1a Hash of Hostname>/<sequence><br />
;FNV-1a Hash of Hostname: The hash is calculated as defined in [https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function FNV-1a Hash] (32-bit)<br />
;sequence: If multiple host names yield the same hash value, different sequence numbers are used to create uniqque names<br />
<br />
The vars create lines in the config file create these vars. Example:<br />
<br />
vars create RPRXY/HD93FC082/00000 ...<br />
<br />
The flags used for these VARS are pb (permanent, binary)<br />
<br />
The content of the VARS is binary, optimized for quick access to the relevant data.<br />
<br />
The general format is<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
word - offset H.323 mappings<br />
word - offset SIP mappings<br />
word - offset LDAP mappings<br />
word - offset HTTP mappings<br />
char [] - Hostname, variable length<br />
byte [] - Mappings<br />
}<br />
</syntaxhighlight><br />
;offsets: The offsets are a byte offset into the var content to where the mappings info is stored<br />
;hostname: A variable length zero terminated hostname<br />
;mappings: Start of the mappings. All mappings are on 4 bytes aligned offsets<br />
<br />
For the mappings the following structure is used:<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
IPaddr addr;<br />
word port;<br />
word ports;<br />
word nets;<br />
word flags;<br />
word next;<br />
word ext1;<br />
word ext2;<br />
word ext3;<br />
char path[];<br />
};<br />
</syntaxhighlight><br />
;addr: The destination IP address, as IPv6 address. If the destination is IPv4 the mapped IPv4 format is used (::ffff:<IPv4>)<br />
;port: The port used for TCP<br />
;ports: The port used for TLS<br />
;nets: Offset from the beginning of the mapping to a network filter list, if present. If no filter is defined 0 is set<br />
;flags: Flags defined for the mapping. The following values are used:<br />
:;MAP_CERT - 1: If set, the certificates of incoming TLS connections are checked<br />
:;MAP_APP - 2: App Login<br />
:;MAP_DEFAULT - 4: Default path for a given host<br />
;path: A zero terminated path<br />
;ext1, ext2, ext3: Fields reseverd for extensions. With these fields, the mapping header is an even 32 bytes<br />
<br />
If nets is set the following structure is used at the indicated offset:<br />
<syntaxhighlight lang="cpp"><br />
struct {<br />
word count;<br />
word spare;<br />
struct {<br />
IPaddr addr;<br />
IPaddr mask;<br />
} net;<br />
}<br />
</syntaxhighlight><br />
;count: Number of networks in the list<br />
;spare: Used for 4 bytes alignment<br />
;net: Array of addr/mask pairs for the networks, as IPv6 addresses.<br />
<br />
=== VARS used for White/Black list entries ===<br />
<br />
RPRXY/I<FNV-1a Hash of address>/<sequence><br />
;FNV-1a Hash of the address: The hash is calculated as defined in [https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function FNV-1a Hash] (32-bit)<br />
;sequence: If multiple addresses yield the same hash value, different sequence numbers are used to create uniqque names<br />
<br />
The vars create lines in the config file create these vars. Example:<br />
<br />
vars create RPRXY/IC2D83483/00000 ...<br />
<br />
The flags used for these VARS are pb (permanent, binary)<br />
<br />
The content of the VARS is binary, optimized for quick access to the relevant data.<br />
<br />
The following structure is used for the content:<br />
<br />
struct {<br />
dword addr[4];<br />
byte white;<br />
byte fill[3];<br />
time_t date;<br />
word description_len;<br />
char description[128];<br />
};<br />
<br />
;addr: The address on the list<br />
;white: True if white list entry<br />
;fill: For alignment<br />
;date: Unix timestamp of entry into list<br />
;description_len: Length of description<br />
;description: Description text without terminating zero. A maximum length of 128 characters is stored.<br />
<br />
== Known Problems ==<br />
<br />
[[:Category:Problem_Reverse_Proxy|Known Problems]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:PHP_based_Update_Server_V2&diff=69510Howto:PHP based Update Server V22023-11-06T10:22:02Z<p>Ckl: </p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
* all innovaphone devices<br />
* web server running PHP 5 (e.g. Linux Application Platform)<br />
<br />
All Versions prior to 13r1 (for 13r1 and later, see [[Reference13r1:Concept App Service Devices]] instead). <br />
<br />
By default, the [[Reference10:Concept_Update_Server | Update Manager]] mechanism reads a file that corresponds to the device type (e.g. <code>update-ip222.htm</code>). While this makes sense (update scripts may vary by device type), it is sometimes tedious, as you have to edit a huge amount of files which typically are (at least partly) identical.<br />
<br />
Here is a PHP script that can be used as an ''Update Server'' that allows you to simplify the handling of update scripts. It also includes a mechanism that makes sure that all devices always have the same firmware installed as a given ''master device'' has. Finally, it implements a straight forward configuration backup scheme.<br />
<br />
This article describes version 2 of this update server (build 2006 and up). The previous version is described in [[Howto:PHP_based_Update_Server]]. The enhancements are<br />
* Status user interface showing all known devices<br />
* Ability to roll out custom device certificates<br />
* Ability to provide configuration files to MTLS-authenticated devices only (e.g. in order to keep certain configuration settings secure)<br />
* Hide configuration files from public read access<br />
<br />
==More Information==<br />
=== Requirements ===<br />
The update server script requires a web server with working PHP 5.3 or higher platform. It has been tested with Apache and ligHTTPD (on a [[Reference10:Concept Linux Application Platform | Linux Application Platform]]). On IIS, neither the certificate roll-out nor the MTLS authentication or configuration backup works with IIS.<br />
<br />
The platform running the PHP scripts must have a valid time setting!<br />
<br />
=== Features ===<br />
The update server can<br />
* update all your devices with boot code and firmware that corresponds to the versions running on a reference device of your choice<br />
* save backups of your devices configuration. Backups are saved only if the configuration changed since the last backup<br />
* invoke your own update scripts depending on <br />
** various phases (e.g. you can have ''staging'' scripts which are only executed once and normal scripts which are executed in normal operation later on)<br />
** devices classes (e.g. you can have different scripts for phones and gateways)<br />
** environments (e.g. you can have scripts for your internal devices and others for devices in home offices)<br />
** the device serial number<br />
* roll out customer specific device certificates<br />
* roll out update scripts to devices only that have identified themselves with a valid device certificate (MTLS)<br />
* maintain a list of devices in your installation along with some vital information about each individual device<br />
<br />
=== Installation ===<br />
<br />
==== On the Linux Application Platform ====<br />
Here is how you would install it on the [[Reference10:Concept_Linux_Application_Platform|LAP]]. Some of the steps may not be necessary if you don't want to use all features.<br />
<br />
On the ''Linux Application Platform'', you would <br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files<br />
* create a new root directory underneath <code>/var/www/innovaphone/mtls</code>, e.g. <code>/var/www/innovaphone/mtls/update</code><br />
* download the complete file package of scripts and files [http://download.innovaphone.com/ice/wiki-src/#php-update-server here] <br />
* copy all files and directories in to this new directory<br />
** create your local config files. We will never overwrite these in further updates.<br />
***Rename <code>config/user-config-sample.xml</code> to <code>config/user-config.xml</code><br />
***Rename <code>scripts/all-all-all-sample.txt</code> to <code>scripts/all-all-all.txt</code><br />
* change owner and group of all files and directories to <code>www-data</code>, change mode to 0600 for all files and 0700 for all directories (see [[#Migrating_from_build_2000_an_newer | below]] for how to do this with WinSCP)<br />
<br />
* log in to the LAP's admin UI (if you have not yet changed it, the default credentials will be <code>admin/linux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]) and open its ''Administration/Web Server/Change web server properties and public access to the web/webdav'' configuration UI. Add the following paths to ''Public Web Paths'' (assuming your base directory is called <code>update</code>):<br />
** <code>/mtls/update</code><br />
** <code>/mtls/update/web</code><br />
** <code>/mtls/update/admin</code><br />
** <code>/mtls/update/fw/</code> (note the trailing slash!)<br />
: make sure that no other sub-directories of ''update'' are listed<br />
: make sure you have no trailing '/' in any of these paths (except ''fw/')<br />
: this will make sure the update server's admin user interface is not accessible to the public<br />
<br />
At this point, you should be able to access the update server's admin user interface, e.g. <code>http://update.yourcompany.com/mtls/update/admin/admin.php</code>. However, the only thing you see is a login page. Please note that your browser should ask you for a password for this page (unless you already entered it before). Cookies must be enabled for the login page to work properly. <br />
<br />
===== If you want to provide HTTPS Acess to the Update Server =====<br />
For HTTPS access to the update server, you may want to install your own server certificate under ''Administration/Certificates/Current server certificate''. However, this is not strictly required (you will experience some warning messages when using your browser to access the update server, however, calling devices will work just fine). <br />
<br />
===== If you want to setup MTLS-restricted Delivery of Update Scripts =====<br />
If you think you have sensitive information in your update scripts, you should make sure to deliver such scripts to your own verified devices only. This can be done by authenticating calling devices with ''mutual TLS'' (MTLS). In this case, the calling device must use HTTPS to retrieve the update script and present a trusted client certificate that identifies itself as the calling device (that is, has the device serial as the certificate's ''common name'' (CN)). <br />
<br />
For this feature, you need to ''Configure mutual TLS'' in the LAP's ''Administration/Web Server'' panel. Simply tick the ''Active'' check-mark and select an appropriate ''MTLS Port''. The port must not conflict with any other TCP port used on the LAP, so neither 80 nor 443 is a good choice. 444 is a good choice. Although it [http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?&page=8 is assigned to the snpp protocol] by IANA, it is not used by the LAP (and probably rarely used anyway).<br />
<br />
If MTLS is already activated on your LAP, simply take note of the currently configured ''MTLS Port''.<br />
<br />
<br/><br />
Please note that MTLS access is ''not possible'' through a ''reverse proxy'' (RP). This is because the RP always will terminate the incoming TLS connection and establish its own to the target. Therefore, the client certificate presented to the target server is the RP's certificate, not the original clients certificate. In our context - where MTLS is used to verify the identity of the original caller - clients must not access the update server through an RP.<br />
<br />
You can either expose your update server directly to the internet (and carefully think through the security implications) or create specific TCP port forwarding towards the update server in your NAT-router/firewall. In this case, we recommend to use non-standard HTTP and HTTPS ports on the NAT router (cause this will already keep most of the HTTP port scanners out there in the internet from functioning).<br />
<br />
<br/><br />
Also, you will need the public key of all the CAs you will trust. These must be configured in to the web server so it can trust the certificates your devices will present to it. See [[#Enforcing_Trust]] for details.<br />
<br />
==== On an Apache Server running on the Windows Operating System ====<br />
The update server will run on Apache too. However, as we did not test this setup thoroughly, we recommend to use the LAP's LigHTTPD instead. <br />
<br />
* For hints on using MTLS on an Apache Web Server, see [[Reference10:Concept_Provisioning#Enforcing_mutual_TLS_on_Apache | Enforcing mutual TLS on Apache]]<br />
* For hints on getting the ''innovaphone device certificate authority'' public keys (which you may or may not need), see [[Reference10:Concept_Provisioning#How_to_get_inno-dev-ca-certificate.crt | How to get inno-dev-ca-certificate.crt ]]<br />
<br />
==== On Microsoft's IIS ====<br />
We do not recommend to use IIS as <br />
* it does not support PUT easily<br />
* it is not compatible with the innovaphone device's MTLS implementation<br />
<br />
===Update existing installation===<br />
<br />
* download the [http://download.innovaphone.com/ice/wiki-src/#php-update-server new sources]<br />
* copy all files and directories to your existing installation folder (overwrite existing files)<br />
* change owner and group of all files and directories to www-data, change mode to 0600 for all files and 0700 for all directories<br />
<br />
===Configuration===<br />
<br />
If you intend to deliver your update scripts with MTLS (that is, with HTTPS and mutual certificate check)<br />
* you will need to have the full name of your ''certificate Authority'' (CA) as it is noted as ''Common Name'' (CN) in your CA's certificate<br />
* also, you will need a PEM version of your CA's public key (a ''PEM version'' of a certificate is a text file that begins with a line like <code>-----BEGIN CERTIFICATE-----</code>)<br />
<br />
Also, you need to know the ''MTLS Port'' configured in your LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts| If you want to setup MTLS-restricted Delivery of Update Scripts ]] above).<br />
<br />
All tweakable parameters are set in <code>user-config.xml</code>. You may want to use an XML-capable editor such as notepad++, netbeans or Visual-Studio for editing. if your editor supports it, you benefit from the ''document type description'' (DTD) provided in <code>full-config.dtd</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="false" debugcerts="false" debugscript="false"><br />
</config><br />
</syntaxhighlight><br />
<br />
The attributes of the <code>config</code> tag control some aspects of debugging. For now, simply set all 3 to <code>"true"</code> instead of <code>"false"</code>. Do not forget to revert them back to <code>"false"</code> when everything runs smoothly, large log files will result if not. <br />
<br />
==== Securing Access ====<br />
To control access to the update server's data, you should set a login. This can be done in the ''master'' tag by specifying both ''user'' and ''password'':<br />
<br />
<syntaxhighlight lang="html5"><br />
<master ... user="myadmin" password="mysecret"/><br />
</syntaxhighlight><br />
Default username and password are admin/password.<br />
<br />
==== Delivering Firmware and Boot Code ====<br />
At this point, you can simulate a device by requesting an update script using an URL like <code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code> in your browser (please note that this URL should not ask you for a password). Most likely, your browser will wait a little while and then say something like:<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# cannot get PBX info: cannot access http://yourmasterdevice.youdomain.tld/CMD0/box_info.xml, reading cache<br />
# cannot get cached PBX info: cannot access cache/master-info.xml - exit<br />
<br />
This is because you did not yet specify the ''master device'' which always runs the reference firmware. <br />
<br />
The idea here is that you have one innovaphone device in your system where the firmware is always manually updated. All other devices shall follow this reference firmware. The update server will deliver the firmware that runs on the master device to calling devices. For this to work, you need to set the ''info'' attribute of the [[#master|''master'']] tag and leave everything else ''as is''. <br />
<br />
Remember that you do all your local configuration changes in <code>user-config.xml</code>.<br />
<br />
As ''master'' is a first level tag, you can add it directly underneath the opening ''<config>'' tag. The only thing we need to define right now is the path to read the firmware information from your master device. Let's say your reference device has the IP address <code>172.16.0.10</code>, you end up with <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true" debugcerts="true" debugscript="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml" user="myadmin" password="mysecret"/><br />
</config><br />
</syntaxhighlight><br />
(you could use a DNS name instead of the IP address of course). <br />
The ''info'' URL is used by the update server itself only, it is never used by devices requesting an update script.<br />
<br />
When you refresh the page that simulates a device requesting an update script, the output should now change to something like<br />
<br />
...<br />
# failed to use cached data (cache not current), retrieving info online<br />
# current firmware (unknown) does not match required firmware (130178)<br />
...<br />
<br />
This is to say that your master device runs firmware 130178 and the calling device does not (actually, as the calling client is simulated by your browser, it does not transmit its current firmware to the update server so it is ''(unknown)'').<br />
<br />
In order to actually update the clients with firmware and boot code, the files must be made available to the calling client somehow. This is done by specifying an URL in the [[Reference10:Concept_Update_Server#Prot_command | prot ]] and [[Reference10:Concept_Update_Server#Boot_command | boot ]] commands sent to the calling device:<br />
<br />
...<br />
# current firmware (unknown) does not match required firmware (130178)<br />
mod cmd UP0 prot http://update.yourcompany.com/mtls/update/fw/130178/ ser 130178<br />
# current boot code (unknown) does not match required boot code (130112)<br />
mod cmd UP0 boot http://update.yourcompany.com/mtls/update/fw/130112/ ser 130112<br />
...<br />
<br />
By default, the URL generated points to a sub-directory of your update server called ''fw'': <code>http://update.yourcompany.com/mtls/update/fw/130178/</code>. Note that this default URL expects sub-directories underneath the ''fw'' directory which correspond to the firmware and boot code build number. The file structure thus would be like<br />
<br />
/var/www/innovaphone/mtls/update/fw<br />
/130178<br />
/ip232.bin<br />
/130112<br />
/boot232.bin<br />
<br />
Note that the URL ends with a slash (<code>/</code>). This instructs the calling device to append the appropriate file name itself when retrieving the file (see [[Reference10:Concept_Update_Server#Prot_command | the prot command ]] for details). <br />
<br />
However, you can also specify any other URL by setting the ''url'' attribute in the ''fwstorage'' tag of your <code>user-config.xml</code>. In this attribute, certain meta words will be replaced (see [[#fwstorage | the ''fwstorage'' tag ]] for details). The default setting for example is <code>fw/{build}/</code>.<br />
<br />
You can disable firmware and boot code updates altogether by setting the ''info'' attribute in the ''master'' tag to an empty value.<br />
<br />
==== Your own Update Script Files ====<br />
The update server will deliver update scripts to calling devices which are synthesized from a number of ''update script snippets'' which you define yourself. The idea is that many devices share parts of their configuration while other parts are different. This depends on<br />
* the device ''class'' (e.g. a phone or a gateway)<br />
: the device class is automatically derived from its model. In fact, a single device may be in multiple classes. For example, an IP232 is in these classes: ''phone, pre_opus_phone, phone_newui'' which basically says that its a phone and has no OPUS codec yet but a "new" user interface (as opposed to the old one found e.g. on an IP110). A number of useful classes are pre-defined, but you can add your own in <code>user-config.xml</code>.<br />
* the device's environment (e.g. ''home-office'', ''branch-office'')<br />
: These reflect the fact that devices may need different configuration if they are in different locations (or environments). The update server does not know about a device's environment, which is why you need to configure it in to the devices update URL if you need this. By default, there is a single environment defined called ''default'', but of course, you can add your own<br />
* phase<br />
: configuring devices may need multiple phases to go through. If more than one phase is defined, the device will go through all phases sequentially, which is why a ''phase'' has a numerical ''seq'' attribute. Phases are went through in order of this attribute. When the device has reached the last phase, it will stay there. By default, there is only one phase called ''update''<br />
<br />
You can define update script snippets for any combination of device, environment and phase. You do so simply by placing appropriate files in to the ''scripts'' directory on your update server.<br />
<br />
Let's have a closer look at the web page we used to simulate a device calling for an update script before (<code>http://update.yourcompany.com/mtls/update/update.php?type=IP232&sn=00-90-33-00-00-00&hwid=IP232-00-00-00&ip=1.2.3.4</code>). It will show you all the possible files it would deliver to the device:<br />
<br />
# possible files (from scripts):<br />
# scripts/update-all-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-all-default.txt does not exist<br />
# scripts/update-phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone-default.txt does not exist<br />
# scripts/update-pre_opus_phone-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-pre_opus_phone-default.txt does not exist<br />
# scripts/update-phone_newui-00_90_33_00_00_00.txt does not exist<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
The update script snippet files need to have proper names to define when they should be delivered. As you can see, they all start with <code>update-</code> which means that they apply to devices which are in the ''update'' phase. As by default there is only a single phase defined, all possible file names start with <code>update-</code>. <br />
<br />
The next part here is either <code>phone-</code>, <code>pre_opus_phone-</code>, <code>phone_newui-</code> or <code>all-</code>. This is because the calling IP232 is in three classes, so all respective snippets will be delivered to it. ''all'' however is a wild-card. That is, such files will be delivered to all devices, no matter what class they are in. You may wonder why there was no ''all'' for the phase. This is because there is only a single phase defined. If there would be more, the ''all''-variation of the phase-part of the file name would be appear.<br />
<br />
The third part finally is either <code>default</code> or <code>00_90_33_00_00_00</code> in our case. ''default'' is the name of the only ''environment'' that is defined by default. ''00_90_33_00_00_00'' is the device's serial number which is treated as an implicitly defined environment name. This allows you to deliver certain snippets to a single device only.<br />
<br />
Now let's create an update script snippet that is delivered to all phones in the default environment when they are in the update phase. The file name (which looks like ''phase''<code>-</code>''class''<code>-</code>''environment''<code>.txt</code>) has to be <code>update-phone-default.txt</code> therefore. Just for an exercise, let us set the device name (as shown in the browser's title bar) to ''This is a Phone!''.<br />
The command to do so is <code>config add CMD0 /name This+is+a+Phone%21</code>, so your file may look like this<br />
<br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
(btw: did you observe that config line arguments need to be url-encoded?). Now create the file and upload it in to the ''scripts'' directory of you update server. When you now refresh the page which simulates the device calling for an update script, you will see something like this:<br />
<br />
...<br />
# newest script (scripts/update-phone-default.txt) 11s old<br />
# files being updated (scripts/update-phone-default.txt 11s old, waiting for at least 90s to expire)<br />
<br />
When you update multiple script snippets, it is often undesirable that a device retrieves an inconsistent state of the scripts you are working on (e.g. if you already have saved one but not yet the other). To avoid this, the update server will look at the files modification dates and if one of those that needs to be delivered to the device is younger than 90 seconds, it will not send any of them at all. <br />
<br />
So when you wait for the 90 seconds to pass and refresh the page again, you will see something like this:<br />
<br />
# firmware build info cache is current<br />
# phase: update, nextphase: , environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
...<br />
# scripts/update-phone-default.txt 4.1 mins<br />
...<br />
# scripts/update-phone-default.txt<br />
# { begin script 'scripts/update-phone-default.txt' <br />
# set the device name<br />
config add CMD0 /name This+is+a+Phone%21<br />
<br />
<br />
# end script 'scripts/update-phone-default.txt' }<br />
<br />
# trigger reset if required<br />
config write<br />
config activate<br />
iresetn<br />
<br />
As you can see, your snippet has been delivered by the update script. However, the update server has also added some trailing commands which write back the config to the devices flash memory (<code>config write</code>), activate it (<code>config activate</code>) and trigger a reset if needed (<code>iresetn</code>).<br />
<br />
If there are multiple snippets available for a calling device, all of them are concatenated in the sequence shown in the header of the returned script (where the possible file names are listed)<br />
<br />
==== Device Status ====<br />
When you have a lot of devices which are served by the update server, you may want to have a list of these devices along with some useful information regarding each devices. <br />
<br />
You can enable this by defining the ''status'' tag in your <code>user-config.xml</code>. So open the file and add the tag right next to the ''master'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
/><br />
</config><br />
</syntaxhighlight><br />
<br />
When you have done so, please refresh the page that simulates your calling device and then open <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>. You will now see a device list (well, a list with a single device, the one you simulated using your browser). The list will show some information regarding the devices that requested an update script lately. For more options, see [[#status]].<br />
<br />
==== Device Backup ====<br />
It is generally useful to have recent backups of all of your device configurations. The update server supports that if you enable it by defining the ''backup'' tag in your <code>user-config.xml</code>. Simply put it right next to the ''master'' or ''status'' tag you added before:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<backup<br />
dir="backup"<br />
/><br />
</syntaxhighlight><br />
<br />
If you have done so and then refresh the page that simulates your calling device, you will now see <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
mod cmd UP0 scfg http://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m<br />
<br />
instead of <br />
<br />
...<br />
# scripts/update-phone_newui-default.txt does not exist<br />
<br />
# Backups not enabled in config<br />
<br />
When a client requests an update script the next time, this will initiate a configuration backup which is stored underneath the ''backup'' directory. For each device, a sub-directory is created and all backup files are stored therein. By default, the last 10 differing configuration backups are kept.<br />
<br />
==== Controlling the Time-frames when Snippets are delivered ====<br />
The update client built-in to innovaphone devices allows to restrict updates to certain time frames (e.g. only during the night). This can be controlled using the [[Reference10:Concept_Update_Server#Times_command | times ]] command.<br />
<br />
You can configure the update server to use the time commands by setting the ''allow'' and ''initial'' attributes in the ''times'' tag in your <code>user-config.xml</code>. <br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"/><br />
</syntaxhighlight><br />
<br />
If either the ''allow'' or ''initial'' attribute is present, the update script will contain a line like<br />
<br />
...<br />
# Restricted times<br />
mod cmd UP1 times /allow 22,23,0,1,2,3,4 /initial 1<br />
<br />
In the example above, all update script activity will occur only between 10pm and 5am (local device time). For more details, see [[Reference10:Concept_Update_Server#Times_command | Concept Update Server]]<br />
<br />
==== Using and Enforcing HTTPS ====<br />
Your devices can either use HTTP or HTTPS to access the update server. In normal operation, the update server will generate URLs (e.g. the URLs used to retrieve firmware or to backup configurations) for the same protocol. <br />
<br />
However, you can enforce use of HTTPS by setting the ''forcehttps'' attribute in the [[Howto:PHP_based_Update_Server_V2#times | ''times'' ]] tag to <code>true</code>. If so, a device calling in with HTTP will be re-configured to use HTTPS:<br />
<br />
...<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
<br />
# changed state query args: polling, phase<br />
# new url=https://update.yourcompany.com/mtls/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i<br />
...<br />
(note that if you are using a non-standard port for HTTPS, you must define it using the ''httpsport'' attribute).<br />
<br />
==== Delivering Custom Certificates ====<br />
innovaphone devices come with pre-defined, trustworthy device certificates. However, all ''soft'' devices (such as the softwarephone, myPBX for Android/iOS or the IPVA) do not. Also, in many scenarios customers run their own ''public key infrastructure'' (PKI) and request their own certificates to be used instead of the pre-defined. This is why there is a need to roll-out custom certificates to devices. <br />
<br />
The update server supports this task to an extend. It can<br />
* check if a calling device has a proper certificate<br />
* instruct a calling device without proper certificate to create a ''certificate signing request'' (CSR)<br />
* download the CSR to the update server for easy access by the administrator<br />
* upload a signed CSR to the device<br />
* upload the public key(s) of the CA in to the device's trust list<br />
<br />
In order to roll-out custom certificates with the update server, the administrator needs to<br />
* run his own PKI (a.k.a. ''certificate authority'' (CA))<br />
* monitor CSRs that appear in the update server's device list<br />
* approve the request by manually submitting it to his own CA<br />
* upload the signed CSR to the update server<br />
<br />
Also, all devices must run ''V12r1 SR8'' or newer before the new certificate can be uploaded. Devices with older firmware will be updated automatically (see [[#Delivering_Firmware_and_Boot_Code]] above). However, the new firmware must be ''V12r1 SR8'' or later. <br />
<br />
To learn how you can create proper device certificates with your own windows CA, see [[Howto:Creating custom Certificates using a Windows Certificate Authority]].<br />
<br />
By default, custom certificates are turned off and you will see a note like<br />
<br />
# certificates: either certificate handling or state tracking not enabled - not doing any certificate checking<br />
<br />
in the delivered update script.<br />
<br />
Custom certificates are turned on by adding a ''customcerts'' tag to your <code>user-config.xml</code>:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
The the page that simulates your calling device will now include a line saying:<br />
<br />
# certificates: we dont know anything about the current certificate state - not doing any certificate checking <br />
<br />
In order to decide if or if not a calling device has a valid certificate, the device needs to send its current public key to the update server. This is done by enabling ''queries'' in your <code>user-config.xml</code>. ''Queries'' is a means to have the device submit certain information to the update server. In the update server's default configuration, two queries are defined but not enabled:<br />
<br />
* the query ''certificates'' sends the current certificate state <br />
* the query ''admin'' sends the current device name (as set in ''General/Admin'')<br />
<br />
To enable a query, you must specify the class a calling device must be in in order to execute the query. This is done by adding an ''applies'' tag for the respective query in your configuration:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
</query><br />
</queries><br />
...<br />
</syntaxhighlight><br />
<br />
This basically says that both queries should be executed by devices of just any class. Unless you enable queries, your update script will include lines such as:<br />
<br />
# no queries defined for any of callers classes: phone+pre_opus_phone+phone_newui<br />
<br />
Once you have enabled them, you will see something like<br />
<br />
...<br />
# query 'certificates'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=certificates ser nop /always mod%20cmd%20X509%20xml-info<br />
# query 'admin'<br />
mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=query&sn=#m&id=admin ser nop /always mod%20cmd%20CMD0%20xml-info<br />
...<br />
<br />
You can display the data sent by a query in the device status display. To do so, you need to define one or more ''show'' tags as part of the respective ''query'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<query id="certificates"><br />
<applies>*</applies><br />
<!-- show device certificate CNs and Issuer CNs in status page --><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
...<br />
</syntaxhighlight><br />
<br />
Two steps however are still missing: <br />
<br />
* a) you need to tell the update server the names of all the CAs you consider trustworthy. This is done by listing their names in the ''CAname'' attribute of the ''customcerts'' tag:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2,innovaphone-INNO-DC-W2K8-Zertifizierungsserver"<br />
/><br />
...<br />
</syntaxhighlight><br />
<br />
: The example shown defines that you will accept both of innovaphone's device certificate authorities and also your own CA called ''innovaphone-INNO-DC-W2K8-Zertifizierungsserver''. Devices with device certificates issued by one of these CAs will be left untouched. For all other devices (for example, any ''myPBX for Android/iOS'' device that has a self-signed certificate), the generation of a custom certificate will be initiated.<br />
<br />
* And b) you need to provide the public key of your CA (so it can be uploaded to the calling device's trust list). You need to place the DER (not PEM) encoded public key in to a file called <code>certs/CAkey-01.cer</code> on your update server (if you want to upload the whole certificate chain, put all of the public keys in the chain in to separate additional files called <code>certs/CAkey-02.cer</code> and so forth.<br />
<br />
For more details on how to approve certificate signing requests, see [[#Approving Certificate Signing Requests]] below.<br />
<br />
==== Enforcing Trust ====<br />
Update script snippets may include sensitive information which you do not want to disclose to the public. Even though you can force the use of HTTPS (see above), this still does not keep your data secure. After all, an attacker could simply request an update script from your update server using HTTPS. To secure your data, you need to make sure that snippets are only sent to devices which identify themselves using a trusted certificate with a correct ''common name'' (CN). This is done using ''mutual transport layer security'' (MTLS). Therefore, you need to configure MTLS in the LAP (see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts | If you want to setup MTLS-restricted Delivery of Update Scripts]] above). <br />
<br />
You can enable this by setting ''forcetrust'' to <code>true</code> and ''httpsport'' to the port configured as ''MTLS Port'' in your web server. This is done in the ''times'' tag of your <code>user-config.xml</code>.<br />
<syntaxhighlight lang="html5"><br />
<times <br />
...<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
</syntaxhighlight><br />
Please note that ''forcetrust'' does nothing unless ''forcehttps'' is also set!<br />
<br />
If so, the update server will deliver update script snippets only to clients which identify themselves with a proper certificate. For this to work, you will need to add the public key of your certificate authority to your web server's list of trusted certificates. <br />
<br />
When using the ''Linux Application Platform'' (LAP), you need to add the PEM-encoded public key of your certificate authority to the ''innovaphone-ca.pem'' file in <code>/home/root/ssl_cert</code>. When you have installed the LAP, this file will contain the PEM-encoded public keys of the 2 innovaphone device certificate authorities. You can simply add the public key of your CA to the end of this file:<br />
<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...inno-ca2 public key...<br />
-----END CERTIFICATE-----<br />
-----BEGIN CERTIFICATE-----<br />
...your-ca public key...<br />
-----END CERTIFICATE-----<br />
<br />
You will need to restart the LigHTTPD then (most easily done by restarting the entire LAP (''Diagnose/Reset'') or by issuing the command <code>/etc/rc2.d/S02lighttpd restart</code> from the linux root command prompt). Finally, you must set the ''forcetrust'' attribute. <br />
(Note that the ''innovaphone-ca.pem'' file may be overwritten when a new LAP version is installed. It is thus a good idea to keep a copy and check it after an upgrade).<br />
<br />
When ''forcetrust'' is effective and the calling device does not use HTTPS, it will be reconfigured to do so:<br />
<br />
# using HTTP and 'forcehttps' is set -> need to switch to HTTPS<br />
...<br />
# new url=https://update.yourcompany.com:444/update/update.php?polling=5&phase=&type=#t&sn=#m&hwid=#h&ip=#i&env=default<br />
<br />
If the calling device uses HTTPS but sends no certificate, you will see a message like<br />
<br />
# cannot verify CN with HTTPS: SSL_CLIENT_S_DN_CN not present - fix web server configuration or use MTLS-enabled port!<br />
<br />
This is probably because it is using a non-MTLS enabled port for HTTPS (e.g. the standard port 443) although MTLS is configured for a different port. <br />
<br />
If the calling device uses HTTPS and sends a certificate but the CN does not match the serial number of the device, you will see a message such as<br />
<br />
# Device claims to be 'IP232-30-00-af' but identifies as 'CKL-CELSIUS-W10.innovaphone.sifi' by TLS<br />
<br />
If the calling device uses HTTPS and sends a certificate but the certificate is not trusted because its issuer is not listed in ''innovaphone-ca.pem'' (see above), the connection will be refused <br />
<br />
In all such cases, no snippet will be delivered. If the certificate is good, you will see a message like <br />
<br />
# good certificate(CN='IP232-30-00-af')<br />
<br />
followed by the appropriate snippets.<br />
<br />
==== Using multiple Phases ====<br />
When you configure multiple phases, all phases up to the final phase are stepped through and when all associated update script snippets for all phases are done, the device will ultimately stay in the final phase. This can be used e.g. to implement update script snippets which are executed once only when the device is initialized. Settings made in all but the last phase can later be overridden by the user or an administrator. The settings done in the update script settings for the final phase however are re-executed whenever one of your update script files for this phase changes.<br />
The process of deploying some initial settings is often called ''staging''.<br />
<br />
To enable staging, you therefore create a new phase that comes before the default phase (which is ''update''). Phases are sorted numerical by an attribute called ''seq''. As the default phase ''update'' is defined with ''seq=200'', your new staging phase must be defined with a lower seq value:<br />
<br />
<syntaxhighlight lang="html5"><br />
...<br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
...<br />
</syntaxhighlight><br />
<br />
When you define such a phase, there will be new possible update script snippet file names: <br />
<br />
# phase: staging, nextphase: update, environment: default, type: IP232, classes: phone+pre_opus_phone+phone_newui<br />
# possible files (from scripts):<br />
# scripts/all-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-all-default.txt does not exist<br />
# scripts/all-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone-default.txt does not exist<br />
# scripts/all-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-pre_opus_phone-default.txt does not exist<br />
# scripts/all-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/all-phone_newui-default.txt does not exist<br />
# scripts/staging-all-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-all-default.txt does not exist<br />
# scripts/staging-phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone-default.txt does not exist<br />
# scripts/staging-pre_opus_phone-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-pre_opus_phone-default.txt does not exist<br />
# scripts/staging-phone_newui-00_90_33_30_00_af.txt does not exist<br />
# scripts/staging-phone_newui-default.txt does not exist<br />
<br />
First of all, there are new names for this particular phase. Furthermore, as we now have multiple phases, the new wild-card name ''all'' is possible. <br />
<br />
An example for a staging script snippet might be a file called <code>staging-all-all.txt</code>: <br />
<br />
# change admin password<br />
config add CMD0 /user admin,my-admin-password<br />
config activate<br />
config rem CMD0 /user<br />
<br />
This will set the admin password to <code>my-admin-password</code> during staging (it should be obvious that such staging should only be done in combination with [[#Enforcing_Trust|Enforcing Trust]], see above).<br />
<br />
=== A complete <code>user-config.xml</code> Sample ===<br />
Here is a complete sample of a configuration file. <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<!DOCTYPE config SYSTEM "full-config.dtd"><br />
<!-- add your local configuration here --><br />
<config debugmerge="true"><br />
<master info="http://172.16.0.10/CMD0/box_info.xml"/><br />
<status<br />
dir="status"<br />
missing="1000"<br />
/><br />
<backup<br />
dir="backup"<br />
/><br />
<times <br />
allow="22,23,0,1,2,3,4" <br />
initial="1"<br />
forcehttps="true"<br />
httpsport="444"<br />
forcetrust="true"<br />
/><br />
<customcerts<br />
dir="certs"<br />
CAname="innovaphone Device Certification Authority,innovaphone Device Certification Authority 2"<br />
CSRsan-dns-1="{name}.company.com"<br />
CSRsan-dns-2="{hwid}.company.com"<br />
CSRsan-dns-3="{rdns}"<br />
CSRsan-ip-1="{realip}"<br />
/><br />
<phases><br />
<phase id="staging" seq="100"/><br />
</phases><br />
<environments><br />
<environment id="intranet"/><br />
<environment id='sifi'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='berlin'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='verona'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='homeoffice'/><br />
<environment id='mobile'/><br />
</environments<br />
<queries><br />
<query id="admin"><br />
<applies>*</applies><br />
</query><br />
<query id="certificates"><br />
<applies>*</applies><br />
<show title="Subject">/state/queries/certificates/info/servercert/certificate/@subject_cn</show><br />
<show title="Issuer">/state/queries/certificates/info/servercert/certificate/@issuer_cn</show><br />
</query><br />
</queries><br />
</config><br />
</syntaxhighlight><br />
<br />
=== Operation ===<br />
<br />
==== Configuring and Debugging a real Device ====<br />
To configure your device for use with the update server, you simply set <code>http://update.yourcompany.com/mtls/update/update.php</code> as ''Command File URL'' in ''Services/Update'' (you can optionally add a <code>?env=envname1,envname2</code> query argument if you want to set one or more specific environments for your device). The update server will re-configure the device later on so that it uses all the required query arguments.<br />
<br />
Generally, there are the following methods to set the ''Command File URL''<br />
* configure it manually using the admin GUI<br />
* provide it to the device using DHCP (this will however not work for the softwarephone or ''myPBX for Android/iOS'')<br />
* use the innovaphone provisioning service (see [[Reference10:Concept_Provisioning|Reference10:Concept Provisioning]] for details)<br />
<br />
Once the ''Command File URL'' is set on the device, you can debug what the update client in the device does, using the normal trace mechanism. For this, you will want to open the ''Debug'' page (<code>http://x.x.x.x/debug.xml</code>) and set the ''Update/Polling'' and ''Update/Execution'' check-marks. When the update client queries the update server, you will see lines like <br />
<br />
0:0826:465:5 - upd_poll: state IDLE -> RECV<br />
0:0826:465:7 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:466:0 - IP.0 -> UPD-POLL.0 : SOCKET_GET_LOCAL_ADDR_RESULT(172.16.100.201,255.255.0.0,0,'',ANY)<br />
0:0826:695:0 - upd_poll: state=RECV sent()<br />
0:0826:752:0 - upd_poll: status 200 headercomplete=1 contentlength=0<br />
0:0826:754:0 - upd_poll: recv_data(2199)<br />
0:0826:754:1 - upd_poll: recv_data(0) EOF<br />
0:0826:754:1 - upd_poll: GET EOF - state=RECV http-status=200 length=2199<br />
0:0826:754:1 - upd_poll: do commands<br />
0:0826:754:1 - upd_poll: state RECV -> EXEC<br />
<br />
in the trace. Commands included in the update script will look like<br />
<br />
0:0826:754:5 - script::get_line: >line(mod cmd UP0 scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m)<br />
0:0826:754:5 - upd_poll: pass 'mod cmd UP0 /sync scfg https://update.yourcompany.com/mtls/update/update.php?mode=backup&hwid=#h&sn=#m')<br />
<br />
Finally, you do not need to wait for the next time the device decides to contact the update server. Instead, you can force this by sending an <code>http://</code>''your-device-ip<code>/!mod cmd UP1 poll</code> to the device.<br />
<br />
==== The Device Status Update Page ====<br />
If status tracking is enabled (see [[#Device_Status | Device Status]] above), the update server will keep a list of devices it knows of. You can see this list by calling <code>http://update.yourcompany.com/mtls/update/admin/admin.php?mode=status</code>.<br />
<br />
By default, the list will not be updated unless you refresh the page. However, if you set the ''refresh'' attribute in the ''status'' tag in your ''user-config.xml'' file to <code>60</code>, all entries in this list will be refreshed every 60 seconds (however, the list itself will not be reloaded, so to see new devices, your need to refresh the entire page). Devices that have not been seen for more than 7 days are shown in a different colour. Devices that have not been seen for longer than 90 days will be silently removed from the list.<br />
<br />
===== Filtering =====<br />
From build 2011, you can filter the devices shown using the ''device filter'' field in the ''Device'' column header. The filter string is matched against ip-address, serial number, type, name, phase, class, environment, firmware and bootcode. Also, you can filter by using the keywords ''present'' and ''missing'' (based on the ''missing'' attribute of the ''status'' tag in your configuration file). If one of these attributes match your filter expression, the device is shown.<br />
<br />
A filter expression must match a complete ''word''. For example, if you filter by <code>16</code> this would match the ip-address <code>172.</code>16<code>.0.20</code> but it would not match <code>192.</code>16<code>8.0.1</code>.<br />
<br />
If you specify multiple filter expressions (separated by white space), only devices which match all of the expressions will be shown. For example, if you filter by <code>172.16. ip222</code> this might match all IP222 in your 172.16.*.* network. Filter expressions are case insensitive.<br />
<br />
==== Approving Certificate Signing Requests ====<br />
When you use custom certificate roll-out, you will see a column ''Certificates'' in the device status list, showing the devices certificate status. <br />
<br />
[[Image:PHP_based_Update_Server_V2_Device_Status.png]]<br />
<br />
If a CSR has been created on the device, this will be available for download in the ''Files'' section of the ''Info'' column. You can submit the CSR to your CA and then upload the signed request (using the ''Upload'' button in the ''Files'' section of the ''Info'' column). The signed request will eventually be uploaded to the device then. On the device itself, the CSR is shown in ''General/Certificates''.<br />
<br />
[[Image:PHP_based_Update_Server_V2_CSR.png]]<br />
<br />
<br />
==== Certificate Errors ====<br />
When a signed certificate request uploaded to the device can not be installed on the device, you will see a message like <code>Certificate upload error - certificate handling stopped</code> in the ''Certificates'' columns for the device. In this case, any further processing will be stopped. Most likely, the reason is that you uploaded the wrong file as signed certificate request (either not a signed certificate at all or a signed request for another device).<br />
<br />
In this case<br />
* remove any certificate request from the device<br />
* remove the <code>X509/REQUESTERROR</code> from the device configuration (i.e. <code>vars del X509/REQUESTERROR</code>)<br />
: these 2 steps can be easily done by resetting the device to factory defaults and then set the ''Update URL'' again<br />
* remove any stored files for the device on the update server (shown in the ''Certificates'' column)<br />
<br />
The normal process will start all over again then.<br />
<br />
=== Migrating old PHP Update Server Installations ===<br />
==== Migrating from build 2000 and newer ====<br />
* Copy all files and folders, '''except''' the ''scripts'' and ''config'' folder, from the source to your destination<br />
* make sure all files and directories have correct owner (''www-data''), group (''www-data'') and mode (''read''/''write'' plus ''execute'' for directories)<br />
: for example, in WinSCP you can use the ''Properties (F9)'' dialogue on the installations root folder:<br />
: [[Image:PHP based Update Server V2-WinSCP-Properties.png]]<br />
* open the ''StatusPage'' and make sure you refresh all cached files in your browser (depending on your browser, this may happen with Ctrl-R or Ctrl-F5)<br />
<br />
==== Migrating from build 1011 and older ====<br />
To upgrade from the [http://download.innovaphone.com/ice/wiki-src/index.php?urloffset=php-update-server%2F&name=php-update-server+%28all+available+builds%29&reverselevel=0&maxbuilds=999&root=c%3A%2Finetpub%2Fwwwroot%2Fdownload%2Fice%2Fdownload%2Fp%2Fwiki-src%2Fphp-update-server%2F1010+%28PHP+based+Update+Server+final%29%2F.. old version (builds up to 1011)] of the PHP based update server (as described in [[Howto:PHP based Update Server]]), do the following<br />
<br />
* diff your <code>config.xml</code> to the one originally shipped (you can download it at [http://download.innovaphone.com/ice/download/p/wiki-src/php-update-server/1011%20(PHP%20based%20Update%20Server%20final)/php-update-server-1011.zip download.innovaphone.com/ice/wiki-src])<br />
: take note of all the changes you made to it<br />
* install the new version of the update server to a different URL<br />
* configure it according to your needs by modifying <code>user-config.xml</code>. Do not modify the new <code>config.xml</code>!<br />
** apply all modifications you did to your old <code>config.xml</code> to your new <code>user-config.xml</code><br />
** if you have used a custom setting for fwstorage/@url, you need to change it a bit. Change for example <code>url="http://myfwserver.mycompany.com</code> to <code>http://myfwserver.mycompany.com/{build}/</code> (note the trailing slash!)<br />
* copy all your update script snippet files from the old server (these are all the .txt files in the old server's root directory) to the <code>scripts</code> directory of your new server<br />
* copy the complete <code>fw</code> tree from the old server to the <code>fw</code> directory of your new server<br />
* thoroughly test your new installation with some test devices<br />
* when satisfied, edit <code>update-migrate.php</code> and change the code as described in the comment inside the file<br />
* copy <code>update-migrate.php</code> from the new installation to the old installation<br />
* on one of the devices which are currently served by the old update server, change the ''Command File URL'' so that it points to <code>update-migrate.php</code> instead of <code>update.php</code>. Do not change anything else in the URL. So if it currently is set to e.g. <br />
: <code>https://172.16.0.90/update/update.php?type=#t&env=sifi&polling=0&phase=update</code>, change it to<br />
: <code>https://172.16.0.90/update/update-migrate.php?type=#t&env=sifi&polling=0&phase=update</code><br />
* verify that this device properly switches to your new installation<br />
** the ''Command File URL'' is changed so that it points to the new installation<br />
** the device shows up in the device status page of your new server<br />
<br />
* rename <code>update.php</code> to <code>update-old.php</code> in your old update server<br />
* rename <code>update-migrate.php</code> to <code>update.php</code> in your old update server<br />
* verify that all of your devices start showing up in the new server's status page<br />
<br />
=== Complete Parameter Reference ===<br />
Note: attributes are marked with an ''at'' (<code>@</code>) prefix. So ''master/@info'' refers to the ''info'' attribute in the ''master'' tag.<br />
<br />
The following tags and attributes are available in the <code>user-config.xml</code> file, their default values are configured in <code>config.xml</code> (which you should not modify):<br />
<br />
==== master ====<br />
Defines the reference device where the firmware and boot code information is taken from.<br />
; master/@info : the URL to the device info data. Set it to an URL like <code>http://</code>''your-reference-device''<code>/CMD0/box_info.xml</code>. Please note that this device must not have the ''Password protect all HTTP pages'' set in ''Services/HTTP/Server''. If you use the literal <code>none</code>, the master device will not be queried<br />
; master/@expire : the firmware info cache lifetime. The firmware information is cached in a file (to make sure this information is present even if the reference device is off-line). The cache will not be refreshed before the ''expire'' time (in seconds) is expired<br />
; master/@cache : the cache file name. The web server must be able to write this file (see [[#Installation | ''Installation'' ]] above)<br />
; master/@user : if non-empty, this is the user name required to access the update server's web ui<br />
; master/@password : the password<br />
<br />
From build 2009, you can control the firmware and bootcode version to use on update:<br />
; master/@firmware : if set, it may be one of <br />
:: <code>master</code> (default if not set) : the firmware version is derived from the master device<br />
:: <code>none</code> : the firmware update is generally disabled<br />
:: ''build-number'' : the firmware is set to a certain ''build-number'' regardless of the firmware running on the master device<br />
; master/@bootcode: if set, it may be one of <br />
:: <code>master</code> (default if not set) : the bootcode version is derived from the master device<br />
:: <code>firmware</code> : the bootcode version is set to the firmware version. This may be useful if the master device has no bootcode (e.g. the ipva)<br />
:: <code>none</code> : the bootcode update is generally disabled<br />
:: ''build-number'' : the bootcode is set to a certain ''build-number'' regardless of the bootcode running on the master device<br />
<br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"<br />
/><br />
</syntaxhighlight><br />
<br />
==== master/applies ====<br />
Available from build 2009.<br />
<br />
Boot code and firmware updates are only executed by devices which match all of the given <applies> conditions. A condition is met if the device belongs to the class that is given as tag content. If the ''env'' attribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; master/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in <br />
<syntaxhighlight lang="html5"><br />
<master <br />
info="http://172.16.0.10/CMD0/box_info.xml"<br />
expire="3600"<br />
cache="cache/master-info.xml"<br />
user="myadmin"<br />
password="mysecret"<br />
firmware="4711"<br />
bootcode="firmware"><br />
<!-- applied to phones in berlin only --><br />
<applies>phone</applies><br />
<applies env="">berlin</applies><br />
</master><br />
</syntaxhighlight><br />
<br />
==== fwstorage ====<br />
Defines from where the device will retrieve the firmware files for updates.<br />
; fwstorage/@url : defines the URL to retrieve the files from. In this URL, the following meta words will be replaced as follows:<br />
:* <code>{build}</code> - the requested firmware or boot-code <br />
:* <code>{model}</code> - the devices type (e.g. <code>IP232</code>). This is derived from the ''type'' query argument used in the update URL which is set to the value <code>#t</code> which in turn is expanded to the [[Reference10:Concept_Update_Server#Scfg_command|''device type'']] of the device requesting the update script<br />
:* <code>{filetype}</code> - the type of the required file, either <code>boot</code> or <code>prot</code><br />
:* <code>{filename}</code> - (from build 2014) the boot code or firmware filename for the device in lower case. Required if you use the LAP as firmware storage and firmware files are falsely requested in uppercase letters (as URLs on the LAP are case sensitive and if the files are requested with upper case names, the original files with lowercase name are not found). This also allows you to use alternate firmware file names (such as e.g. <code>ip110-sip.bin</code>). See the ''filenames'' tag<br />
Note that if the ''url'' ends with a slash (<code>/</code>), the calling device will automatically [[Reference10:Concept_Update_Server#Prot_command|append the name of the requested file]]. In this case, the file system the URL points to must therefore contain sub-directories for each firmware build which contain the corresponding firmware builds (e.g, ''fw/12345/ip232.bin''). If ''url'' is not an URL (as is the case for the default value <code>fw/{build}/</code>), it is treated as a sub-directory underneath the update server's root directory<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="fw/{build}/"/><br />
</syntaxhighlight><br />
<br />
===== Loading firmware files from innovaphone.com =====<br />
You can also load firmware from the innovaphone.com web site. To do so, set ''url'' like so:<br />
<br />
<syntaxhighlight lang="html5"><br />
<fwstorage url="http://webbuild.innovaphone.com/{build}/"/><br />
</syntaxhighlight><br />
<br />
Please note that ''this is a voluntary service, no guarantee of availability, no service level agreement''.<br />
<br />
[[User:Ckl|ckl]] 15:40, 21 February 2023 (CET) This service has been discontinued. You may consider using <code>https://store.innovaphone.com/release/download/{build}/</code> instead.<br />
<br />
==== backup ====<br />
Defines where backup files are kept.<br />
; backup/@dir : the directory underneath the script directory where backups are stored<br />
; backup/@nbackups : the number of different backup files kept<br />
; backup/@scfg : the target URL for the scfg command. If this is not an url, it is interpreted relative to the script directory URL. You can add more options for the ''scfg'' command, like in <code>scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m ser hourly /force 1"</code> (this example will make sure backups are attempted only once per hour, so as to reduce load on the server).<br />
<br />
<syntaxhighlight lang="html5"><br />
<backup <br />
dir="backup"<br />
nbackups="10"<br />
scfg="update.php?mode=backup&amp;hwid=#h&amp;sn=#m"<br />
/><br />
</syntaxhighlight><br />
<br />
==== status ====<br />
Defines details regarding the state kept for a device requesting an update script.<br />
; status/@dir : the directory the status files are kept in (e.g. <code>status</code>). Used as the name for a sub-directory underneath your install directory on your web server. You must make sure that files in this directory cannot be read by anyone without proper authentication, as such files may contain sensitive information.<br />
; status/@expire : if set and not empty, devices which have not requested an update script will be removed from the inventory after ''n'' seconds. For example, ''expire="7776000"'' will forget about devices after 90 days with no contact<br />
; status/@missing : devices are shown in a different colour (indicating that they are ''missing'') after ''n'' seconds. For example, ''missing="604800"'' will flag devices as missing after 7 days with no contact<br />
; status/@refresh : if set and not empty and larger than zero, the admin user interface showing the known devices will refresh the status of all shown devices each ''n'' seconds<br />
; status/@logkeep : number of seconds log messages for individual devices will be kept<br />
; status/usehttpsdevlinks : if set to true, links to devices in the status page are created using the https:// scheme<br />
<syntaxhighlight lang="html5"><br />
<status <br />
dir="status" <br />
expire="7776000" <br />
missing="200" <br />
refresh="10"<br />
logkeep="90"<br />
usehttpsdevlinks="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== times ====<br />
Defines details regarding the delivery of update scripts to requesting devices. <br />
<br />
; times/@dir : the directory the update scripts are stored in. For security reasons, you may want to make sure that files in this directory cannot be read without proper authentication.<br />
<br />
These attributes define the arguments of the [[Reference10:Concept_Update_Server#Times_command | times command]]. If neither ''allow'' nor ''initial'' is set, no ''times'' command is used (that is, the update scripts will be processed at all times).<br />
; times/@allow : the hours to be used in the ''times'' command (as in <code>mod cmd UP1 times /allow </code>''hours'')<br />
; times/@initial <br />
: the minutes to be used in the ''times'' command (as in <code>mod cmd UP1 times /initial </code>''minutes''). If you want to use initial staging of virgin devices ''forcestaging'' must be <code>true</code>.<br />
<br />
Delivered update scripts can include a [[Reference10:Concept_Update_Server#Check_command | check command ]] if the ''check'' attribute is set to <code>true</code>. . In this case, the devices will executed the scripts again only when you have edited/changed them.<br />
; times/@check : if set to true, update scripts will be executed once only (unless their contents change)<br />
<br />
When editing a number of update scripts, it is often not desirable if one changed script is already executed before another is changed too. When the ''grace'' attribute is set to a positive value, no script at all is delivered to requesting devices unless ''n'' seconds have expired since the last edit. For example, setting ''grace'' to 90 gives you one and a half minute to finish all your edits. <br />
<br />
; times/@grace : defines the number of seconds that must expire before update script changes take effect<br />
<br />
The update server works in either ''fast'' or ''normal'' mode. In ''fast'' mode, update scripts have to be requested more frequently, for example to step through multiple staging phases faster. This is enforced by modifying the calling device's ''Interval'' setting in [[Reference11r1:Services/Update | ''Services/Update '']] and setting it to 1 in ''fast'' mode.<br />
<br />
; times/@interval : polling interval in minutes for normal operation (that is, when all staging is done)<br />
; times/@polling : when you are using multiple ''phases'' (e.g. ''staging'' and ''update''), this defines the number of seconds to wait before the device reads the scripts for the next phase (see [[Reference10:Concept_Update_Server#Provision_command | provision command]]) (please do not modify the default value)<br />
<br />
In some installations it may be desired to deliver update scripts with HTTPS only or even only to calling devices which have identified themselves with a proper TLS certificate. <br />
<br />
; times/@forcehttps : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS. If the device is ready to receive an update script and still calls in with HTTP only, its ''Command File URL'' will be re-configured to use HTTPS automatically.<br />
; times/@httpsport : if you use a non-standard port for HTTPS access to update scripts, it can be defined here<br />
; times/@httpsurlmod : if your HTTPS URL differs from the HTTP URL, you can specifiy a ''modifier''. It has the form <code>!</code>''pattern''<code>!</code>''replacement''<code>!</code> where ''pattern'' is a [http://docs.php.net/manual/en/pcre.pattern.php PHP PCRE pattern]. For example, <code>!mtls/!!i</code> will remove the string <code>mtls/</code> from the URL used by the device to create the HTTPS URL to use. The delimiter (in this case "!") can be changed according to your own wishes, the first character defines the delimiter. The "i" at the end ignores uppercase.<br />
<br />
Note: If you want to change the hostname of your URL, you have to add the Port 444 to your hostname. The code should then look like this: <code>!hostname1:444/mtls/!hostname2:444/!i</code>.<br />
; times/@forcetrust : if set to <code>true</code>, update scripts will only be delivered if the device call in with HTTPS and a valid and trusted client certificate that identifies itself as the devices it claims to be<br />
; times/@forcestaging : before build 2009, the restrictions imposed by the times/@allow settings affected all update ''phases''. This however makes staging cumbersome, as you usually want to restrict normal configuration changes to off-working-hours. If it is <code>true</code>, the restriction will be applied only to the last phase (that is, the ''staging'' phases will execute immediately). Also, the final phase will be executed once by staged devices. <br />
: Since build 2021 the standard value was changed to <code>true</code>.<br />
<br />
<syntaxhighlight lang="html5"><br />
<times <br />
dir="scripts" <br />
allow="" <br />
initial="" <br />
check="true" <br />
polling="5" <br />
interval="15" <br />
grace="90" <br />
forcehttps="true" <br />
httpsport="444" <br />
forcetrust="false" <br />
httpsurlmod="!mtls/!!i"<br />
forcestaging="true"<br />
/><br />
</syntaxhighlight><br />
<br />
==== phases ==== <br />
Defines the scripting phases. In many installations, there are initializations which should be performed once only. This is known as the ''staging'' phase. Once this is done, the devices continue with the execution of the normal update scripts (this phase is known as ''update'' by default). In the (unlikely) event that you want more or less phases, you can add/remove ''phase'' tags.<br />
==== phases/phase ====<br />
; phases/phase/@id : the name for the phase<br />
; phases/phase/@seq: the sequential number for the phase. Phases are stepped-through in the numeric order defined by their ''seq'' attribute. <br />
<br />
By virtue of the ''seq'' attribute, you can insert phases in to the set of phases defined by default, which is<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<!-- sequence is important! --><br />
<phase id="staging" seq="100"/><br />
<phase id="update" seq="200"/><br />
</phases><br />
</syntaxhighlight><br />
For example,<br />
<syntaxhighlight lang="html5"><br />
<phases><br />
<phase id="phase" seq="150"></phase><br />
</phases><br />
</syntaxhighlight><br />
will insert a custom phase ''myphase'' as second phase.<br />
<br />
==== environments ====<br />
Defines the distinguished environments. Devices can have zero or more ''environments'' associated. The environments a device is considered to be in must be passed as <code>?env=</code>''name1,name2,..'' URL query arg in the update url. <br />
<br />
Update scripts for all environments listed will be applied.<br />
First the environment scripts itself, then all implied environments in the same order as the ''implies'' tags are listed in the config. <br />
<br />
The default config file defines the environments ''intern'' and ''homeoffice'' but you can define your own if you like.<br />
<br />
==== environments/environment ====<br />
; environments/environment/@id : the name for the environment<br />
<br />
==== environments/environment/implies ====<br />
Each ''environment'' may have a number of sub-tags of type ''implies''. It contains the ''id'' of another ''environment''. If a device is in an environment with an ''implies'' sub-tag, it is assumed that it is in the implied environment also. <br />
<syntaxhighlight lang="html5"><br />
<environments><br />
<environment id='hq'><br />
<implies>intranet</implies><br />
</environment><br />
<environment id='intranet'/><br />
<environment id='homeoffice'/><br />
</environments><br />
</syntaxhighlight><br />
The <code>implies</code> tag handles no recursion (therefore, in the above example, if ''intranet'' would have an implies, a device in environment ''hq'' would not inherit this implication. Instead, you must list it explicitly for the ''hq'' environment).<br />
<br />
If multiple environments will be used they will be applied also in the same order as the ''environment'' tags are listed in the config.<br />
<br />
;There are some special things to keep in mind when adding multiple environments in the ''env'' attribute and they use the ''implies'' tag in the config.<br />
:- The first environment specified in the arg ''env'' will be processed as expected with all children.<br />
:- Recursive ''implies'' from an environment added via ''implies'' is not performed. (No children's children)<br />
:- Only the first environment which is specified in the ''env'' arg with implies is processed, all additional values in the arg ''env'' will be ignored.<br />
:- If the environment in the arg ''env'' it is not the first value, then it must also use the ''implies'' config to implies itself. Otherwise only child are processed.<br />
<br />
==== classes ====<br />
Defines the available device classes. Devices of different classes (e.g. phones and gateways) can have different update scripts. The device class is derived from the <code>#t</code> (device type) query arg of the update script URL (which is why you must configure it in the update URL). By default, the classes ''phone'', ''opus_phone'', ''pre_opus_phone'', ''phone_oldui'', ''phone_newui'', ''mobile'', ''gw'', ''fxogw'', ''fxsgw'', ''brigw'', ''prigw'' are recognized (newer versions may include more and/or updated class definitions). Also, any device is considered to be member of a class that has the device type as class-name. For example, an IP112 is in a class called ''ip112''.<br />
<br />
A given device can be in multiple classes and will execute all update scripts available for these classes.<br />
<br />
==== classes/class ====<br />
Each ''class'' has a number of sub-tags of type ''model''. It contains the value replaced for the <code>#t</code> query arg. The models listed belong to the class.<br />
; classes/class/@id : the name of the class<br />
<br />
<syntaxhighlight lang="html5"><br />
<classes><br />
<class id="gw"><br />
<model>ip0010</model><br />
...<br />
</class><br />
<class id="phone"><br />
<model>ip110</model><br />
<model>ip232</model><br />
...<br />
</class><br />
<class id="phone_oldui"><br />
<model>ip110</model><br />
...<br />
</class><br />
<class id="phone_newui"><br />
<model>ip232</model><br />
...<br />
</class><br />
</classes><br />
</syntaxhighlight><br />
<br />
==== nobootdev ====<br />
Device types listed here do not receive boot code updates.<br />
===== nobootdev/model =====<br />
Each model tag defines a device type that shall not receive boot code updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nobootdev> <br />
<model>ipva</model><br />
<model>ipvadec</model><br />
<model>swphone</model><br />
<model>mypbxa</model><br />
<model>mypbxi</model><br />
</nobootdev><br />
</syntaxhighlight><br />
<br />
==== nofirmdev ====<br />
Device types listed here do not receive firmware updates.<br />
===== nofirmdev/model =====<br />
Each model tag defines a device type that shall not receive firmware updates.<br />
<br />
<syntaxhighlight lang="html5"><br />
<nofirmdev> <br />
<model>swphone</model><br />
<model>mypbxi</model><br />
</nofirmdev><br />
</syntaxhighlight><br />
<br />
==== filenames ====<br />
Available from build 2014. Allows you to define alternate firmware and boocode names for use with the <code>{filename}</code> replacement (see the ''fwstorage'' tag), if required.<br />
<br />
===== filenames/model =====<br />
Each model defines a set of alternative file names for a certain device type.<br />
; filenames/model/@id : the (lowercase) device type identfier (e.g. <code>ip112</code>). If this matches the device type of the calling device, the alternate file names are replaced<br />
; filenames/model/@prot: the alternate firmware file name<br />
; filenames/model/@boot: the alternate boot file name<br />
<syntaxhighlight lang="html5"><br />
<filenames><br />
<model id="mypbxa" prot="mypbx.apk"/><br />
</filenames><br />
</syntaxhighlight><br />
<br />
defines the firmware file name <code>mypbx.apk</code> for the device type <code>mypbxa</code>.<br />
<br />
==== stdargs ====<br />
Better don't touch :-)<br />
<br />
==== customcerts ====<br />
innovaphone hardware devices come with a device specific, trust-able ''device certificate''. However, in many situations it is desirable to roll-out customer-created ''custom certificates'' to all devices. The update server can do this. This will replace the shipped devices certificates both on hardware devices (where all such certificates are derived from innovaphone's certificate authority) as well as on soft devices which run with a self.-signed certificate by default (such as the software-phone or ''myPBX for Android/iOS'').<br />
<br />
; customcerts/@dir : the name of the directory underneath your installation directory which stores device certificates. you should make sure that this directory cannot be accessed without proper authentication<br />
; customcerts/@CAkeys : a file name pattern (e.g. <code>CAkey*</code>). All files in ''customcerts/@dir'' which match the pattern must contain DER or PEM encoded public keys which form the ''certificate chain'' of your CA (usually it is only one and the public key of your CA). One key per file. The files, when sorted by name, must contain the CA key itself in the last file (any intermediate certificates, if any, in the other files in correct order)<br />
; customcerts/@CAtype : must be <code>manual</code> currently<br />
; customcerts/@CAname : a comma-separated list of CA names. Devices presenting a certificate signed by one of these CAs will be considered as having a valid certificate. Otherwise, they wil be instructed to create a ''certificate signing request'' (CSR) for subsequent submission to your CA<br />
; customcerts/@CAnamesep : defines the name separator for ''customcerts/@CAname''. The default is '<code>,</code>' and you must change it to a different value if one of your CAs includes a comma in its name<br />
; customcerts/@renew : if set and not 0, certificates are replaced by new ones ''n'' days before they expire<br />
; customcerts/@CAwildcard : normally, the update server will accept a certificate only if its CN matches the device's serial number. However, sometimes, so-called ''wildcard certificates'' such as <code>*.innovaphone.com</code> are used on a device (e.g. on a PBX or on a ''reverse proxy''). If set, the update server will also accept a certificate that contains a CN which equals the value of ''customcerts/@CAwildcard''. Note that the CN must match literally. For example, if ''''customcerts/@CAwildcard'' is set to <code>*.innovaphone.com</code> a CN like <code>host.innovaphone.com</code> is not accepted<br />
<br />
<br />
When the update server determines that a device calls in with an un-trusted or expired certificate, it will have it create a signing-request for a new certificate. The requested certificate properties can be defined:<br />
<br />
; customcerts/@CSRkey : the key size, e.g. <code>2048-bit</code>. Note that we do not recommend keys larger than 2048 bit (see [[Reference7:Certificate_management#Certificate_Key_Length_and_CPU_Usage]] for details)<br />
; customcerts/@CSRsignature : the signature algorithm, e.g. <code>SH256</code><br />
; customcerts/@CSRdn-cn : the CN (''common name'') part used for the DN<br />
; customcerts/@CSRdn-ou : the OU (''organizational unit'') part used for the DN<br />
; customcerts/@CSRdn-o : the O (''organization'') part used for the DN<br />
; customcerts/@CSRdn-l : the L (''locality'') part used for the DN<br />
; customcerts/@CSRdn-st : the ST (''state or province'') part used for the DN<br />
; customcerts/@CSRdn-c : the C (''country'') part used for the DN (note: ''CSRdn'' for many CAs, must be a 2-letter ISO country code)<br />
; customcerts/@CSRsan-dns-1 : the first of up to three DNS names <br />
; customcerts/@CSRsan-ip-1 : the first of up to two IP addresses<br />
<br />
Within the set of CSR attributes, the following meta-words will be replaced:<br />
<br />
* <code>{realip}</code> : the real IP address of the device as reported in the <code>ip=#i</code> query argument<br />
* <code>{ip}</code> : the IP address of the device as seen by the update server<br />
* <code>{proxy}</code> : the IP address of the reverse proxy the device used to reach the update server<br />
* <code>{sn}</code> : the serial number of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>009033030df0</code>)<br />
* <code>{hwid}</code> : the hardware-id of the device as reported in the <code>sn=#m</code> query argument (e.g. <code>IP1200-03-0d-f0</code>)<br />
* <code>{name}</code> : the ''Device Name'' (set in ''General/Admin'') of the device<br />
* <code>{rdns}</code> : the DNS name found in a reverse DNS lookup for the real-ip address reported by the device (see ''{realip}'') above<br />
<br />
<syntaxhighlight lang="html5"><br />
<customcerts <br />
dir="certs" <br />
CAkeys="CAkey*" <br />
CAtype="manual" <br />
CAname="innovaphone-INNO-DC-W2K8-Zertifizierungsserver" <br />
CAnamesep="," <br />
CAwildcard="*.innovaphone.com"<br />
CSRkey="2048-bit" <br />
CSRsignature="SH256" <br />
CSRdn-cn="{hwid}" <br />
CSRdn-ou="" <br />
CSRdn-o="" <br />
CSRdn-l="" <br />
CSRdn-st="" <br />
CSRdn-c="" <br />
CSRsan-dns-1="{name}.company.com" <br />
CSRsan-dns-2="{hwid}.company.com" <br />
CSRsan-dns-3="{rdns}" <br />
CSRsan-ip-1="{realip}" <br />
CSRsan-ip-2="" <br />
renew="90" /><br />
</syntaxhighlight><br />
<br />
==== queries ====<br />
If queries are defined and applicable for the calling device, it will be instructed to send certain information to the update server which will be stored in the device status file kept on the server. Each piece of such information is defined using a separate ''query'' tag.<br />
<br />
; queries/@scfg : defines the URL used by the device to send the information. Normally not changed from the default<br />
<br />
==== queries/query ====<br />
Defines one piece of information to be submitted to the update server.<br />
<br />
; queries/query/@id : a unique name for the query<br />
; queries/query/@title : a title for this piece of information shown in the device status<br />
<br />
<syntaxhighlight lang="html5">.<br />
<!-- retrieve and show some details from IP4/General/STUN page --><br />
<query id="media" title="Media"><br />
<cmd>mod cmd MEDIA xml-info</cmd><br />
<applies>*</applies><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
<show title="Server">concat('STUN: ', /state/queries/media/info/@stun, ' TURN: ', /state/queries/media/info/@turn, ' (', /state/queries/media/info/@turn-user, ')')</show><br />
</query><br />
<br />
<!-- show primary phone registration --><br />
<query id="registration" title="Registration"><br />
<cmd>mod cmd PHONE/USER phone-regs /cmd phone-regs</cmd><br />
<applies>phone</applies><br />
<show title="State: User/Number">concat(/state/queries/registration/info/reg[@id = "0"]/@state, ': ' , /state/queries/registration/info/reg[@id = "0"]/@h323, '/', /state/queries/registration/info/reg[@id = "0"]/@e164)</show><br />
<show title="PBX/ID">concat(/state/queries/registration/info/reg[@id = "0"]/@gk-addr, '/', /state/queries/registration/info/reg[@id = "0"]/@gk-id)</show><br />
</query><br />
</syntaxhighlight><br />
<br />
==== queries/query/cmd ====<br />
The content of this tag defines the command to be executed on the device which outputs the requested information. These are ''mod cmd''s typically.<br />
<br />
<syntaxhighlight lang="html5"><br />
<!-- get info for custom certificates --><br />
<cmd>mod cmd X509 xml-info</cmd><br />
</syntaxhighlight><br />
<br />
See [[Howto:Effect_arbitrary_Configuration_Changes_using_a_HTTP_Command_Line_Client_or_from_an_Update#Using_the_Mechanism_for_Device_Status_Queries ]] for details on how to find out which commands to use.<br />
<br />
==== queries/query/applies ====<br />
Defined queries are only executed by devices which belong to the class that is given as tag content. If the ''env'' atribute is set, the tag content is interpreted as an ''environment'' name which has to match.<br />
<br />
; queries/query/applies/@env : if this attribute exists, the tag content is compared with the environments the device is in. Otherwise, it is compared with the classes it is in (unfortunately, you can not combine bith)<br />
<syntaxhighlight lang="html5"><br />
<!-- applied to phones only --><br />
<applies>phone</applies><br />
</syntaxhighlight><br />
<br />
==== queries/query/show====<br />
If one or more ''show'' tags are defined for the ''query'' tag, the data will be shown in the device status page. The values shown are defined by the tag content which is an XPath expression selecting the data from the device state. The XPath expression always begins with <code>/state/queries/</code>''query-id'' (as defined by the ''id'' attribute in the query tag). <br />
<br />
; queries/query/show/@title : used as title when the values are displayed in the status page<br />
<br />
<syntaxhighlight lang="html5"><br />
<show title="NAT">concat(/state/queries/media/info/nat/@result, ' (', /state/queries/media/info/nat/@public-addr, ')')</show><br />
</syntaxhighlight><br />
<br />
== Download ==<br />
*[http://download.innovaphone.com/ice/wiki-src#php-update-server http://download.innovaphone.com/ice/wiki-src/] - download the complete file package of scripts and files described in this article<br />
: you need to use build 2000 or higher. Older versions are described in [[Howto:PHP_based_Update_Server]]<br />
<br />
== Hints for writing your update snippets ==<br />
Writing update scripts is largely undocumented and sometimes tricky. Here are some general guidelines.<br />
<br />
=== Using <code>config add/del</code> ===<br />
Many setting are done using <code>config change</code> commands. So these are very useful in update scripts too.<br />
<br />
The straight forward method to find out which commands you need is as follows:<br />
* get a device of the type in question and do factory reset<br />
* save the configuration to a file<br />
* do the desired settings using the normal web admin UI<br />
* save the resulting configuration to another file<br />
* compare the saved files<br />
<br />
Usually, you want to set only specific parts of the configuration line. For example, assume you want to set the default coder to ''OPUS-WB''. Your saved configuration line might look like<br />
<br />
config change PHONE SIG /prot SH323 /gk-addr pbx.innovaphone.com /gk-id innovaphone.com /tones 0 /lcoder OPUS-WB,20, /coder OPUS-WB,20,k1<br />
<br />
To set only the coder settings, you would set the relevant options with the <code>config add</code> command as follows:<br />
<br />
config add PHONE SIG /lcoder OPUS-WB,20,<br />
config add PHONE SIG /coder OPUS-WB,20,k1<br />
<br />
You can also remove specific options, as in <br />
<br />
config rem PHONE SIG /tones<br />
<br />
which would remove the <code>/tones 0</code> from the configuration line for <code>PHONE SIG</code>. <br />
<br />
Note that the PHP update server will add the<br />
<br />
config write<br />
config activate <br />
iresetn<br />
<br />
at the end of the delivered script automatically, so you do not need to do it yourself. In fact, it is not recommended to have any reset command in your snippets.<br />
<br />
==== <code>config change</code> Lines with associated Passwords ====<br />
Sometimes, there is a password associated with certain configurations. Consider the STUN/TURN configuration: <br />
<br />
config change MEDIA /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd 2 /nat-detect 61<br />
<br />
The TURN password, being sensitive, needs to be encrypted in your configuration. This is why it is stored as a ''VAR'', which provides encryption support (see below):<br />
<br />
vars create MEDIA/TURN-PWD pc ....<br />
<br />
Unfortunately, the relationship between an option and an associated VAR needs to be guessed. You can be sure that the module name in the <code>config change</code> matches the modules name in the <code>vars create</code> command. However, the variable name must be guessed. <br />
<br />
You can also set the password in your script using the <code>vars create</code><!-- , but this does make sense only in staging (that is pre-update-phase) scripts (see below for a discussion why). If you need to do it in a script snippet for the update phase, you should -->, however, you may also consider using a <code>mod cmd</code> (see below) such as e.g.<br />
<br />
mod cmd MEDIA form /cmd form /del %2Fstun-slow /stun stun.innovaphone.com /turn turn.innovaphone.com /turn-user innovaphone /turn-pwd my-turn-pw /nat-detect 61<br />
<br />
=== Using <code>vars create</code> ===<br />
<br />
Sometimes it makes sense to use <code>vars create</code> commands. The syntax is<br />
<br />
<code> vars create </code>''<name> <flags> <value>''<br />
<br />
A ''<name>'' is a module name followed by a variable name, optionally followed by an index. For example, a phone will store the currently selected main registration as <code>vars create PHONE/ACTIVE-USER p </code>''<index>''. So the module is <code>PHONE</code> and the variable name is <code>ACTIVE-USER</code>. When the variable is multi-valued, an index is added to the name. For example, the registration settings for all but the first registration are stored as indexed variable, as in <code>vars create PHONE/USER-REG/00001 p </code>''<settings>'', where <code>00001</code> is the index (registrations count from 0 in this case).<br />
<br />
There are a number of flags which can be combined, the most prominent used in update scripts are:<br />
; p : permanent. The var is stored in flash memory (you will almost always use this flag in update scripts)<br />
; c : crypted. The var value needs to be encrypted and the ''<value>'' given is crypted<br />
; x : crypted, plain value. The var needs to be encrypted but the ''<value>'' is given as plain (that is, un-encrypted) text. This allows you to set an encrypted variable without encrypting the desired value<br />
; b : binary. The var value is binary. Its ''<value>'' is given as a bin2hex string<br />
<br />
Please note that using <code>vars create</code> will ''always'' set the internal ''reset needed condition'' in the device (regardless which ''<value>'' is set). This means that a subsequent <code>resetn</code> or <code>iresetn</code> will always trigger a reset. So if you use it, make sure that you use the ''times/@check'' ([[#times | see above]]) to protect your script from re-executing the snippet (and thus rebooting the device) all the time. <br />
<!--<br />
When you use a <code>mod cmd UP1 check ...</code> command in your script (see times/@check [[#times | above]]), this will create an endless loop if you use <code>vars create</code> followed by one of the <code>resetn</code> commands in the code guarded by the <code>check</code> command. This code will never be fully executed, as the ''reset needed'' condition will always be set and the reset will always happen. You can use it in ''pre-update-phase'' scripts (that is, during the staging process) as this is not protected by a <code>check</code> command.<br />
--><br />
<br />
=== Using <code>mod cmd</code> ===<br />
See [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]] for a discussion of how to use <code>mod cmd</code> in update scripts.<br />
<br />
== Writing your own Dynamic Scripting Code ==<br />
NB: this feature is available from build 2020.<br />
<br />
By default, the update server delivers update scripts which are composed from a number of static files (so-called ''snippets''). Sometimes it makes sense however, to create such snippets dynamically (e.g. based on a database lookup). Here is how to do this.<br />
<br />
Dynamic scripting is implemented by a user-supplied custom <code>class CustomUpdateSnippet</code>. The code for this class must be placed in to the file <code>config/scripting.class.php</code>. As a starter, sample class code is available in <code>config/scripting.class-sample.php</code>.<br />
<br />
<syntaxhighlight lang="php"><br />
<?php<br />
<br />
/**<br />
* to provide your own runtime generated snippets, rename this file to 'scripting.class.php' and <br />
* implement the member functions<br />
* $property will have one member called <br />
*/<br />
<br />
class CustomUpdateSnippet extends UpdateSnippet {<br />
<br />
/**<br />
* snippet to deliver after standard text snippets<br />
* @return array of string<br />
*/<br />
public function getPostSnippet() {<br />
return parent::getPostSnippet();<br />
}<br />
<br />
/**<br />
* snippet to deliver before standard text snippets<br />
* @return array of strings<br />
*/<br />
public function getPreSnippet() {<br />
return parent::getPreSnippet();<br />
}<br />
<br />
/** <br />
* do we want to suppress the standard text snippets?<br />
* @return boolean<br />
*/<br />
public function sendStandardSnippets() {<br />
return parent::sendStandardSnippets();<br />
}<br />
}<br />
?><br />
</syntaxhighlight><br />
<br />
The sample code overrides of the classes member functions just call the base classes which do nothing at all. So if you just copy the sample code into <code>scripting.class.php</code>, nothing will change. However, if <code>getPreSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''before'' the standard snippets are sent. Likewise, if <code>getPostSnippet()</code> returns an array of strings (the parent class function returns an empty array), each array member will be sent to the calling device ''after'' the standard snippets are sent. If <code>sendStandardSnippets()</code> returns false, the standard snippets will not be sent.<br />
<br />
To determine the dynamic snippets to send, the user-provided <code>class CustomUpdateSnippet</code> has access to the members of its base <code>class UpdateSnippet</code>, namely the <code>var $statexml</code> member. This member contains a <code>SimpleXMLElement</code> object with all state information available for the calling device.<br />
<br />
Here is a sample state found in <code>$statexml</code> (as output by the <code>dump()</code> member function): <br />
<br />
<syntaxhighlight lang="html5"><br />
<?xml version="1.0"?><br />
<state seen="1522065435" requested="130774bootcode" phase="update"><br />
<device sn="00-90-33-3e-0c-57" type="IP112" classes="phone, opus_phone, phone_newui, hstrace, ip112" ip="127.0.0.1" rp="false" phase="update"<br />
environments="sifi, 172net" certstat="either certificate handling or state tracking not enabled - not doing any certificate checking" hwid="IP222-46-5c-77" realip="172.16.80.241" requestDownloaded="false"/><br />
<firmware requested="130986" requested-at="1522065435"/><br />
<bootcode requested-at="1522065435"/><br />
<config filename="scripts/all-all-all.txt" delivered="1522065426" version="1486559970"/><br />
</state><br />
</syntaxhighlight><br />
<br />
The most interesting elements in this XML are probably the attributes of the <code>state</code> and the <code>device</code> tags.<br />
<br />
== Hints for using custom SSL Certificates ==<br />
<br />
Using your own SSL certificates can be useful depending on the scenario.<br />
<br />
We have two methods to accomplish this:<br />
<br />
=== Standard scenario as described in this wiki article ===<br />
The idea is that each certificate request is checked manually, edited and the UpdateServer is provided with the new device certificate.<br />
<br />
=== Special scenario with automated certificate renewal ===<br />
The idea is that every new/unknown (untrusted) device connects to the staging server. Here the device is released by manual checking/authorization by getting its first certificate. All further extensions can happen automatically afterwards.<br />
<br />
'''Important: All important settings concerning file permissions, access and co must be used from the above article and are only skipped in this part!'''<br />
<br />
We will create two Update-Server instances with the following configuration.<br />
<br />
# Staging Server (HTTPS/443)<br />
#*Put the sources in <code>/var/www/innovaphone/update</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]].<br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!update/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code| above]]<br />
#* '''Important''': Do not submit any further customer specific device configurations in the staging Server<br />
# Final MTLS / 444 update server<br />
#*Put the sources in <code>/var/www/innovaphone/mts</code><br />
#*Use your specific SSL configuration ''<customcerts>'' as described [[#customcerts | above]] with <code>renew = "x"</code><br />
#*Use the following settings in the configuration of ''<times>'':<br />
#*:<code>force https = "true"</code><br />
#*:<code>httpsport = "444"</code><br />
#*:<code>force trust = "true"</code><br />
#*:<code>httpsurlmod = "!mtls/i!!"</code><br />
#*Activate the query ''certificates'' as described [[#Delivering_Custom_Certificates | above]]<br />
#*Configure the firmware update via the master tag as described [[#Delivering_Firmware_and_Boot_Code | above]]<br />
#*Store all customer specific device configurations<br />
<br />
<br />
The meaning of the two different instances is:<br />
*The staging server <code>https://[ip]/update/admin/admin.php</code> will generate the provisioning URLs for new devices.<br />
*All new/unknown (untrusted) devices are only in the staging server.<br />
**Here you can check the devices and provide them a valid certificate after check/authorization.<br />
*With a valid certificate, the UpdateURL is automatically changed to the MTLS instance.<br />
*As soon as a device connects via MTLS we trust this device and we can use an automatic extension of the certificates.<br />
** Waiting CSR requests are in the configured folder ''certs'' in the notation <code>"[mac]-request.p10.pem"</code><br />
** If you operate your own CA, feel free to automate the reissue of the certificates via a sheduled Cronjob<br />
*** Help for Linux: [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
*** Help for Windows: [[Howto:Creating_custom_Certificates_using_a_Windows_Certificate_Authority]]<br />
** If a signed certificate with the format <code>"[mac]-signedrequest.cer"</code> is placed in the certs folder, the certificate is automatically updated on the device.<br />
<br />
== Optional Configurations on the Linux Application Platform ==<br />
===Support for more Connections===<br />
By default the LAP's web server lighttpd allows for 512 concurrent connections with 1024 open file descriptors. When you serve a huge number of devices with the update server, then this might be too low. You will see some devices not being able to contact the update server for a while. This should not be a real issue as the devices will retry. However, updating all devices may take long due to this.<br />
<br />
In the lighttpd log (in <code>/var/log/lighttpd</code> on the LAP), you will see entries like this:<br />
<br />
2017-10-16 13:45:18: (network_linux_sendfile.c.140) open failed: Too many open files <br />
2017-10-16 13:45:18: (mod_fastcgi.c.3075) write failed: Too many open files 24 <br />
2017-10-16 13:45:18: (server.c.1434) [note] sockets disabled, out-of-fds <br />
2017-10-16 16:23:25: (response.c.634) file not found ... or so: Too many open files /mtls/updatev2/web/innovaphone_logo_claim_fisch.png -><br />
2017-10-16 17:57:45: (server.c.1432) [note] sockets disabled, connection limit reached <br />
<br />
If you experience such issues, proceed as follows:<br />
<br />
* connect to the LAP with SFTP (e.g. using WinSCP)<br />
* change directory to <code>/etc/lighttpd</code><br />
* edit <code>lighttpd.conf</code><br />
* search for the 2 lines which set <code>server.max-fds</code> and <code>server.max-connections</code><br />
* replace the standard values. We recommend to set the number of file descriptors to 4 times the number of requests when using the update server, e.g. <br />
: old<br />
:: server.max-fds = 1024<br />
:: server.max-connections = 512<br />
: new<br />
:: server.max-fds = 8000<br />
:: server.max-connections = 2000<br />
* save the file<br />
* restart linux (''Diagnostics/Reset/Reboot'')<br />
<br />
Be sure to closely monitor the ''Diagnostics/Status'' page for a while after such configuration. <br />
<br />
Also, when you update the LAP, you will have to re-do the changes (as always when you change something ''under the hood'').<br />
<br />
=== Debug files rotation based on logrotate ===<br />
If you have to debug during operation and a lot of devices access the PHP Update Server V2, you have to keep track of the debug files or automatically limit them. For this you can use logrotate on the innovaphone Linux AP. With logrotate you can apply time-based or size-based rules when files are packed and when they are deleted.<br />
* open the LAP's file system using a SFTP client such as e.g. [https://winscp.net/eng/index.php WinSCP] (if you have not yet changed it, the default credentials will be <code>root/iplinux</code>, see [[Reference10:Concept_Linux_Application_Platform#Default_Credentials | Concept Linux Application Platform]]). Note that you must use SFTP rather than WebDAV, as WebDAV will not give access to the executable web server files. <br />
* Under the path <code>/etc/logrotate.d</code> create a file e.g. <code>updateserver</code>.<br />
<br />
<code>/etc/logrotate.d/updateserver</code><br />
<br />
In this file now enter the following content and save it.<br />
<br />
<code><br />
/var/www/innovaphone/mtls/update/debug/*.txt {<br />
size 5M<br />
missingok<br />
rotate 2<br />
compress<br />
delaycompress<br />
notifempty<br />
create 644 www-data www-data<br />
}<br />
</code><br />
<br />
This will include all *.txt files from the debug directory of the PHP Update Server V2 in the logrotate process of the operating system.<br />
<br />
; size 5M : means every 5MB the file is rotated or zipped<br />
; size 5k : means every 5kB the file is rotated or zipped<br />
Alternatively, you can also rotate the file based on time<br />
; daily : means that every day the file is rotated or zipped<br />
; weekly : means that the file is rotated or zipped daily<br />
<br />
; missingok : if a debug file does not exist, it will be ignored<br />
; rotate n : files are removed before the last ones are deleted.<br />
; compress : compresses the old debug files<br />
; delaycompress : compresses the debug data only after it has been moved once<br />
; notifempty : empty log files are not rotated<br />
; create 644 www-data www-data : creates a new, empty debug file with appropriate permissions<br />
<br />
== Update Server and Reverse Proxy ==<br />
It is possible to implement access to the update server through a ''reverse proxy'' (RP). However, you have to keep some issues in mind:<br />
* ''Mutual Transport Layer Security'' (MTLS) is not possible<br />
: MTLS requires a direct TLS connection between the two parties. An RP however would terminate the TLS connection and entertain two independent connections to the parties. This breaks MTLS. If you want to use MTLS and serve external clients, you must therefore provide a direct access to the update server (that is an external IP address either directly or through port forwarding)<br />
: see [[#If_you_want_to_setup_MTLS-restricted_Delivery_of_Update_Scripts]] for details<br />
* When using the RP, you can choose to forward encrypted traffic (HTTPS) arriving from external to the update server using HTTP (unencrypted). The update server however eventually rewrites the update URL in the calling devices configuration. In order to do this, it determines the type of the internal connection. So if the connection from the RP to the update server is using HTTP, it would create a ''http://...'' URL. Otherwise it would create an ''https://..'' URL (also, it would be using the same port). This way, if the RP forwards requests to the update server with HTTP, the synthesized new URL for the calling client would use HTTP too, even though the original request was sent with HTTPS. This may or may not be what you want (as all of your clients would end up using non-encrypted traffic). Even worse, it might not work at all, if the RP does not accept HTTP for example.<br />
<br />
== Related Articles ==<br />
* [[Reference10:Concept_Update_Server]]<br />
* [[Reference12r1:DHCP_client]]<br />
* [[Reference10:Concept_Linux_Application_Platform]]<br />
* [[Reference10:Concept_Provisioning]]<br />
* [[Howto:PHP_based_Update_Server]] (old version)<br />
* [[Howto:Creating_custom_Certificates_using_a_OpenSSL_Certificate_Authority]]<br />
* [[Howto:Creating custom Certificates using a Windows Certificate Authority]]<br />
* [[Howto:Effect arbitrary Configuration Changes using a HTTP Command Line Client or from an Update]]<br />
<br />
<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference12r2:Concept_IPVA_On_Hyper-V&diff=69225Reference12r2:Concept IPVA On Hyper-V2023-10-23T12:12:53Z<p>Ckl: /* Applies To */</p>
<hr />
<div>[[Category:Concept|{{PAGENAME}}]]<br />
=Applies To=<br />
This information applies to<br />
*IPVA<br />
*Firmware Version 12r2<br />
*Microsoft Hyper-V<br />
**Hyper-V on Windows 8.1<br />
**Hyper-V on Windows 10<br />
**Hyper-V Server 2012 R2<br />
**Hyper-V Server 2016 CTP4<br />
*For support of VMWare, refer to this [[Reference12r1:Concept_Innovaphone_Virtual_Appliance|article]].<br />
<br />
Disclaimer: Since versions of Hyper-V hosts other than those specified here have not been tested, their use is at your own risk.<br />
<br />
=More Information=<br />
This article explains deployment of the IPVA for the Hyper-V family of Microsoft virtualizer products. For support of VMWare, refer to this [[Reference12r1:Concept_Innovaphone_Virtual_Appliance|article]].<br />
<br />
=Download=<br />
IPVA.zip<br />
<br />
* v12r2: http://download.innovaphone.com/ice/12r2/#firmware<br />
* v13r1 and up: http://store.innovaphone.com/release/download.htm (switch to the ''Firmware'' tab, scroll down to ''IPVA'', then in this box scroll down to the release you need)<br />
<br />
Download the IPVA.<br />
*The folder ''vhd'' contains 2 subdirectories:<br />
**''Virtual Machines'' This directory contains the manifest file (configuration/meta data) of the virtual machine<br />
**''Virtual Hard Disks'' This directory contains 4 files representing the 4 IDE disks of the virtual machine<br />
***hd-boot.vhd: The virtual boot harddisk (hd)<br />
***hd-cf.vhd: The hd for emulation of a Compact Flash card<br />
***hd-flash.vhd: The hd for emulation of flash memory<br />
***hd-dump.vhd: The hd serving as a store for a crash dump<br />
<br />
=Hyper-V on Windows Client Machines=<br />
From on Windows 8 up to now Windows 10 (Pro), Hyper-V can be enabled underneath System/Programs & Features/Windows Features/. <br />
*There, activate the Hyper-V checkmark(s).<br />
*Reboot<br />
<br />
==Create The Virtual Switch==<br />
Now a virtual switch of the type ''external'' needs to be created. The IPVA's manifest file configures a network adapter that instructs Hyper-V to bind that adapter on a virtual switch named ''Virtual Switch External''. A different networking/switch configuration can be administrated, of course. Here're the steps to create the switch with the expected name from scratch:<br />
*Start ''Start/Programs/Windows Management Tools/Hyper-V-Manager''.<br />
**The Hyper-V Manager Application is going to start up.<br />
*Right-Click Hyper-V-Manager/<Local Machine>/<br />
*Select ''Manager for virtual switches'' or ''Virtual Switch Manager...''<br />
*Create a new virtual switch via the ''Create Virtual Switch''-button<br />
**Name: ''Virtual Switch External''<br />
**Description or Note: Enter arbitrary text<br />
**Connection Type: Select ''External Network''<br />
**Select a physical adapter from the drop-down list<br />
===Networking Implications For The Windows Host===<br />
The creation of the ''Virtual Switch External'' alters the networking adapter configuration of the windows host.<br />
*The old network adapter ''Ethernet'' remains, however its property/network checkmarks will all be disabled, except for ''Hyper-V Extensible Virtual Switch''.<br />
*A completely new network adapter ''vEthernet (Virtual Switch External)'' will be created. All previously enabled checkmarks from the ''Ethernet'' adapter will also be enabled. The checkmark ''Hyper-V Extensible Virtual Switch'' will be disabled.<br />
*The windows host is going to be communicating over the new adapter ''vEthernet (Virtual Switch External)''. <br />
*During the configuration changes take effect, a Yes/No-Box is likely to be displayed <code type="text"><br />
Apply Networking Changes<br />
--<br />
Pending changes may disrupt network connectivity.<br />
This computer may lose its network connection while the changes are applied.<br />
This may affect any network operations in progress.<br />
These changes also may overwrite some static changes. <br />
If that happens you must reapply the static changes to restore network connectivity..<br />
</code><br />
<br />
==Import An IPVA==<br />
The import is the easiest procedure for guest creation. [[#Download|Download]] the IPVA and extract the files. <br />
*Start ''Start/Programs/Windows Management Tools/Hyper-V-Manager''.<br />
*Right-Click Hyper-V-Manager/<Local Machine>/<br />
*Select ''Import Virtual Machine...''<br />
*Browse to the directory where the downloaded package was extracted to. Browse to the subdirectory ''Virtual Machines'' and click ''Ok''.<br />
*Select an ''Import Type''. A ''Direct Registration'' means to import the virtual machine in-place. I.e. the files will remain at their location.<br />
**Note: The in-place direct registration can only be applied once<br />
**'''Note''': The better option for multiple IPVAs is ''Copy the Virtual Machine''. With this option the the manually chosen disk pathes must be unique.<br />
*Finish the import.<br />
**The newly imported IPVA appears within the ''Virtual Computer'' list of the local machine.<br />
<br />
==Create An IPVA Guest From Scratch==<br />
An IPVA guest can be created manually from scratch by means of the 4 *.vhd disk following the given recipe:<br />
*Add a new virtual machine<br />
**Right-Click ''Hyper-V-Manager/<Local Machine>''.<br />
**Select ''New/Virtual Computer''.<br />
**Enter a Name and the storage path for the virtual machine.<br />
**As ''Generation'' select ''Generation 1''<br />
**Assign the desired amount of RAM. 256MB is recommended.<br />
**Configure the network. Select the ''Virtual Switch External'' created earlier.<br />
**Connect virtual harddisk. Select ''Connect virtual harddisk later''.<br />
**Click ''Finish''<br />
*Remove unneeded components<br />
**Right-Click the newly created entry in the list entitled ''Virtual Computer''.<br />
**Select ''Settings''<br />
**Remove the ''DVD Drive''<br />
**Remove the ''Network Adapter''<br />
**Click ''Apply''<br />
*Connect the harddisks<br />
**Click ''IDE Controller 0''/''Add''/''Harddisk''.<br />
**Browse to hd-boot.vhd<br />
**Click ''IDE Controller 0''/''Add''/''Harddisk''.<br />
**Browse to hd-cf.vhd<br />
**Click ''IDE Controller 1''/''Add''/''Harddisk''.<br />
**Browse to hd-flash.vhd<br />
**Click ''IDE Controller 1''/''Add''/''Harddisk''.<br />
**Browse to hd-dump.vhd<br />
*Connect the Network Adapter<br />
**Click ''Add Hardware''<br />
**Select ''Network Adapter''<br />
**As ''Virtual Switch'' select ''Virtual Switch External'' created earlier.<br />
*Assign the boot medium<br />
**Click ''BIOS''/''Boot Sequence''<br />
**Move-up ''IDE'' to the very front<br />
*Click Ok<br />
[[Image:Screenshot-hyperv-mgr-new.png|center|thumb|200px|Hyper-V-Manager, Manual creation of an IPVA guest]]<br />
<br />
=Windows Hyper-V Server 2012 R1=<br />
We have never tried this ourselves. However, we have received reports to the effect that it worked after ''adding a legacy network adapter''. So you may try it, but you are on your own. <br />
=Windows Hyper-V Server 2012 R2 (Core Installation)=<br />
This free product lacks a GUI. Instead, Microsoft offers RSAT, the Remote Server Administration Tools. The RSAT do also contain a Hyper-V-Manager, however it practically appeared impossible to prepare a proper versioned client machine, in order to get the RSAT-stack working. The Hyper-V core server comes with a minimal setup console and an administrator command line, targeting the Hyper-V administration by means of Windows powershell commands (Hyper-V CmdLets). This section focusses on this latter administration means.<br />
[[Image:Screenshot-hyperv-r2-console.png|center|thumb|200px|Hyper-V 2012 R2, Administrator Console & Sconfig]]<br />
<br />
==Sconfig==<br />
Sconfig addresses the textual menu with the blue background color.<br />
*Option 1): If unsure, the server may simply be left as member in the regular WORKGROUP<br />
*Option 2): Configure the NetBIOS computer name (<NBNAME>).<br />
*Option 4): Enable Remote Management.<br />
*Option 7): Enable Remote Desktop (RDP) for all client versions.<br />
<br />
==Firewall==<br />
There are specific management steps necessary to even allow for RDP. Although not recommended a quick disabling of the firewall avoids warm-up troubles<br />
*To disable the firewall:<br />
**Click into the Administrator command line console.<br />
**Enter ''powershell'' and confirm. Enter<code type="text"><br />
netsh advfirewall set allprofiles state off</code><br />
<br />
==Remote Desktop(RDP)==<br />
Now it is possible to setup a Remote Desktop Session to the Hyper-V server.<br />
*Login as ''administrator'' or ''<NBNAME>\administrator''<br />
<br />
In order to move files from/to the Hyper-V server<br />
*A network mount of the server's c-drive may be established:<br />
**On the client machine place a command within a command line window<code type="text"><br />
net use * \\<IP of Hyper-V server>\c$ /u:<NBNAME>\administrator<br />
</code><br />
*Or the client machines c-drive may be projected into the RDP-session<br />
**''Connection Settings/Options/Local Ressources/Further../Local Drive''<br />
<br />
==Create The Virtual Switch==<br />
As above a virtual switch named ''Virtual Switch External'' is required and must be bound on a physical network adapter. <br />
*The physical adapters can be listed by means of a regular ''ipconfig /all''<br />
*Create and edit a powershell script named ''create-switch.ps1''<code type="text"><br />
# Create-Switch.ps1:<br />
# Example<br />
# PS C:\vms> .\create-switch.ps1<br />
# Supply values for the following parameters:<br />
# PhysAdapterName: Ethernet 2<br />
# Creating virtual switch 'Virtual Switch External' on physical Adapter Ethernet 2<br />
#<br />
# Name SwitchType NetAdapterInterfaceDescription<br />
# ---- ---------- ------------------------------<br />
# Virtual Switch External External Intel(R) Ethernet Connection I217-LM<br />
<br />
param(<br />
[Parameter(Mandatory=$true)]<br />
[string]$PhysAdapterName<br />
)<br />
<br />
<br />
$VirtualSwitchName = "Virtual Switch External"<br />
echo "Creating virtual switch '$VirtualSwitchName' on physical Adapter $PhysAdapterName";<br />
New-VMSwitch -Name "$VirtualSwitchName" -NetAdapterName "$PhysAdapterName"<br />
</code><br />
*Invoke the script from within a powershell session by means of ''.\create-switch.ps1''<br />
*The script will ask for the name of the physical network adapter.<br />
*It is assumed the name be ''Ethernet 2''. So, enter<code type="text"><br />
Ethernet 2<br />
</code><br />
*Or invoke the script with<code type="text"><br />
.\create-switch.ps1 -PhysAdapterName "Ethernet 2"<br />
</code><br />
<br />
==Import An IPVA==<br />
It is assumed the downloaded files reside underneath ''c:\vms'' as ''c:\vms\vhd\Virtual Machines'', ''c:\vms\vhd\Virtual Hard Disks''.<br />
*[[#Download|Download]] the IPVA and extract the files. <br />
*Copy the files to the Hyper-V host machine<br />
*Start-up a powershell session. Enter(Note/hint: The Tab-key aides in path completion towards the xml-file )<code type="text"><br />
import-vm -Path 'C:\vms\vhd\Virtual Machines\A09F7653-BBD0-41AE-9A63-53921CE90E26.XML' -register<br />
</code><br />
*The output may look like<code type="text"><br />
Name State CPUUsage(%) MemoryAssigned(M) Uptime Status<br />
---- ----- ----------- ----------------- ------ ------<br />
IPVA-VHD Off 0 0 00:00:00 Operating normally<br />
</code><br />
The method above imports the guest machine in-place, i.e. without altering file locations. This method can be applied only once, because a guest's machine id must be kept unique.<br />
*To import and assign a new guest id, the command must be applied slightly different<code type="text"><br />
import-vm -Path 'C:\vms\vhd\Virtual Machines\A09F7653-BBD0-41AE-9A63-53921CE90E26.XML' -GenerateNewId -Copy c:\vms\my-new-guest<br />
</code><br />
;-Copy ''c:\vms\my-new-guest'': The script imports by copying into a new directory ''c:\vms\my-new-guest\''.<br />
;-GenerateNewId: The imported IPVA gets assigned a new guest id/guid.<br />
*After that, a new guest named ''ipva-vhd'' will be available. To rename towards a new name, the ''rename-vm'' command can be placed as such<code type="text"><br />
rename-vm "ipva-vhd" -newname my-new-guest<br />
</code><br />
<br />
==Create An IPVA Guest From Scratch==<br />
An IPVA guest can be created by means of the 4 *.vhd Disk Files. It is assumed the 4 disk files reside as c:\vms\ipva01\hd-boot.vhd, c:\vms\ipva01\hd-cf.vhd,...<br />
*Create and edit a powershell script named ''create-vm.ps1''<code type="text"><br />
# Create-VM.ps1:<br />
# Example: powershell /ExecutionPolicy Unrestricted .\create.vm.ps1 -VMName 'ipva01' -RootDir 'c:\vms'" <br />
# will create a vm 'ipva01' in the directory 'c:\vms\ipva01'<br />
<br />
param(<br />
[Parameter(Mandatory=$true)]<br />
[string]$VMName,<br />
[Parameter(Mandatory=$true)]<br />
[string]$RootDir<br />
)<br />
<br />
echo "Creating $VMName in directory $RootDir";<br />
<br />
New-VM -VMName "$VMName" -Generation 1 -MemoryStartupBytes 67108864 -BootDevice IDE -Path "$RootDir/$VMName";<br />
Remove-VMNetworkAdapter -VMName "$VMName"<br />
Add-VMNetworkAdapter -VMName "$VMName" -DynamicMacAddress -SwitchName 'Virtual Switch External';<br />
Remove-VMScsiController -VMName "$VMName" -ControllerNumber 0<br />
Remove-VMDvDDrive -VMName "$VMName" -ControllerNumber 1 -ControllerLocation 0;<br />
Add-VMHardDiskDrive -VMName "$VMName" -ControllerType IDE -ControllerNumber 0 -ControllerLocation 0 -Path "$RootDir/$VMName/hd-boot.vhd";<br />
Add-VMHardDiskDrive -VMName "$VMName" -ControllerType IDE -ControllerNumber 0 -ControllerLocation 1 -Path "$RootDir/$VMName/hd-cf.vhd";<br />
Add-VMHardDiskDrive -VMName "$VMName" -ControllerType IDE -ControllerNumber 1 -ControllerLocation 0 -Path "$RootDir/$VMName/hd-flash.vhd";<br />
Add-VMHardDiskDrive -VMName "$VMName" -ControllerType IDE -ControllerNumber 1 -ControllerLocation 1 -Path "$RootDir/$VMName/hd-dump.vhd";<br />
</code><br />
*Invoke the script by means of<code type="text"><br />
.\create.vm.ps1 -VMName "ipva01" -RootDir "c:\vms"<br />
</code><br />
<br />
==Starting An IPVA==<br />
Within a powershell session <br />
*enter<code type="text"><br />
start-vm ipva01<br />
</code><br />
*or<code type="text"><br />
start-vm -vmname ipva01<br />
</code><br />
<br />
==Further Hyper-V CmdLet Commands==<br />
See <ref>Hyper-V Cmdlets: https://technet.microsoft.com/de-de/library/hh848559.aspx</ref>. That documentation addresses the latest CmdLets. The set of Hyper-V Server 2012 commands for the powershell 2.0 is smaller and can be listed by<br />
*<code type="text"><br />
Get-Command -Module Hyper-V<br />
</code><br />
===Increase Amount of RAM===<br />
Set RAM to 512MB for a guest ''ipva01''<br />
*<code type="text"><br />
Stop-Vm ipva01<br />
Set-VMMemory -vmname ipva01 -StartupBytes 536870912<br />
Start-Vm ipva01<br />
</code><br />
===Mass-Import from a Template IPVA===<br />
A pool of IPVAs can be provided, if a powershell script was applied like the one below.<br />
<code type="text"><br />
for($i=1; $i -le 50; $i++){<br />
import-vm -path 'C:\Users\mst\Desktop\vhd-template\Virtual Machines\D9449806-CBE4-41C9-B518-B5840EB75503.XML' -GenerateNewId -Copy ipva-$i<br />
rename-vm "ipva-vhd" -newname ipva-$i<br />
start-vm -name ipva-$i<br />
}<br />
</code><br />
;-Copy ipva-$i:The script imports from a given IPVA and copies into a new directory (/ipva-1, /ipva-2,..). <br />
;-GenerateNewId:Each imported IPVA gets assigned a new id/guid.<br />
;rename-vm "ipva-vhd" -newname ipva-$i:Each imported IPVA gets assigned a new name (ipva-1, ipva-2,..) by renaming from the template's name (ipva-vhd).<br />
;start-vm -name ipva-$i:Each imported IPVA is going to be started<br />
[[Image:Hvmgr-screenshot.PNG|center|thumb|200px|Hyper-V-Manager, Mass-imported IPVAs]]<br />
<br />
=Windows Hyper-V Server 2016 (Core Installation)=<br />
The powershell instructions from above [[##Windows_Hyper-V_Server_2012_R2_.28Core_Installation.29|Windows Hyper-V Server 2012 R2(Core_Installation)]] are generally applicable for the Hyper-V 2016 server. <br />
==Activating The Hyper-V Role==<br />
The Hyper-V role wasn't enabled after installation.<br />
*To install the Hyper-V server role including the powershell CmdLets, enter from a powershell command line<code type="text"><br />
Install-WindowsFeature Hyper-V -IncludeManagementTools<br />
</code><br />
<br />
<br />
=Known Issues=<br />
==Windows Host Cannot Access IPVA Guest==<br />
The IPVA guest machine may not be network-accessible from the host machine executing the hypervisor. In contrast, the guest is accessible from other hosts.<br />
*The cause might be that a 'Legacy Network Adapter'<ref>Legacy Network Adapter => deutsch: ''Ältere Netzwerkkarte''</ref> was configured instead of a 'Network Adapter'.<br />
**Ensure a regular 'Network Adapter' provides connectivity<br />
*VMware Player/Workstation interferes<br />
**Ensure the Windows host machine solely runs Hyper-V. Uninstall any VMware Player/Workstation product.<br />
<br />
==Hyper-V and VMware Cannot Co-Exist==<br />
Hyper-V and VMware are mutually exclusive. Either Hyper-V or VMware can be run on a single windows host machine. Do not try to install both hypervisor products. E.g. a VMware Player installation may complain: <code type="text"><br />
VMware Player and Hyper-V are not compatible.<br />
Remove the Hyper-V role from the system before running VMware Player.<br />
</code><br />
[[Image:Hv-vmware-incompatible.png|center|thumb|200px|Hyper-V & VMware Player are incompatible]]<br />
<br />
==IPVA V12r1 and before won't run on Hyper-V==<br />
Hyper-V is not supported by IPVA versions v12r1 and before. Consequently, you can't downgrade a running Installation to such a firmware.<br />
<br />
==No Fault Tolerance on Hyper-V==<br />
The Hyper-V solution does not provide software based fault tolerance for hardware, as VMware ''Fault Tolerance'' does. However, there are solutions available on the market, that implements fault tolerant server hardware like Stratus or Nec.<br />
<br />
=Notes=<br />
<references/><br />
<br />
== Related Articles ==<br />
* [[Howto:Convert a V10 LinuxAP to VHDX to run on Hyper-V]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Central_SIP_trunk_with_multiple_subscriber_numbers&diff=68875Howto13r3:Central SIP trunk with multiple subscriber numbers2023-10-11T13:28:17Z<p>Ckl: Protected "Howto13r3:Central SIP trunk with multiple subscriber numbers" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))</p>
<hr />
<div>__NOCACHE__<br />
This is a duplicate of [[Course13:IT Plus- E.164 and consolidated trunk lines]]<br />
<br />
{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Central_SIP_trunk_with_multiple_subscriber_numbers&diff=68874Howto13r3:Central SIP trunk with multiple subscriber numbers2023-10-11T13:26:58Z<p>Ckl: Removed protection from "Howto13r3:Central SIP trunk with multiple subscriber numbers"</p>
<hr />
<div>__NOCACHE__<br />
This is a duplicate of [[Course13:IT Plus- E.164 and consolidated trunk lines]]<br />
<br />
{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Central_SIP_trunk_with_multiple_subscriber_numbers&diff=68873Howto13r3:Central SIP trunk with multiple subscriber numbers2023-10-11T13:26:07Z<p>Ckl: Protected "Howto13r3:Central SIP trunk with multiple subscriber numbers" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))</p>
<hr />
<div>__NOCACHE__<br />
This is a duplicate of [[Course13:IT Plus- E.164 and consolidated trunk lines]]<br />
<br />
{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Course13:IT_Plus-_E.164_and_consolidated_trunk_lines&diff=68867Course13:IT Plus- E.164 and consolidated trunk lines2023-10-11T09:08:21Z<p>Ckl: Protected "Course13:IT Plus- E.164 and consolidated trunk lines" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite))</p>
<hr />
<div>{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Central_SIP_trunk_with_multiple_subscriber_numbers&diff=68866Howto13r3:Central SIP trunk with multiple subscriber numbers2023-10-10T17:30:56Z<p>Ckl: </p>
<hr />
<div>This is a duplicate of [[Course13:IT Plus- E.164 and consolidated trunk lines]]<br />
<br />
{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Central_SIP_trunk_with_multiple_subscriber_numbers&diff=68865Howto13r3:Central SIP trunk with multiple subscriber numbers2023-10-10T17:24:24Z<p>Ckl: Created page with "{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}"</p>
<hr />
<div>{{#moodlebook: Master Templates / V13 Templates / Plus| E.164 and consolidated trunk lines | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto13r3:Conferences,_Resources_and_Licenses&diff=68562Howto13r3:Conferences, Resources and Licenses2023-09-18T15:26:13Z<p>Ckl: Created page with "==Applies To== This information applies to all innovaphone devices supporting a ''CONF'' or ''SCNF'' interface and hence ''Conference Channels'' (as shown in the devices home-page). <!-- Keywords: Konferenzen, Lizenzen, 3PTY, 3er Konferenz 3erkonferenz, multi-party conference, mehrfachkonferenz, conferencing, conferences --> ==More Information== Performing conferencing on a Reference10:Gateway/Interfaces#Conferencing_interface_.28CONF.29 | ''CONF or SCNF'' interface..."</p>
<hr />
<div>==Applies To==<br />
This information applies to<br />
<br />
all innovaphone devices supporting a ''CONF'' or ''SCNF'' interface and hence ''Conference Channels'' (as shown in the devices home-page).<br />
<br />
<!-- Keywords: Konferenzen, Lizenzen, 3PTY, 3er Konferenz 3erkonferenz, multi-party conference, mehrfachkonferenz, conferencing, conferences --><br />
<br />
==More Information==<br />
Performing conferencing on a [[Reference10:Gateway/Interfaces#Conferencing_interface_.28CONF.29 | ''CONF or SCNF'' interface]] consumes both resources and licenses. Here is an overview of which and how much. <br />
<br />
=== Overview ===<br />
<br />
First let us have a look at the overall scheme:<br />
<br />
[[Image:Conferences,_Ressources_and_Licenses_-_Scheme2.png]]<br />
<br />
==== Coder channels ====<br />
Both the ''CONF'' and ''SCNF'' interface provides the mixing of 8KHz G711 PCM audio channels. <br />
<br />
If a call is done towards a CONF interface, it will allocate a ''Coder channel'' from the DSP coder channel bank for processing of the audio from the VoIP codec (e.g. G.711, G.722, OPUS) to PCM audio. <br />
<br />
The SCNF however supports G.711a/u coder only (which is basically PCM). For this reason, no ''Coder channel'' needs to be allocated for a call.<br />
<br />
==== (Soft) Conference Channels ====<br />
Each call to a CONF interface consumes one of the ''Conference Channels''.<br />
<br />
Each call to an SCNF interface consumes one of the ''Soft Conference Channels''.<br />
<br />
==== Channels license ====<br />
<br />
In addition to that, a call to either the CONF or SCNF requires a ''Channel'' license. This license can be obtained from the PBX if the ''PBX Channels licenses'' switch in the PBX [[Reference13r1:PBX/Objects/Conference | ''Conference'' object ]] is activated (if the call comes through such an object) or the ''Obtain Channels lic on outgoing call'' check-mark is ticked in a [[Reference13r1:PBX/Objects/Gateway | PBX ''Gateway'' object ]] (if the call comes through such an object). For this, ''PBX Channels licenses'' must be installed on the PBX.<br />
<br />
This is the recommended configuration. However, the ''Channel'' license can also be obtained locally from the gateway where the CONF/SCNF is located on. All innovaphone gateways have a number of ''Channel'' licenses built-in. The number of licenses available is equal to the number of ''Coder'' channels the box supports. If no license is sent along with the call to the CONF or SCNF interface, the interface will try to obtain one from the pool of built-in licenses. This can save you some cost. However, be aware that these licenses (as well as the corresponding ''Channel'' coders) are also required for calls through the ISDN BRI/PRI interfaces or for audio fax calls. Calls to CONF/SCNF interfaces which consume local ''Channel'' licenses may inhibit such calls therefor. <br />
<br />
The PBX-Channels License has order no. 02-00020-007 according to chapter "3.5 PBX Channels license" in the [https://www.innovaphone.com/content/downloads/innovaphone-Licensing%20Guidelines-V13r2-EN.pdf innovaphone license guide]. <br />
<br />
The [[Reference13r1:PBX/Config/General#License_Status|status of the PBX Licenses]] shows assigned PBX-Channel licenses:<br />
<br />
[[Image:Conferences,_Ressources_and_Licenses_-_pbx-channel_license_01.png]]<br />
<br />
==== Port license ====<br />
The registration of the CONF and/or SCNF to the PBX Conference object does not require a Port license.<br />
<br />
Although this is not directly related to the CONF or SCNF interface, please note that a single ''Port'' license is required if any of the rooms defined in a PBX ''Conference'' object is accessed using the conference web access (from 13r3).<br />
<br />
==== CPU usage ====<br />
For a calculation of CPU usage see [[Howto:V13_Firmware_Upgrade_V13r2_V13r3#Conferences]].<br />
<br />
===Example Scenarios===<br />
<br />
====Conference on hardware gateway with local ISDN====<br />
Let us assume we have a conference running on a CONF interface with one PSTN and 3 VoIP participants. In this case, we have <br />
<br />
{| border=1 <br />
| ||Caller || DSP Coder Channel || Conference Channel || PBX Channel License<br />
|-<br />
| ||1 PSTN || 1 (CONF) + 1 (ISDN) **|| 1 || 1 <br />
|-<br />
| ||3 VoIP || 3|| 3 || 3 <br />
|-<br />
| Total || 4 || 5 || 4 || 4<br />
|}<br />
<nowiki>**</nowiki>: {{FIXME|reason=do we need only one or would this require ''enable PCM''?}}<br />
<br />
<br />
Looking at an IP411, it supports the following resources:<br />
<br />
[[Image:Conferences,_Ressources_and_Licenses_-_example_gateway_channels_IP411.png]]<br />
<br />
<br />
We can see that our sample scenario will not work on an IP411 as there are no ''Conference Channels'' available. However, if we change the scenario so that an SCNF is used instead of a CONF, it would look as follows:<br />
<br />
{| border=1 <br />
| ||Caller || DSP Coder Channel || Soft Conference Channel || PBX Channel License<br />
|-<br />
| ||1 PSTN || 1 (ISDN) **|| 1 || 1 <br />
|-<br />
| ||3 VoIP || 3|| 3 || 3 <br />
|-<br />
| Total || 4 || 4 || 4 || 4<br />
|}<br />
<br />
This will work on an IP411.<br />
<br />
<br />
=== 3PTY Conferencing ===<br />
3-way conferencing is special as it is usually implemented internally in the innovaphone IP phones. That is, each user of an innovaphone IP phone (except the IP61) has internal conferencing resources built-in to allow for a 3-way conference. No external ''CONF/SCNF'' interface is needed.<br />
<br />
However, on innovaphone DECT phones, no internal conferencing resource is present and hence no internal 3PTY is possible. The DECT system can thus be [[Reference9:3pty conference on DECT phones|configured to use an external conferencing resource]] for 3PTY. In this case, the rules above apply.<br />
<br />
== Related Articles ==<br />
* [[Reference9:Concept myPBX]]<br />
* [[Reference10:Concept Innovaphone Virtual Appliance]]<br />
* [[Reference9:3pty conference on DECT phones]]<br />
* [[Reference10:Gateway/Interfaces#Conferencing_interface_.28CONF.29]]<br />
* [[Howto:How_to_implement_large_PBXs#Technical_data_and_recommended_number_of_users_supported]], lists DSP and CONF resources for the different gateway platforms<br />
<br />
<br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept_Conference]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:Pcap&diff=68433Howto:Pcap2023-08-15T08:01:36Z<p>Ckl: /* Versions */</p>
<hr />
<div>With remote PCAP, network traffic can be captured directly from another network device, instead of capturing the network traffic from the own device.<br />
<br />
==Remote PCAP==<br />
===Requirements===<br />
<br />
* You should have installed the latest wireshark Stable release 1.12.X - [http://www.wireshark.org/download.html Wireshark Download]<br />
: You may also use newer builds, but make sure they are supported by our plugin DLL. See [[#Versions | Versions ]] below for a list of supported versions<br />
* To view the standard debug output of ISDN LAPD/Q.931 packets, you have to install the ''innovaphone plugin'' (<code>innovaphone_win32.dll</code> or <code>innovaphone_win64.dll</code>, depending on your installed wireshark version, 32 bit or 64 bit). To convert text log output (from the ''Maintenance/Tracing'' page) you can use ''log2pcap.exe''.<br />
: To download both items, open the [http://download.innovaphone.com/ice/download/p/6.00/apps/ V6 application page], select the '''6000055''' folder and download the '''tools.zip'''.<br />
: Again, if you use newer builds, make sure you download the DLLs from the appropriate ''apps'' package (see [[#Versions | Versions ]] below)<br />
: To '''install''' a DLL version '''1059''' or previous, just copy it to your wireshark plugin directory and pay attention on your currently used version (e.g.: c:\programme\wireshark\plugins\1.12.0\). Note that you need to re-install the DLL each time you update wireshark<br />
: To '''install''' a DLL version '''1060''' or later, just copy it to your wireshark plugin '''epan''' directory and pay attention on your currently used version (e.g.: c:\programme\wireshark\plugins\2.6\epan\). Note that you need to re-install the DLL each time you update wireshark<br />
* Open the [http://wiki.innovaphone.com/index.php?title=Image:Pcap_example_isdn.zip example pcap file with lapd and q.931 packets] to check your current installation. It should look like this, if you have the innovaphone_winXX.dll correctly installed:<br />
<br />
[[Image:Pcap_sample_isdn.jpg|center|thumb|200px|PCAP ISDN example]]<br />
<br />
==== Using Wireshark ''Legacy'' ====<br />
Starting with version 2, wireshark has a new user interface. Unfortunately, we found this to be sluggish and buggy. For that reason, we strongly recommend to use wireshark's ''legacy'' version. It is available as an option (''Wireshark 1, The classic user interface'') in the installer's item selection. You also may want to associate the trace file extensions (.pcap, .pcapng etc.) with ''Wireshark Legacy'' instead of the standard version (also available in the setup dialogue).<br />
<br />
===Setting up the rpcap server===<br />
<br />
* The rpcap server can be any innovaphone device. <br />
* The remote pcap server is disabled per default. To enable it, just go to Diagnostics->Tracing and check the "Enable" flag in the "Remote PCAP" group. If you are experiencing problems, also enable the trace flag with "config add PCAP /trace".<br />
* To capture all ip traffic (udp and tcp), enable the "IP (all tcp/udp traffic)" flag in the group "IP". Otherwise just enable all the trace flags on the modules you want to capture.<br />
<br />
===Capturing with wireshark===<br />
<br />
===1.x.x - 1.7.x===<br />
Open your wireshark and the capture options dialogue. Choose "remote" from the dropdown list and <br />
Type "<IP-ADDRESS>/TRACE" into the host field.<br />
<br />
It should look like this: (Screenshot from older Wireshark, v.1.2.2)<br />
[[Image:Wireshark_1.2.2_trace_settings.PNG|center|thumb|200px|Wireshark capture options]]<br />
<br />
Then just click on "Start" to start capturing.<br />
<br><br />
<br />
=== >= 1.8.x ===<br />
<br />
Open your wireshark and „Capture Options“->“Manage Interfaces“->“Remote Interfaces“. Add the IP address of your device.<br><br />
The remote interface will be listed in your interface now and you can select it for capturing data.<br />
<br />
[[Image:Wireshark_1.8.6_settings.png|center|thumb|200px|Wireshark capture options]]<br />
<br><br />
<br />
=== >= 2.0.0 ===<br />
<br />
[[Image:Wireshark_2.0.0_settings.jpg|center|thumb|200px|Wireshark capture options]]<br />
<br />
=== >= 3.0.0 ===<br />
<br />
With the latest innovaphone Firmware (e.g. 13r1 SR11), Wireshark is supported again if you use the latest wireshark DLL.<br><br />
<br><br />
If you're running older firmware versions:<br><br />
<br />
The innovaphone.dll is not supported currently with this Wireshark-version. Please do not upgrade to Wireshark 3.0 or up.<br />
<br />
<br />
RCAP does not currently work with innovaphone devices in newer Wireshark versions. If you only want to pull general remote PCAPs with it, you can also try the Wireshark Pipe feature. For this you have to create a new pipe interface in Wireshark in the following format.<br />
<syntaxhighlight lang="bash"><br />
rpcap://[ip-address]/trace<br />
</syntaxhighlight><br />
<br />
During our tests we experienced some problems with it. Alternatively, you can add the pipe interface via the command line. First change to the Wireshark directory and replace [ip-address] with the innovaphone IP address.<br />
<syntaxhighlight lang="bash"><br />
C:\Program Files\Wireshark>wireshark -ni rpcap://[ip-address]/trace<br />
</syntaxhighlight><br />
<br />
=== >= 4.0.7 ===<br />
Wireshark doesn't support 32bit Windows builds anymore, so there is just a 64bit version of the DLL now.<br />
<br />
===Supported protocols===<br />
<br />
* ISDN: LAPD L2/L3 with dissector innovaphone.dll (enable Diagnostics->Tracing TELX/PRIX/PPP)<br />
* AC DSP: dsp with dissector Ac49xPacketRecording.dll (enable Diagnostics->Tracing->VOIP DSP)<br />
* PPPoE: flag "/pcap" on module(s) PPPOE0/PPPOE1 enables pcap tracing<br />
<br />
* All TCP/UDP protocols which are supported by native wireshark dissectors or other dissectors which can be found searching the internet.<br />
e.g.:<br />
SIP<br />
H.323<br />
H.245<br />
<br />
Enable the corresponding flags under Diagnostics->Tracing, if you only want to see specific UDP/TCP protocols. To see all, enable the "All TCP/UDP Traffic" flag under Diagnostics->Tracing.<br />
<br />
==PCAP Log==<br />
<br />
Another possibility to get a pcap log file is to open http://IP/log.pcap<br />
This file has a limited size just as the normal log file.<br />
<br />
==log2pcap==<br />
<br />
You need the tool log2pcap from the tools package, if you have a log.txt file, which contains pcap packets and you want to view them in wireshark. You can find the tool in the apps tool package (see above).<br />
<br />
Usage:<br />
# log2pcap.exe input1 input2 ... inputX<br />
# drag&drop one or more files on the log2pcap.exe<br />
# use an asterisk like "log2pcap c:\*.txt" to convert all txt files into pcap files. Things like c:\test*.txt are not supported.<br />
<br />
* The resulting file name is always inputx.pcap (e.g. log.txt is converted into log.txt.pcap).<br />
<br />
Note: if you have a trace of a little endian box (e.g. IP3000, IP21) with V6 SR1 or SR2, you have to use the "-srlefix" switch (available since 08-1007):<br />
<br />
log2pcap.exe input1 -srlefix<br />
<br />
==General Informations==<br />
<br />
===Reading PCAP Traces===<br />
<br />
====Non-IP Pcap packets==== <br />
It will nevertheless show source and destination IP addresses. 127.0.0.1 stand in for the traced device. So if for example a Q.931 SETUP messages is sent from 127.0.0.0 to 127.0.0.1, then it is an incoming setup.<br />
<br />
====MAC: 00:90:33:00:00:00====<br />
Sometimes people wonder why a pcap ''00:90:33:00:00:00'' appears as source or destination mac address.<br />
The direction of the packets can be analyzed based on the Mac address.<br />
: We use the devices MAC adress as source only if a packet '''is sent'''<br />
: We use the devices MAC adress as destination only if a packet '''is received'''<br />
The other field will be filled with ''00:90:33:00:00:00''<br />
<br />
===Disabling PCAP traces===<br />
<br />
You can disable the whole pcap tracing. Just configure a /disable-pcap to the CMD0 module. This can be useful if you do not want to see pcap traces in your log file.<br />
<br />
===Used ports===<br />
<br />
* The debug traces are encapsulated in UDP packets with port 4.<br />
* The isdn traces are encapsulated in UDP packets with port 4.<br />
* The ac dsp traces are encapsulated in UDP packets with port 50001.<br />
* Wireshark uses port 2002 to connect to the running rpcap-server<br />
* rpcap packets are transfered over a dynamically assigned port between server and client<br />
<br />
===Additional Remote PCAP trace===<br />
<br />
You can trace the remote pcap protocol with adding the trace flag by "config add PCAP /trace" if you are experiencing connection issues.<br />
<br />
===Timestamps===<br />
<br />
Since V7 Hotfix 26 and V8 Hotfix 13, the ntp timestamp is used instead of the uptime in rpcap packages. In converted log files with log2pcap, uptime is still used.<br />
<br />
===Decode TURN Traffic as RTP===<br />
RTP Traffic encapsulated in TURN and encoded as STUN per default. You can change this behaviour as global setting in your Wireshark.<br /><br />
To activate RTP heuristic for TURN traffic go to "''Analyze::Enabled Protocols''" and enable the "''rtp_stun''" dissector.<br />
<br />
==Versions==<br />
Older versions can be downloaded from the respective [http://download.innovaphone.com/ice/download/p/6.00/apps/ ''tools'' package]:<br />
* Wireshark 1.6.8: DLL Version 1043 - V6 6000043 Application Packet<br />
* Wireshark 1.11.1: DLL Version 1049<br />
* Wireshark 1.12.2: DLL Version 1055 - V6 6000054 Application Packet<br />
* Wireshark 2.0.x: DLL Version 1057 - V6 6000055 Application Packet<br />
* Wireshark 2.2.x: DLL Version 1058 - V6 6000056 Application Packet<br />
* Wireshark 2.4.x: DLL Version 1059 - V6 6000059 Application Packet<br />
* Wireshark 2.6.1: DLL Version 1060 - V6 6000061 Application Packet<br />
<br />
Newer versions can be downloaded from [https://store.innovaphone.com the store] (look for ''Wireshark DLLs'' in the ''Software'' tab):<br />
* Wireshark 3.2.x: DLL Version 1061<br />
* Wireshark 3.4.x: DLL Version 1066<br />
* Wireshark 4.0.x: DLL Version 1068<br />
<br />
== Offline generation of PCAP Files ==<br />
In version v12r1 and up, you can capture and store PCAP files without running Wireshark. This is done by setting the [[Reference12r1:Maintenance/Diagnostics/Tracing | ''Write PCAP to URL'' property ]] in ''Maintenance/Diagnostics/Tracing'' to an URL which points to a writeable WebDAV folder.<br />
<br />
This is useful if you need to trace a device for a long time or if you cannot run Wireshark to capture the trace. However, it is not useful if the traced device restarts, as the last trace file will be incomplete then (due to buffered IO when writing the file).<br />
<br />
When you remove the URL, the current trace file will be flushed and no further one will be created.<br />
<br />
=== Using V13 File App ===<br />
*create a folder in the File App<br />
*share this folder with username and password<br />
*copy the URL from this folder<br />
*go to services/http/client on the innovaphone device for which you need a pcap and store the URL and access data here<br />
*store the same URL as "Write PCAP to URL" at maintenance/diagnostic/tracing<br />
<br />
Attention: <br />
As soon as the URL was successfully deposited and "OK" was pressed, the trace is written into the folder.<br />
To end the trace, the URL can simply be removed and confirmed again with "OK".<br />
<br />
==Known Problems==<br />
<br />
* Converting a log from a little endian box (like IP3000 and IP21) with firmware V6 SR1 or SR2 with the tool log2pcap will only work with log2pcap 08-1007 or higher and the switch "-srlefix", see [[:#log2pcap|log2pcap]].<br />
* Ac49xPacketRecording.dll works only with 0.99.7. Higher versions of wireshark won't start, if this dll was copied to the dll folder!<br />
* Also some other dlls, contained in the tools package, won't work with each wireshark version. Just innovaphone.dll is always working.<br />
* Even though ''All TCP/UDP Traffic'' is turned on, packets sent to the box acting as rpcap provider to a port that is not handled by the box (that is, where no listening socket is active) will currently not be shown<br />
* If you use a 64-bit Windows Pc then you will need another innovaphone.dll, which is also contained in the latest tool package.<br />
* The custom IP header from captured innovaphone packets contains dummy values for TOS (0), id (0), fragment offset (0) and TTL (128)<br />
* After version 1.8.6 of wireshark the h225 packets are listed as malformed. So higher versions of wireshark could give troubles debugging the h.323 calls.<br />
* '''Couldn't set the capture buffer size!''': if you're experiencing this error message, please start wireshark with the option "-B 1" to set the buffer size to 1 MB<br />
<br />
* Wrong time stamp when using PCAP-to-URL:<br />
** open the first frame of the trace and expand the 'innovaphone: DEBUG'-section<br />
** last line shows the correct time: 'Debug: YYYY-MM-DD HH:MM:SS' in this case<br />
** Click on 'Edit -> Time Shift...' in the menu or use the shortcut Ctrl+Shift+T<br />
** Tick 'Set the time for packet 1 to ...' and fill in the time found in the first frame<br />
** Click 'Apply'<br />
<br />
===Missing msvcr120.dll or "module not found"/"Das angegebene Modul wurde nicht gefunden"===<br />
You have to install the Visual C++ Redistributable Packages für Visual Studio 2013: http://www.microsoft.com/de-de/download/details.aspx?id=40784<br />
<br />
===Inconsistent timestamps with Write PCAP to URL===<br />
There are sporadically inconsistent timestamps where a newer packet might have an older timestamp than the previous packet (a fraction of a second).<br/><br />
This issue just happens with Write PCAP to URL and the default configuration.<br/><br />
<br/><br />
If you use Wireshark for jitter analysis and consistent timestamps are important, you may do a configuration change:<br />
* disable the epoch-ts option on the PCAP module:<br />
** !config add PCAP /epoch-ts false<br />
** !config write<br />
** !reset<br />
* now the PCAP trace just contains the uptime beginning from 1970-01-01 but without any wrong timestamps<br />
* Wireshark offers an option to set the time on the first packet, so the time can be manually set to the file creation time (right click first packet -> move time -> second option and enter e.g. "2023-01-03 13:30:00")<br />
<br />
==Related Articles==<br />
* [[Reference12r1:Maintenance/Diagnostics/Tracing]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
<!-- Kewwords: sniffer ethereal packet capture network monitor --></div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:Pcap&diff=68419Howto:Pcap2023-08-14T13:52:00Z<p>Ckl: /* Versions */</p>
<hr />
<div>With remote PCAP, network traffic can be captured directly from another network device, instead of capturing the network traffic from the own device.<br />
<br />
==Remote PCAP==<br />
===Requirements===<br />
<br />
* You should have installed the latest wireshark Stable release 1.12.X - [http://www.wireshark.org/download.html Wireshark Download]<br />
: You may also use newer builds, but make sure they are supported by our plugin DLL. See [[#Versions | Versions ]] below for a list of supported versions<br />
* To view the standard debug output of ISDN LAPD/Q.931 packets, you have to install the ''innovaphone plugin'' (<code>innovaphone_win32.dll</code> or <code>innovaphone_win64.dll</code>, depending on your installed wireshark version, 32 bit or 64 bit). To convert text log output (from the ''Maintenance/Tracing'' page) you can use ''log2pcap.exe''.<br />
: To download both items, open the [http://download.innovaphone.com/ice/download/p/6.00/apps/ V6 application page], select the '''6000055''' folder and download the '''tools.zip'''.<br />
: Again, if you use newer builds, make sure you download the DLLs from the appropriate ''apps'' package (see [[#Versions | Versions ]] below)<br />
: To '''install''' a DLL version '''1059''' or previous, just copy it to your wireshark plugin directory and pay attention on your currently used version (e.g.: c:\programme\wireshark\plugins\1.12.0\). Note that you need to re-install the DLL each time you update wireshark<br />
: To '''install''' a DLL version '''1060''' or later, just copy it to your wireshark plugin '''epan''' directory and pay attention on your currently used version (e.g.: c:\programme\wireshark\plugins\2.6\epan\). Note that you need to re-install the DLL each time you update wireshark<br />
* Open the [http://wiki.innovaphone.com/index.php?title=Image:Pcap_example_isdn.zip example pcap file with lapd and q.931 packets] to check your current installation. It should look like this, if you have the innovaphone_winXX.dll correctly installed:<br />
<br />
[[Image:Pcap_sample_isdn.jpg|center|thumb|200px|PCAP ISDN example]]<br />
<br />
==== Using Wireshark ''Legacy'' ====<br />
Starting with version 2, wireshark has a new user interface. Unfortunately, we found this to be sluggish and buggy. For that reason, we strongly recommend to use wireshark's ''legacy'' version. It is available as an option (''Wireshark 1, The classic user interface'') in the installer's item selection. You also may want to associate the trace file extensions (.pcap, .pcapng etc.) with ''Wireshark Legacy'' instead of the standard version (also available in the setup dialogue).<br />
<br />
===Setting up the rpcap server===<br />
<br />
* The rpcap server can be any innovaphone device. <br />
* The remote pcap server is disabled per default. To enable it, just go to Diagnostics->Tracing and check the "Enable" flag in the "Remote PCAP" group. If you are experiencing problems, also enable the trace flag with "config add PCAP /trace".<br />
* To capture all ip traffic (udp and tcp), enable the "IP (all tcp/udp traffic)" flag in the group "IP". Otherwise just enable all the trace flags on the modules you want to capture.<br />
<br />
===Capturing with wireshark===<br />
<br />
===1.x.x - 1.7.x===<br />
Open your wireshark and the capture options dialogue. Choose "remote" from the dropdown list and <br />
Type "<IP-ADDRESS>/TRACE" into the host field.<br />
<br />
It should look like this: (Screenshot from older Wireshark, v.1.2.2)<br />
[[Image:Wireshark_1.2.2_trace_settings.PNG|center|thumb|200px|Wireshark capture options]]<br />
<br />
Then just click on "Start" to start capturing.<br />
<br><br />
<br />
=== >= 1.8.x ===<br />
<br />
Open your wireshark and „Capture Options“->“Manage Interfaces“->“Remote Interfaces“. Add the IP address of your device.<br><br />
The remote interface will be listed in your interface now and you can select it for capturing data.<br />
<br />
[[Image:Wireshark_1.8.6_settings.png|center|thumb|200px|Wireshark capture options]]<br />
<br><br />
<br />
=== >= 2.0.0 ===<br />
<br />
[[Image:Wireshark_2.0.0_settings.jpg|center|thumb|200px|Wireshark capture options]]<br />
<br />
=== >= 3.0.0 ===<br />
<br />
With the latest innovaphone Firmware (e.g. 13r1 SR11), Wireshark is supported again if you use the latest wireshark DLL.<br><br />
<br><br />
If you're running older firmware versions:<br><br />
<br />
The innovaphone.dll is not supported currently with this Wireshark-version. Please do not upgrade to Wireshark 3.0 or up.<br />
<br />
<br />
RCAP does not currently work with innovaphone devices in newer Wireshark versions. If you only want to pull general remote PCAPs with it, you can also try the Wireshark Pipe feature. For this you have to create a new pipe interface in Wireshark in the following format.<br />
<syntaxhighlight lang="bash"><br />
rpcap://[ip-address]/trace<br />
</syntaxhighlight><br />
<br />
During our tests we experienced some problems with it. Alternatively, you can add the pipe interface via the command line. First change to the Wireshark directory and replace [ip-address] with the innovaphone IP address.<br />
<syntaxhighlight lang="bash"><br />
C:\Program Files\Wireshark>wireshark -ni rpcap://[ip-address]/trace<br />
</syntaxhighlight><br />
<br />
===Supported protocols===<br />
<br />
* ISDN: LAPD L2/L3 with dissector innovaphone.dll (enable Diagnostics->Tracing TELX/PRIX/PPP)<br />
* AC DSP: dsp with dissector Ac49xPacketRecording.dll (enable Diagnostics->Tracing->VOIP DSP)<br />
* PPPoE: flag "/pcap" on module(s) PPPOE0/PPPOE1 enables pcap tracing<br />
<br />
* All TCP/UDP protocols which are supported by native wireshark dissectors or other dissectors which can be found searching the internet.<br />
e.g.:<br />
SIP<br />
H.323<br />
H.245<br />
<br />
Enable the corresponding flags under Diagnostics->Tracing, if you only want to see specific UDP/TCP protocols. To see all, enable the "All TCP/UDP Traffic" flag under Diagnostics->Tracing.<br />
<br />
==PCAP Log==<br />
<br />
Another possibility to get a pcap log file is to open http://IP/log.pcap<br />
This file has a limited size just as the normal log file.<br />
<br />
==log2pcap==<br />
<br />
You need the tool log2pcap from the tools package, if you have a log.txt file, which contains pcap packets and you want to view them in wireshark. You can find the tool in the apps tool package (see above).<br />
<br />
Usage:<br />
# log2pcap.exe input1 input2 ... inputX<br />
# drag&drop one or more files on the log2pcap.exe<br />
# use an asterisk like "log2pcap c:\*.txt" to convert all txt files into pcap files. Things like c:\test*.txt are not supported.<br />
<br />
* The resulting file name is always inputx.pcap (e.g. log.txt is converted into log.txt.pcap).<br />
<br />
Note: if you have a trace of a little endian box (e.g. IP3000, IP21) with V6 SR1 or SR2, you have to use the "-srlefix" switch (available since 08-1007):<br />
<br />
log2pcap.exe input1 -srlefix<br />
<br />
==General Informations==<br />
<br />
===Reading PCAP Traces===<br />
<br />
====Non-IP Pcap packets==== <br />
It will nevertheless show source and destination IP addresses. 127.0.0.1 stand in for the traced device. So if for example a Q.931 SETUP messages is sent from 127.0.0.0 to 127.0.0.1, then it is an incoming setup.<br />
<br />
====MAC: 00:90:33:00:00:00====<br />
Sometimes people wonder why a pcap ''00:90:33:00:00:00'' appears as source or destination mac address.<br />
The direction of the packets can be analyzed based on the Mac address.<br />
: We use the devices MAC adress as source only if a packet '''is sent'''<br />
: We use the devices MAC adress as destination only if a packet '''is received'''<br />
The other field will be filled with ''00:90:33:00:00:00''<br />
<br />
===Disabling PCAP traces===<br />
<br />
You can disable the whole pcap tracing. Just configure a /disable-pcap to the CMD0 module. This can be useful if you do not want to see pcap traces in your log file.<br />
<br />
===Used ports===<br />
<br />
* The debug traces are encapsulated in UDP packets with port 4.<br />
* The isdn traces are encapsulated in UDP packets with port 4.<br />
* The ac dsp traces are encapsulated in UDP packets with port 50001.<br />
* Wireshark uses port 2002 to connect to the running rpcap-server<br />
* rpcap packets are transfered over a dynamically assigned port between server and client<br />
<br />
===Additional Remote PCAP trace===<br />
<br />
You can trace the remote pcap protocol with adding the trace flag by "config add PCAP /trace" if you are experiencing connection issues.<br />
<br />
===Timestamps===<br />
<br />
Since V7 Hotfix 26 and V8 Hotfix 13, the ntp timestamp is used instead of the uptime in rpcap packages. In converted log files with log2pcap, uptime is still used.<br />
<br />
===Decode TURN Traffic as RTP===<br />
RTP Traffic encapsulated in TURN and encoded as STUN per default. You can change this behaviour as global setting in your Wireshark.<br /><br />
To activate RTP heuristic for TURN traffic go to "''Analyze::Enabled Protocols''" and enable the "''rtp_stun''" dissector.<br />
<br />
==Versions==<br />
Older versions can be downloaded from the respective [http://download.innovaphone.com/ice/download/p/6.00/apps/ ''tools'' package]:<br />
* Wireshark 1.6.8: DLL Version 1043 - V6 6000043 Application Packet<br />
* Wireshark 1.11.1: DLL Version 1049<br />
* Wireshark 1.12.2: DLL Version 1055 - V6 6000054 Application Packet<br />
* Wireshark 2.0.x: DLL Version 1057 - V6 6000055 Application Packet<br />
* Wireshark 2.2.x: DLL Version 1058 - V6 6000056 Application Packet<br />
* Wireshark 2.4.x: DLL Version 1059 - V6 6000059 Application Packet<br />
* Wireshark 2.6.1: DLL Version 1060 - V6 6000061 Application Packet<br />
<br />
Newer versions can be downloaded from [https://store.innovaphone.com the store] (look for ''Wireshark DLLs'' in the ''Software'' tab):<br />
* Wireshark 3.2.x: DLL Version 1061<br />
* Wireshark 3.4.x: DLL Version 1065<br />
<br />
== Offline generation of PCAP Files ==<br />
In version v12r1 and up, you can capture and store PCAP files without running Wireshark. This is done by setting the [[Reference12r1:Maintenance/Diagnostics/Tracing | ''Write PCAP to URL'' property ]] in ''Maintenance/Diagnostics/Tracing'' to an URL which points to a writeable WebDAV folder.<br />
<br />
This is useful if you need to trace a device for a long time or if you cannot run Wireshark to capture the trace. However, it is not useful if the traced device restarts, as the last trace file will be incomplete then (due to buffered IO when writing the file).<br />
<br />
When you remove the URL, the current trace file will be flushed and no further one will be created.<br />
<br />
=== Using V13 File App ===<br />
*create a folder in the File App<br />
*share this folder with username and password<br />
*copy the URL from this folder<br />
*go to services/http/client on the innovaphone device for which you need a pcap and store the URL and access data here<br />
*store the same URL as "Write PCAP to URL" at maintenance/diagnostic/tracing<br />
<br />
Attention: <br />
As soon as the URL was successfully deposited and "OK" was pressed, the trace is written into the folder.<br />
To end the trace, the URL can simply be removed and confirmed again with "OK".<br />
<br />
==Known Problems==<br />
<br />
* Converting a log from a little endian box (like IP3000 and IP21) with firmware V6 SR1 or SR2 with the tool log2pcap will only work with log2pcap 08-1007 or higher and the switch "-srlefix", see [[:#log2pcap|log2pcap]].<br />
* Ac49xPacketRecording.dll works only with 0.99.7. Higher versions of wireshark won't start, if this dll was copied to the dll folder!<br />
* Also some other dlls, contained in the tools package, won't work with each wireshark version. Just innovaphone.dll is always working.<br />
* Even though ''All TCP/UDP Traffic'' is turned on, packets sent to the box acting as rpcap provider to a port that is not handled by the box (that is, where no listening socket is active) will currently not be shown<br />
* If you use a 64-bit Windows Pc then you will need another innovaphone.dll, which is also contained in the latest tool package.<br />
* The custom IP header from captured innovaphone packets contains dummy values for TOS (0), id (0), fragment offset (0) and TTL (128)<br />
* After version 1.8.6 of wireshark the h225 packets are listed as malformed. So higher versions of wireshark could give troubles debugging the h.323 calls.<br />
* '''Couldn't set the capture buffer size!''': if you're experiencing this error message, please start wireshark with the option "-B 1" to set the buffer size to 1 MB<br />
<br />
* Wrong time stamp when using PCAP-to-URL:<br />
** open the first frame of the trace and expand the 'innovaphone: DEBUG'-section<br />
** last line shows the correct time: 'Debug: YYYY-MM-DD HH:MM:SS' in this case<br />
** Click on 'Edit -> Time Shift...' in the menu or use the shortcut Ctrl+Shift+T<br />
** Tick 'Set the time for packet 1 to ...' and fill in the time found in the first frame<br />
** Click 'Apply'<br />
<br />
===Missing msvcr120.dll or "module not found"/"Das angegebene Modul wurde nicht gefunden"===<br />
You have to install the Visual C++ Redistributable Packages für Visual Studio 2013: http://www.microsoft.com/de-de/download/details.aspx?id=40784<br />
<br />
===Inconsistent timestamps with Write PCAP to URL===<br />
There are sporadically inconsistent timestamps where a newer packet might have an older timestamp than the previous packet (a fraction of a second).<br/><br />
This issue just happens with Write PCAP to URL and the default configuration.<br/><br />
<br/><br />
If you use Wireshark for jitter analysis and consistent timestamps are important, you may do a configuration change:<br />
* disable the epoch-ts option on the PCAP module:<br />
** !config add PCAP /epoch-ts false<br />
** !config write<br />
** !reset<br />
* now the PCAP trace just contains the uptime beginning from 1970-01-01 but without any wrong timestamps<br />
* Wireshark offers an option to set the time on the first packet, so the time can be manually set to the file creation time (right click first packet -> move time -> second option and enter e.g. "2023-01-03 13:30:00")<br />
<br />
==Related Articles==<br />
* [[Reference12r1:Maintenance/Diagnostics/Tracing]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
<!-- Kewwords: sniffer ethereal packet capture network monitor --></div>Cklhttps://wiki.innovaphone.com/index.php?title=Course13:IT_Plus-_E.164_PBX_Setups&diff=68355Course13:IT Plus- E.164 PBX Setups2023-08-08T14:14:57Z<p>Ckl: Created page with "{{#moodlebook: Master Templates / V13 Templates / Plus| E164 PBX Setups | 133}}"</p>
<hr />
<div>{{#moodlebook: Master Templates / V13 Templates / Plus| E164 PBX Setups | 133}}</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:The_IPv4_TOS_field_and_DiffServ&diff=68350Howto:The IPv4 TOS field and DiffServ2023-08-08T07:39:54Z<p>Ckl: /* Background */</p>
<hr />
<div>==Applies To==<br />
This information applies to all innovaphone devices, V5 and up<br />
<br />
==More Information==<br />
<br />
===Problem Details===<br />
When voice traffic shall be prioritized by routing equipments in the network, it usually needs to be marked as somehow "special", so that the routing equipments knows that it needs to perform prioritization. This is normally done by setting the IP TOS header field to an appropriate value. This article discusses what "appropriate" shall mean.<br />
<br />
===Background===<br />
The innovaphone devices allow you to [[Reference:Configuration/IP/Settings|specify the IP v4 TOS header field value]] used for media (i.e. voice) traffic. By default, this value is set to a value which depends on the firmware version used (see below). The configured value directly maps to the the header field. innovaphone devices will not impose any sort of interpretation on this value (except that traffic flowing through one of the PPP interfaces is subject to prioritization if its TOS field exactly matches the configured value). Instead, routing equipment within the network infrastructure is expected to interpret this value.<br />
<br />
There is a certain degree of confusion regarding the correct setting of this value. Historically, this is due to several changes in the corresponding RFCs that describe the correct interpretation. RFC 2474 and RFC 2475 define the current understanding. RFC 4594 defines service classes and their intended usage.<br />
<br />
RFC 791, RFC 795 and RFC 1349 describe previous and deprecated interpretations. <br />
<br />
RFC 4594 defines a number of so-called "DiffServ codepoint (DSCP)" values and also suggest certain DCSPs to be used for certain applications. For VoIP traffic, there are:<br />
<br />
{|<br />
|-<br />
| Application || DSCP || Binary Value <br />
|-<br />
| Call Signaling || CS5 || 101000 <br />
|-<br />
| Audio || EF || 101110 <br />
|-<br />
| Video || AF41 || 100010 <br />
|}<br />
<br />
As we (currently [ [[User:Ckl|ckl]] 18:38, 28 August 2012 (CEST) see [[Reference9:IP4/General/Settings]] for v9-related information, [[Reference:Configuration/IP/Settings]] for v7/v8 related information ]) only tag RTP traffic with the TOS value configured, EF(Expedited Forwarding) would be an appropriate DSCP. So how does the DSCPs ''binary value'' maps to the IP TOS header field then?<br />
<br />
RFC 2474says:<br />
<br />
A replacement header field, called the DS field, is defined, which is<br />
intended to supersede the existing definitions of the IPv4 TOS octet<br />
[RFC791] and the IPv6 Traffic Class octet [IPv6].<br />
...<br />
Six bits of the DS field are used as a codepoint (DSCP).<br />
<br />
So the lower 6 bits of the IPv4 TOS field are used to store the DSCP.<br />
<br />
===Configuration===<br />
At the end of the day, if you want to have RTP traffic sent with e.g. DSCP EF (see above), you would set the TOS field to 0xb8 (DSCP=101110 and ECN=00), as the DS field according to RFC 2475 is put into the IPv4 header TOS octet.<br />
<br />
However, always keep in mind that the "good" setting for this value depends on how your routing equipment (which needs to interpret it correctly) interprets it. So, if your routing gear is configured in some way, set the TOS value so that it does what it is supposed to do, regardless of what any RFC says. <br />
<br />
==== V8 and earlier ====<br />
The default value 0x10 (010000) corresponds DSCP CS2 (OAM). The choice of this default is historical, as it corresponds to the (now deprecated) TOS precedence 0, Normal Delay, High Throughput as per RFC 795. This doesn't make sense nowadays, however, changing it would create desaster when upgrading existing installations, so it stays as it is.<br />
<br />
==== V9 and up ====<br />
The default value for RTP data is 0xb8 and for signalling 0x68.<br />
<br />
===Known Problems===<br />
Some (especially older) routing equipment will use different terminology, probably derived from older, deprecated RFCs. <br />
For example, older Cisco routers use RFC 1349 terminology which says that in the TOS header field, bit 0 to 2 are the "precedence" and 3 to 6 are the "type of service (TOS), which causes a naming conflict with the IP header TOS field.<br />
<br />
In this terminology, the precedence is just a 3 bit number, whereas the "type of service" is a 4-bit bit field specifying<br />
<br />
{|<br />
| 1000 || minimize delay<br />
|-<br />
| 0100 || maximize throughput<br />
|- <br />
| 0010 ||maximize reliability<br />
|-<br />
| 0001 || minimize monetary cost<br />
|-<br />
| 0000 || normal service<br />
|}<br />
<br />
Thus, a precedence of 3 with "type of service" minimize delay, maximize throughput would be coded as<br />
<br />
0 1 2 3 4 5 6 7<br />
+-----+-----+-----+-----+-----+-----+-----+-----+<br />
| | | |<br />
| PRECEDENCE | TOS | |<br />
| | | |<br />
+-----+-----+-----+-----+-----+-----+-----+-----+<br />
<br />
1 1 0 1 1 0 0 0<br />
<br />
which is 0x1B<br />
<br />
==Related Articles==<br />
<br />
[[Howto:Calculate Values for Type of Service (ToS) from DiffServ or DSCP Values]]<br />
<br />
[[Howto:Firmware Upgrade V6 V7 and later#Default ToS Values]]<br />
<br />
[[Howto:Set Type of Service (ToS) DiffServ DSCP Values for innovaphone Windows Applications (SoftwarePhone%2C myPBX Video)]]<br />
<br />
[[Reference9:IP4/General/Settings]]<br />
<br />
[[Reference:Configuration/IP/Settings]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Howto:The_IPv4_TOS_field_and_DiffServ&diff=68349Howto:The IPv4 TOS field and DiffServ2023-08-08T07:38:06Z<p>Ckl: /* Applies To */</p>
<hr />
<div>==Applies To==<br />
This information applies to all innovaphone devices, V5 and up<br />
<br />
==More Information==<br />
<br />
===Problem Details===<br />
When voice traffic shall be prioritized by routing equipments in the network, it usually needs to be marked as somehow "special", so that the routing equipments knows that it needs to perform prioritization. This is normally done by setting the IP TOS header field to an appropriate value. This article discusses what "appropriate" shall mean.<br />
<br />
===Background===<br />
The innovaphone devices allow you to [[Reference:Configuration/IP/Settings|specify the IP v4 TOS header field value]] used for media (i.e. voice) traffic. By default, this value is set to 0x10. The configured value directly maps to the the header field. innovaphone devices will not impose any sort of interpretation on this value (except that traffic flowing through one of the PPP interfaces is subject to prioritization if its TOS field exactly matches the configured value). Instead, routing equipment within the network infrastructure is expected to interpret this value.<br />
<br />
There is a certain degree of confusion regarding the correct setting of this value. Historically, this is due to several changes in the corresponding RFCs that describe the correct interpretation. RFC 2474 and RFC 2475 define the current understanding. RFC 4594 defines service classes and their intended usage.<br />
<br />
RFC 791, RFC 795 and RFC 1349 describe previous and deprecated interpretations. <br />
<br />
RFC 4594 defines a number of so-called "DiffServ codepoint (DSCP)" values and also suggest certain DCSPs to be used for certain applications. For VoIP traffic, there are:<br />
<br />
{|<br />
|-<br />
| Application || DSCP || Binary Value <br />
|-<br />
| Call Signaling || CS5 || 101000 <br />
|-<br />
| Audio || EF || 101110 <br />
|-<br />
| Video || AF41 || 100010 <br />
|}<br />
<br />
As we (currently [ [[User:Ckl|ckl]] 18:38, 28 August 2012 (CEST) see [[Reference9:IP4/General/Settings]] for v9-related information, [[Reference:Configuration/IP/Settings]] for v7/v8 related information ]) only tag RTP traffic with the TOS value configured, EF(Expedited Forwarding) would be an appropriate DSCP. So how does the DSCPs ''binary value'' maps to the IP TOS header field then?<br />
<br />
RFC 2474says:<br />
<br />
A replacement header field, called the DS field, is defined, which is<br />
intended to supersede the existing definitions of the IPv4 TOS octet<br />
[RFC791] and the IPv6 Traffic Class octet [IPv6].<br />
...<br />
Six bits of the DS field are used as a codepoint (DSCP).<br />
<br />
So the lower 6 bits of the IPv4 TOS field are used to store the DSCP.<br />
<br />
===Configuration===<br />
At the end of the day, if you want to have RTP traffic sent with e.g. DSCP EF (see above), you would set the TOS field to 0xb8 (DSCP=101110 and ECN=00), as the DS field according to RFC 2475 is put into the IPv4 header TOS octet.<br />
<br />
However, always keep in mind that the "good" setting for this value depends on how your routing equipment (which needs to interpret it correctly) interprets it. So, if your routing gear is configured in some way, set the TOS value so that it does what it is supposed to do, regardless of what any RFC says. <br />
<br />
==== V8 and earlier ====<br />
The default value 0x10 (010000) corresponds DSCP CS2 (OAM). The choice of this default is historical, as it corresponds to the (now deprecated) TOS precedence 0, Normal Delay, High Throughput as per RFC 795. This doesn't make sense nowadays, however, changing it would create desaster when upgrading existing installations, so it stays as it is.<br />
<br />
==== V9 and up ====<br />
The default value for RTP data is 0xb8 and for signalling 0x68.<br />
<br />
===Known Problems===<br />
Some (especially older) routing equipment will use different terminology, probably derived from older, deprecated RFCs. <br />
For example, older Cisco routers use RFC 1349 terminology which says that in the TOS header field, bit 0 to 2 are the "precedence" and 3 to 6 are the "type of service (TOS), which causes a naming conflict with the IP header TOS field.<br />
<br />
In this terminology, the precedence is just a 3 bit number, whereas the "type of service" is a 4-bit bit field specifying<br />
<br />
{|<br />
| 1000 || minimize delay<br />
|-<br />
| 0100 || maximize throughput<br />
|- <br />
| 0010 ||maximize reliability<br />
|-<br />
| 0001 || minimize monetary cost<br />
|-<br />
| 0000 || normal service<br />
|}<br />
<br />
Thus, a precedence of 3 with "type of service" minimize delay, maximize throughput would be coded as<br />
<br />
0 1 2 3 4 5 6 7<br />
+-----+-----+-----+-----+-----+-----+-----+-----+<br />
| | | |<br />
| PRECEDENCE | TOS | |<br />
| | | |<br />
+-----+-----+-----+-----+-----+-----+-----+-----+<br />
<br />
1 1 0 1 1 0 0 0<br />
<br />
which is 0x1B<br />
<br />
==Related Articles==<br />
<br />
[[Howto:Calculate Values for Type of Service (ToS) from DiffServ or DSCP Values]]<br />
<br />
[[Howto:Firmware Upgrade V6 V7 and later#Default ToS Values]]<br />
<br />
[[Howto:Set Type of Service (ToS) DiffServ DSCP Values for innovaphone Windows Applications (SoftwarePhone%2C myPBX Video)]]<br />
<br />
[[Reference9:IP4/General/Settings]]<br />
<br />
[[Reference:Configuration/IP/Settings]]<br />
<br />
[[Category:Howto|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r3:Concept_myApps_platform_services&diff=68193Reference13r3:Concept myApps platform services2023-07-12T09:50:34Z<p>Ckl: /* Using Sennheiser headsets */</p>
<hr />
<div>[[Category:Concept|myApps]]<br />
<br />
myApps platform services provide various operating system specific services which can be used by other ''Apps'' running in the [[{{NAMESPACE}}:Concept myApps|myApps client]]. Those services typically are not available in the browser's JavaScript environment and hence must be implemented in native platform code. Therefore, the platform services are installed as native executable on the respective platform.<br />
<br />
When myApps is started in a web browser (and hence has no access to the platform services), some Apps will use [https://en.wikipedia.org/wiki/WebRTC WebRTC] services implemented by the browser instead. For ease of reference, features available in this scenario are also described here.<br />
<br />
On windows, the platform services also come with their own web browser in which the myApps web App will be started then. This browser is based on google's [https://en.wikipedia.org/wiki/Chromium_(web_browser) Chromium] open source software.<br />
= Applies To =<br />
<br />
* [[{{NAMESPACE}}:Concept myApps|myApps]]<br />
* myApps for Windows<br />
* myApps for macOS<br />
* myApps for iOS<br />
* myApps for Android<br />
<br />
* myApps Web App (WebRTC)<br />
version 13r3<br />
<br />
=Features=<br />
Not all features are available or required on all platforms.<br />
{|<br />
! style="text-align: left; font-weight: bold" | Feature<br />
<br />
! style="text-align: left; font-weight: bold" | Description<br />
<br />
! style="text-align: left; font-weight: bold"| Availability<br />
|-<br />
<br />
| || || Windows || iOS || Android || macOS || Browser<ref>This refers to the myApps web application running in a browser with no platform services available</ref><br />
<br />
|-<br />
| audio || manage local audio devices to record and playback audio conversations || &#10004; || &#10004; || &#10004; || &#10004; || &#10004; (audio available but devices managed by web browser)<br />
|-<br />
<br />
| video || manage local displays and cameras to capture and render video live stream || &#10004; || &#10004; || &#10004; || &#10004; || &#10004; (video available but devices managed by web browser)<br />
<br />
|-<br />
<br />
| ringer || manage local ringing device || &#10004; || &#10004; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| application sharing<br />
<br />
|-<br />
<br />
| &nbsp; presenter || share an application || &#10004; || &#10007; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| &nbsp; consumer || view an application shared by the peer || &#10004; || &#10007; || &#10004; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| hot keys || capture key presses for quick invocation of phone apps (e.g. dial selected number) || &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
|-<br />
| tel: and sip: URI handler || intercept clicks on tel: and sip: links in web sites to invoke phone apps || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| user activity || set presence state according to user activity || &#10004; || &#10007; || &#10007; || &#10004; || &#10004;<ref>limited, see [[#User_activity|User activity]] below</ref><br />
<br />
|-<br />
<br />
| docking || myApps can be docked persistently to the right or left edge of your screens || &#10004; || &#10007; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| multi-windowing|| Apps can be launched in separate windows|| &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| recording|| Calls can be recorded to recording app|| &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| notifications || display notifications with OS standard mechanism || &#10004; || &#10004; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| phone book access || access local phone book || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
|-<br />
| office presence provider || maps PBX presence state to Microsoft office presence state || &#10004; || &#10007; || &#10007; || &#10007; || &#10007;<br />
|-<br />
<br />
| external application start || start arbitrary external applications for calls || &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| push || wake-up app from background mode on event (e.g. incoming call) || &#10007; || &#10004; || &#10004; || &#10004; || &#10007;<br />
|-<br />
<br />
| app proxy|| a caching proxy that provides app persistence || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| auto update || automatically updates myApps platform services to the same version the PBX has <ref>The then-current web app is always loaded from the PBX upon startup and hence up-to-date by definition</ref>|| &#10004; || &#10004; || &#10007; || &#10007; || &#10007;<br />
|-<br />
<br />
| three party conference || initiate 3-pty-conference using Softphone-App || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| exclude VPN || disable use of VPN connections for audio/video/appsharing || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|}<br />
<br />
<references/><br />
<br />
=Requirements=<br />
* innovaphone PBX 13r3 and up<br />
<br />
<br />
== myApps for Windows ==<br />
* Windows 10 and up<br />
* Windows Server 2016 and later versions<br />
<br />
=== 32 & 64 bit Windows ===<br />
* 32 bit Windows: install the myAppsSetup32.msi from the App Store<br />
* 64 bit Windows: install the myAppsSetup.msi from the App Store<br />
** the 64 bit variant still installs into Program Files (x86), as the main myApps.exe is still a 32bit application<br />
** the 64 bit variant just contains an additional 64 bit binary for the outlook search<br />
<br />
=== Windows N editions ===<br />
<br />
Windows N editions are missing the ''Media Feature Pack'' which is pre installed on other Windows versions.<br />
<br />
Please install the pack from [https://www.microsoft.com/en-us/software-download/mediafeaturepack Microsoft (Windows 10 pack)] before you install myApps. The installer will check if the file <code>C:\Windows\SysWOW64\mfplat.dll</code> exist on your system.<br />
<br />
Make sure to install the correct pack depending on your Windows version! There are different packs for Windows 10 1703, 1803, 1809 and 32bit or 64bit etc.<br />
<br />
NB: Sometimes the myApps installation will not work even though the media pack is already installed. This is because the installer has no read access to check if the package is already installed. If the above-mentioned file exists and the installer asks to install the Windows Media Feature Pack nevertheless, you have to start the myApps install with administrative rights.<br />
<br />
== myApps for macOS ==<br />
* macOS 10.13 or higher<br />
<br />
== myApps for iOS ==<br />
* iOS 12 or higher<br />
<br />
== myApps for Android ==<br />
* Android 6.0 or higher. Android 6.x may need an update of the Chrome browser.<br />
<br />
= Licenses =<br />
* No license needed for myApps platform services<br />
<br />
= Overview =<br />
myApps platform services is a native executable that is installed using the standard mechanisms on the respective operating system. It provides various advanced services which can be used by the myApps web client code as well as the Apps running in the myApps context. <br />
<br />
Also, on Windows, the platform services come with their own, dedicated browser to run myApps in. This browser is based on [https://en.wikipedia.org/wiki/Chromium_(web_browser) Chromium]. On iOS, macOS and Android, it is based upon native embedded web view facilities (such as WKWebView) instead.<br />
== Components ==<br />
<br />
=== RTP service for audio, video and data ===<br />
The RTP service provides audio, video and data (app sharing) VoIP RTP endpoints (e.g. for softphones). It supports STUN, TURN, ICE, SRTP, DTLS. Note however that unlike WebRTC, these endpoints do not ''require'' ICE and DTLS. In other words, they can communicate also with non-compliant (i.e. older) VoIP devices.<br />
<br />
Note that the available capabilities when not running the myApps platform services depend on the used browser's WebRTC implementation. See your browser documentation for details.<br />
<br />
Apps can request RTP channels using the [https://sdk.innovaphone.com/doc/launcher/Media.htm Media Protocol]'s ''AllocChannel'' message.<br />
<br />
===== RTP ports=====<br />
<br />
{|<br />
<br />
<br />
| audio || 50000 -> 50099<br />
<br />
|-<br />
<br />
| video || 50100 -> 50199<br />
<br />
|-<br />
<br />
| data || 50200 -> 50299<br />
<br />
|}<br />
<br />
<br />
The RTP service will enumerate all local interfaces and create local HOST candidates for ICE. There is an option however to disregard VPN interfaces (more precisely such interfaces with type of ''IF_TYPE_PPP'' or ''IF_TYPE_TUNNEL''). This can eliminate quality issues when RTP data is transmitted through TCP based VPN tunnels.<br />
<br />
SRFLX and RELAY candidates are obtained using the STUN and TURN server configuration passed by the App (e.g the ''softphone'' App) as part of the ''AllocChannel'' request.<br />
<code>{"mt":"AllocChannel","channel":"81429cba-396d-43de-8a76-ec020ba8796e","iceServers":[{"urls":"turn:myturn.domaincom:4077?transport=udp","username":"turnuser","credential":"pwd","credentialType":"password"},{"urls":"stun:mystun.domain.com:4077"}],"dn":"Foo Bar","type":"RemoteRtp","kind":"video"}</code><br />
<br />
===== Codecs =====<br />
<br />
The installed myApps launchers provide codecs that can be used by softphone apps for media streams. When running in a web browser the codecs depend on the browser version and operating system. See the documentation of your browser for details.<br />
<br />
The following codecs are supported:<br />
<br />
{|<br />
!style="text-align:left;width:100px;"|Codec<br />
!style="width:100px"|Windows-Launcher<br />
!style="width:100px"|Android<br />
!style="width:100px"|iOS<br />
!style="width:100px"|macOS<br />
!style="width:100px"|Firefox (Browser)<br />
!style="width:100px"|Chrome (Browser)<br />
!style="width:100px"|Edge (Browser)<br />
!style="width:100px"|Safari (Browser)<br />
!style="width:100px"|Opera (Browser)<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Audio<br />
|-<br />
|G711A<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G711u<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G722<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G729<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729A<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729B<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729AB<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|[https://caniuse.com/#search=Opus OPUS-NB]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|[https://caniuse.com/#search=Opus OPUS-WB]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Video<br />
|-<br />
|[https://caniuse.com/#search=VP8 VP8]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|-<br />
|[https://caniuse.com/#search=VP9 VP9]<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔**<br />
|-<br />
|[https://caniuse.com/#search=H264 H264]<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Application Sharing<br />
|-<br />
|Share<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|Watch<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔*<br />
|style="color:green;text-align:center"|✔*<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|}<br />
<br />
''* small presentation only''<br><br />
''** only for 1:1 calls, not for conferences<br />
<br />
===== Video capture =====<br />
<br />
The default resolution for video capture is 1280x720 if available. Otherwise, 640x480, 352x288 or 320x240 will be used. The frame rate is 30 fps if available, otherwise 15 fps. The resulting average bandwidth could reach 1 Mbps.<br />
<br />
===== Application sharing =====<br />
<br />
Screen content will be transmitted by the presenter.<br />
<br />
===== Device handling =====<br />
<br />
The RTP service enumerates microphones, loudspeaker, cameras and ringing devices and notifies apps when devices come and go. It is up to the apps using the devices to store preferences.<br />
<br />
The RTP service also enables some extended features (such as hook switch or volume control) for supported USB headsets or Bluetooth headsets connected to myApps.<br />
<br />
For this to work, vendor specific development kits from Jabra, Epos (formerly Sennheiser) and Poly (formerly Plantronics) are integrated.<br />
<br />
Note that it is possible to inhibit the start of the Sennheiser SDK (SenncomSDK.exe) using the <code>DISABLEHEADSETS</code> directive of the installer (see [[#MSI_parameters | MSI parameters ]] below).<br />
The supported headset-SDKs determine which headset vendors are recommended to be used with the myApps softphone app. <br />
<!--Keywords: myapps softphone supported headsets sdk--><br />
<br />
===== Ring tones =====<br />
<br />
Ring tones can be played. Apps can choose the tone from a pre-defined list of ring tones.<br />
<br />
On Windows, custom ring tones can be uploaded as .mp3 files to the <code>ringtones</code> sub-directory of myApps' roaming directory (which usually is in <code>C:\Users\...\AppData\Roaming\innovaphone\myApps\ringtones</code>).<br />
<br />
On Android, custom ring tones can be added to the system via Android settings.<br />
<br />
On iOS, custom ring tones can be uploaded as .mp3 files to the <code>Ringtones</code> subdirectory of the myApps file share that is available in iTunes if the iPhone has been connected via USB.<br />
<br />
On macOS, custom ring tones can be uploaded as .mp3 files to <code>~/Library/Containers/com.innovaphone.client-macos/Data/Documents/Ringtones</code>.<br />
<br />
===== Debugging =====<br />
For extended debugging, turn on the ''Audio'', ''Media'' and ''AppSharing'' traces in myApps.<br />
<br />
=== Hot keys ===<br />
On Windows and macOS systems, myApps platform services can listen for hot keys and invoke certain functions. Invocation is done by sending API messages to myApps which passes it to an appropriate API provider (in the cases described here, this will be a ''phone'' or ''softphone'' or ''rcc'' App typically. See [[{{NAMESPACE}}:Concept_myApps#Client_APIs_and_default_apps | Client APIs and default apps]] for more details about this mechanism.<br />
<br />
The hot keys can be specified using the ''advanced settings'' user interface (see [[#UI_elements | UI elements]] below. Any of the function keys F1 to F11 (optionally combined with up to two modifier keys ''alt'', ''ctrl'', ''shift'' or ''win'') can be chosen for each function. If you do not want to start the call with "Hotkey+Enter" because you would have to wait for the focus, the hotkey can also be pressed twice and the number is dialled directly.<br />
<br />
; dial selected number : Initiates a call using the currently selected text as target.<br />
<br />
: A ''PrepareCall'' message with the ''text'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":"mt":"PrepareCall","text":"13","adjust":true}}</code><br />
<br />
; accept call : Accepts a currently alerting call.<br />
<br />
: A ''ConnectCall'' message will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"ConnectCall"}}</code><br />
<br />
; reject/disconnect call : Rejects a currently alerting call or disconnects an active call.<br />
<br />
: A ''DisconnectCall'' message will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"DisconnectCall"}}</code><br />
<br />
=== URL Handler ===<br />
<br />
On Windows systems, two URI-handler are installed with the myApps platform services. Windows will call up this URI handler when a user clicks on an appropriate link, for example in a web site.<br />
<br />
The handler will the send an API message to myApps which passes it to an appropriate API provider (in the cases described here, this will be a ''phone'' or ''softphone'' or ''rcc'' App typically. See [[{{NAMESPACE}}:Concept_myApps#Client_APIs_and_default_apps | Client APIs and default apps]] for more details about this mechanism.<br />
<br />
; tel URI : call a number, e.g. <code>tel:4711</code><br />
<br />
: A ''PrepareCall'' message with the ''num'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"StartCall","num":"4711","adjust":true}}</code><br />
; sip URI : call a SIP name, e.g. <code>sip:zkl@innovaphone.com</code><br />
<br />
: A ''PrepareCall'' message with the ''sip'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"StartCall","sip":"zkl@innovaphone.com","adjust":true}}</code><br />
<br />
On macOS systems myApps might be made the default application to handle tel URI e.g. <code>tel:4711</code> via Apple FaceTime. Open the "FaceTime" menu "Settings..." and select myApps as "Default for phone calls".<br />
<br />
=== User activity ===<br />
On Windows and macOS systems, the myApps platform services can monitor user keyboard/mouse activity and change the user's presence state after a certain amount of inactivity. The timeout can be specified using the ''advanced settings'' user interface (see [[#UI_elements | UI elements]] below.<br />
<br />
myApps will then send a [https://sdk.innovaphone.com/doc/appwebsocket/myApps.htm#SetUserActivity''SetUserActivity''] message to the PBX using the ''myApps'' protocol.<br />
<br />
: <code>{"mt":"SetUserActivity","inactive":true}</code><br />
<br />
This will change the ''status'' property of the ''im:'' contact for the user's own presence and hence result in a presence update from the PBX to myApps<br />
<br />
: <code>{"mt":"UpdateOwnPresence","presence":[{...},{"contact":"im:","activity":"","status":"closed"}]}</code><br />
The ''closed'' status is reflected in the grey status color when displaying a contact [[Image:myapps-inactive.png]].<br />
<br />
On iOS and Android, the state is set to ''inactive'' as soon as the App is brought to background.<br />
When myApps platform services are not available (i.e. when running the web application in a browser solely) a limited user activity monitoring is available: the state is set to active when the web page is not used for more than 5 minutes.<br />
<br />
=== Recording ===<br />
<br />
The new launcher offers the possibility to record the audio of incoming and outgoing calls. In order to activate that functionality the URL of the recording instance must be configured in either the PBX (PBX->myApps->Config: Recording URL) or the softphone App (Settings->Audio Recording (URL)) <br />
<br />
[[Image:PBX-Recording-Settings.png]] [[Image:Recording-Softphone-Settings.png]]. <br />
<br />
As long as that URL is configured the audio data of all calls are stored as pcap-files under that URL.<br />
If the URL points to a CF device in the PBX, write access must be granted for that URL (PBX->Services->HTTP->Server:Public compact flash access) and if the URL points to the recording app, the files can be accessed via the recording app [[{{NAMESPACE}}:Concept_App_Service_Recordings|recording]].<br />
<br />
<br />
Under PBX->myApps the administrator can set a certain default behaviour of the audio recording like whether or not the recording should start automatically at the beginning of the call (Recording by Default ON/OFF), only calls with external numbers should be recorded (Record external calls only) or whether or not the user should be able to start/stop the recording himself (Allow user incall recording control). Except for the last parameter these parameters can also be modified by the user in its softphone settings if the administrator doesn't set the FORCE flag.<br />
<br />
If the user was allowed by the admin to control the recording a recording switch is active during the call when the "Media" Panel is opened. There the audio recording may be stopped and continued at will. A red recording notice is shown in the top right corner when the recording actually takes place.<br />
<br />
[[Image:Recording-incall-switch.png]]<br />
<br />
=== Notifications ===<br />
<br />
The myApps platform services can use the OS specific notification mechanism (e.g. ''desktop notifications'' on Windows) to display messages (e.g. ''incoming new chat message'') to the user.<br />
<br />
Note that the actual rendering of the notification is under control of the OS. Therefore, myApps must be allowed to show notifications and its appearance can be restricted by OS native settings.<br />
<br />
==== Microsoft Windows Notifications ====<br />
<br />
Microsoft Windows Server editions (2016, 2019, 2022) are just capable of showing a single ''IncomingCall'' notification at the same time (we couldn't find a workaround for this limitation).<br/><br />
An ''IncomingCall'' notification is visible the whole time instead of being moved to the action center after a certain time.<br/><br />
<br/><br />
A notification about a missed call uses the ''IncomingCall'' type so that this notification is visible until the user returns.<br/><br />
Due to the above limitation, on a new arriving call such a missed call notification is transformed to a default notification which will be moved to the action center automatically.<br/><br />
<br/><br />
On non server editions, you can have multiple IncomingCall notifications at the same time (so two parallel incoming calls will be indeed notified at the same time), but the missed call notification handling is the same on both platforms!<br />
<br />
Thus there will be always just '''one''' missed call notification visible and previous missed calls can be found inside your action center!<br />
<br />
To see myApps notifications, ensure:<br />
* System -> Notifications <br />
** enable notifications<br />
** disable "Do not disturb" or allow myApps as priority application while "Do not disturb" is active<br />
** enable notifications for myApps in the list of applications<br />
* System -> Focus <br />
** if a focus session is active and the "Do not disturb" is activated during a focus session, make sure that myApps is a priority application (see above)<br />
<br />
=== Local phonebook access ===<br />
'''Contact Search:''' The myApps platform services implement an ''API provider'' for the [http://sdk.innovaphone.com/web1/com.innovaphone.search/lib1_api_search.htm ''com.innovaphone.search'' API]]. They perform search capabilities on the OS' local phone books which can be used by Apps like the ''phoneapp''.<br />
<br />
Apps would send a ''Search'' request to the API:<br />
<br />
: <code>{"mt":"ApiRequest","consumer":"dev:SwPh_zkl_5e42e884","provider":"*","src":"4","msg":{"mt":"Search","type":"contact","search":"john doe"},"apiId":"com.innovaphone.search"}</code><br />
Search results are delivered as ''SearchInfo'' messages:<br />
<br />
: <code>{"mt":"ApiResult","src":"3","provider":"@local-8125d22e37-519d-4056-bfe5-c52ef2ae8fabb0","consumer":"dev:SwPh_zkl_5e42e884","client":"@client-f62702dd86-be3f-47fc-b4bc-7a21627b75b2ea","msg":{"mt":"SearchInfo","relevance":2000,"adjust":true,"type":"contact","contact":{"givenname":"John","sn":"Doe","company":"ACME","position":"Head of everything","telephonenumber":["11111","22222"],"homephone":["+4944444","33333"],"mobile":["+49 (123) 55555"]}},"api":"com.innovaphone.search"}</code><br />
<br />
'''Reverse Lookup:''' The myApps platform services implement an ''API provider'' for the [http://sdk.innovaphone.com/web1/com.innovaphone.phonelookup/lib1_api_phonelookup.htm ''com.innovaphone.phonelookup'' API]. They perform search capabilities on the OS' local phone books which can be used by Apps like the ''phoneapp''.<br />
<br />
Apps would send a ''Lookup'' request to the API: <br />
<br />
: <code>{"mt":"ApiRequest","consumer":"dev:SwPh_zkl_5e42e884","provider":"*","src":"4","msg":{ mt: "Lookup", prefixIntl: "000", prefixNtl: "00", prefixExt:"0", area: "7031", country: "49", lookup: "0004970311234567" },"apiId":"com.innovaphone.lookup"}</code><br />
<br />
Search results are delivered as ''LookupInfo'' messages:<br />
<br />
: <code>{"mt":"ApiResult","src":"3","provider":"@local-8125d22e37-519d-4056-bfe5-c52ef2ae8fabb0","consumer":"dev:SwPh_zkl_5e42e884","client":"@client-f62702dd86-be3f-47fc-b4bc-7a21627b75b2ea","msg":{mt: "LookupInfo", dn: "Jake Blues", contact: { telephonenumber: ["0004970311234567"], givenname: "Jake", sn: "Blues", company: "Blues Brothers" </code><br />
<br />
==== Windows ====<br />
On Windows, the search and lookup are performed in all of the user's Outlook contact folders. As opposed to the search implemented in the ''Contacts'' and ''Users'' App, all items are returned which match any of the search words (i.e. searching for ''a b'' will return items matching either ''a'' or ''b'').<br />
<br />
; searched properties : firstname, lastname<br />
; returned properties : Following Outlook contact phone number properties are returned (if available):<br />
<br />
:* OFFICE_TELEPHONE_NUMBER as ''telephonenumber''<br />
:* OFFICE2_TELEPHONE_NUMBER as ''telephonenumber''<br />
:* HOME_TELEPHONE_NUMBER as ''homephone''<br />
:* HOME2_TELEPHONE_NUMBER as ''homephone''<br />
:* MOBILE_TELEPHONE_NUMBER as ''mobile''<br />
:* BUSINESS_FAX_NUMBER as ''facsimiletelephonenumber''<br />
Note that contact information is cached in the search provider. Updated contacts may therefore become effective after a while only.<br />
Outlook search will create its own trace file <code>myAppsOutlookSearch-</code>''date-time''<code>.txt</code> in the standard trace directory.<br />
<br />
This search provider is always installed and can be disabled. There is no need (nor possibility) to enable it in the ''Apps'' tab of the PBX's user object. Also, no ''App'' object needs to be created for it.<br />
<br />
==== Android/iOS ====<br />
The search and lookup are performed in the contacts.<br />
<br />
==== macOS ====<br />
The search and lookup are performed in the contacts. If you wish to disable local contact lookup, go to system settings - Security & Privacy and disable the access to contacts for myapps.<br />
<br />
=== Microsoft Office integration ===<br />
<br />
The myApps platform services has a ''office presence provider'' that can provide the user's presence state to Office applications. See [[{{NAMESPACE}}:Concept_myApps_Office_Integration|myApps Office Integration]] for details.<br />
<br />
This feature is installed by default. However, it can be disabled using the ''OFFICEPRESENCE'' MSI Parameter. Also, a check-mark is available in the setup dialog.<br />
<br />
=== Call an external application for calls ===<br />
<br />
Phone Apps (such as the phoneapp or softphone) can initiate the start of an external application when a new call appears (either incoming or outgoing). The actual spawning of the application is done by the myApps platform service. Also, the application properties (such as e.g. the executable's path) is configured in the myApps platform services (see [[#UI_elements|Advanced settings]] in the ''UI elements'' section below).<br />
<br />
A number of arguments can be passed to the application by substituting $-variables in the ''Parameter'' field:<br />
<br />
; $n : phone number as dialed (called party number for outgoing calls) or received (calling party number for incoming calls)<br />
<br />
; $N : called or calling party number in ''national'' format (e.g. 07031730090)<br />
<br />
; $I : called or calling party number in ''international'' format (e.g. +497031730090)<br />
<br />
: note that both $N and $I only work if $n includes both subscriber number and area code (e.g. 07031730090). Otherwise they are equal to $n<br />
<br />
; $d : display name of peer (if known)<br />
<br />
; $c : conference id<br />
<br />
: this is a globally unique ID for this call and may be used to relate the call to the ''guid'' found in the CallInfo structure in the [http://wiki.innovaphone.com/index.php?title=Reference10:SOAP_API#CallInfo SOAP-API] and [http://sdk.innovaphone.com/doc/appwebsocket/RCC.htm RCC-API ]. Also, corresponding [[Reference10:Call_Detail_Record_CDR_PBX|CDRs]] can be related using the ''event'' tag's ''conf'' attribute.<br />
The start of an external application can be requested using the ''com.innovaphone.externalapps'' API.<br />
<br />
Some setup examples are [[Howto:Integrate_External_Apps_in_innovaphone_UC_clients|shown here]].<br />
<br />
=== Push ===<br />
<br />
Mobile operating systems usually inhibit network operation of apps which run in the background or are closed by the user. This is done in order to reduce battery consumption. Unfortunately, this also stops such apps to maintain a registration by regularly sending ''keep alive'' messages to a server (in our case to the PBX). As a result, myApps will be disconnected from the PBX. When the PBX determines that there is an event for the application which needs a response, it needs to wake up the app using a dedicated channel provided by the operating system. This mechanism is know as ''push''. When running on iOS or Android, myApps supports ''push''. <br />
<br />
For ''push'' to work, a [[{{NAMESPACE}}:PBX/Objects/Push|''push object'']] needs to [[Course13:IT Connect - 10.1 Push Object | be configured in the PBX ]]. Also, it needs to be enabled on the mobile phone for the myApps app.<br />
This mechanism is quite similar in v12 and v13, so you can refer to [[{{NAMESPACE}}:Concept Push Notifications for myPBX iOS and Android]] for more details. <br />
<br />
Also, helpful hints can be found in [[Howto:Troubleshoot v13 Push with myApps for Android and iOS]].<br />
<br />
=== App Proxy ===<br />
<br />
myApps runs further ''Apps'' (such as e.g. the ''phoneapp'') as a web page in an IFRAME of the browser myApps is running in. The App's page code is loaded either from the PBX or from an ''application platform'' (AP). This however would mean that the App's IFRAME would remain empty (a dead white screen) when the PBX or AP is not available. To make sure the App can start-up anyway, the myApps platform services feature the so-called ''App Proxy''. This is a caching proxy that caches all the App code so it is available even in case of network failure. When myApps runs in the context of the platform services, Apps are therefore not loaded from the App source directly, but from the local App proxy. <br />
<br />
The cached files are stored in the PCs local file system in the <code>C:\Users\...\AppData\Local\innovaphone\myApps\appproxy</path></code>. There is no configuration required. However, if myApps seems to run with outdated or corrupt cached copies of the App, you can safely delete the entire directory.<br />
<br />
=== Auto update ===<br />
<br />
On Windows and on macOS, the myApps platform services can auto-update themselves to a common version. This is controlled by the [[{{NAMESPACE}}:PBX/Config/myApps#Launcher_Software_Update | ''Launcher Software Update'' ]] settings under ''PBX/Config/myApps'' in the PBX. <br />
<br />
When myApps is started or the user logs in or myApps needs to re-connect to the PBX, the platform services will use the [http://sdk.innovaphone.com/web1/com.innovaphone.client/lib1_api_client.htm com.innovaphone.client API] to learn the desired version (''launcherUpdateBuild'', which is part of the API's ''model''). If this differs from the current version, the platform services will try to download the respective new version. <br />
<br />
<code><br />
{<br />
"mt": "ApiUpdate",<br />
"apis": {<br />
"com.innovaphone.client": {<br />
"@client": {<br />
"title": "innovaphone myApps",<br />
"model": {<br />
"launcher": true,<br />
"launcherUpdateBuild": "134906",<br />
"appStoreUrl": "http://store.innovaphone.com/alpha/download/"<br />
}<br />
}<br />
}<br />
}<br />
}</code><br />
<br />
The installation of the downloaded version is done by the ''innovaphonemyAppsUpdateService''. This service is installed and enabled during the initial installation of the myApps platform services. To disable auto-update, either leave the ''Launcher Software Update'' settings empty or set the service's start mode to ''disabled'' in the Windows ''services control panel''.<br />
<br />
Note that on Windows the update service does not work on terminal servers. Administrators must do myApps base services updates using standard windows mechanisms.<br />
<br />
Note that on macOS if myApps has been installed from the Apple Store it is assumed that auto update from the PBX is not desired and disabled therefore.<br />
<br />
On Android/iOS/macOS updates can be downloaded from the respective app store.<br />
<br />
The ''Devices'' app can not update software installed on Windows PCs directly. However, when the PBX is updated using an ''update job'' in the ''Devices'' App, the ''Launcher Software Update'' settings will be updated accordingly and hence the myApps base services will ultimately also be updated to the same version.<br />
<br />
==== Auto update flow on Windows ====<br />
<br />
* On start of myApps, myApps checks if an update is available and ready for installation<br />
** if yes, the update is installed directly, without user interaction (a popup is shown during the installation)<br />
** if nto, myApps starts<br />
* if an update is available while myApps is already running, an update notification will be shown which let's the user choose to install the update now or later (the notification will then popup again after one hour)<br />
<br />
==UI elements ==<br />
There are a few user interfaces provided by the platform services:<br />
===tray-icon (Windows only) ===<br />
::[[Image:myapps-tray.png]]<br />
:Allows to<br />
:* terminate myApps<br />
:* toggle the ''autostart'' state<br />
:* toggle the ''show in task bar'' state<br />
:* open the trace folder<br />
:<br />
=== PBX connect form===<br />
:: [[Image:myapps-connect.png]]<br />
: Allows the user to specify the connect data for the PBX (i.e. IP address or DNS name)<br />
:<br />
=== Advanced settings===<br />
::[[Image:myapps-settings0.png]]<br />
::[[Image:myapps-settings.png]] [[Image:myapps-settings2.png]] [[Image:myapps-settings3.png]]<br />
<br />
: Allows to modify various platform dependant settings (such as e.g. the hotkey selection on Windows)<br />
<br />
== Interfaces ==<br />
=== Provided APIs ===<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.search/lib1_api_search.htm com.innovaphone.search] : access to local phone book entries by the [[#Local_phonebook_access|Local phonebook access]] component.<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.launcher/com.innovaphone.launcher.htm com.innovaphone.launcher] : display of OS specific user notifications and receipt of related user actions<br />
; com.innovaphone.notificationhandler : ???<br />
; com.innovaphone.externalapps : to start external applications, see [[#Call_an_external_application_for_calls |Call an external application for calls ]] above<br />
; com.innovaphone.callkit : ???<br />
<br />
=== Used APIs ===<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm com.innovaphone.phone] : used to initiate new or manipulate existing calls by the [[#Hot_keys|Hot keys]] and [[#URL_handler|URL handler]] components.<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.client/lib1_api_client.htm com.innovaphone.client] : the model is used to learn the update settings, see [[#Auto_update|Auto update]] above<br />
<br />
=== Protocols ===<br />
<br />
; [https://sdk.innovaphone.com/doc/launcher/Media.htm Media Protocol] : used by apps to allocate RTP channels, see [[#RTP_service_for_audio.2C_video_and_data|RTP service for audio, video and data]] above<br />
<br />
== Related App Services ==<br />
<br />
none<br />
<br />
== Known limitations ==<br />
; Button on Bluetooth headsets not functional on myApps for iOS : The button of Bluetooth headsets doesn't provide to accept or disconnect calls. The button on wired earphones does.<br />
<br />
; Incoming call as banner on myApps for iOS : Since iOS 14 the iOS CallKit presents incoming calls as a banner leaving the original green answer button of myApps visible. Use only the blue button of the banner to accept the call or change iPhone Settings, App "Phone", "Incoming Calls" to "Full Screen" to hide the myApps user interface again during call answering.<br />
<br />
; Windows Server 2016 (Windows 10 Build 1607) : windows just shows the first notification. Further notifications aren't displayed until the previous ones are removed from the notification center. Current windows builds do not show this behaviour anymore.<br />
<br />
; Problems on Mac computers with Yealink USB headsets<br />
: we have received reports that myApps quits unexpectedly on some Mac computers when a Yealink headset is plugged in. Unfortunately, we could not find out the cause yet. If you use Yealink USB headsets and have a similar issue, please open a support ticket and send myApps traces.<br />
<br />
; Windows surface devices may not work correctly<br />
: Chromium does not get touch keyboard events. USB Keyboards may not be recognized either.<br />
<br />
= Installation =<br />
<br />
== Windows ==<br />
<br />
myApps platform services are installed on Windows using the .msi file found in the ''myApps Windows'' package from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
myApps can update itself automatically, see [[#Auto_update|Auto update]] above.<br />
<br />
=== MSI Parameters and install options ===<br />
<br />
The MSI installer of myApps for Windows supports the following parameters and can be edited with [https://docs.microsoft.com/en-us/windows/win32/msi/orca-exe Microsoft Orca]. You can add your parameters in the table ''property''.<br />
<br />
; SERVER (REG_SZ): the PBX's server URL<br />
; OFFICEPRESENCE (REG_DWORD): '''false''' to disable presence integration in Microsoft Office<br />
: this is also available as a check-mark when running the install manually<br />
<br />
; DISABLEHEADSETS (REG_DWORD): '''true''' to disable headsets support, see [[#Device_handling|Device handling]] above<br />
<br />
; EXTERNALAPPS (REG_SZ): pre-define external applications, see [[#Call_an_external_application_for_calls|Call an external application for calls]] above<br />
: e.g. <code>"{""externalApps"":[{""id"":0,""name"":""Wireshark"",""path"":""C:\\Program Files\\Wireshark\\Wireshark.exe"",""param"":""test $I""}]}"</code><br />
<br />
; FORCERESTART (REG_DWORD): '''true''' (or any string ...) kills myApps during the installation and restarts it for the currently logged in user, if it was running<br />
<br />
; DISABLELOCALHOST (REG_DWORD): '''true''' to disable use of '''localhost''' string to access the local webserver. Use '''127.0.0.1''' instead<br />
<br />
Current settings are stored in the registry at <code>Computer\HKEY_CURRENT_USER\Software\innovaphone\myApps</code> or at <code>Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\innovaphone\myApps</code><br />
<br />
Boolean values like OfficePresence are stored in registry entries with type REG_DWORD and values 1 or 0. 0 disables the setting and 1 enables it.<br />
<br />
== iOS ==<br />
<br />
myApps platform services are installed on iOS by loading ''innovaphone myApps'' from the ''App Store''.<br />
<br />
== macOS ==<br />
<br />
myApps platform services might be installed from the Apple store directly. An installer package <code>myapps.pkg</code> and a disk image <code>myapps.dmg</code> is also available from the innovaphone app store. Install <code>myapps.pkg</code> by double-click on the file and follow the instructions of the installer. myApps becomes available in the Applications folder and can be opened by double-click. Or download and open <code>myapps.dmg</code> and double klick myApps. If desired integrate it into the app dock by right click, ''Options, Keep in the dock''.<br />
<br />
If installed from the innovaphone app store, myApps can update itself automatically, see [[#Auto_update|Auto update]] above.<br />
<br />
If installed from the Apple store, macOS notifies about updates on the Apple store. myApps [[#Auto_update|Auto update]] is disabled then.<br />
<br />
If a clean-install of the client is necessary, the folder "/Users/username/Library/Containers/myapps" needs to be deleted. To be on the safe side also delete it from the trash bin.<br />
<br />
=== Preferences ===<br />
<br />
macOS supports preference settings that can be set via a shell command or via Mac remote management<br />
<br />
<code>> defaults write com.innovaphone.client-ios server "PBX-server-URL"</code><br />
<br />
The following parameters and can be set through this method:<br />
<br />
; server: the PBX's server URL<br />
<br />
=== Using Sennheiser headsets ===<br />
If you use Sennheiser headsets, you should also install the then-current <code>DSEA_SDK_v</code>''version''<code>.pkg</code> package. Without that, audio will still work, but not the controls on the headset. You will need to keep that up-to-date yourself, as it is not updated by myApps's aut-update function.<br />
<br />
== Android ==<br />
<br />
myApps platform services are installed on Android by loading ''innovaphone myApps'' from the ''Play Store''.<br />
<br />
= Configuration =<br />
<br />
== Server configuration ==<br />
When opening myApps for the first time, the user is prompted for the Server. Usually only the hostname (DNS host name or IP address) needs to be configured.<br />
<br />
But there are more options for special PBX configurations.<br />
<br />
; Non-standard HTTPS port<br />
: If the PBX uses a non-standard HTTPS port, it must be appended to the host name separated by a colon (<code>:</code>).<br />
: Example: <code>pbx.example.com:4444</code> (expands to <code>https://pbx.example.com:4444/PBX0/APPCLIENT/appclient.htm</code>)<br />
; DynPBX module name<br />
: If the PBX is a DynPBX, the module name must be appended to the host name separated by a slash (<code>/</code>).<br />
: Example: <code>pbx.example.com/PBX0-slave1</code> (expands to <code>https://pbx.example.com/PBX0-slave1/APPCLIENT/appclient.htm</code>)<br />
; Softphone physical location<br />
: If user defined physical location shall be used for softphone, you can append it using a parameter <code>#phys=</code>.<br />
: Example: <code>pbx.example.com#phys=slave</code> (expands to <code>https://pbx.example.com/PBX0/APPCLIENT/appclient.htm#phys=slave</code>)<br />
<br />
Example 1: PBX pbx.example.com with standard configuration<br />
pbx.example.com<br />
<br />
Example 2: PBX slave.example with DynPBX module ID PBX0-slave, HTTPS port 4444 and physical location master<br />
slave.example.com:4444/PBX0-slave#phys=master<br />
<br />
=== HTTP proxy support ===<br />
<br />
myApps platform services do support operation via HTTP proxy now. If one or more proxies have been configured in the network settings of the operating system for the active network connection, HTTP CONNECT tunnels are established.<br />
<br />
On Windows user name and password can be specified for the tunnel servers as generic credentials in the credentials manager (Anmeldeinformationsverwaltung). The name of the credentials must be the tunnel server hostname.<br />
<br />
On Android user name and password can be specified through Android ''Settings, Accounts'' by adding a myApps ''HTTP Proxy Credentials'' account. The name of the account must be the tunnel server hostname.<br />
<br />
== Platform specific settings ==<br />
When myApps runs under the myApps platform services, it will show various platform specific settings as part of its ''burger menu'', so the user can set them. See ''Advanced settings'' in [[#UI_elements|UI elements]] above.<br />
<br />
Some options can also be set globally for all myApps clients in the PBX's [[{{NAMESPACE}}:PBX/Config/myApps#Client_Settings|PBX/Config/myApps ''Client Settings'']]<br />
{|<br />
! style="text-align: left; font-weight: bold" | Option<br />
<br />
! style="text-align: left; font-weight: bold" | Description<br />
<br />
! style="text-align: left; font-weight: bold" | Where to set<br />
<br />
!<br />
! style="text-align: left; font-weight: bold"| Availability<br />
|-<br />
<br />
| || || User menu || PBX ''Client Settings'' || Windows || iOS || Android || macOS<br />
<br />
|-<br />
| Autostart || Launch myApps on login || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Appear offline after || controls after which idle time a user is considered ''inactive''. See [[#User_activity|User activity]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
|-<br />
<br />
| Hotkeys || Hotkeys for call dial, accept, reject. See [[#Hot_keys|Hot keys]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Docking || Docking mode (left, right, none). See [[#???|??]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| Desktop notifications|| Turn on/off platform notifications. See [[#Notifications| Notifications]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| VPN || Disable VPN address for ICE candidate selection. See [[#RTP_ports| RTP ports]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| Show in taskbar|| Show myApps in the taskbar in addition to it's tray icon. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| Log flags || turn on/off certain trace levels. See [[#Troubleshooting|Troubleshooting]] below. || &#10004; ||&#10004; ||&#10004; || &#10004; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| External applications || define the applications available for Apps to be started. See [[#Call_an_external_application_for_calls|Call an external application for calls]] above. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Ring in headset || send ring tone for incoming to headset instead of loudspeaker. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
|}<br />
== Start parameters for Windows ==<br />
<br />
On Windows, it is not possible to pass start parameters from the [https://www.chromium.org/developers Chromium documentation] to the myApps process.<br />
<br />
== OS Settings for Windows ==<br />
Windows settings can influence the display of ''Desktop notifications''. See [https://support.microsoft.com/en-us/help/4028678/windows-10-change-notification-settings Change notification settings in Windows 10] for details.<br />
<br />
== OS settings for Android ==<br />
; Events : The appearance of notifications can be controlled here.<br />
<br />
; Call accounts : For proper incoming call signaling, the call account ''myApps'' needs to be enabled. Note that on Samsung smartphones the call account switch likely toggles back and a few tries may need to be done until it persists. Please double-check the state.<br />
<br />
; Preferred Calling Account : Choose which calling account (myApps/SIM/..) should be used for outgoing calls initiated from within the native phone app / phone book.<br />
<br />
; Background data, unlimited data usage : Grant background data use to enable ''myApps'' to connect to the PBX immediately on an incoming call.<br />
<br />
; Overlaying : This setting is not needed if call account ''myApps'' has been enabled. Should there be a reason for not enabling call account ''myApps'', the permission for overlaying needs to be granted on Android 10 or higher for proper call signaling.<br />
<br />
Note: If no SIM card is installed some Android smartphones exhibit a problem dialing from the smartphone contacts. The contacts app shows a choice ''Select SIM card for this call'' but all possible dialers are greyed out. In this case make myApps the default phone app in Android settings ''Apps, Default apps, Telephony''.<br />
<br />
== OS settings for iOS ==<br />
; Notifications : The appearance of notifications can be controlled in iOS ''Settings, myApps''.<br />
<br />
== OS settings for macOS ==<br />
<br />
; Notifications : The appearance of notifications can be controlled in macOS ''Preferences, Notifications, myApps''.<br />
<br />
=Troubleshooting=<br />
<br />
myApps platform services can write various traces for debugging. Trace can be turned on and off selectively in the [[#Advanced settings|Advanced settings]].<br />
<br />
The following trace flags can be set:<br />
{|<br />
!style="text-align: left; font-weight: bold" | Abbreviation<br />
<br />
!style="text-align: left; font-weight: bold" |code<br />
<br />
!style="text-align: left; font-weight: bold" | description<br />
<br />
|-<br />
| App||0x000000001|| logs from the App Service itself<br />
|-<br />
| DNS||0x000000008|| logs DNS requests and results<br />
|-<br />
| HTTP client||0x000000080|| http client logs<br />
|-<br />
| TLS||0x000000400|| TLS logs<br />
|-<br />
| TCP||0x000000800|| TCP logs<br />
|-<br />
| LDS||0x000001000|| local domain sockets<br />
|-<br />
| WebSocket client||0x000004000|| logs outgoing websocket connections<br />
|-<br />
| App WebSocket||0x000008000|| logs app websocket connections (e.g. from PBX objects to an App Service or from the UI to the App Service)<br />
|-<br />
| UDP||0x000200000|| UDP logs<br />
|-<br />
| DTLS||0x000400000|| logs DTLS handshake and messages<br />
|-<br />
| Media||0x000800000|| logs media events<br />
|-<br />
| Media channel||0x001000000|| logs RTP/SCTP media connections<br />
|-<br />
| ICE||0x002000000|| logs ICE messages between peers<br />
|-<br />
| TURN||0x004000000|| logs TURN messages between peers<br />
|-<br />
| AppSharing||0x008000000|| logs AppSharing connection<br />
|-<br />
| Audio||0x010000000|| logs Audio connection and headset events<br />
|-<br />
| Video||0x020000000|| logs video connection and webcam events<br />
|-<br />
| Browser||0x040000000|| logs Chromium events<br />
|-<br />
| Browser Console||0x080000000|| logs browser console events<br />
|-<br />
| AppProxy||0x100000000|| logs requests which are proxied between the local webserver and the remote server<br />
|-<br />
| Webserver||0x400000000|| enables webserver specific logs<br />
|-<br />
| Signaling||0x800000000|| enables logs in the signaling module for debugging calls<br />
|}<br />
''code'' can be or'ed and used as value for the ''Log flags'' field in [[{{NAMESPACE}}:PBX/Config/myApps#Client_Settings|PBX/Config/myApps/Client Settings]].<br />
<br />
; Windows :On Windows, traces are written to the <code>C:\Users\[UserName]\AppData\Local\innovaphone\myApps</code> directory.<br />
<br />
:* myApps-''date-time''.txt : main log file for the platform services<br />
<br />
:* myAppsOutlookSearch-''date-time''.txt : log file for the Outlook phone book access<br />
<br />
:* myAppsHookController-''date-time''.txt : log file for the hot-key interceptor (see [[#Hot_keys|Hot keys]])<br />
<br />
; :myApps update traces are written to the <code>%windir%\temp\</code> directory.<br />
:* myAppsInstall.txt: MSI installation file<br />
:* myAppsUpdateService-''date-time''.txt: myApps update service traces<br />
<br />
;Android : traces can be sent by e-mail.<br />
<br />
: also, an Android device might also be connected to a PC via an USB cable to get the traces. The files can be found in <code>Android/data/com.innovaphone.clientandroid/files</code><br />
<br />
; iOS : traces can be sent by e-mail.<br />
<br />
; macOS : traces can be sent by e-mail.<br />
<br />
: also, the files can be found in <code>~/Library/Containers/com.innovaphone.client-ios/Data/Documents/</code>. Press ''Alt+N'' followed by space to get tilde ''~''.<br />
<br />
= Known Problems =<br />
[[:Category:Problem_myApps_platform_services|Known Problems]]<br />
<br />
= Related Articles =<br />
* [[{{NAMESPACE}}:Concept_myApps]]<br />
* [[{{NAMESPACE}}:Concept_myApps_Redundancy]]<br />
* [[{{NAMESPACE}}:Concept_myApps_Office_Integration]]<br />
* [[{{NAMESPACE}}:Concept_myAPPs_Search_in_local-Outlook_Contacts]]<br />
* [[{{NAMESPACE}}:Call_Detail_Record_CDR_PBX]]<br />
* [[{{NAMESPACE}}:Concept Push Notifications for myPBX iOS and Android]]<br />
* [[Howto:Troubleshoot v13 Push with myApps for Android and iOS]]<br />
* [[{{NAMESPACE}}:PBX/Config/myApps]]<br />
<br />
[[Category:Concept_myApps_platform_services]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r3:Concept_myApps_platform_services&diff=68192Reference13r3:Concept myApps platform services2023-07-12T09:42:01Z<p>Ckl: /* macOS */</p>
<hr />
<div>[[Category:Concept|myApps]]<br />
<br />
myApps platform services provide various operating system specific services which can be used by other ''Apps'' running in the [[{{NAMESPACE}}:Concept myApps|myApps client]]. Those services typically are not available in the browser's JavaScript environment and hence must be implemented in native platform code. Therefore, the platform services are installed as native executable on the respective platform.<br />
<br />
When myApps is started in a web browser (and hence has no access to the platform services), some Apps will use [https://en.wikipedia.org/wiki/WebRTC WebRTC] services implemented by the browser instead. For ease of reference, features available in this scenario are also described here.<br />
<br />
On windows, the platform services also come with their own web browser in which the myApps web App will be started then. This browser is based on google's [https://en.wikipedia.org/wiki/Chromium_(web_browser) Chromium] open source software.<br />
= Applies To =<br />
<br />
* [[{{NAMESPACE}}:Concept myApps|myApps]]<br />
* myApps for Windows<br />
* myApps for macOS<br />
* myApps for iOS<br />
* myApps for Android<br />
<br />
* myApps Web App (WebRTC)<br />
version 13r3<br />
<br />
=Features=<br />
Not all features are available or required on all platforms.<br />
{|<br />
! style="text-align: left; font-weight: bold" | Feature<br />
<br />
! style="text-align: left; font-weight: bold" | Description<br />
<br />
! style="text-align: left; font-weight: bold"| Availability<br />
|-<br />
<br />
| || || Windows || iOS || Android || macOS || Browser<ref>This refers to the myApps web application running in a browser with no platform services available</ref><br />
<br />
|-<br />
| audio || manage local audio devices to record and playback audio conversations || &#10004; || &#10004; || &#10004; || &#10004; || &#10004; (audio available but devices managed by web browser)<br />
|-<br />
<br />
| video || manage local displays and cameras to capture and render video live stream || &#10004; || &#10004; || &#10004; || &#10004; || &#10004; (video available but devices managed by web browser)<br />
<br />
|-<br />
<br />
| ringer || manage local ringing device || &#10004; || &#10004; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| application sharing<br />
<br />
|-<br />
<br />
| &nbsp; presenter || share an application || &#10004; || &#10007; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| &nbsp; consumer || view an application shared by the peer || &#10004; || &#10007; || &#10004; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| hot keys || capture key presses for quick invocation of phone apps (e.g. dial selected number) || &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
|-<br />
| tel: and sip: URI handler || intercept clicks on tel: and sip: links in web sites to invoke phone apps || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| user activity || set presence state according to user activity || &#10004; || &#10007; || &#10007; || &#10004; || &#10004;<ref>limited, see [[#User_activity|User activity]] below</ref><br />
<br />
|-<br />
<br />
| docking || myApps can be docked persistently to the right or left edge of your screens || &#10004; || &#10007; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| multi-windowing|| Apps can be launched in separate windows|| &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| recording|| Calls can be recorded to recording app|| &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| notifications || display notifications with OS standard mechanism || &#10004; || &#10004; || &#10004; || &#10004; || &#10004;<br />
|-<br />
<br />
| phone book access || access local phone book || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
|-<br />
| office presence provider || maps PBX presence state to Microsoft office presence state || &#10004; || &#10007; || &#10007; || &#10007; || &#10007;<br />
|-<br />
<br />
| external application start || start arbitrary external applications for calls || &#10004; || &#10007; || &#10007; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| push || wake-up app from background mode on event (e.g. incoming call) || &#10007; || &#10004; || &#10004; || &#10004; || &#10007;<br />
|-<br />
<br />
| app proxy|| a caching proxy that provides app persistence || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
<br />
| auto update || automatically updates myApps platform services to the same version the PBX has <ref>The then-current web app is always loaded from the PBX upon startup and hence up-to-date by definition</ref>|| &#10004; || &#10004; || &#10007; || &#10007; || &#10007;<br />
|-<br />
<br />
| three party conference || initiate 3-pty-conference using Softphone-App || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|-<br />
| exclude VPN || disable use of VPN connections for audio/video/appsharing || &#10004; || &#10004; || &#10004; || &#10004; || &#10007;<br />
<br />
|}<br />
<br />
<references/><br />
<br />
=Requirements=<br />
* innovaphone PBX 13r3 and up<br />
<br />
<br />
== myApps for Windows ==<br />
* Windows 10 and up<br />
* Windows Server 2016 and later versions<br />
<br />
=== 32 & 64 bit Windows ===<br />
* 32 bit Windows: install the myAppsSetup32.msi from the App Store<br />
* 64 bit Windows: install the myAppsSetup.msi from the App Store<br />
** the 64 bit variant still installs into Program Files (x86), as the main myApps.exe is still a 32bit application<br />
** the 64 bit variant just contains an additional 64 bit binary for the outlook search<br />
<br />
=== Windows N editions ===<br />
<br />
Windows N editions are missing the ''Media Feature Pack'' which is pre installed on other Windows versions.<br />
<br />
Please install the pack from [https://www.microsoft.com/en-us/software-download/mediafeaturepack Microsoft (Windows 10 pack)] before you install myApps. The installer will check if the file <code>C:\Windows\SysWOW64\mfplat.dll</code> exist on your system.<br />
<br />
Make sure to install the correct pack depending on your Windows version! There are different packs for Windows 10 1703, 1803, 1809 and 32bit or 64bit etc.<br />
<br />
NB: Sometimes the myApps installation will not work even though the media pack is already installed. This is because the installer has no read access to check if the package is already installed. If the above-mentioned file exists and the installer asks to install the Windows Media Feature Pack nevertheless, you have to start the myApps install with administrative rights.<br />
<br />
== myApps for macOS ==<br />
* macOS 10.13 or higher<br />
<br />
== myApps for iOS ==<br />
* iOS 12 or higher<br />
<br />
== myApps for Android ==<br />
* Android 6.0 or higher. Android 6.x may need an update of the Chrome browser.<br />
<br />
= Licenses =<br />
* No license needed for myApps platform services<br />
<br />
= Overview =<br />
myApps platform services is a native executable that is installed using the standard mechanisms on the respective operating system. It provides various advanced services which can be used by the myApps web client code as well as the Apps running in the myApps context. <br />
<br />
Also, on Windows, the platform services come with their own, dedicated browser to run myApps in. This browser is based on [https://en.wikipedia.org/wiki/Chromium_(web_browser) Chromium]. On iOS, macOS and Android, it is based upon native embedded web view facilities (such as WKWebView) instead.<br />
== Components ==<br />
<br />
=== RTP service for audio, video and data ===<br />
The RTP service provides audio, video and data (app sharing) VoIP RTP endpoints (e.g. for softphones). It supports STUN, TURN, ICE, SRTP, DTLS. Note however that unlike WebRTC, these endpoints do not ''require'' ICE and DTLS. In other words, they can communicate also with non-compliant (i.e. older) VoIP devices.<br />
<br />
Note that the available capabilities when not running the myApps platform services depend on the used browser's WebRTC implementation. See your browser documentation for details.<br />
<br />
Apps can request RTP channels using the [https://sdk.innovaphone.com/doc/launcher/Media.htm Media Protocol]'s ''AllocChannel'' message.<br />
<br />
===== RTP ports=====<br />
<br />
{|<br />
<br />
<br />
| audio || 50000 -> 50099<br />
<br />
|-<br />
<br />
| video || 50100 -> 50199<br />
<br />
|-<br />
<br />
| data || 50200 -> 50299<br />
<br />
|}<br />
<br />
<br />
The RTP service will enumerate all local interfaces and create local HOST candidates for ICE. There is an option however to disregard VPN interfaces (more precisely such interfaces with type of ''IF_TYPE_PPP'' or ''IF_TYPE_TUNNEL''). This can eliminate quality issues when RTP data is transmitted through TCP based VPN tunnels.<br />
<br />
SRFLX and RELAY candidates are obtained using the STUN and TURN server configuration passed by the App (e.g the ''softphone'' App) as part of the ''AllocChannel'' request.<br />
<code>{"mt":"AllocChannel","channel":"81429cba-396d-43de-8a76-ec020ba8796e","iceServers":[{"urls":"turn:myturn.domaincom:4077?transport=udp","username":"turnuser","credential":"pwd","credentialType":"password"},{"urls":"stun:mystun.domain.com:4077"}],"dn":"Foo Bar","type":"RemoteRtp","kind":"video"}</code><br />
<br />
===== Codecs =====<br />
<br />
The installed myApps launchers provide codecs that can be used by softphone apps for media streams. When running in a web browser the codecs depend on the browser version and operating system. See the documentation of your browser for details.<br />
<br />
The following codecs are supported:<br />
<br />
{|<br />
!style="text-align:left;width:100px;"|Codec<br />
!style="width:100px"|Windows-Launcher<br />
!style="width:100px"|Android<br />
!style="width:100px"|iOS<br />
!style="width:100px"|macOS<br />
!style="width:100px"|Firefox (Browser)<br />
!style="width:100px"|Chrome (Browser)<br />
!style="width:100px"|Edge (Browser)<br />
!style="width:100px"|Safari (Browser)<br />
!style="width:100px"|Opera (Browser)<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Audio<br />
|-<br />
|G711A<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G711u<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G722<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|G729<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729A<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729B<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|G729AB<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|-<br />
|[https://caniuse.com/#search=Opus OPUS-NB]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|[https://caniuse.com/#search=Opus OPUS-WB]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Video<br />
|-<br />
|[https://caniuse.com/#search=VP8 VP8]<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|-<br />
|[https://caniuse.com/#search=VP9 VP9]<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔**<br />
|-<br />
|[https://caniuse.com/#search=H264 H264]<br />
|style="color:green;text-align:center"|✔**<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|style="text-align:left; background-color:lightgray" colspan="10"|Application Sharing<br />
|-<br />
|Share<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|-<br />
|Watch<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔*<br />
|style="color:green;text-align:center"|✔*<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:green;text-align:center"|✔<br />
|style="color:red;text-align:center;font-weight:bold;"|X<br />
|style="color:green;text-align:center"|✔<br />
|}<br />
<br />
''* small presentation only''<br><br />
''** only for 1:1 calls, not for conferences<br />
<br />
===== Video capture =====<br />
<br />
The default resolution for video capture is 1280x720 if available. Otherwise, 640x480, 352x288 or 320x240 will be used. The frame rate is 30 fps if available, otherwise 15 fps. The resulting average bandwidth could reach 1 Mbps.<br />
<br />
===== Application sharing =====<br />
<br />
Screen content will be transmitted by the presenter.<br />
<br />
===== Device handling =====<br />
<br />
The RTP service enumerates microphones, loudspeaker, cameras and ringing devices and notifies apps when devices come and go. It is up to the apps using the devices to store preferences.<br />
<br />
The RTP service also enables some extended features (such as hook switch or volume control) for supported USB headsets or Bluetooth headsets connected to myApps.<br />
<br />
For this to work, vendor specific development kits from Jabra, Epos (formerly Sennheiser) and Poly (formerly Plantronics) are integrated.<br />
<br />
Note that it is possible to inhibit the start of the Sennheiser SDK (SenncomSDK.exe) using the <code>DISABLEHEADSETS</code> directive of the installer (see [[#MSI_parameters | MSI parameters ]] below).<br />
The supported headset-SDKs determine which headset vendors are recommended to be used with the myApps softphone app. <br />
<!--Keywords: myapps softphone supported headsets sdk--><br />
<br />
===== Ring tones =====<br />
<br />
Ring tones can be played. Apps can choose the tone from a pre-defined list of ring tones.<br />
<br />
On Windows, custom ring tones can be uploaded as .mp3 files to the <code>ringtones</code> sub-directory of myApps' roaming directory (which usually is in <code>C:\Users\...\AppData\Roaming\innovaphone\myApps\ringtones</code>).<br />
<br />
On Android, custom ring tones can be added to the system via Android settings.<br />
<br />
On iOS, custom ring tones can be uploaded as .mp3 files to the <code>Ringtones</code> subdirectory of the myApps file share that is available in iTunes if the iPhone has been connected via USB.<br />
<br />
On macOS, custom ring tones can be uploaded as .mp3 files to <code>~/Library/Containers/com.innovaphone.client-macos/Data/Documents/Ringtones</code>.<br />
<br />
===== Debugging =====<br />
For extended debugging, turn on the ''Audio'', ''Media'' and ''AppSharing'' traces in myApps.<br />
<br />
=== Hot keys ===<br />
On Windows and macOS systems, myApps platform services can listen for hot keys and invoke certain functions. Invocation is done by sending API messages to myApps which passes it to an appropriate API provider (in the cases described here, this will be a ''phone'' or ''softphone'' or ''rcc'' App typically. See [[{{NAMESPACE}}:Concept_myApps#Client_APIs_and_default_apps | Client APIs and default apps]] for more details about this mechanism.<br />
<br />
The hot keys can be specified using the ''advanced settings'' user interface (see [[#UI_elements | UI elements]] below. Any of the function keys F1 to F11 (optionally combined with up to two modifier keys ''alt'', ''ctrl'', ''shift'' or ''win'') can be chosen for each function. If you do not want to start the call with "Hotkey+Enter" because you would have to wait for the focus, the hotkey can also be pressed twice and the number is dialled directly.<br />
<br />
; dial selected number : Initiates a call using the currently selected text as target.<br />
<br />
: A ''PrepareCall'' message with the ''text'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":"mt":"PrepareCall","text":"13","adjust":true}}</code><br />
<br />
; accept call : Accepts a currently alerting call.<br />
<br />
: A ''ConnectCall'' message will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"ConnectCall"}}</code><br />
<br />
; reject/disconnect call : Rejects a currently alerting call or disconnects an active call.<br />
<br />
: A ''DisconnectCall'' message will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"DisconnectCall"}}</code><br />
<br />
=== URL Handler ===<br />
<br />
On Windows systems, two URI-handler are installed with the myApps platform services. Windows will call up this URI handler when a user clicks on an appropriate link, for example in a web site.<br />
<br />
The handler will the send an API message to myApps which passes it to an appropriate API provider (in the cases described here, this will be a ''phone'' or ''softphone'' or ''rcc'' App typically. See [[{{NAMESPACE}}:Concept_myApps#Client_APIs_and_default_apps | Client APIs and default apps]] for more details about this mechanism.<br />
<br />
; tel URI : call a number, e.g. <code>tel:4711</code><br />
<br />
: A ''PrepareCall'' message with the ''num'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"StartCall","num":"4711","adjust":true}}</code><br />
; sip URI : call a SIP name, e.g. <code>sip:zkl@innovaphone.com</code><br />
<br />
: A ''PrepareCall'' message with the ''sip'' argument set to the selected text and the ''adjust'' argument set to <code>true</code> will be sent to the [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm ''com.innovaphone.phone'' API].<br />
:: <code>{"mt":"ApiRequest","apiId":"com.innovaphone.phone","consumer":"@local-ae2fc2ab74-3f1e-4ab9-b215-d42f213520317","msg":{"mt":"StartCall","sip":"zkl@innovaphone.com","adjust":true}}</code><br />
<br />
On macOS systems myApps might be made the default application to handle tel URI e.g. <code>tel:4711</code> via Apple FaceTime. Open the "FaceTime" menu "Settings..." and select myApps as "Default for phone calls".<br />
<br />
=== User activity ===<br />
On Windows and macOS systems, the myApps platform services can monitor user keyboard/mouse activity and change the user's presence state after a certain amount of inactivity. The timeout can be specified using the ''advanced settings'' user interface (see [[#UI_elements | UI elements]] below.<br />
<br />
myApps will then send a [https://sdk.innovaphone.com/doc/appwebsocket/myApps.htm#SetUserActivity''SetUserActivity''] message to the PBX using the ''myApps'' protocol.<br />
<br />
: <code>{"mt":"SetUserActivity","inactive":true}</code><br />
<br />
This will change the ''status'' property of the ''im:'' contact for the user's own presence and hence result in a presence update from the PBX to myApps<br />
<br />
: <code>{"mt":"UpdateOwnPresence","presence":[{...},{"contact":"im:","activity":"","status":"closed"}]}</code><br />
The ''closed'' status is reflected in the grey status color when displaying a contact [[Image:myapps-inactive.png]].<br />
<br />
On iOS and Android, the state is set to ''inactive'' as soon as the App is brought to background.<br />
When myApps platform services are not available (i.e. when running the web application in a browser solely) a limited user activity monitoring is available: the state is set to active when the web page is not used for more than 5 minutes.<br />
<br />
=== Recording ===<br />
<br />
The new launcher offers the possibility to record the audio of incoming and outgoing calls. In order to activate that functionality the URL of the recording instance must be configured in either the PBX (PBX->myApps->Config: Recording URL) or the softphone App (Settings->Audio Recording (URL)) <br />
<br />
[[Image:PBX-Recording-Settings.png]] [[Image:Recording-Softphone-Settings.png]]. <br />
<br />
As long as that URL is configured the audio data of all calls are stored as pcap-files under that URL.<br />
If the URL points to a CF device in the PBX, write access must be granted for that URL (PBX->Services->HTTP->Server:Public compact flash access) and if the URL points to the recording app, the files can be accessed via the recording app [[{{NAMESPACE}}:Concept_App_Service_Recordings|recording]].<br />
<br />
<br />
Under PBX->myApps the administrator can set a certain default behaviour of the audio recording like whether or not the recording should start automatically at the beginning of the call (Recording by Default ON/OFF), only calls with external numbers should be recorded (Record external calls only) or whether or not the user should be able to start/stop the recording himself (Allow user incall recording control). Except for the last parameter these parameters can also be modified by the user in its softphone settings if the administrator doesn't set the FORCE flag.<br />
<br />
If the user was allowed by the admin to control the recording a recording switch is active during the call when the "Media" Panel is opened. There the audio recording may be stopped and continued at will. A red recording notice is shown in the top right corner when the recording actually takes place.<br />
<br />
[[Image:Recording-incall-switch.png]]<br />
<br />
=== Notifications ===<br />
<br />
The myApps platform services can use the OS specific notification mechanism (e.g. ''desktop notifications'' on Windows) to display messages (e.g. ''incoming new chat message'') to the user.<br />
<br />
Note that the actual rendering of the notification is under control of the OS. Therefore, myApps must be allowed to show notifications and its appearance can be restricted by OS native settings.<br />
<br />
==== Microsoft Windows Notifications ====<br />
<br />
Microsoft Windows Server editions (2016, 2019, 2022) are just capable of showing a single ''IncomingCall'' notification at the same time (we couldn't find a workaround for this limitation).<br/><br />
An ''IncomingCall'' notification is visible the whole time instead of being moved to the action center after a certain time.<br/><br />
<br/><br />
A notification about a missed call uses the ''IncomingCall'' type so that this notification is visible until the user returns.<br/><br />
Due to the above limitation, on a new arriving call such a missed call notification is transformed to a default notification which will be moved to the action center automatically.<br/><br />
<br/><br />
On non server editions, you can have multiple IncomingCall notifications at the same time (so two parallel incoming calls will be indeed notified at the same time), but the missed call notification handling is the same on both platforms!<br />
<br />
Thus there will be always just '''one''' missed call notification visible and previous missed calls can be found inside your action center!<br />
<br />
To see myApps notifications, ensure:<br />
* System -> Notifications <br />
** enable notifications<br />
** disable "Do not disturb" or allow myApps as priority application while "Do not disturb" is active<br />
** enable notifications for myApps in the list of applications<br />
* System -> Focus <br />
** if a focus session is active and the "Do not disturb" is activated during a focus session, make sure that myApps is a priority application (see above)<br />
<br />
=== Local phonebook access ===<br />
'''Contact Search:''' The myApps platform services implement an ''API provider'' for the [http://sdk.innovaphone.com/web1/com.innovaphone.search/lib1_api_search.htm ''com.innovaphone.search'' API]]. They perform search capabilities on the OS' local phone books which can be used by Apps like the ''phoneapp''.<br />
<br />
Apps would send a ''Search'' request to the API:<br />
<br />
: <code>{"mt":"ApiRequest","consumer":"dev:SwPh_zkl_5e42e884","provider":"*","src":"4","msg":{"mt":"Search","type":"contact","search":"john doe"},"apiId":"com.innovaphone.search"}</code><br />
Search results are delivered as ''SearchInfo'' messages:<br />
<br />
: <code>{"mt":"ApiResult","src":"3","provider":"@local-8125d22e37-519d-4056-bfe5-c52ef2ae8fabb0","consumer":"dev:SwPh_zkl_5e42e884","client":"@client-f62702dd86-be3f-47fc-b4bc-7a21627b75b2ea","msg":{"mt":"SearchInfo","relevance":2000,"adjust":true,"type":"contact","contact":{"givenname":"John","sn":"Doe","company":"ACME","position":"Head of everything","telephonenumber":["11111","22222"],"homephone":["+4944444","33333"],"mobile":["+49 (123) 55555"]}},"api":"com.innovaphone.search"}</code><br />
<br />
'''Reverse Lookup:''' The myApps platform services implement an ''API provider'' for the [http://sdk.innovaphone.com/web1/com.innovaphone.phonelookup/lib1_api_phonelookup.htm ''com.innovaphone.phonelookup'' API]. They perform search capabilities on the OS' local phone books which can be used by Apps like the ''phoneapp''.<br />
<br />
Apps would send a ''Lookup'' request to the API: <br />
<br />
: <code>{"mt":"ApiRequest","consumer":"dev:SwPh_zkl_5e42e884","provider":"*","src":"4","msg":{ mt: "Lookup", prefixIntl: "000", prefixNtl: "00", prefixExt:"0", area: "7031", country: "49", lookup: "0004970311234567" },"apiId":"com.innovaphone.lookup"}</code><br />
<br />
Search results are delivered as ''LookupInfo'' messages:<br />
<br />
: <code>{"mt":"ApiResult","src":"3","provider":"@local-8125d22e37-519d-4056-bfe5-c52ef2ae8fabb0","consumer":"dev:SwPh_zkl_5e42e884","client":"@client-f62702dd86-be3f-47fc-b4bc-7a21627b75b2ea","msg":{mt: "LookupInfo", dn: "Jake Blues", contact: { telephonenumber: ["0004970311234567"], givenname: "Jake", sn: "Blues", company: "Blues Brothers" </code><br />
<br />
==== Windows ====<br />
On Windows, the search and lookup are performed in all of the user's Outlook contact folders. As opposed to the search implemented in the ''Contacts'' and ''Users'' App, all items are returned which match any of the search words (i.e. searching for ''a b'' will return items matching either ''a'' or ''b'').<br />
<br />
; searched properties : firstname, lastname<br />
; returned properties : Following Outlook contact phone number properties are returned (if available):<br />
<br />
:* OFFICE_TELEPHONE_NUMBER as ''telephonenumber''<br />
:* OFFICE2_TELEPHONE_NUMBER as ''telephonenumber''<br />
:* HOME_TELEPHONE_NUMBER as ''homephone''<br />
:* HOME2_TELEPHONE_NUMBER as ''homephone''<br />
:* MOBILE_TELEPHONE_NUMBER as ''mobile''<br />
:* BUSINESS_FAX_NUMBER as ''facsimiletelephonenumber''<br />
Note that contact information is cached in the search provider. Updated contacts may therefore become effective after a while only.<br />
Outlook search will create its own trace file <code>myAppsOutlookSearch-</code>''date-time''<code>.txt</code> in the standard trace directory.<br />
<br />
This search provider is always installed and can be disabled. There is no need (nor possibility) to enable it in the ''Apps'' tab of the PBX's user object. Also, no ''App'' object needs to be created for it.<br />
<br />
==== Android/iOS ====<br />
The search and lookup are performed in the contacts.<br />
<br />
==== macOS ====<br />
The search and lookup are performed in the contacts. If you wish to disable local contact lookup, go to system settings - Security & Privacy and disable the access to contacts for myapps.<br />
<br />
=== Microsoft Office integration ===<br />
<br />
The myApps platform services has a ''office presence provider'' that can provide the user's presence state to Office applications. See [[{{NAMESPACE}}:Concept_myApps_Office_Integration|myApps Office Integration]] for details.<br />
<br />
This feature is installed by default. However, it can be disabled using the ''OFFICEPRESENCE'' MSI Parameter. Also, a check-mark is available in the setup dialog.<br />
<br />
=== Call an external application for calls ===<br />
<br />
Phone Apps (such as the phoneapp or softphone) can initiate the start of an external application when a new call appears (either incoming or outgoing). The actual spawning of the application is done by the myApps platform service. Also, the application properties (such as e.g. the executable's path) is configured in the myApps platform services (see [[#UI_elements|Advanced settings]] in the ''UI elements'' section below).<br />
<br />
A number of arguments can be passed to the application by substituting $-variables in the ''Parameter'' field:<br />
<br />
; $n : phone number as dialed (called party number for outgoing calls) or received (calling party number for incoming calls)<br />
<br />
; $N : called or calling party number in ''national'' format (e.g. 07031730090)<br />
<br />
; $I : called or calling party number in ''international'' format (e.g. +497031730090)<br />
<br />
: note that both $N and $I only work if $n includes both subscriber number and area code (e.g. 07031730090). Otherwise they are equal to $n<br />
<br />
; $d : display name of peer (if known)<br />
<br />
; $c : conference id<br />
<br />
: this is a globally unique ID for this call and may be used to relate the call to the ''guid'' found in the CallInfo structure in the [http://wiki.innovaphone.com/index.php?title=Reference10:SOAP_API#CallInfo SOAP-API] and [http://sdk.innovaphone.com/doc/appwebsocket/RCC.htm RCC-API ]. Also, corresponding [[Reference10:Call_Detail_Record_CDR_PBX|CDRs]] can be related using the ''event'' tag's ''conf'' attribute.<br />
The start of an external application can be requested using the ''com.innovaphone.externalapps'' API.<br />
<br />
Some setup examples are [[Howto:Integrate_External_Apps_in_innovaphone_UC_clients|shown here]].<br />
<br />
=== Push ===<br />
<br />
Mobile operating systems usually inhibit network operation of apps which run in the background or are closed by the user. This is done in order to reduce battery consumption. Unfortunately, this also stops such apps to maintain a registration by regularly sending ''keep alive'' messages to a server (in our case to the PBX). As a result, myApps will be disconnected from the PBX. When the PBX determines that there is an event for the application which needs a response, it needs to wake up the app using a dedicated channel provided by the operating system. This mechanism is know as ''push''. When running on iOS or Android, myApps supports ''push''. <br />
<br />
For ''push'' to work, a [[{{NAMESPACE}}:PBX/Objects/Push|''push object'']] needs to [[Course13:IT Connect - 10.1 Push Object | be configured in the PBX ]]. Also, it needs to be enabled on the mobile phone for the myApps app.<br />
This mechanism is quite similar in v12 and v13, so you can refer to [[{{NAMESPACE}}:Concept Push Notifications for myPBX iOS and Android]] for more details. <br />
<br />
Also, helpful hints can be found in [[Howto:Troubleshoot v13 Push with myApps for Android and iOS]].<br />
<br />
=== App Proxy ===<br />
<br />
myApps runs further ''Apps'' (such as e.g. the ''phoneapp'') as a web page in an IFRAME of the browser myApps is running in. The App's page code is loaded either from the PBX or from an ''application platform'' (AP). This however would mean that the App's IFRAME would remain empty (a dead white screen) when the PBX or AP is not available. To make sure the App can start-up anyway, the myApps platform services feature the so-called ''App Proxy''. This is a caching proxy that caches all the App code so it is available even in case of network failure. When myApps runs in the context of the platform services, Apps are therefore not loaded from the App source directly, but from the local App proxy. <br />
<br />
The cached files are stored in the PCs local file system in the <code>C:\Users\...\AppData\Local\innovaphone\myApps\appproxy</path></code>. There is no configuration required. However, if myApps seems to run with outdated or corrupt cached copies of the App, you can safely delete the entire directory.<br />
<br />
=== Auto update ===<br />
<br />
On Windows and on macOS, the myApps platform services can auto-update themselves to a common version. This is controlled by the [[{{NAMESPACE}}:PBX/Config/myApps#Launcher_Software_Update | ''Launcher Software Update'' ]] settings under ''PBX/Config/myApps'' in the PBX. <br />
<br />
When myApps is started or the user logs in or myApps needs to re-connect to the PBX, the platform services will use the [http://sdk.innovaphone.com/web1/com.innovaphone.client/lib1_api_client.htm com.innovaphone.client API] to learn the desired version (''launcherUpdateBuild'', which is part of the API's ''model''). If this differs from the current version, the platform services will try to download the respective new version. <br />
<br />
<code><br />
{<br />
"mt": "ApiUpdate",<br />
"apis": {<br />
"com.innovaphone.client": {<br />
"@client": {<br />
"title": "innovaphone myApps",<br />
"model": {<br />
"launcher": true,<br />
"launcherUpdateBuild": "134906",<br />
"appStoreUrl": "http://store.innovaphone.com/alpha/download/"<br />
}<br />
}<br />
}<br />
}<br />
}</code><br />
<br />
The installation of the downloaded version is done by the ''innovaphonemyAppsUpdateService''. This service is installed and enabled during the initial installation of the myApps platform services. To disable auto-update, either leave the ''Launcher Software Update'' settings empty or set the service's start mode to ''disabled'' in the Windows ''services control panel''.<br />
<br />
Note that on Windows the update service does not work on terminal servers. Administrators must do myApps base services updates using standard windows mechanisms.<br />
<br />
Note that on macOS if myApps has been installed from the Apple Store it is assumed that auto update from the PBX is not desired and disabled therefore.<br />
<br />
On Android/iOS/macOS updates can be downloaded from the respective app store.<br />
<br />
The ''Devices'' app can not update software installed on Windows PCs directly. However, when the PBX is updated using an ''update job'' in the ''Devices'' App, the ''Launcher Software Update'' settings will be updated accordingly and hence the myApps base services will ultimately also be updated to the same version.<br />
<br />
==== Auto update flow on Windows ====<br />
<br />
* On start of myApps, myApps checks if an update is available and ready for installation<br />
** if yes, the update is installed directly, without user interaction (a popup is shown during the installation)<br />
** if nto, myApps starts<br />
* if an update is available while myApps is already running, an update notification will be shown which let's the user choose to install the update now or later (the notification will then popup again after one hour)<br />
<br />
==UI elements ==<br />
There are a few user interfaces provided by the platform services:<br />
===tray-icon (Windows only) ===<br />
::[[Image:myapps-tray.png]]<br />
:Allows to<br />
:* terminate myApps<br />
:* toggle the ''autostart'' state<br />
:* toggle the ''show in task bar'' state<br />
:* open the trace folder<br />
:<br />
=== PBX connect form===<br />
:: [[Image:myapps-connect.png]]<br />
: Allows the user to specify the connect data for the PBX (i.e. IP address or DNS name)<br />
:<br />
=== Advanced settings===<br />
::[[Image:myapps-settings0.png]]<br />
::[[Image:myapps-settings.png]] [[Image:myapps-settings2.png]] [[Image:myapps-settings3.png]]<br />
<br />
: Allows to modify various platform dependant settings (such as e.g. the hotkey selection on Windows)<br />
<br />
== Interfaces ==<br />
=== Provided APIs ===<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.search/lib1_api_search.htm com.innovaphone.search] : access to local phone book entries by the [[#Local_phonebook_access|Local phonebook access]] component.<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.launcher/com.innovaphone.launcher.htm com.innovaphone.launcher] : display of OS specific user notifications and receipt of related user actions<br />
; com.innovaphone.notificationhandler : ???<br />
; com.innovaphone.externalapps : to start external applications, see [[#Call_an_external_application_for_calls |Call an external application for calls ]] above<br />
; com.innovaphone.callkit : ???<br />
<br />
=== Used APIs ===<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.phone/com.innovaphone.phone.htm com.innovaphone.phone] : used to initiate new or manipulate existing calls by the [[#Hot_keys|Hot keys]] and [[#URL_handler|URL handler]] components.<br />
<br />
; [http://sdk.innovaphone.com/web1/com.innovaphone.client/lib1_api_client.htm com.innovaphone.client] : the model is used to learn the update settings, see [[#Auto_update|Auto update]] above<br />
<br />
=== Protocols ===<br />
<br />
; [https://sdk.innovaphone.com/doc/launcher/Media.htm Media Protocol] : used by apps to allocate RTP channels, see [[#RTP_service_for_audio.2C_video_and_data|RTP service for audio, video and data]] above<br />
<br />
== Related App Services ==<br />
<br />
none<br />
<br />
== Known limitations ==<br />
; Button on Bluetooth headsets not functional on myApps for iOS : The button of Bluetooth headsets doesn't provide to accept or disconnect calls. The button on wired earphones does.<br />
<br />
; Incoming call as banner on myApps for iOS : Since iOS 14 the iOS CallKit presents incoming calls as a banner leaving the original green answer button of myApps visible. Use only the blue button of the banner to accept the call or change iPhone Settings, App "Phone", "Incoming Calls" to "Full Screen" to hide the myApps user interface again during call answering.<br />
<br />
; Windows Server 2016 (Windows 10 Build 1607) : windows just shows the first notification. Further notifications aren't displayed until the previous ones are removed from the notification center. Current windows builds do not show this behaviour anymore.<br />
<br />
; Problems on Mac computers with Yealink USB headsets<br />
: we have received reports that myApps quits unexpectedly on some Mac computers when a Yealink headset is plugged in. Unfortunately, we could not find out the cause yet. If you use Yealink USB headsets and have a similar issue, please open a support ticket and send myApps traces.<br />
<br />
; Windows surface devices may not work correctly<br />
: Chromium does not get touch keyboard events. USB Keyboards may not be recognized either.<br />
<br />
= Installation =<br />
<br />
== Windows ==<br />
<br />
myApps platform services are installed on Windows using the .msi file found in the ''myApps Windows'' package from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
myApps can update itself automatically, see [[#Auto_update|Auto update]] above.<br />
<br />
=== MSI Parameters and install options ===<br />
<br />
The MSI installer of myApps for Windows supports the following parameters and can be edited with [https://docs.microsoft.com/en-us/windows/win32/msi/orca-exe Microsoft Orca]. You can add your parameters in the table ''property''.<br />
<br />
; SERVER (REG_SZ): the PBX's server URL<br />
; OFFICEPRESENCE (REG_DWORD): '''false''' to disable presence integration in Microsoft Office<br />
: this is also available as a check-mark when running the install manually<br />
<br />
; DISABLEHEADSETS (REG_DWORD): '''true''' to disable headsets support, see [[#Device_handling|Device handling]] above<br />
<br />
; EXTERNALAPPS (REG_SZ): pre-define external applications, see [[#Call_an_external_application_for_calls|Call an external application for calls]] above<br />
: e.g. <code>"{""externalApps"":[{""id"":0,""name"":""Wireshark"",""path"":""C:\\Program Files\\Wireshark\\Wireshark.exe"",""param"":""test $I""}]}"</code><br />
<br />
; FORCERESTART (REG_DWORD): '''true''' (or any string ...) kills myApps during the installation and restarts it for the currently logged in user, if it was running<br />
<br />
; DISABLELOCALHOST (REG_DWORD): '''true''' to disable use of '''localhost''' string to access the local webserver. Use '''127.0.0.1''' instead<br />
<br />
Current settings are stored in the registry at <code>Computer\HKEY_CURRENT_USER\Software\innovaphone\myApps</code> or at <code>Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\innovaphone\myApps</code><br />
<br />
Boolean values like OfficePresence are stored in registry entries with type REG_DWORD and values 1 or 0. 0 disables the setting and 1 enables it.<br />
<br />
== iOS ==<br />
<br />
myApps platform services are installed on iOS by loading ''innovaphone myApps'' from the ''App Store''.<br />
<br />
== macOS ==<br />
<br />
myApps platform services might be installed from the Apple store directly. An installer package <code>myapps.pkg</code> and a disk image <code>myapps.dmg</code> is also available from the innovaphone app store. Install <code>myapps.pkg</code> by double-click on the file and follow the instructions of the installer. myApps becomes available in the Applications folder and can be opened by double-click. Or download and open <code>myapps.dmg</code> and double klick myApps. If desired integrate it into the app dock by right click, ''Options, Keep in the dock''.<br />
<br />
If installed from the innovaphone app store, myApps can update itself automatically, see [[#Auto_update|Auto update]] above.<br />
<br />
If installed from the Apple store, macOS notifies about updates on the Apple store. myApps [[#Auto_update|Auto update]] is disabled then.<br />
<br />
If a clean-install of the client is necessary, the folder "/Users/username/Library/Containers/myapps" needs to be deleted. To be on the safe side also delete it from the trash bin.<br />
<br />
=== Preferences ===<br />
<br />
macOS supports preference settings that can be set via a shell command or via Mac remote management<br />
<br />
<code>> defaults write com.innovaphone.client-ios server "PBX-server-URL"</code><br />
<br />
The following parameters and can be set through this method:<br />
<br />
; server: the PBX's server URL<br />
<br />
=== Using Sennheiser headsets ===<br />
If you use Sennheiser headsets, you should also install the <code>DSEA_SDK_v</code>''version''<code>.pkg</code> package. Without that, audio will still work, but not the controls on the headset.<br />
<br />
== Android ==<br />
<br />
myApps platform services are installed on Android by loading ''innovaphone myApps'' from the ''Play Store''.<br />
<br />
= Configuration =<br />
<br />
== Server configuration ==<br />
When opening myApps for the first time, the user is prompted for the Server. Usually only the hostname (DNS host name or IP address) needs to be configured.<br />
<br />
But there are more options for special PBX configurations.<br />
<br />
; Non-standard HTTPS port<br />
: If the PBX uses a non-standard HTTPS port, it must be appended to the host name separated by a colon (<code>:</code>).<br />
: Example: <code>pbx.example.com:4444</code> (expands to <code>https://pbx.example.com:4444/PBX0/APPCLIENT/appclient.htm</code>)<br />
; DynPBX module name<br />
: If the PBX is a DynPBX, the module name must be appended to the host name separated by a slash (<code>/</code>).<br />
: Example: <code>pbx.example.com/PBX0-slave1</code> (expands to <code>https://pbx.example.com/PBX0-slave1/APPCLIENT/appclient.htm</code>)<br />
; Softphone physical location<br />
: If user defined physical location shall be used for softphone, you can append it using a parameter <code>#phys=</code>.<br />
: Example: <code>pbx.example.com#phys=slave</code> (expands to <code>https://pbx.example.com/PBX0/APPCLIENT/appclient.htm#phys=slave</code>)<br />
<br />
Example 1: PBX pbx.example.com with standard configuration<br />
pbx.example.com<br />
<br />
Example 2: PBX slave.example with DynPBX module ID PBX0-slave, HTTPS port 4444 and physical location master<br />
slave.example.com:4444/PBX0-slave#phys=master<br />
<br />
=== HTTP proxy support ===<br />
<br />
myApps platform services do support operation via HTTP proxy now. If one or more proxies have been configured in the network settings of the operating system for the active network connection, HTTP CONNECT tunnels are established.<br />
<br />
On Windows user name and password can be specified for the tunnel servers as generic credentials in the credentials manager (Anmeldeinformationsverwaltung). The name of the credentials must be the tunnel server hostname.<br />
<br />
On Android user name and password can be specified through Android ''Settings, Accounts'' by adding a myApps ''HTTP Proxy Credentials'' account. The name of the account must be the tunnel server hostname.<br />
<br />
== Platform specific settings ==<br />
When myApps runs under the myApps platform services, it will show various platform specific settings as part of its ''burger menu'', so the user can set them. See ''Advanced settings'' in [[#UI_elements|UI elements]] above.<br />
<br />
Some options can also be set globally for all myApps clients in the PBX's [[{{NAMESPACE}}:PBX/Config/myApps#Client_Settings|PBX/Config/myApps ''Client Settings'']]<br />
{|<br />
! style="text-align: left; font-weight: bold" | Option<br />
<br />
! style="text-align: left; font-weight: bold" | Description<br />
<br />
! style="text-align: left; font-weight: bold" | Where to set<br />
<br />
!<br />
! style="text-align: left; font-weight: bold"| Availability<br />
|-<br />
<br />
| || || User menu || PBX ''Client Settings'' || Windows || iOS || Android || macOS<br />
<br />
|-<br />
| Autostart || Launch myApps on login || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Appear offline after || controls after which idle time a user is considered ''inactive''. See [[#User_activity|User activity]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
|-<br />
<br />
| Hotkeys || Hotkeys for call dial, accept, reject. See [[#Hot_keys|Hot keys]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Docking || Docking mode (left, right, none). See [[#???|??]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| Desktop notifications|| Turn on/off platform notifications. See [[#Notifications| Notifications]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| VPN || Disable VPN address for ICE candidate selection. See [[#RTP_ports| RTP ports]] above || &#10004; ||&#10004; ||&#10004; || &#10007; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| Show in taskbar|| Show myApps in the taskbar in addition to it's tray icon. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
<br />
|-<br />
<br />
| Log flags || turn on/off certain trace levels. See [[#Troubleshooting|Troubleshooting]] below. || &#10004; ||&#10004; ||&#10004; || &#10004; || &#10004; || &#10004;<br />
<br />
|-<br />
<br />
| External applications || define the applications available for Apps to be started. See [[#Call_an_external_application_for_calls|Call an external application for calls]] above. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10004;<br />
<br />
|-<br />
<br />
| Ring in headset || send ring tone for incoming to headset instead of loudspeaker. || &#10004; ||&#10007; ||&#10004; || &#10007; || &#10007; || &#10007;<br />
|}<br />
== Start parameters for Windows ==<br />
<br />
On Windows, it is not possible to pass start parameters from the [https://www.chromium.org/developers Chromium documentation] to the myApps process.<br />
<br />
== OS Settings for Windows ==<br />
Windows settings can influence the display of ''Desktop notifications''. See [https://support.microsoft.com/en-us/help/4028678/windows-10-change-notification-settings Change notification settings in Windows 10] for details.<br />
<br />
== OS settings for Android ==<br />
; Events : The appearance of notifications can be controlled here.<br />
<br />
; Call accounts : For proper incoming call signaling, the call account ''myApps'' needs to be enabled. Note that on Samsung smartphones the call account switch likely toggles back and a few tries may need to be done until it persists. Please double-check the state.<br />
<br />
; Preferred Calling Account : Choose which calling account (myApps/SIM/..) should be used for outgoing calls initiated from within the native phone app / phone book.<br />
<br />
; Background data, unlimited data usage : Grant background data use to enable ''myApps'' to connect to the PBX immediately on an incoming call.<br />
<br />
; Overlaying : This setting is not needed if call account ''myApps'' has been enabled. Should there be a reason for not enabling call account ''myApps'', the permission for overlaying needs to be granted on Android 10 or higher for proper call signaling.<br />
<br />
Note: If no SIM card is installed some Android smartphones exhibit a problem dialing from the smartphone contacts. The contacts app shows a choice ''Select SIM card for this call'' but all possible dialers are greyed out. In this case make myApps the default phone app in Android settings ''Apps, Default apps, Telephony''.<br />
<br />
== OS settings for iOS ==<br />
; Notifications : The appearance of notifications can be controlled in iOS ''Settings, myApps''.<br />
<br />
== OS settings for macOS ==<br />
<br />
; Notifications : The appearance of notifications can be controlled in macOS ''Preferences, Notifications, myApps''.<br />
<br />
=Troubleshooting=<br />
<br />
myApps platform services can write various traces for debugging. Trace can be turned on and off selectively in the [[#Advanced settings|Advanced settings]].<br />
<br />
The following trace flags can be set:<br />
{|<br />
!style="text-align: left; font-weight: bold" | Abbreviation<br />
<br />
!style="text-align: left; font-weight: bold" |code<br />
<br />
!style="text-align: left; font-weight: bold" | description<br />
<br />
|-<br />
| App||0x000000001|| logs from the App Service itself<br />
|-<br />
| DNS||0x000000008|| logs DNS requests and results<br />
|-<br />
| HTTP client||0x000000080|| http client logs<br />
|-<br />
| TLS||0x000000400|| TLS logs<br />
|-<br />
| TCP||0x000000800|| TCP logs<br />
|-<br />
| LDS||0x000001000|| local domain sockets<br />
|-<br />
| WebSocket client||0x000004000|| logs outgoing websocket connections<br />
|-<br />
| App WebSocket||0x000008000|| logs app websocket connections (e.g. from PBX objects to an App Service or from the UI to the App Service)<br />
|-<br />
| UDP||0x000200000|| UDP logs<br />
|-<br />
| DTLS||0x000400000|| logs DTLS handshake and messages<br />
|-<br />
| Media||0x000800000|| logs media events<br />
|-<br />
| Media channel||0x001000000|| logs RTP/SCTP media connections<br />
|-<br />
| ICE||0x002000000|| logs ICE messages between peers<br />
|-<br />
| TURN||0x004000000|| logs TURN messages between peers<br />
|-<br />
| AppSharing||0x008000000|| logs AppSharing connection<br />
|-<br />
| Audio||0x010000000|| logs Audio connection and headset events<br />
|-<br />
| Video||0x020000000|| logs video connection and webcam events<br />
|-<br />
| Browser||0x040000000|| logs Chromium events<br />
|-<br />
| Browser Console||0x080000000|| logs browser console events<br />
|-<br />
| AppProxy||0x100000000|| logs requests which are proxied between the local webserver and the remote server<br />
|-<br />
| Webserver||0x400000000|| enables webserver specific logs<br />
|-<br />
| Signaling||0x800000000|| enables logs in the signaling module for debugging calls<br />
|}<br />
''code'' can be or'ed and used as value for the ''Log flags'' field in [[{{NAMESPACE}}:PBX/Config/myApps#Client_Settings|PBX/Config/myApps/Client Settings]].<br />
<br />
; Windows :On Windows, traces are written to the <code>C:\Users\[UserName]\AppData\Local\innovaphone\myApps</code> directory.<br />
<br />
:* myApps-''date-time''.txt : main log file for the platform services<br />
<br />
:* myAppsOutlookSearch-''date-time''.txt : log file for the Outlook phone book access<br />
<br />
:* myAppsHookController-''date-time''.txt : log file for the hot-key interceptor (see [[#Hot_keys|Hot keys]])<br />
<br />
; :myApps update traces are written to the <code>%windir%\temp\</code> directory.<br />
:* myAppsInstall.txt: MSI installation file<br />
:* myAppsUpdateService-''date-time''.txt: myApps update service traces<br />
<br />
;Android : traces can be sent by e-mail.<br />
<br />
: also, an Android device might also be connected to a PC via an USB cable to get the traces. The files can be found in <code>Android/data/com.innovaphone.clientandroid/files</code><br />
<br />
; iOS : traces can be sent by e-mail.<br />
<br />
; macOS : traces can be sent by e-mail.<br />
<br />
: also, the files can be found in <code>~/Library/Containers/com.innovaphone.client-ios/Data/Documents/</code>. Press ''Alt+N'' followed by space to get tilde ''~''.<br />
<br />
= Known Problems =<br />
[[:Category:Problem_myApps_platform_services|Known Problems]]<br />
<br />
= Related Articles =<br />
* [[{{NAMESPACE}}:Concept_myApps]]<br />
* [[{{NAMESPACE}}:Concept_myApps_Redundancy]]<br />
* [[{{NAMESPACE}}:Concept_myApps_Office_Integration]]<br />
* [[{{NAMESPACE}}:Concept_myAPPs_Search_in_local-Outlook_Contacts]]<br />
* [[{{NAMESPACE}}:Call_Detail_Record_CDR_PBX]]<br />
* [[{{NAMESPACE}}:Concept Push Notifications for myPBX iOS and Android]]<br />
* [[Howto:Troubleshoot v13 Push with myApps for Android and iOS]]<br />
* [[{{NAMESPACE}}:PBX/Config/myApps]]<br />
<br />
[[Category:Concept_myApps_platform_services]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r3:Unite/WebSocket&diff=68186Reference13r3:Unite/WebSocket2023-07-11T09:54:40Z<p>Ckl: </p>
<hr />
<div>Define WebSocket connection to be used<br />
<br />
'''Configuration'''<br />
* '''Enable''': enable websocket server connection<br />
* '''Allow Untrusted''': allow untrusted TLS connections to the server.<br>Note: for security reasons, allowance is recommended to be active only temporarily to enable provisioning of a trusted server certificate<br />
* '''Server Address''': WebSocket server IPv4 or host address<br />
* '''Path''': path part of the URI<br />
* '''User''': authentication user<br />
* '''Password''': authentication password<br />
<br />
'''Status'''<br />
* '''WebSocket URI''': used WebSocket URI<br />
* '''Mode''': current connection mode (Persistent/tbd)<br />
* '''State''': current connection state (Active/Inactive)</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68169Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T09:46:58Z<p>Ckl: /* More sample code */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to authenticate towards the PBX<br />
<syntaxhighlight lang="json"><br />
{<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes a ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to the service. This is done using the ''postEvent'' mechanism again:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return an ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-admin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we were using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' messages in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r2 (or later)<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer (if not, use <code>htps://<ip-of-our-pbx/install.htm</code>)<br />
* complete the installer using IP addresses instead of DNS names, so that it installs a PBX and a fresh ''App Platform'' (you can of course also use DNS names during the Install if you have them operational for the PBX and App platform IP addresses. For running the sample code, it does not matter)<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. <br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68168Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T09:43:10Z<p>Ckl: /* Tandems */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to authenticate towards the PBX<br />
<syntaxhighlight lang="json"><br />
{<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes a ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to the service. This is done using the ''postEvent'' mechanism again:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return an ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-admin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' messages in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r2 (or later)<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer (if not, use <code>htps://<ip-of-our-pbx/install.htm</code>)<br />
* complete the installer using IP addresses instead of DNS names, so that it installs a PBX and a fresh ''App Platform'' (you can of course also use DNS names during the Install if you have them operational for the PBX and App platform IP addresses. For running the sample code, it does not matter)<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. <br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68165Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T09:28:06Z<p>Ckl: /* JSON */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to authenticate towards the PBX<br />
<syntaxhighlight lang="json"><br />
{<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' messages in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r2 (or later)<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer (if not, use <code>htps://<ip-of-our-pbx/install.htm</code>)<br />
* complete the installer using IP addresses instead of DNS names, so that it installs a PBX and a fresh ''App Platform'' (you can of course also use DNS names during the Install if you have them operational for the PBX and App platform IP addresses. For running the sample code, it does not matter)<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. <br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r1:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68164Reference13r1:Concept Talking to the v13 Application Platform using PHP2023-07-10T09:11:03Z<p>Ckl: </p>
<hr />
<div>'''Deprecation note:'''<br />
<br />
This article describes methods for connecting to the PBX as a user with the myApps protocol, that is not recommended.<br />
Instead automation scripts and external applications should connect to both the PBX and app services using the AppWebsocket protocol with an app object account.<br />
<br />
An updated version of this article is available in [[Reference13r2:Concept Talking to the v13 Application Platform using PHP]].<br />
<br />
Protocol details can be found in the SDK, see<br />
* [http://sdk.innovaphone.com/doc/appwebsocket/AppWebsocket.htm http://sdk.innovaphone.com/doc/appwebsocket/AppWebsocket.htm]<br />
* [http://sdk.innovaphone.com/doc/appwebsocket/Services.htm http://sdk.innovaphone.com/doc/appwebsocket/Services.htm]<br />
* [http://sdk.innovaphone.com/doc/tutorials/RCCfromExternalWebPage.htm http://sdk.innovaphone.com/doc/tutorials/RCCfromExternalWebPage.htm]<br />
<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the v13 ''App Platform ''<br />
* PBX running v13 firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs various ''App Services'' providing services which are used by ''Apps'' running in the ''myApps'' client context. ''Apps'' (written in JavaScript) are loaded from the ''App Platform'' and launched by the ''myApps'' client. They communicate to the ''App Services'' using a JSON based WebSocket protocol. ''Apps'' may also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. Creation of such ''App Services'' is facilitated by the [http://sdk.innovaphone.com/ ''v13 SDK'']. <br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to existing ''App Services'' from your own application directly (that is, not by virtue of an ''App'' running in the ''myApps'' client). This article is on how to do that. General considerations are given which are true for all programming languages, some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to an ''App Service'', you need to be logged in to the PBX. An ''App'' does not need to care about it, as the ''myApps'' client (which is the context all ''Apps'' run in) would take care of this. <br />
<br />
The process is as follows:<br />
* the application must log-in to the PBX<br />
** depending on the configuration of the PBX, the authentication type can be ''digest'' (the PBX itself knows the user credentials) or ''ntlm'' (the PBX passes the user credentials to a 3rd party authentication service using ''netlogon'')<br />
** depending on the configuration of the PBX, two factor authentication may be required. In this case, the calling application must be prepared that the login process may take a long time (the time needed to complete the two factor process, e.g. to click on the verification link sent by email)<br />
* the PBX will create and store a permanent session for this login and return special ''session credentials'' for this new session<br />
* subsequent log-ins can be done using the session credentials instead of the user credentials<br />
<br />
Please note that the PBX will create new session credentials for each such login. Also, the PBX will only store the last 8 such sessions. Excessive older sessions are deleted. For this reason, it is important that an application stores the session credentials received and uses them for all subsequent log-ins. Otherwise, some older sessions (which may be sessions created by the ''myApps'' client or other applications) would be removed. <br />
<br />
Once the calling application is logged in to the PBX, the authentication towards the ''App Service'' can take place. The process is as follows:<br />
* the calling application requests a ''challenge'' from the ''App Service''<br />
* the calling application passes this challenge to the PBX<br />
* the PBX (knowing the ''shared secret'' defined for the ''AppService'') creates a hash from some information about the user<br />
* the calling application passes the hash to the ''App Service''<br />
* the ''App Service'' computes the same hash and compares it to the one received from the application<br />
* if both match, the application is authenticated<br />
<br />
==== WebSocket ====<br />
''App Services'' communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code>{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are two message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use an optional configuration from a file called ''my-pbx-data.php''. If it is not present, default values are used.<br />
<br />
To have the scripts proper data for your environment, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxuser = "your-pbx-user-name";<br />
$pbxpw = "your-pbx-user-password";<br />
$pbxapp = "your-app-object-name";<br />
</syntaxhighlight><br />
<br />
==== Access App services using user credentials ====<br />
Our little example (available in <code>websocket-sample.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, an existing PBX user with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the ''Install'' procedure.<br />
<br />
===== Login =====<br />
As discussed above, to do so, we need to log-in to the PBX and then connect and authenticate to the respective ''App Services''. <br />
<syntaxhighlight lang="php"><br />
// login to PBX and devices and users<br />
$connector = new AppPlatform\AppServiceLogin(<br />
$pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(<br />
$devicesspec = new AppPlatform\AppServiceSpec("innovaphone-devices"),<br />
$usersspec = new AppPlatform\AppServiceSpec("innovaphone-users"),<br />
),<br />
true<br />
);<br />
$connector->connect();<br />
</syntaxhighlight><br />
<br />
To perform the necessary steps, an instance of class <code>AppServiceLogin</code> (note that in our sample code, all library code provided is in the <code>AppPlatform</code> name space) is used. The constructor arguments are <br />
; $PBX (string) : either the FQDN or the full websocket URI of the PBX . In the sample code, we use the FQDN <code>sindelfingen.sample.dom</code><br />
; $credentials (AppUserCredentials) : the users ''Name'' and ''Password'' wrapped in an object of type ''AppUserCredentials''<br />
; $appServiceSpec (AppServiceSpec[]) : an array of ''App Service'' specifications to select the ones to connect to, each wrapped in an object of type ''AppServiceSpec''<br />
<br />
The call to <code>connect()</code> creates and authenticates the WebSocket connections to the ''App Services'' and the ''PBX''. Note that a connection is made only if the user used for log-in has access to the selected ''App Service''.<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
// look at the PBX login<br />
if ($connector->getPbxA()->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to PBX");<br />
$pbxws = $connector->getPbxWS();<br />
$pbxloginresult = ($connector->getPbxA()->getResults());<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to PBX");<br />
exit;<br />
}<br />
</syntaxhighlight><br />
<br />
If the login succeeded, the WebSocket connection to the PBX and the log-in messages are retrieved. Next step is to verify the respective connections to the ''App Services'':<br />
<br />
<syntaxhighlight lang="php"><br />
// look at devices<br />
if ($connector->getAppAutomaton($devicesspec)->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to Devices");<br />
$devicesws = $connector->getAppAutomaton($devicesspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to Devices");<br />
exit;<br />
}<br />
<br />
// look at users<br />
if ($connector->getAppAutomaton($usersspec)->getIsLoggedIn()) {<br />
<br />
AppPlatform\Log::log("Logged in to Users");<br />
$usersws = $connector->getAppAutomaton($usersspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to Users");<br />
exit;<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the 2 ''App Services'' we are interested in. <br />
<br />
<syntaxhighlight lang="php"><br />
// now we have the authenticated websockets to our AppServices, so we can release the connector<br />
$connector = null;<br />
</syntaxhighlight><br />
That was the easy part :-)<br />
<br />
===== Implementing the WebSocket Protocol =====<br />
Life was easy so far as we have used the ''AppServiceLogin'' utility class. Now we want to implement a conversation with our ''App Services'' using the WebSocket connections we have set up before.<br />
<br />
Generally, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes and netlogon protocol peculiarities<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The Asynchronous Programming Model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called <code>FinitStateAutomaton</code>. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called <code>WSClient</code>). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the <code>AppServiceLogin</code> utility class creates such <code>WSClient</code> objects (which we retrieved by calling the <code>getAppWebSocket()</code> member function. <br />
<br />
However, if you try to instantiate such a class (like <code>$mya = new FinitStateAutomaton($mywebsocket)</code> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
So here we go and define a derived class:<br />
<syntaxhighlight lang="php"><br />
// an automaton which lists all devices in Devices<br />
class DeviceLister extends AppPlatform\FinitStateAutomaton {<br />
...<br />
}<br />
</syntaxhighlight><br />
<br />
''Devices'' by the way is the ''App Service'' that manages all physical devices that belong to a single innovaphone PBX installation. Our sample task now is to talk to ''Devices'' to find out which devices exist.<br />
<br />
When we instantiate this derived class, we will provide the ''WSClient'' towards the ''Devices App Service''.<br />
<br />
<syntaxhighlight lang="php"><br />
$dl = new DeviceLister($devicesws);<br />
</syntaxhighlight><br />
<br />
In the base class, there is only one function declared as ''abstract'':<br />
<syntaxhighlight lang="php"><br />
// this function at least must be overriden by any derived class<br />
abstract public function ReceiveInitialStart(\AppPlatform\Message $msg);<br />
</syntaxhighlight><br />
<br />
This function will be called by the base class, when a message with ''message type'' (mt, see above) of ''Start'' is received in the automaton state ''Initial''. When a message is received and there is no appropriate ''Receive'' function defined, the message is discarded. Otherwise the message is passed to the ''Receive'' function and the derived class can handle it. <br />
<br />
The ''Start'' is a little special. In fact, the ''FinitStateAutomaton'' class will send this pseudo-message to the derived class to start the automaton defined by the derived class. This message has nothing but the ''mt'' member. The derived class will usually send the first message to its ''App Service'' in this ''Receive'' function.<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("Requesting Device List");<br />
$this->sendMessage(new AppPlatform\Message("GetDevices"));<br />
}<br />
</syntaxhighlight><br />
<br />
It is the ''sendMessage'' member function of the base class that is used to send a message to the ''App Service''. The message itself is an instance of a class of type ''Message''. The first argument of its constructor is the ''mt'' member of the message. All further arguments are optional. If they appear, they must appear in pairs of ''member/value''. In the code above, the ''mt'' is "GetDevices".<br />
<br />
''Devices'' will respond to this message with a message that looks like so:<br />
<pre><br />
{<br />
"mt": "GetDevicesResult",<br />
"devices": [{<br />
"id": 2,<br />
"hwId": "029033410109",<br />
"name": "AP - sample.dom",<br />
"domain": 1,<br />
"product": "AppPlatform ARM",<br />
"version": "60002 dvl",<br />
"type": "APP_PLATFORM",<br />
"pbxActive": false,<br />
"online": true<br />
}, {<br />
"id": 3,<br />
"hwId": "0090333000af",<br />
"name": "ckl's IP232",<br />
"domain": 1,<br />
"product": "IP232",<br />
"version": "13r1 dvl [13.1102/125119/501]",<br />
"type": "PHONE",<br />
"pbxActive": false,<br />
"online": true<br />
}, {<br />
"id": 1,<br />
"hwId": "009033410109",<br />
"name": "PBX - sindelfingen.sample.dom",<br />
"domain": 1,<br />
"product": "IP811",<br />
"version": "13r1 dvl [13.1133/131094/200]",<br />
"type": "GW",<br />
"pbxActive": true,<br />
"online": true<br />
}]<br />
}<br />
</pre><br />
As we see, the message type ''mt'' is "GetDevicesResult" so the ''ReceiveInitialGetDevicesResult'' member function of our derived class will be called:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");<br />
$this->devices = array_merge($this->devices, $msg->devices);<br />
if (isset($msg->last) && $msg->last) {<br />
AppPlatform\Log::log("Last chunk");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Here we save the data in some class-local storage (<code>$this->devices</code>). More interesting is what we do if the message contains a member ''last'' which is set to true. In this case, the ''Receive'' function returns the name of the new state the automaton is supposed to be in. In our case, it is "Dead". "Dead" is a magic state name (just as "Initial" is) in that it tells the system that your automaton has finished. You could as well change the state name to something else (say, "NewState"). If further ''GetDevicesResult'' messages would appear, a member function Receive''NewState''GetDevicesResult() would be called.<br />
<br />
So here is the complete derived class:<br />
<syntaxhighlight lang="php"><br />
// an automaton which lists all devices in Devices<br />
class DeviceLister extends AppPlatform\FinitStateAutomaton {<br />
<br />
protected $devices = array();<br />
<br />
public function getDevices() {<br />
return $this->devices;<br />
}<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("Requesting Device List");<br />
$this->sendMessage(new AppPlatform\Message("GetDevices"));<br />
}<br />
<br />
public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");<br />
$this->devices = array_merge($this->devices, $msg->devices);<br />
if (isset($msg->last) && $msg->last) {<br />
AppPlatform\Log::log("Last chunk");<br />
return "Dead";<br />
}<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
Its as simple as that!<br />
<br />
Have a look at the second derived class in the sample code (<code>websocket-sample.php</code>) called ''UserLister''. It is a bit more complicated (as it handles more message types) but you will see the same mechanisms.<br />
<br />
===== Running the Automatons =====<br />
So far, we have not talked about who actually runs the automatons we create in our derived classes. You could think that this is done by calling something like <code>$myauto = new myAuto($ws); $myauto->run()</code>. However, it is done a little different. The reason for this is that it comes in handy once in a while when you can run several automatons concurrently. <br />
<br />
Think of the log-in scenario we discussed above. In this scenario, we must talk both to the PBX and to one or more ''App Services''. As we learned above, a single automaton only talks to a single ''App Service'' (or actually a PBX, as a PBX can act just like an ''App Service''. This is known as ''PBX App''). <br />
<br />
Therefore, automatons are run by another utility class called ''Transitioner''. This is instantiated with a list of automatons (which are in turn instantiated with their respective ''WSClients''). We then can start and run all those automatons concurrently:<br />
<br />
<syntaxhighlight lang="php"><br />
$dl = new DeviceLister($devicesws);<br />
$ul = new UserLister($usersws, $pbxloginresult->loginResultMsg);<br />
$t = new AppPlatform\Transitioner($dl, $ul);<br />
$t->run();<br />
</syntaxhighlight><br />
<br />
For convenience however, the ''FiniteStateAutomaton'' also has a (trivial) member function ''run'' indeed. It will run only the automaton itself and comes in handy if you want to run a single automaton only. <br />
<syntaxhighlight lang="php"><br />
<br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run() {<br />
$auto = new Transitioner($this);<br />
$auto->run();<br />
}<br />
</syntaxhighlight><br />
===== Communicating between Automatons =====<br />
When automatons run concurrently, you most likely will need to be able to exchange message between them. This can be done by the ''FinitStateAutomaton'' classes ''postEvent'' member function. <br />
<syntaxhighlight lang="php"><br />
/**<br />
* post an Event to other automatons, this function is synchronous (i.e. Receive*() member functions will be called directly)<br />
* @param Message $msg<br />
*/<br />
protected function postEvent(Message $msg, $dst = null);<br />
</syntaxhighlight><br />
The function allows you to send a message to the other automatons (when ''$dst'' is ''null'') or to a specific automaton. For an example, see the ''UserPBXLoginWithAppAutomaton'' and ''AppLoginViaPBXAutomaton'' which work in tandem using the ''postEvent'' mechanism. <br />
<br />
Please note that messages sent by this function are handled synchronously by the target automatons (i.e the target automatons have already processed the posted event when ''postEvent'' returns).<br />
<br />
==== Access App services using the service's secret ====<br />
This sample code (in file ''pbxapi.php'') accesses an App service using the secret as defined in an App object. No user account is required therefore. As a sample, the [https://sdk.innovaphone.com/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] provided by the PBX itself is used. <br />
<br />
This sample is new from build 1006.<br />
===== PBX configuration =====<br />
To access an API provided by the PBX, an ''App'' object is required. In this sample, we assume that <br />
* an ''App'' type object called <code>pbxadminapi</code> (this is the ''Name'' property) is created<br />
* it has <code>ip411</code> as ''Password'' <br />
* in the object's ''App'' tab, the 'Admin' and 'PbxApi' check-marks are ticked in the ''Grant access to APIs'' section<br />
<br />
===== Authentication =====<br />
To authenticate towards an App service directly, the ''AppPlatform\AppLoginAutomaton'' (available in websocket.class.php) is used. As we intend to talk to an API provided by the PBX itself, we derive the class ''PbxAppLoginAutomaton''. It only overrides the constructor to create the proper URL to connect to the PBX.<br />
<br />
<syntaxhighlight lang="php"><br />
/*<br />
* authenticate towards the PBX as an App service<br />
*/<br />
class PbxAppLoginAutomaton extends AppPlatform\AppLoginAutomaton {<br />
<br />
/**<br />
* @var string PBX IP address<br />
*/<br />
protected $pbxUrl;<br />
<br />
/**<br />
* @var \WebSocket\WSClient websocket to PBX<br />
*/<br />
protected $pbxWS;<br />
<br />
function __construct($pbx, AppPlatform\AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new AppPlatform\WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
Please note the relative URL path ''/PBX0/APPS/websocket'' used. This is required for direct API access. The path ''/PBX0/APPCLIENT/130000/websocket'' which is used in the ''AppServiceLogin'' (as shown in the previous sample code) can only be used for a user login. Apart from that, the provedure to access an API provided by an App service on an App platform is exactly the same.<br />
<br />
To authenticate towards the PBX, we run the <br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
</syntaxhighlight><br />
<br />
As soon as this is completed, we are logged in to the PBX (we could verify the login success by calling ''$app->getIsLoggedIn()''. <br />
===== Using the API =====<br />
To use the API, we define a new automaton class (so it is derived from ''AppPlatform\FinitStateAutomaton''):<br />
<br />
<syntaxhighlight lang="php"><br />
// the class utilizes the PbxApi<br />
class PbxApiSample extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));<br />
}<br />
<br />
public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {<br />
return "Dead";<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
This automaton will merely send a ''GetStun'' message to the API (note the additional ''api'' member that always needs to be set to the API identifier, ''PbxAdminApi'' in our case) and wait for the result. It terminates as soon as the result is received.<br />
<br />
We instantiate the automaton and run it with the authenticated WebSocket we obtained earlier (retrieved from the ''PbxAppLoginAutomaton'' class using it's ''getWs()'' member function.<br />
<br />
<syntaxhighlight lang="php"><br />
$pbxapi = new PbxApiSample($app->getWs(), "PBX");<br />
$pbxapi->run();<br />
</syntaxhighlight><br />
<br />
===== Using the RCC API =====<br />
If you want to use the [http://sdk.innovaphone.com/doc/appwebsocket/RCC.htm RCC] API instead, you can take a look at the sample ''rccapi.php''.<br><br />
<br><br />
To use this example, you need:<br />
* a PBX App Object named ''rccapi''<br />
* the PBX App Object must have the password ''ip411'' (just for using the example of course ...)<br />
* the PBX App Object doesn't need an App URL set, as it points to the local PBX by default<br />
* the PBX App Object must have the RCC flag set under App<br />
<br><br />
In the PHP code you'll see the Initialize message which must be sent first. You'll receive UserInfo messages for all users, '''which''' have the '''rccapi''' App flag set in their Apps.<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("Initialize", "api", "RCC"));<br />
}<br />
<br />
public function ReceiveInitialUserInfo(\AppPlatform\Message $msg) {<br />
$this->log("UserInfo");<br />
}<br />
<br />
public function ReceiveInitialInitializeResult(\AppPlatform\Message $msg) {<br />
$this->log("InitializeResult");<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
This sample code is available from build 1012.<br />
<br />
==== Access a PBX App service using a PBX user login ====<br />
You might think: if the websocket authentication procedure used towards an API provided by the PBX is identical to the procedure for an API provided by an App service running on the Apps platform, why can't I use the approach shown in the first sample (''websocket-sample.php'') for the PBX too?<br />
<br />
In fact, you can! The code would look much like the first example in ''websocket-sample.php'', except you need to use a different service specification.<br />
<br />
This sample is new from build 1007.<br />
===== PBX configuration =====<br />
To make this work, you must create the same App object (''pbxadminapi'') as before in ''pbxapi.php''. However, in addition to that you must fill in its ''URL'' property so that it points to the PBX's API service access point: <code>http://</code>''your-pbx-dns''<code>/PBX0/APPS/websocket</code>. <br />
<br />
===== Authentication =====<br />
<br />
To login, you would use the ''AppPlatform\AppServiceLogin'' class again:<br />
<syntaxhighlight lang="php"><br />
// login to PBX and devices and users<br />
$connector = new AppPlatform\AppServiceLogin(<br />
$pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(<br />
$apispec = new AppPlatform\AppServiceSpec("websocket", "APPS", "PBX0"),<br />
),<br />
true<br />
);<br />
$connector->connect();<br />
</syntaxhighlight><br />
Note the new AppServiceSpec though!<br />
<br />
The code will authenticate towards the PBX and towards the API websocket now (just like it did towards the PBX and the App platform services in the ''websocket-sample.php'' sample). <br />
===== Using the API =====<br />
The API can then be used just like before:<br />
<br />
<syntaxhighlight lang="php"><br />
// the class utilizes the PbxApi<br />
class PbxApiSample extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));<br />
}<br />
<br />
public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {<br />
return "Dead";<br />
}<br />
<br />
}<br />
<br />
// AppPlatform\Log::setLogLevel("", "debug", true);<br />
$pbxapi = new PbxApiSample($apiws, "PBX");<br />
$pbxapi->run();<br />
</syntaxhighlight><br />
<br />
==== Access the App manager ====<br />
<small>(thanks to Forum user james99 who [http://forum.innovaphone.com/moodle2/mod/forum/discuss.php?d=25205#p67980 provided this solution])</small> {{Template:3rd Party Input}} <br />
<syntaxhighlight lang="php"><br />
$connector = new AppPlatform\AppServiceLogin($pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw),<br />
array($managerspec = new AppPlatform\AppServiceSpec("manager")<br />
), true);<br />
$connector->connect();<br />
<br />
// manager socket connection<br />
if ($connector->getAppAutomaton($managerspec)->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to app manager");<br />
$managerws = $connector->getAppAutomaton($managerspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log in to app manager");<br />
exit;<br />
} <br />
</syntaxhighlight><br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r1 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r1 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 5.4 or up (the code has not been tested with PHP 7, but should run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* open <code>websocket-sample.php</code><br />
<br />
You should now see the following output:<br />
<br />
<code><br />
07/09/18 12:21:33.5742 0.000 0000.000 smsg AppPlatform\Transitioner: sent->PBXWS {"mt":"Login","type":"user","userAgent":"websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"}<br><br />
07/09/18 12:21:33.5750 0.000 0000.000 smsg AppPlatform\Transitioner: received<-PBXWS {"mt":"Authenticate","type":"user","method":"digest","domain":"sample.dom","challenge":"3e6bf6a1ba658959"}<br><br />
...<br />
</code><br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp5 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r1:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68163Reference13r1:Concept Talking to the v13 Application Platform using PHP2023-07-10T09:09:11Z<p>Ckl: </p>
<hr />
<div>'''Deprecation note:'''<br />
<br />
This article describes methods for connecting to the PBX as a user with the myApps protocol, that is not recommended.<br />
Instead automation scripts and external applications should connect to both the PBX and app services using the AppWebsocket protocol with an app object account.<br />
<br />
An updated version of this article is available in [[Reference13r1:Concept Talking to the v13 Application Platform using PHP]].<br />
<br />
Protocol details can be found in the SDK, see<br />
* [http://sdk.innovaphone.com/doc/appwebsocket/AppWebsocket.htm http://sdk.innovaphone.com/doc/appwebsocket/AppWebsocket.htm]<br />
* [http://sdk.innovaphone.com/doc/appwebsocket/Services.htm http://sdk.innovaphone.com/doc/appwebsocket/Services.htm]<br />
* [http://sdk.innovaphone.com/doc/tutorials/RCCfromExternalWebPage.htm http://sdk.innovaphone.com/doc/tutorials/RCCfromExternalWebPage.htm]<br />
<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the v13 ''App Platform ''<br />
* PBX running v13 firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs various ''App Services'' providing services which are used by ''Apps'' running in the ''myApps'' client context. ''Apps'' (written in JavaScript) are loaded from the ''App Platform'' and launched by the ''myApps'' client. They communicate to the ''App Services'' using a JSON based WebSocket protocol. ''Apps'' may also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. Creation of such ''App Services'' is facilitated by the [http://sdk.innovaphone.com/ ''v13 SDK'']. <br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to existing ''App Services'' from your own application directly (that is, not by virtue of an ''App'' running in the ''myApps'' client). This article is on how to do that. General considerations are given which are true for all programming languages, some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to an ''App Service'', you need to be logged in to the PBX. An ''App'' does not need to care about it, as the ''myApps'' client (which is the context all ''Apps'' run in) would take care of this. <br />
<br />
The process is as follows:<br />
* the application must log-in to the PBX<br />
** depending on the configuration of the PBX, the authentication type can be ''digest'' (the PBX itself knows the user credentials) or ''ntlm'' (the PBX passes the user credentials to a 3rd party authentication service using ''netlogon'')<br />
** depending on the configuration of the PBX, two factor authentication may be required. In this case, the calling application must be prepared that the login process may take a long time (the time needed to complete the two factor process, e.g. to click on the verification link sent by email)<br />
* the PBX will create and store a permanent session for this login and return special ''session credentials'' for this new session<br />
* subsequent log-ins can be done using the session credentials instead of the user credentials<br />
<br />
Please note that the PBX will create new session credentials for each such login. Also, the PBX will only store the last 8 such sessions. Excessive older sessions are deleted. For this reason, it is important that an application stores the session credentials received and uses them for all subsequent log-ins. Otherwise, some older sessions (which may be sessions created by the ''myApps'' client or other applications) would be removed. <br />
<br />
Once the calling application is logged in to the PBX, the authentication towards the ''App Service'' can take place. The process is as follows:<br />
* the calling application requests a ''challenge'' from the ''App Service''<br />
* the calling application passes this challenge to the PBX<br />
* the PBX (knowing the ''shared secret'' defined for the ''AppService'') creates a hash from some information about the user<br />
* the calling application passes the hash to the ''App Service''<br />
* the ''App Service'' computes the same hash and compares it to the one received from the application<br />
* if both match, the application is authenticated<br />
<br />
==== WebSocket ====<br />
''App Services'' communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code>{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are two message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use an optional configuration from a file called ''my-pbx-data.php''. If it is not present, default values are used.<br />
<br />
To have the scripts proper data for your environment, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxuser = "your-pbx-user-name";<br />
$pbxpw = "your-pbx-user-password";<br />
$pbxapp = "your-app-object-name";<br />
</syntaxhighlight><br />
<br />
==== Access App services using user credentials ====<br />
Our little example (available in <code>websocket-sample.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, an existing PBX user with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the ''Install'' procedure.<br />
<br />
===== Login =====<br />
As discussed above, to do so, we need to log-in to the PBX and then connect and authenticate to the respective ''App Services''. <br />
<syntaxhighlight lang="php"><br />
// login to PBX and devices and users<br />
$connector = new AppPlatform\AppServiceLogin(<br />
$pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(<br />
$devicesspec = new AppPlatform\AppServiceSpec("innovaphone-devices"),<br />
$usersspec = new AppPlatform\AppServiceSpec("innovaphone-users"),<br />
),<br />
true<br />
);<br />
$connector->connect();<br />
</syntaxhighlight><br />
<br />
To perform the necessary steps, an instance of class <code>AppServiceLogin</code> (note that in our sample code, all library code provided is in the <code>AppPlatform</code> name space) is used. The constructor arguments are <br />
; $PBX (string) : either the FQDN or the full websocket URI of the PBX . In the sample code, we use the FQDN <code>sindelfingen.sample.dom</code><br />
; $credentials (AppUserCredentials) : the users ''Name'' and ''Password'' wrapped in an object of type ''AppUserCredentials''<br />
; $appServiceSpec (AppServiceSpec[]) : an array of ''App Service'' specifications to select the ones to connect to, each wrapped in an object of type ''AppServiceSpec''<br />
<br />
The call to <code>connect()</code> creates and authenticates the WebSocket connections to the ''App Services'' and the ''PBX''. Note that a connection is made only if the user used for log-in has access to the selected ''App Service''.<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
// look at the PBX login<br />
if ($connector->getPbxA()->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to PBX");<br />
$pbxws = $connector->getPbxWS();<br />
$pbxloginresult = ($connector->getPbxA()->getResults());<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to PBX");<br />
exit;<br />
}<br />
</syntaxhighlight><br />
<br />
If the login succeeded, the WebSocket connection to the PBX and the log-in messages are retrieved. Next step is to verify the respective connections to the ''App Services'':<br />
<br />
<syntaxhighlight lang="php"><br />
// look at devices<br />
if ($connector->getAppAutomaton($devicesspec)->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to Devices");<br />
$devicesws = $connector->getAppAutomaton($devicesspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to Devices");<br />
exit;<br />
}<br />
<br />
// look at users<br />
if ($connector->getAppAutomaton($usersspec)->getIsLoggedIn()) {<br />
<br />
AppPlatform\Log::log("Logged in to Users");<br />
$usersws = $connector->getAppAutomaton($usersspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log-in to Users");<br />
exit;<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the 2 ''App Services'' we are interested in. <br />
<br />
<syntaxhighlight lang="php"><br />
// now we have the authenticated websockets to our AppServices, so we can release the connector<br />
$connector = null;<br />
</syntaxhighlight><br />
That was the easy part :-)<br />
<br />
===== Implementing the WebSocket Protocol =====<br />
Life was easy so far as we have used the ''AppServiceLogin'' utility class. Now we want to implement a conversation with our ''App Services'' using the WebSocket connections we have set up before.<br />
<br />
Generally, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes and netlogon protocol peculiarities<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The Asynchronous Programming Model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called <code>FinitStateAutomaton</code>. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called <code>WSClient</code>). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the <code>AppServiceLogin</code> utility class creates such <code>WSClient</code> objects (which we retrieved by calling the <code>getAppWebSocket()</code> member function. <br />
<br />
However, if you try to instantiate such a class (like <code>$mya = new FinitStateAutomaton($mywebsocket)</code> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
So here we go and define a derived class:<br />
<syntaxhighlight lang="php"><br />
// an automaton which lists all devices in Devices<br />
class DeviceLister extends AppPlatform\FinitStateAutomaton {<br />
...<br />
}<br />
</syntaxhighlight><br />
<br />
''Devices'' by the way is the ''App Service'' that manages all physical devices that belong to a single innovaphone PBX installation. Our sample task now is to talk to ''Devices'' to find out which devices exist.<br />
<br />
When we instantiate this derived class, we will provide the ''WSClient'' towards the ''Devices App Service''.<br />
<br />
<syntaxhighlight lang="php"><br />
$dl = new DeviceLister($devicesws);<br />
</syntaxhighlight><br />
<br />
In the base class, there is only one function declared as ''abstract'':<br />
<syntaxhighlight lang="php"><br />
// this function at least must be overriden by any derived class<br />
abstract public function ReceiveInitialStart(\AppPlatform\Message $msg);<br />
</syntaxhighlight><br />
<br />
This function will be called by the base class, when a message with ''message type'' (mt, see above) of ''Start'' is received in the automaton state ''Initial''. When a message is received and there is no appropriate ''Receive'' function defined, the message is discarded. Otherwise the message is passed to the ''Receive'' function and the derived class can handle it. <br />
<br />
The ''Start'' is a little special. In fact, the ''FinitStateAutomaton'' class will send this pseudo-message to the derived class to start the automaton defined by the derived class. This message has nothing but the ''mt'' member. The derived class will usually send the first message to its ''App Service'' in this ''Receive'' function.<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("Requesting Device List");<br />
$this->sendMessage(new AppPlatform\Message("GetDevices"));<br />
}<br />
</syntaxhighlight><br />
<br />
It is the ''sendMessage'' member function of the base class that is used to send a message to the ''App Service''. The message itself is an instance of a class of type ''Message''. The first argument of its constructor is the ''mt'' member of the message. All further arguments are optional. If they appear, they must appear in pairs of ''member/value''. In the code above, the ''mt'' is "GetDevices".<br />
<br />
''Devices'' will respond to this message with a message that looks like so:<br />
<pre><br />
{<br />
"mt": "GetDevicesResult",<br />
"devices": [{<br />
"id": 2,<br />
"hwId": "029033410109",<br />
"name": "AP - sample.dom",<br />
"domain": 1,<br />
"product": "AppPlatform ARM",<br />
"version": "60002 dvl",<br />
"type": "APP_PLATFORM",<br />
"pbxActive": false,<br />
"online": true<br />
}, {<br />
"id": 3,<br />
"hwId": "0090333000af",<br />
"name": "ckl's IP232",<br />
"domain": 1,<br />
"product": "IP232",<br />
"version": "13r1 dvl [13.1102/125119/501]",<br />
"type": "PHONE",<br />
"pbxActive": false,<br />
"online": true<br />
}, {<br />
"id": 1,<br />
"hwId": "009033410109",<br />
"name": "PBX - sindelfingen.sample.dom",<br />
"domain": 1,<br />
"product": "IP811",<br />
"version": "13r1 dvl [13.1133/131094/200]",<br />
"type": "GW",<br />
"pbxActive": true,<br />
"online": true<br />
}]<br />
}<br />
</pre><br />
As we see, the message type ''mt'' is "GetDevicesResult" so the ''ReceiveInitialGetDevicesResult'' member function of our derived class will be called:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");<br />
$this->devices = array_merge($this->devices, $msg->devices);<br />
if (isset($msg->last) && $msg->last) {<br />
AppPlatform\Log::log("Last chunk");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Here we save the data in some class-local storage (<code>$this->devices</code>). More interesting is what we do if the message contains a member ''last'' which is set to true. In this case, the ''Receive'' function returns the name of the new state the automaton is supposed to be in. In our case, it is "Dead". "Dead" is a magic state name (just as "Initial" is) in that it tells the system that your automaton has finished. You could as well change the state name to something else (say, "NewState"). If further ''GetDevicesResult'' messages would appear, a member function Receive''NewState''GetDevicesResult() would be called.<br />
<br />
So here is the complete derived class:<br />
<syntaxhighlight lang="php"><br />
// an automaton which lists all devices in Devices<br />
class DeviceLister extends AppPlatform\FinitStateAutomaton {<br />
<br />
protected $devices = array();<br />
<br />
public function getDevices() {<br />
return $this->devices;<br />
}<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("Requesting Device List");<br />
$this->sendMessage(new AppPlatform\Message("GetDevices"));<br />
}<br />
<br />
public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {<br />
AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");<br />
$this->devices = array_merge($this->devices, $msg->devices);<br />
if (isset($msg->last) && $msg->last) {<br />
AppPlatform\Log::log("Last chunk");<br />
return "Dead";<br />
}<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
Its as simple as that!<br />
<br />
Have a look at the second derived class in the sample code (<code>websocket-sample.php</code>) called ''UserLister''. It is a bit more complicated (as it handles more message types) but you will see the same mechanisms.<br />
<br />
===== Running the Automatons =====<br />
So far, we have not talked about who actually runs the automatons we create in our derived classes. You could think that this is done by calling something like <code>$myauto = new myAuto($ws); $myauto->run()</code>. However, it is done a little different. The reason for this is that it comes in handy once in a while when you can run several automatons concurrently. <br />
<br />
Think of the log-in scenario we discussed above. In this scenario, we must talk both to the PBX and to one or more ''App Services''. As we learned above, a single automaton only talks to a single ''App Service'' (or actually a PBX, as a PBX can act just like an ''App Service''. This is known as ''PBX App''). <br />
<br />
Therefore, automatons are run by another utility class called ''Transitioner''. This is instantiated with a list of automatons (which are in turn instantiated with their respective ''WSClients''). We then can start and run all those automatons concurrently:<br />
<br />
<syntaxhighlight lang="php"><br />
$dl = new DeviceLister($devicesws);<br />
$ul = new UserLister($usersws, $pbxloginresult->loginResultMsg);<br />
$t = new AppPlatform\Transitioner($dl, $ul);<br />
$t->run();<br />
</syntaxhighlight><br />
<br />
For convenience however, the ''FiniteStateAutomaton'' also has a (trivial) member function ''run'' indeed. It will run only the automaton itself and comes in handy if you want to run a single automaton only. <br />
<syntaxhighlight lang="php"><br />
<br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run() {<br />
$auto = new Transitioner($this);<br />
$auto->run();<br />
}<br />
</syntaxhighlight><br />
===== Communicating between Automatons =====<br />
When automatons run concurrently, you most likely will need to be able to exchange message between them. This can be done by the ''FinitStateAutomaton'' classes ''postEvent'' member function. <br />
<syntaxhighlight lang="php"><br />
/**<br />
* post an Event to other automatons, this function is synchronous (i.e. Receive*() member functions will be called directly)<br />
* @param Message $msg<br />
*/<br />
protected function postEvent(Message $msg, $dst = null);<br />
</syntaxhighlight><br />
The function allows you to send a message to the other automatons (when ''$dst'' is ''null'') or to a specific automaton. For an example, see the ''UserPBXLoginWithAppAutomaton'' and ''AppLoginViaPBXAutomaton'' which work in tandem using the ''postEvent'' mechanism. <br />
<br />
Please note that messages sent by this function are handled synchronously by the target automatons (i.e the target automatons have already processed the posted event when ''postEvent'' returns).<br />
<br />
==== Access App services using the service's secret ====<br />
This sample code (in file ''pbxapi.php'') accesses an App service using the secret as defined in an App object. No user account is required therefore. As a sample, the [https://sdk.innovaphone.com/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] provided by the PBX itself is used. <br />
<br />
This sample is new from build 1006.<br />
===== PBX configuration =====<br />
To access an API provided by the PBX, an ''App'' object is required. In this sample, we assume that <br />
* an ''App'' type object called <code>pbxadminapi</code> (this is the ''Name'' property) is created<br />
* it has <code>ip411</code> as ''Password'' <br />
* in the object's ''App'' tab, the 'Admin' and 'PbxApi' check-marks are ticked in the ''Grant access to APIs'' section<br />
<br />
===== Authentication =====<br />
To authenticate towards an App service directly, the ''AppPlatform\AppLoginAutomaton'' (available in websocket.class.php) is used. As we intend to talk to an API provided by the PBX itself, we derive the class ''PbxAppLoginAutomaton''. It only overrides the constructor to create the proper URL to connect to the PBX.<br />
<br />
<syntaxhighlight lang="php"><br />
/*<br />
* authenticate towards the PBX as an App service<br />
*/<br />
class PbxAppLoginAutomaton extends AppPlatform\AppLoginAutomaton {<br />
<br />
/**<br />
* @var string PBX IP address<br />
*/<br />
protected $pbxUrl;<br />
<br />
/**<br />
* @var \WebSocket\WSClient websocket to PBX<br />
*/<br />
protected $pbxWS;<br />
<br />
function __construct($pbx, AppPlatform\AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new AppPlatform\WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
Please note the relative URL path ''/PBX0/APPS/websocket'' used. This is required for direct API access. The path ''/PBX0/APPCLIENT/130000/websocket'' which is used in the ''AppServiceLogin'' (as shown in the previous sample code) can only be used for a user login. Apart from that, the provedure to access an API provided by an App service on an App platform is exactly the same.<br />
<br />
To authenticate towards the PBX, we run the <br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
</syntaxhighlight><br />
<br />
As soon as this is completed, we are logged in to the PBX (we could verify the login success by calling ''$app->getIsLoggedIn()''. <br />
===== Using the API =====<br />
To use the API, we define a new automaton class (so it is derived from ''AppPlatform\FinitStateAutomaton''):<br />
<br />
<syntaxhighlight lang="php"><br />
// the class utilizes the PbxApi<br />
class PbxApiSample extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));<br />
}<br />
<br />
public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {<br />
return "Dead";<br />
}<br />
<br />
}<br />
</syntaxhighlight><br />
<br />
This automaton will merely send a ''GetStun'' message to the API (note the additional ''api'' member that always needs to be set to the API identifier, ''PbxAdminApi'' in our case) and wait for the result. It terminates as soon as the result is received.<br />
<br />
We instantiate the automaton and run it with the authenticated WebSocket we obtained earlier (retrieved from the ''PbxAppLoginAutomaton'' class using it's ''getWs()'' member function.<br />
<br />
<syntaxhighlight lang="php"><br />
$pbxapi = new PbxApiSample($app->getWs(), "PBX");<br />
$pbxapi->run();<br />
</syntaxhighlight><br />
<br />
===== Using the RCC API =====<br />
If you want to use the [http://sdk.innovaphone.com/doc/appwebsocket/RCC.htm RCC] API instead, you can take a look at the sample ''rccapi.php''.<br><br />
<br><br />
To use this example, you need:<br />
* a PBX App Object named ''rccapi''<br />
* the PBX App Object must have the password ''ip411'' (just for using the example of course ...)<br />
* the PBX App Object doesn't need an App URL set, as it points to the local PBX by default<br />
* the PBX App Object must have the RCC flag set under App<br />
<br><br />
In the PHP code you'll see the Initialize message which must be sent first. You'll receive UserInfo messages for all users, '''which''' have the '''rccapi''' App flag set in their Apps.<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("Initialize", "api", "RCC"));<br />
}<br />
<br />
public function ReceiveInitialUserInfo(\AppPlatform\Message $msg) {<br />
$this->log("UserInfo");<br />
}<br />
<br />
public function ReceiveInitialInitializeResult(\AppPlatform\Message $msg) {<br />
$this->log("InitializeResult");<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
This sample code is available from build 1012.<br />
<br />
==== Access a PBX App service using a PBX user login ====<br />
You might think: if the websocket authentication procedure used towards an API provided by the PBX is identical to the procedure for an API provided by an App service running on the Apps platform, why can't I use the approach shown in the first sample (''websocket-sample.php'') for the PBX too?<br />
<br />
In fact, you can! The code would look much like the first example in ''websocket-sample.php'', except you need to use a different service specification.<br />
<br />
This sample is new from build 1007.<br />
===== PBX configuration =====<br />
To make this work, you must create the same App object (''pbxadminapi'') as before in ''pbxapi.php''. However, in addition to that you must fill in its ''URL'' property so that it points to the PBX's API service access point: <code>http://</code>''your-pbx-dns''<code>/PBX0/APPS/websocket</code>. <br />
<br />
===== Authentication =====<br />
<br />
To login, you would use the ''AppPlatform\AppServiceLogin'' class again:<br />
<syntaxhighlight lang="php"><br />
// login to PBX and devices and users<br />
$connector = new AppPlatform\AppServiceLogin(<br />
$pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(<br />
$apispec = new AppPlatform\AppServiceSpec("websocket", "APPS", "PBX0"),<br />
),<br />
true<br />
);<br />
$connector->connect();<br />
</syntaxhighlight><br />
Note the new AppServiceSpec though!<br />
<br />
The code will authenticate towards the PBX and towards the API websocket now (just like it did towards the PBX and the App platform services in the ''websocket-sample.php'' sample). <br />
===== Using the API =====<br />
The API can then be used just like before:<br />
<br />
<syntaxhighlight lang="php"><br />
// the class utilizes the PbxApi<br />
class PbxApiSample extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));<br />
}<br />
<br />
public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {<br />
return "Dead";<br />
}<br />
<br />
}<br />
<br />
// AppPlatform\Log::setLogLevel("", "debug", true);<br />
$pbxapi = new PbxApiSample($apiws, "PBX");<br />
$pbxapi->run();<br />
</syntaxhighlight><br />
<br />
==== Access the App manager ====<br />
<small>(thanks to Forum user james99 who [http://forum.innovaphone.com/moodle2/mod/forum/discuss.php?d=25205#p67980 provided this solution])</small> {{Template:3rd Party Input}} <br />
<syntaxhighlight lang="php"><br />
$connector = new AppPlatform\AppServiceLogin($pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw),<br />
array($managerspec = new AppPlatform\AppServiceSpec("manager")<br />
), true);<br />
$connector->connect();<br />
<br />
// manager socket connection<br />
if ($connector->getAppAutomaton($managerspec)->getIsLoggedIn()) {<br />
AppPlatform\Log::log("Logged in to app manager");<br />
$managerws = $connector->getAppAutomaton($managerspec)->getWs();<br />
} else {<br />
AppPlatform\Log::log("Failed to log in to app manager");<br />
exit;<br />
} <br />
</syntaxhighlight><br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r1 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r1 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 5.4 or up (the code has not been tested with PHP 7, but should run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* open <code>websocket-sample.php</code><br />
<br />
You should now see the following output:<br />
<br />
<code><br />
07/09/18 12:21:33.5742 0.000 0000.000 smsg AppPlatform\Transitioner: sent->PBXWS {"mt":"Login","type":"user","userAgent":"websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"}<br><br />
07/09/18 12:21:33.5750 0.000 0000.000 smsg AppPlatform\Transitioner: received<-PBXWS {"mt":"Authenticate","type":"user","method":"digest","domain":"sample.dom","challenge":"3e6bf6a1ba658959"}<br><br />
...<br />
</code><br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp5 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68162Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:38:16Z<p>Ckl: /* PBX / App Platform */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' messages in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r2 (or later)<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer (if not, use <code>htps://<ip-of-our-pbx/install.htm</code>)<br />
* complete the installer using IP addresses instead of DNS names, so that it installs a PBX and a fresh ''App Platform'' (you can of course also use DNS names during the Install if you have them operational for the PBX and App platform IP addresses. For running the sample code, it does not matter)<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. <br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68161Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:29:44Z<p>Ckl: /* Using the RCC API */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' messages in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68160Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:15:56Z<p>Ckl: /* Using the RCC API */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-mark ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68159Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:13:55Z<p>Ckl: /* Using the RCC API */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine if the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68158Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:09:31Z<p>Ckl: /* Using the RCC API */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine of the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68157Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-10T08:08:07Z<p>Ckl: /* PHP Script */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine of the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open the file ''application.php'' in your browser<br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68100Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-05T21:38:18Z<p>Ckl: /* Using the RCC API */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. It is available in the ''rccapi.php'' sample code file. <br />
<br />
The code uses a derivative of the the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine of the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open <code><br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68099Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-05T21:34:45Z<p>Ckl: /* Tandems */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. <br />
<br />
The code uses a derivative of the the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine of the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open <code><br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Cklhttps://wiki.innovaphone.com/index.php?title=Reference13r2:Concept_Talking_to_the_v13_Application_Platform_using_PHP&diff=68098Reference13r2:Concept Talking to the v13 Application Platform using PHP2023-07-05T21:29:33Z<p>Ckl: /* Tandems */</p>
<hr />
<div>This is a slight rewrite of the [[Reference13r1:Concept Talking to the v13 Application Platform using PHP | older article]] in the Reference13r1 namespace. While there is nothing technically wrong with the old article, some of the authentication methods described there are no longer recommended for use with scripts. In particular, it is not recommended that a script authenticate as a PBX user. Instead, it should always authenticate against a PBX ''App'' type object. If the script needs to access other App services, it should use the ''Services'' API described in https://sdk.innovaphone.com/13r2/doc/appwebsocket/Services.htm.<br />
<br />
==Applies To==<br />
This information applies to<br />
<br />
* innovaphone platform running the 13r2 (or later) ''App Platform ''<br />
* PBX running 13r2 (or later) firmware <br />
* PHP script accessing the ''App Services'' available on the ''App Platform''<br />
<br />
==More Information==<br />
===Problem Details===<br />
The v13 App Platform runs several ''App Services'' that provide services used by ''Apps'' running in the context of the ''myApps'' client. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate with the App Services using a JSON-based WebSocket protocol. ''Apps'' can also be loaded from the PBX (so called ''PBX Apps'').<br />
<br />
The ''vanilla'' way to go when it comes to 3rd party integration is to write a ''custom App Service'' and install it on the ''App Platform''. The creation of such ''App Services'' is facilitated by the [https://sdk.innovaphone.com/13r2/doc/sdk.htm ''v13 SDK'']. <br />
<br />
Note: The mechanisms described here are available in the 13r2 SDK, so all links to the SDK are given for the 13r2 version. To access other versions of the SDK as they become available, go to [https://sdk.innovaphone.com SDK root] and follow the links to the version of interest.<br />
<br />
[[Image:app-platform-php-appplatform-simplified.png]]<br />
<br />
However, in some scenarios you may want to talk to the PBX or existing App Services directly from your own application (i.e. not via an App running in the myApps client). This article describes how to do this. General considerations are given that apply to all programming languages, and some sample code is given in PHP.<br />
<br />
[[Image:app-platform-php-appplatform-simplified-3rd-party.png]]<br />
<br />
==== Authentication ====<br />
To talk to the PBX or another ''App Service'', your application (written in PHP or any other language that can talk to a WebSocket) needs to be logged in to the PBX. While an ''App'' does not need to worry about this, as the ''myApps'' client (which is the context in which all ''Apps'' run) would take care of this, an App Service needs to implement the protocol itself.<br />
<br />
The process is as follows:<br />
* An ''App'' type object must be created on the PBX for your application.<br />
* The ''App'' object defines the authentication credentials (''Name'' and ''Password'') as well as the access rights of your application (i.e., the APIs in the ''App'' tab, the licenses in the ''License'' tab, and the Apps in the ''Apps'' tab)<br />
* The application must log in to the PBX using the credentials configured for the ''App'' type object that corresponds to the application. In other words, it logs in as the App, not as a specific user<br />
* The application can then use the allowed PBX APIs (according to the definitions in the ''Grant access to APIs'' section in the ''App'' tab of the ''App'' object).<br />
* The application can authenticate to other allowed App services (as defined in the ''Apps'' tab of the ''App'' object) using the PBX ''Services'' API. It can then request services from these App Services<br />
<br />
==== WebSocket ====<br />
''App Services'' (as well as the aforementioned PBX APIs) communicate using messages sent through WebSockets. The content of those messages has JSON syntax. <br />
<br />
Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are ''asynchronous''. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous ''request/response'' cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.<br />
<br />
==== JSON ====<br />
JSON stands for ''J''ava''S''cript''O''bject''N''otation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: <code >{"mystring":"a","myint":42}</code>. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as ''serialization/unserialization'' in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.<br />
<br />
Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the <code>json_encode()</code> and <code>json_decode()</code> functions, C# has the <code> DataContractJsonSerializer</code> class. <br />
<br />
Here is a full example of the JSON message that might be used to initiate a log-in to the PBX<br />
<pre><br />
{<br />
"mt": "Login",<br />
"type": "session",<br />
"userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"<br />
}<br />
</pre><br />
<br />
When working with ''App Services'', there are three message members which are somehow special for all such services. <br />
; mt (string) : the ''m''essage ''t''ype. Each message must have an <code>mt</code> member which denotes the message type<br />
; api (string, optional) : some App services (and in particular the PBX) support different defined sets of messages (referred to as ''api''). In this case, the ''mt'' message member is not sufficient to specify the type of message. Instead, the API identifier must be given in the ''api'' message member. The [https://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC api] provided by the PBX would be an example where all messages sent to or received from the PBX for this API must have an ''api'' message member with the (string) value <code>"RCC"</code><br />
; src (string, optional) : this member may be sent along with messages sent to an ''App Service''. If the message triggers responses, the ''App Service'' will copy the <code>src</code> member in to the responses. This allows the client to associate responses to the message that initiated them.<br />
All other members (if any) are defined by the application protocol.<br />
<br />
==== Definition of Application Protocols ====<br />
Message types, their members and the message flow are part of the documentation in the [https://sdk.innovaphone.com/13r2 software development kit (SDK)] that comes with the ''App Platform''. In this article, we only touch them for educational purposes.<br />
<br />
=== PHP Sample Code ===<br />
These scripts use a configuration from a file called ''my-pbx-data.php''. <br />
<br />
To provide proper data for your environment to the scripts, create the file ''my-pbx-data.php'' as follows:<br />
<syntaxhighlight lang="php"><br />
<?php<br />
$pbxdns = "your-pbx-dns-or-ip";<br />
$pbxapp = "your-app-object-name";<br />
$pbxpw = "your-pbx-app-password";<br />
</syntaxhighlight><br />
<br />
Our little example (available in <code>application.php</code>) will connect to 2 ''App Services'' on the ''App Platform'', ''Devices'' and ''Users'' to retrieve some configuration data. To authenticate towards the App service instances, a dedicated PBX ''App'' object in the PBX with appropriate rights is used.<br />
<br />
===== PBX configuration =====<br />
You will need a PBX which has been set up using the standard ''Install'' procedure (http://$pbxdns /install.htm).<br />
<br />
On that PBX, you additionally need to create an ''App'' type object <br />
* whose ''Name'' property is equal to the value of ''$pbxapp''<br />
* whose ''Password'' is equal to the value of ''$pbxpw''<br />
* that has ticked ''Services'' in the ''Grant access to APIs'' section of the ''App'' tab<br />
* that has ticked ''devices-api'' and ''users-admin'' in the ''Apps'' tab<br />
<br />
===== Login =====<br />
Logging-in to the PBX requires the following steps:<br />
* request a challenge from the PBX using the ''AppChallenge'' message<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"AppChallenge"}</syntaxhighlight ><br />
* receive the challenge from the PBX<br />
: <syntaxhighlight lang="json">received<-PBXWS {"mt":"AppChallengeResult","challenge":"8b7d8a1a3a281efd"}</syntaxhighlight ><br />
* compute the digest based on the App data, the secret and the challenge<br />
: <syntaxhighlight lang="json">sent->PBXWS <br />
{<br />
"mt": "AppLogin",<br />
"digest": "1a07f3c20d2a4f6b117c64d845b9bed7b884b49b82868f0e2b73200553c40e2d",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}</syntaxhighlight ><br />
* receive the confirmation from the PBX<br />
<syntaxhighlight lang="json">received<-PBXWS {"mt":"AppLoginResult","ok":true}</syntaxhighlight ><br />
<br />
This handshake is implemented in the ''AppPlatform\AppLoginAutomaton'' class available in classes\websocket.class.php. The thing that needs to be added is the WebSocket connection to the PBX (an ''AppPlatform\WSClient'' object required as constructor argument for the ''AppPlatform\AppLoginAutomaton'' class). We do this using a derived class named ''PbxAppLoginAutomaton'':<br />
<br />
<syntaxhighlight lang="php"><br />
class PbxAppLoginAutomaton extends AppLoginAutomaton {<br />
<br />
function __construct($pbx, AppServiceCredentials $cred, $useWS = false) {<br />
$this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :<br />
$this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";<br />
// create websocket towards the well known PBX URI<br />
$this->pbxWS = new WSClient("PBXWS", $this->pbxUrl);<br />
parent::__construct($this->pbxWS, $cred);<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
This class takes the URL to the PBX and the credentials (wrapped in an object of class ''AppPlatform\AppServiceCredentials'') as constructor arguments:<br />
<br />
<syntaxhighlight lang="php"><br />
$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
</syntaxhighlight><br />
<br />
Our new class ultimately is a derivative of the ''AppPlattform\FinitStateAutomaton'' class, so we can run the automaton like:<br />
<br />
<syntaxhighlight lang="php"><br />
$app->run();<br />
</syntaxhighlight><br />
<br />
The automaton will do the authentication towards the PBX as shown above and then terminate (which causes the ''run()'' member function to return).<br />
<br />
The code then verifies that the log-in to the PBX succeeded:<br />
<br />
<syntaxhighlight lang="php"><br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
Eh voilà, we are connected to the PBX. <br />
<br />
That was the easy part :-)<br />
<br />
Note that the code for the ''PbxAppLoginAutomaton'' can be found in the classes/websocket.class.php file.<br />
<br />
===== Determination of available services =====<br />
Now that we are successfully connected to the PBX, we can determine the services that are available to our application. This is done using the ''Services'' API available in the PBX (which is described in the SDK's [https://sdk.innovaphone.com/13r3/doc/appwebsocket/Services.htm ''Services'' page]).<br />
<br />
Only a few steps are required:<br />
* subscribe to the available services information<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"SubscribeServices","api":"Services"}</syntaxhighlight><br />
: note the additional <code>api</code> member <br />
* receive the respones<br />
: <syntaxhighlight lang="json">received<-PBXWS {"api":"Services","mt":"SubscribeServicesResult"}</syntaxhighlight><br />
: note that there is no further information in this message. The actual list of services will be received later on<br />
* unsubscribe from the list of services as we do not need dynamic updates<br />
: <syntaxhighlight lang="json">sent->PBXWS {"mt":"UnsubscribeServices","api":"Services"}</syntaxhighlight><br />
* receive the actual list of services available to us<br />
: <code>received< - PBXWS </code><br />
: <syntaxhighlight lang="json"><br />
{<br />
"api": "Services",<br />
"mt": "ServicesInfo",<br />
"services": [{<br />
"name": "devices-api",<br />
"title": "DevicesApi",<br />
"url": "https://192.168.178.71/sample.dom/devices/innovaphone-devices-api",<br />
"info": {<br />
"apis": {<br />
"com.innovaphone.devices": {}<br />
}<br />
}<br />
}, {<br />
"name": "users-admin",<br />
"title": "Users Admin",<br />
"url": "https://192.168.178.71/sample.dom/usersapp/innovaphone-usersadmin"<br />
}]<br />
}<br />
</syntaxhighlight><br />
: note that the actual list of services depends on the configuration of the ''Apps'' tab in the ''App'' object for our application<br />
<br />
<br />
Life was easy so far as we have derived the ''PbxAppLoginAutomaton'' utility class which simply did what we wanted so far, except for the initialization in its constructor. Now we want to implement further conversation with the PBX, which requires some more fundamental understanding of the finite state automaton classes.<br />
<br />
===== The finite state automaton classes =====<br />
Generally, to talk to the PBX or any App service, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the [https://github.com/Textalk/websocket-php Textalk] websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in <code>classes/textalk.class.php</code>. <br />
<br />
However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:<br />
* websocket is async by nature<br />
: you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create ''event driven'' code which processes messages whenever they come in and not when you expect them to come in<br />
* PBX login requires some fiddling with encryption schemes <br />
<br />
The sample code includes some classes to create ''finite state automatons''. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the ''App Services''<br />
<br />
Those extensions can be found in <code>classes/websocket.class.php</code>.<br />
<br />
===== The asynchronous programming model =====<br />
PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called ''FinitStateAutomaton''. This class handles the communication to a single ''App Service''. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called ''WSClient''). The class will use this WebSocket to talk to the ''App Service''. As we have seen above, the ''PbxAppLoginAutomaton'' utility class creates such an ''WSClient'' object and passes it to the ''AppLoginAutomaton'' (which extends the ''FinitStateAutomaton'' class).<br />
<br />
However, if you try to instantiate such a class (like <syntaxhighlight lang="php">$mya = new FinitStateAutomaton($mywebsocket)</syntaxhighlight> you will see that this is not possible, as the class is ''abstract''. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the ''App Service''.<br />
<br />
Let us look at how the ''AppLoginAutomaton '' class does this:<br />
<br />
<syntaxhighlight lang="php"><br />
class AppLoginAutomaton extends FinitStateAutomaton {<br />
public function ReceiveInitialStart(Message $msg) {<br />
<br />
$this->log("requesting challenge");<br />
$this->sendMessage(new Message("AppChallenge"));<br />
<br />
}<br />
}<br />
</syntaxhighlight><br />
It defines an override for the abstract ''ReceiveInitialStart'' member function. This function, as all functions whose name begins with ''Receive'' is called when an event is fed into the automaton. Usually, this event is a message (of type ''AppPlatform\Message'') received from an app service. In this special case however, it is a pseudo event generated by the system indicating that the automaton should start (hence the name ''ReceiveInitial'''Start'''''). So it implements the first action the automaton performs.<br />
<br />
In our case, as discussed above, it sends an ''AppChallenge'' message to the PBX. Recall that such an automaton is always instantiated with a single ''WSClient'' argument, which is the WebSocket connection used to talk to the App service (or the PBX which behaves like an App service). In other words, a single instance of a ''FinitStateAutomaton'' always talks to a single App service only. This is why we can simply say <br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage(new Message("AppChallenge"));<br />
</syntaxhighlight><br />
without specifying a destination and resulting in an AppChallenge message<br />
<syntaxhighlight lang="json"><br />
sent->PBXWS {"mt":"AppChallenge"}<br />
</syntaxhighlight><br />
sent to the PBX.<br />
<br />
Eventually, the PBX will respond with an ''AppChallengeResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppChallengeResult","challenge":"95ed003a24c3fc89"}<br />
</syntaxhighlight><br />
When this happens, the system will call the ''ReceiveInitial'''AppChallengeResult''''' member function:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$infoObj = new \stdClass();<br />
$infoHashString = json_encode($infoObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);<br />
$hashcode = hash('sha256', $sha = "{$this->cred->app}:::::{$infoHashString}:{$msg->challenge}:{$this->cred->pw}");<br />
// $this->log("computed challenge $sha", "debug"); // do not output in any other category than "debug" coz it includes the password<br />
$this->sessionKey = hash('sha256', "innovaphoneAppSessionKey:{$msg->challenge}:{$this->cred->pw}");<br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
}<br />
</syntaxhighlight><br />
<br />
It does some cryptographic magic to compute a digest from the available information and then sends an ''AppLogin'' message to the PBX:<br />
<br />
<syntaxhighlight lang="php"><br />
$this->sendMessage($this->loginData = new Message("AppLogin", "digest", $hashcode, "domain", "", "sip", "", "guid", "", "dn", "", "app", $this->cred->app, "info", $infoObj));<br />
</syntaxhighlight><br />
resulting in a message such as <br />
<syntaxhighlight lang="json"><br />
sent - >PBXWS {<br />
"mt": "AppLogin",<br />
"digest": "955da4b8351557c54c25b99fa8aad9e8b4ba7a4090ac5c2664e7ef7a301e6586",<br />
"domain": "",<br />
"sip": "",<br />
"guid": "",<br />
"dn": "",<br />
"app": "myapplication",<br />
"info": {}<br />
}<br />
</syntaxhighlight><br />
being sent to the PBX.<br />
<br />
The PBX would verify the correctness of the provided digest and then return an ''AppLoginResult'' message. <br />
<syntaxhighlight lang="json"><br />
received<-PBXWS {"mt":"AppLoginResult","ok":true}<br />
</syntaxhighlight><br />
Due to the reception of such a message, the ''ReceiveInitialAppLoginResult'' member function is called. It does a bit of internal housekeeping and then does two interesting things:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
<br />
$response->setMt("AppLoginSuccess");<br />
$this->postEvent($response);<br />
<br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
This code creates a ''$response'' message and sets the ''mt'' member to <code>AppLoginSuccess</code>. It returns the string "Dead" then.<br />
<br />
The event function (''ReceiveInitialAppChallengeResult'') we looked at so far did not have any explicit return statement. In PHP terms that means that it returns a ''null'' value. When an event returns a string however, it indicates that the automaton shall move to a new state. <br />
<br />
In our case, the new state is named "Dead" and it has a special meaning. An automaton in state "Dead" is considered to be terminated. The system will not listen for incoming messages on the automaton's WSClient socket any further. <br />
<br />
Note that the initial state of any automaton is named <code>Initial</code>. This is why event member functions are named ''Receive'''Initial'''Start'' for example. If a member function returns a new state name other than <code>Dead</code>, the member functions called upon subsequent incoming messages will be named ''Receive'''NewStateName'''Event''. Also, the first thing the system will do is call the ''Receive'''NewStateName'''Start'' member function.<br />
<br />
===== Working with multiple automatons ===== <br />
Obviously, applications may want to talk to multiple destinations, e.g. to the PBX for authentication and to one or more other App services. As a single ''FiniteStateAutomaton'' always talks to a single ''WSClient'' connection, we need to instantiate multiple automatons to be able to talk to multiple destinations:<br />
<br />
<syntaxhighlight lang="php"><br />
$a1 = new Type1Automaton();<br />
$a2 = new Type2Automaton();<br />
$transitioner = new AppPlatform\Transitioner($a1, $a2);<br />
$transitioner->run();<br />
</syntaxhighlight><br />
<br />
''AppPlatform\Transitioner'' is a helper class that takes a number of automatons (either listed as multiple arguments or wrapped in an array and given as single argument) as constructor arguments. When its ''run'' member function is called, it will in turn call all of the automatons ''ReceiveInitialStart'' member functions and then listen for messages coming in on any of the automatons ''WSClient'' connections. <br />
<br />
By the way, the ''FinitStateAutomaton'''s own ''run'' member function is trivial:<br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* utility function for the simple case you want to run a single (i.e. this) automaton only<br />
*/<br />
public function run($sockettimeout = 5) {<br />
$auto = new Transitioner($this);<br />
$auto->run($sockettimeout);<br />
}<br />
</syntaxhighlight><br />
<br />
The remaining question is how different automaton instances communicate to each other. This is done using the ''FinitStateAutomaton'''s ''postMessage'' member function as in <br />
<br />
<syntaxhighlight lang="php"><br />
$this->postEvent($response);<br />
</syntaxhighlight><br />
<br />
shown above in the ''ReceiveInitialAppLoginResult'' event function. When ''postEvent'' is called, the system would call the corresponding event functions in all currently active automatons. Note that these member functions are called synchronously, that is, they are already executed when the ''postEvent'' function returns. <br />
<br />
===== Tandems =====<br />
The ''postEvent'' mechanisms allows two or more automatons to work in tandem. To see how that works, we can have a look at the ''Pbx2AppAuthenticator'' class. <br />
<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class that authenticates to an App service with help from the PBX <br />
*/<br />
class Pbx2AppAuthenticator extends \AppPlatform\FinitStateAutomaton {<br />
</syntaxhighlight><br />
This class takes an ''ServiceConnector'' as constructor argument (for example one delivered from the ''PbxAppLoginAutomaton'') and creates a WebSocket connection (actually a ''WSClient'' object) towards the App service described by the ''ServiceConnector''.<br />
<br />
<syntaxhighlight lang="php"><br />
public function __construct(ServiceConnector $svc) {<br />
$this->svc = $svc;<br />
parent::__construct($svc->connect(), $svc->name);<br />
}<br />
</syntaxhighlight><br />
The first thing it then does is to send an ''AppChallenge'' message to the service:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
$this->sendMessage(new Message ("AppChallenge"));<br />
}<br />
</syntaxhighlight><br />
When the service returns the challenge, the class needs help from the ''PbxAppLoginAutomaton'' class (which talks to the PBX as we have seen before). To get help, it posts a ''GotChallenge'' message which includes the challenge received from the service and the name of that service.<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppChallengeResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallenge", "from", $this->svc->name, "challenge", $msg->challenge));<br />
}<br />
</syntaxhighlight><br />
<br />
When the ''PbxAppLoginAutomaton'' class (which has already been used to authenticate towards the PBX) is activated again (resulting in a call of its ''ReceiveInitialStart'' event function) it will determine that it already obtained a service list from the PBX (see above) and will therefore move into the new state named ''SvcAuthenticate''. <br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialStart(Message $msg) {<br />
if (empty($this->services)) {<br />
// initial login to the PBX<br />
return parent::ReceiveInitialStart($msg);<br />
}<br />
else {<br />
// PBX assisted login towards App services<br />
return "SvcAuthenticate";<br />
}<br />
}<br />
</syntaxhighlight><br />
When the ''GotChallenge'' event is posted, the ''ReceiveSvcAuthenticateGotChallenge'' event function is called<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGotChallenge(Message $msg) {<br />
$this->log("got challenge info from $msg->from");<br />
$this->sendMessage(new Message ("GetServiceLogin", "api", "Services", "app", $msg->from, "challenge", $msg->challenge, "src", $msg->from));<br />
}<br />
</syntaxhighlight><br />
and sends a ''GetServiceLogin'' message to the PBX which includes the service name and the challenge. It also includes a ''src'' property set to the name of the App. This will instruct the PBX to include the same property when the response is sent, so that the returned information can be associated with the proper service later on.<br />
<br />
The PBX will eventually respond with a ''GetServiceLoginResult'' message. This needs to be passed to the ''Pbx2AppAuthenticator'' class instance which will send it to teh service. This is done using the ''postEvent'' mechanism of course:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveSvcAuthenticateGetServiceLoginResult(Message $msg) {<br />
$this->postEvent(new Message ("GotChallengeResult", "for", $msg->src, "msg", $msg));<br />
</syntaxhighlight><br />
The ''Pbx2AppAuthenticator'' class instance which is interested in exactly this ''GetServiceLoginResult'' response has a ''ReceiveInitialGotChallengeResult'' event function which is called due to this ''postEvent'':<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialGotChallengeResult(Message $msg) {<br />
if ($msg->for == $this->svc->name) {<br />
$this->sendMessage(new Message ("AppLogin",<br />
"app", $msg->msg->app,<br />
"domain", $msg->msg->domain,<br />
"sip", $msg->msg->sip,<br />
"guid", $msg->msg->guid,<br />
"dn", $msg->msg->dn,<br />
"digest", $msg->msg->digest,<br />
"pbxObj", $msg->msg->pbxObj,<br />
"info", $msg->msg->info));<br />
}<br />
}<br />
</syntaxhighlight><br />
The function verifies that it has been called for the challenge result it is actually interested in and then passes it to its service. The service will return a ''AppLoginResult'' message which is processed by the ''ReceiveInitialAppLoginResult'' function:<br />
<syntaxhighlight lang="php"><br />
public function ReceiveInitialAppLoginResult(Message $msg) {<br />
if (empty($msg->ok) || $msg->ok != 1) {<br />
$this->log("FAILED to log in to $msg->app ($this->svc->name", "error");<br />
}<br />
else {<br />
$this->log("logged in to $msg->app ({$this->svc->name})", "runtime");<br />
$this->svc->authenticated = true;<br />
}<br />
return "User";<br />
}<br />
</syntaxhighlight><br />
The event function checks if the login was successfull (it should have been) and then moves into the new "User" state. This will trigger the ''ReceiveUserStart'' to be called which simply moves to the ''Dead'' state (that is, it terminates the automaton). <br />
<syntaxhighlight lang="php"><br />
/**<br />
* this simply ends the automaton. Override it if you derive a class from this<br />
* @param Message $msg<br />
* @return string <br />
*/<br />
public function ReceiveUserStart(Message $msg) {<br />
// <br />
return "Dead";<br />
}<br />
</syntaxhighlight><br />
<br />
So why is this? This is just convenience. In a real application, we obviously would want to continue talking to the service but the ''Pbx2AppAuthenticator'' class doesn't know how to. So we would certainly create a new class of our own which extends the ''Pbx2AppAuthenticator'' class and overrides the '' event function. A very simple example can be seen in the ''application.php'' script:<br />
<syntaxhighlight lang="php"><br />
class UsersLister extends AppPlatform\Pbx2AppAuthenticator {<br />
public function ReceiveUserStart(\AppPlatform\Message $msg) {<br />
$this->sendMessage(new AppPlatform\Message("UserData",<br />
"maxID", 9999, "offset", 0, "filter", "%", "update", false, "col", "id", "asc", true));<br />
}<br />
<br />
public function ReceiveUserUserDataInfo(\AppPlatform\Message $msg) {<br />
$this->log("from {$this->svc->name}: user: id $msg->id, name $msg->username", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
</syntaxhighlight><br />
This sample class simply sends a ''UserData'' message to the ''users-adin'' service and prints out the user information from the resulting ''UserDataInfo'' response.<br />
<br />
So this was an example of how automatons can work in tandem. However, the mechanism can also be used to work with more than two automatons. In the sample code, an instance of the ''Pbx2AppAuthenticator'' class (more precisely, a derived class such as the ''UsersLister'' class above) is created for each of the services listed by the PBX as available to our App. They are pushed into an array of ''FinitStateAutomaton''s in addition to the instance of the ''PbxAppLoginAutomaton'' class used to talk to the PBX:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
</syntaxhighlight><br />
The resulting list of tasks is then run using the ''Transitioner'' helper class:<br />
<syntaxhighlight lang="php"><br />
// connect to services we need<br />
$authenticators = [$app];<br />
<br />
// scan list of available app services for those we are interested ion<br />
foreach ($app->getServices() as $svc) {<br />
/* consider if we need this */<br />
switch ($svc->type) {<br />
case "innovaphone-devices-api":<br />
$authenticators[] = new DevicesLister($svc);<br />
break;<br />
case "innovaphone-usersadmin":<br />
$authenticators[] = new UsersLister($svc);<br />
break;<br />
}<br />
}<br />
// tell class talking to PBX how many authentications we need to do<br />
$app->setNumberOfAuthentications(count($authenticators) - 1);<br />
<br />
$tr = new AppPlatform\Transitioner($authenticators);<br />
$tr->run();<br />
</syntaxhighlight><br />
<br />
=== More sample code ===<br />
==== Using the PbxAdminApi ====<br />
This is a simple piece of code that uses the [https://sdk.innovaphone.com/13r3/doc/appwebsocket/PbxAdminApi.htm PbxAdminApi] to create a duplicate of the App object we wre using in the first sample code (''application.php''). It is available in ''pbxadminapi.php''. Before you can use the sample, you must make sure that the ''Admin'' check-mark is ticked in the ''App'' tab of your App object.<br />
<br />
It first uses the ''PbxAppLoginAutomaton'' to log in to the PBX. <br />
<br />
<syntaxhighlight lang="php"><br />
// Login to PBX<br />
$app = new AppPlatform\PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));<br />
$app->run();<br />
if (!$app->getIsLoggedIn()) {<br />
die("login to the PBX failed - check credentials");<br />
}<br />
</syntaxhighlight><br />
<br />
It then uses a new derivative of the ''FiniteStateAutomaton'' class to create the cloned object, passing the WSClient object used by the ''PbxAppLoginAutomaton'' class to its constructor:<br />
<syntaxhighlight lang="php"><br />
/**<br />
* class to create a new PBX App object just like the one we use for this application<br />
*/<br />
class AppObjectCreator extends AppPlatform\FinitStateAutomaton {<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// <br />
{<br />
return "CopyObject";<br />
}<br />
}<br />
<br />
/**<br />
* <br />
* @global string $pbxapp h323-name of source App object<br />
* @param AppPlatform\Message $msg<br />
*/<br />
public function ReceiveCopyObjectStart(AppPlatform\Message $msg) {<br />
global $pbxapp;<br />
$this->sendMessage(new AppPlatform\Message(<br />
"GetObject",<br />
"api", "PbxAdminApi",<br />
"h323", $pbxapp));<br />
}<br />
<br />
public function ReceiveCopyObjectGetObjectResult(AppPlatform\Message $msg) {<br />
// patch $msg so it can be used for object creation<br />
$msg->setMt("UpdateObject");<br />
// a new one shall be created, no update of the exiting one<br />
unset($msg->guid);<br />
// assert unique identifiers<br />
$msg->h323 .= "-clone";<br />
$msg->cn .= " (clone)";<br />
// we don't want any "Devices" entries<br />
unset($msg->devices);<br />
<br />
$this->sendMessage($msg);<br />
}<br />
<br />
public function ReceiveCopyObjectUpdateObjectResult(AppPlatform\Message $msg) {<br />
if (isset($msg->guid)) {<br />
$this->log("App object clone created with Guid $msg->guid", "runtime");<br />
} else {<br />
$this->log("App object clone could not be created: $msg->error", "runtime");<br />
}<br />
return "Dead";<br />
}<br />
}<br />
$me = new AppObjectCreator($app->getWs());<br />
$me->run();<br />
</syntaxhighlight><br />
<br />
==== Using the RCC API ====<br />
To run this sample code, you must make sure<br />
* there is a waiting queue with ''Name'' (h323/sip) <code>sink</code><br />
: * it has an ''Alert Timeout'' set to a reasonable number (some seconds, e.g. 3)<br />
: * it has the ''1st Announcement URL'' set to <code>MOH</code><br />
* there must be a user object<br />
: * it has our application (''myapplication'') check-makr ticked in its ''Apps'' tab<br />
: * it has a phone registered or a softphone provisioned<br />
* the App object for our application (''myapplication'') must have the ''RCC'' check-mark ticked in its ''App'' tab <br />
<br />
This sample code demonstrates use of the [http://sdk.innovaphone.com/13r2/doc/appwebsocket/RCC.htm RCC] API. It monitors some PBX user objects and shows all related calls. If a peer named ''sink'' is called, the call is forcefully terminated by our script. <br />
<br />
The code uses a derivative of the the ''FinitStateAutomaton'' class called ''RemoteControlUser''. The first thing it does is to send an ''Initialize'' message to the PBX which initializes the use of the ''RCC'' api.<br />
<br />
<syntaxhighlight lang="php"><br />
class RemoteControlUser extends AppPlatform\FinitStateAutomaton {<br />
<br />
private $myusers = [];<br />
private $mycalls = [];<br />
<br />
public function ReceiveInitialStart(AppPlatform\Message $msg) {<br />
// move to Monitoring state<br />
return "Monitoring";<br />
}<br />
<br />
public function ReceiveMonitoringStart(AppPlatform\Message $msg) {<br />
// Initialize RCC Api<br />
$this->sendMessage(new AppPlatform\Message(<br />
"Initialize",<br />
"api", "RCC"<br />
));<br />
}<br />
</syntaxhighlight><br />
<br />
The PBX will send a number of ''UserInfo'' message in response to the ''Initialize'' message. One message will be sent for each user that has our application check-mark ticked in its ''Apps'' tab. Those users then are said to be monitored by the application using the RCC API.<br />
<br />
For each new monitored user that is announced this way, the user information is stored in a class-local array. Also, an ''UserInitialize'' message is sent. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInfo(AppPlatform\Message $msg) {<br />
// remember UserInfo and do UserInitialize on the user<br />
if (isset($this->myusers[$msg->h323])) {<br />
$this->log("user '$msg->h323' updated", "runtime");<br />
}<br />
else {<br />
$this->log("new user '$msg->h323'", "runtime");<br />
$this->myusers[$msg->h323] = new stdClass();<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserInitialize",<br />
"api", "RCC",<br />
"cn", $msg->cn,<br />
"src", $msg->h323 // use "src" to be able to associate response<br />
));<br />
}<br />
$this->myusers[$msg->h323]->info = $msg;<br />
}<br />
</syntaxhighlight><br />
<br />
The ''UserInitializeResponse'' message will include a client-local identifier for the initialized user called ''user''. We remember it in our class-local array of users:<br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringUserInitializeResult(AppPlatform\Message $msg) {<br />
// remember local user id returned from UserInitialize (associated by "src")<br />
$this->myusers[$msg->src]->user = $msg->user;<br />
}<br />
</syntaxhighlight><br />
<br />
When a user object is monitored (that is, when there was a successful ''UserInitializeResponse'' message), the PBX will start to send additional ''CallInfo'' messages for each call related to the user and for all call-state changes.<br />
<br />
We look at those messages and determine of the remote party (the ''peer'') is called ''sink''. If so, we remember this call. If such remembered call later announces to be connected (a ''CallInfo'' message with ''msg'' member ''r-conn'' is received, we terminate the call by sending an appropriate ''UserEnd'' message. <br />
<br />
<syntaxhighlight lang="php"><br />
public function ReceiveMonitoringCallInfo(AppPlatform\Message $msg) {<br />
// a call state update<br />
$this->log("user $msg->user call $msg->call event $msg->msg", "runtime");<br />
// see if the call is towards the waiting queue "sink" <br />
if (isset($msg->peer) && isset($msg->peer->h323) && $msg->peer->h323 == "sink") {<br />
$this->log("user $msg->user call $msg->call with peer h323=sink (notified with $msg->msg)", "runtime");<br />
// remember this call for monitoring<br />
$this->mycalls[$msg->call] = $msg;<br />
}<br />
<br />
// check if we monitor this call and if we are connected<br />
if (isset($this->mycalls[$msg->call])) {<br />
switch ($msg->msg) {<br />
case "r-conn" :<br />
$this->log("call $msg->call connected ($msg->msg) - disconnecting", "runtime");<br />
$this->sendMessage(new AppPlatform\Message(<br />
"UserClear",<br />
"api", "RCC",<br />
"call", $msg->call,<br />
"cause", 88, // "Incompatible destination" see https://wiki.innovaphone.com/index.php?title=Reference:ISDN_Cause_Codes<br />
));<br />
break;<br />
case "del" : <br />
$this->log("call $msg->call ended ($msg->msg) - terminating", "runtime");<br />
return "Dead";<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
There is one other interesting mechanism which is used in this script:<br />
<syntaxhighlight lang="php"><br />
public function timeout() {<br />
$this->log("timeout", "runtime");<br />
}<br />
</syntaxhighlight><br />
<br />
When the system waits for incoming events, there is (by default) a timeout of 5 seconds. If there are no incoming events during this time, an error message is issued and all the automatons are terminated. However, if an automaton implements an override for the ''timeout'' function and it does not return true, the the timeout will be ignored. This is why our script waits a long time for the end of the call, occasionally spitting out a log message.<br />
<br />
===System Requirements===<br />
To run the sample code, you need<br />
* a platform that is able to run v13r2 PBX firmware (e.g. an IP411, but any other will do too)<br />
* a platform that can run the v13r2 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD<br />
* a web server running PHP 8.x or up (the code has not been tested with PHP 5.6 or 7, but it may run with no problems)<br />
* your favourite PHP IDE<br />
<br />
You can download the PBX firmware from [https://store.innovaphone.com/release/download.htm store.innovaphone.com].<br />
<br />
===Installation===<br />
The PBX and App Platform is installed using the ''Install''. <br />
==== PBX / ''App Platform'' ====<br />
* if you choose to use a gateway platform, install an SSD<br />
* upgrade your box (or IPVA) to the latest v13r1<br />
* you will need two extra IP address. One for the ''App Platform'', one for the PBX<br />
* perform a factory reset<br />
* access the box and you will see the installer<br />
* before you can run the installer, you will need do create a DNS name for your PBX and ''App Platform''<br />
** the easiest way to do this, is to use your box itself as an (additional) DNS server<br />
** access your PBX using the URL <code>https://</code>''your-pbx-ip-address''<code>/admin.xml?xsl=admin.xsl</code> to bypass the installer for a moment<br />
** go to ''Services/DNS''<br />
** tick ''Enable DNS Server''<br />
** add a ''New Resource Record'' of type <code>A</code><br />
** use <code>sindelfingen.sample.dom</code> as ''Name''<br />
** use ''your-pbx-ip-address'' as ''IP Address''<br />
** add another ''New Resource Record'' of type <code>A</code><br />
** use <code>apps.sample.dom</code> as ''Name''<br />
** use ''your-app-platform-ip-address'' as ''IP Address''<br />
* restart the box and access the installer again<br />
* complete the installer using DNS names set above, so that it installs a PBX and a fresh ''App Platform''<br />
* be sure to note the ''Admin Password'' shown in the installer<br />
* the installer will ask for the name of an ''admin account''. Be sure to note name and password. To make sure the sample code will run right away, use <code>ckl</code> as name here and <code>pwd</code> as password<br />
* for convenience, consider to not turn on ''two factor authentication''<br />
<br />
==== PHP Script ==== <br />
* unpack the sample sources in to your web server's content directories<br />
* make sure PHP scripts can be executed in the ''.../sample/sources'' directory<br />
* configure the PBX according to the hints given with the individual sample code above<br />
* open <code><br />
<br />
===Known Problems===<br />
<br />
<br />
====Missing php_openssl extension====<br />
In case following error appears, make sure to enable php_openssl in your php environment configuration:<br />
Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()<br />
<br />
=== Download ===<br />
The sample code can be downloaded [http://wiki.innovaphone.com/index.php?title=Howto:Wiki_Sources#websocketphp8 here ]<br />
<!-- == Related Articles == --><br />
<br />
[[Category:Howto|{{PAGENAME}}]]<br />
[[Category:Concept|{{PAGENAME}}]]<br />
[[Category:Sample|{{PAGENAME}}]]</div>Ckl