Ruby Dial Plans

Support zone
Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Ruby Dial Plans

Post by Aaron » Tue Mar 25, 2008 10:08 am

Hi All,

[Ed. 13 Aug 2008 - Small change to sys.Respond]
[Ed. 16 Aug 2008 - Added new method sys.GTalk]
[Ed: 21 Sep 2008 - Clarified dial string for sys.Dial command]

As of 25 Mar 2008 dial plans can now be written as Ruby (http://www.ruby-doc.org/docs/ProgrammingRuby/) scripts. Using a script instead of a single command allows much greater flexibility in power for processing calls.

To specify a dialplan should be interpreted as Ruby the dialplan must have #Ruby on the first line. An example of a minimalist Ruby dialplan is:

#Ruby
sys.Dial("blueface")

All the standard Ruby expressions and constructs are available for use and in addition two locally scoped variables are added to all dialplan scripts for each call processed:

- sys (stands for System) and contains some helper methods and dialplan commands.
- req (stands for Request) and contains the received INVITE request,

Reference:

sys.Callback(string dest1, string dest2) - Initiates a call to dest1 and if successful then calls dest2 and bridges the calls together. The dest1 and dest2 parameters are SIP Provider constructs.

Code: Select all

# Ruby dialplan example for sys.Callback
sys.Log("call received uri=#{req.URI.User}")
sys.Callback("012345@blueface", "023456@voipstunt")
sys.Dial(string provider) - Equivalent of the current dialplan Switch, SwitchCall or Dial command. The provider parameter must match a SIP Provider name and the Dial command will forward the call to that SIP Provider.

Code: Select all

# Ruby dialplan example for sys.Dial
sys.Log("call received uri=#{req.URI.User}")
sys.Dial("blueface") if req.URI.User.StartsWith("3")
sys.Respond(404, "No dialplan match")
The dial string format is:

[dst@]provider[, true]

dst - is the optional call destination. If not specified the call destination of the initiating request will be used. The dst can also make use of the initiating request value by using ${dst} or ${dst:n} to trim n characters from the start.

provider - must be a provider name or a valid DNS host name.

true - if present inidcates a trace is required.

Examples:

Code: Select all

sys.Dial("provider1")
sys.Dial("1234@provider1")
sys.Dial("1234@provider1, true")
sys.Dial("${dst:1}@provider1")

sys.Dial(string provider, int timeout) - Same as the previous Dial method except that the timeout indicates a maximum amount of time the call will ring for before giving up and returning execution to the script.

Code: Select all

# Ruby dialplan example for sys.Dial
sys.Log("call received uri=#{req.URI.User}")
sys.Dial("provider1", 10) 
sys.Dial("provider2", 15) 
sys.Dial("provider3", 10) 
sys.Respond(404, "No forwards answered")
string sys.ENUMLookup(string number) - Attempts to resolve an e164 number to a SIP URI, if successful the SIP URI is returned otherwise nil. The number passwed to the ENUMLookup method must have the domain the ENUM lookup is to be performed in included.
Examples of correctly formatted numbers:
0.0.0.0.2.4.2.5.1.3.5.3.enum.org
35315242000.enum.org
+35315242000.enum.org
003531524200.enum.org
Examples of incorrectly formatted numbers:
35315242000
0.0.0.0.2.4.2.5.1.3.5.3

Code: Select all

# Ruby dialplan example for sys.ENUMLookup
enumuri = sys.ENUMLookup("35315242000.mysipswitch.com")
if enumuri != nil
 sys.Dial(enumuri)
else
 sys.Respond(404, "ENUM lookup failed")
end
SIPAddressBinding[] sys.GetBindings() - Gets a list of bindings for your SIP account. If you do not have a device registered with the sipswitch then None will be returned.

Code: Select all

#Ruby dialplan example for sys.GetBindings()
if sys.IsAvailable() then
  bindings = sys.GetBindings()
  bindings.each { |binding|  sys.Log("#{binding.ContactSIPURI.Host}.") }
end
sys.GTalk(string username, string password, string sendToUser, string message) - Attempts to send a message to a gTalk user. The username and password must belong to a valid gTalk user and should be entered without @gmail.com or any other host portion. The sendToUser is the gTalk username of the account to send the message to (can be the same as username) also without the @gtalk.com or any other host portion. The message is the text that will be attempted to be sent.

Code: Select all

#Ruby dialplan example for sys.GTalk.
sys.GTalk("joe.bloggs", "password", "jane.doe", "Hello World!")
sys.GTalk("joe.bloggs", "password", "jane.doe", "#{req.URI.User}")
Note that at times it can take quite a while to establish a connection to the gTalk network and the sipswitch dialplan will only wait for a maximum of 5s. In the very limited testing undertaken so far the first attempt to send a message wil often timeout but subsequent ones will then work.

bool sys.In and bool sys.Out - Can be used to determine whether a call being processed by the dialplan is an outgoing or incoming call. An outgoing call is one you as the owner of the dialplan are placing. An incoming call is one being received from an external caller to you.

Code: Select all

#Ruby dialplan example for sys.In and sys.Out
if sys.Out then # sys.In can also be used in the same manner.
 sys.Log("Outgoing call from user domain=#{sys.FromDomain}.")
 sys.Dial("blueface")
else 
 sys.Log("Incoming call to domain=#{sys.ToDomain}.") 
 if sys.IsAvailable() then
   sys.Log("#{sys.Username} is online.")
   sys.Dial("local")
 else
   sys.Log("#{sys.Username} is not online.")
   sys.Respond(480, "#{sys.Username} Not online")
 end
end
Note about sys.Username , this is a variable representing your username so it must not be changed! If you want to test it, use the log function : sys.Log("#{sys.Username} is really cool")

bool sys.IsAvailable() - Is used to check whether the dial plan owner's account has a SIP account online. The function returns true if there is a current binding and false otherwise.

Code: Select all

#Ruby dialplan example for sys.IsAvailable()
sys.Log("call received uri=#{req.URI.User} from #{req.Header.From.FromURI.User}")
sys.Log("isavailable=#{sys.IsAvailable().ToString()}.")

if sys.IsAvailable()
 sys.Dial("me@local")
else
 sys.Dial("mymobile@provider")
end
sys.Log(string message) - Logs a message to both the monitoring screen and the telnet console. Allows debugging and informational messages to be provided as part of Ruby dialplans.

Code: Select all

# Ruby dialplan example for sys.Log
sys.Log("Log message from Ruby dialplan.")
sys.Log("Incoming call for uri=#{req.URI.User}.").
sys.Respond(int statusCode, string reasonPhrase) - Sends a SIP response to the caller with the specified SIP response code and reason.

Code: Select all

# Ruby dialplan example for sys.Respond
sys.Respond(404, "No dialplan match")
If the status code is <= 199 then it is treated as an informational response and the dialplan script keeps running. If the status code is >= 200 then it's treated as a final response to the client and the script terminates after the response is sent.

sys.Trace = true|false - Activates or deactivates tracing of dial plan calls. The trace will be sent to the email address registered on the owning sipsiwtch account once the call is answered or is unsuccessful.

Code: Select all

# Ruby dialplan example to turn tracing on
sys.Trace = true

req.URI - The SIP URI for the incoming request.
req.URI.User - The user portion of the request URI this is the value used in the non-Ruby dial plans as ${EXTEN} or ${dst}.
req.URI.Host - The host portion of the request URI.
req.Header - The SIP Headers for the incoming request.
req.Header.From - The SIP From header for the incoming request.
req.Header.From.FromURI - The URI portion of the From header.
req.Header.From.FromName - The name portion of the From header.
req.Header.To - The SIP To header for the incoming request.
req.Header.To.ToURI - The URI portion of the To header.
req.Header.To.ToName - The name portion of the To header.
req.Header.Contact[0] - The first Contact header in the incoming Request.
req.Header.Contact[0].ContactURI - The URI of the first Contact header.
req.Header.Contact[0].ContactName - The Name of the first Contact header.
req.Header.CSeq
req.Header.CallId
etc.

A special case is req.Header.From which is what most SIP Providers will use to set custom callerid's if they support it. Unfortunately the From header is also normally used to identify who you are so changing it can affect your ability to authenticate your call.

It is possible to customise the From header on a forwarded call.

Code: Select all

req.Header.From.FromURI.User = "01234567"
req.Header.from.FromURI.Host = "mydomain.com"
req.Header.From.FromName = "Joe Bloggs"
The above script would result in a From header of:

From: "Joe Bloggs" <sip:01234567@mydomain.com>

The field that is generally used for authentication is req.Header.From.FromURI.User so be aware your calls could fail if it is modified.

Some of the above fields are strings and can be used directly in log messages. If you get an error logging an object add ToString() at the end of it. For example:

sys.Log("Call received for #{req.URI.ToString()}")

An example of a Ruby dialplan that is equivalent to the non-Ruby approach is:

#Ruby
sys.Log("call received uri=#{req.URI.ToString()}")
case req.URI.User
when "123" then sys.Dial("fwd")
when "456" then sys.Dial("voipstunt")
else sys.Dial("blueface")
end

The documentation above is far from complete but should serve to help get anyone interested started.

Note of warning: The Ruby engine in use by the sipswitch is a development project called IronRuby (http://www.ironruby.net/) and there are almost certainly going to be issues with the implementation at this early stage.

Second note of warning: The following strings are not permitted anywhere in a Ruby dialplan: require, include, system, :: and `. This is to prevent scripts from executing privileged operations on the sipswitch server.

Regards,

Aaron
Last edited by Aaron on Sun Sep 21, 2008 12:24 pm, edited 17 times in total.

markcs
Posts: 42
Joined: Sat Feb 16, 2008 5:44 pm

GREAT!

Post by markcs » Tue Mar 25, 2008 10:17 pm

HI Aaron!

This will be fantastic and create really configurable dial plans. I guess even the 'follow-me' feature can be implemented now - I tested it quickly and seemed to work.

One question: Does the implementation support regular expressions?

I had this for test:

Code: Select all

def dialSwe(number)
   sys.Log("DialSwe to uri=#{req.URI.ToString()}") 
   sys.Dial("Digisip")
end

sys.Log("New call received uri=#{req.URI.ToString()}") 
case req.URI.User 
when /^020.*/
     dialSwe(req.URI.User)
else sys.Log("No match for #{req.URI.User}")
end
I would have expected a number begining with 020 to call the method dialSwe. This didn't work and in the monitor window I see:

New call received uri=sip:020489324@sip.mysipswitch.com
No match for 020489324

Any ideas what I have done wrong?

Thanks again!

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Tue Mar 25, 2008 10:57 pm

Hi markcs,

I was hoping it would be a week or so before someone asked the regular expression question :D .

I encountered the same thing during my testing and need to check if it's a bug with IronRuby.

Regards,

Aaron

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Wed Mar 26, 2008 5:07 am

Hi markcs,

Regular expression support in IronRuby has not been implemented yet but hopefully it's not too far away. [edit: as of 26 Apr 2008 regular expression support is now in IronRuby and the answer tomarkcs's original question is now yes.]

http://www.ruby-forum.com/topic/147230

In the meantime I have created a workaround method that allows the use of regular expressions but only as a method call and not with the standard Ruby /../ or =~ syntax.

#Ruby
sys.Log("call received uri=#{req.URI.User}")
sys.Dial("blueface") if sys.RegexMatch(req.URI.User, "^3")
sys.Dial("voipstunt") if sys.RegexMatch(req.URI.User, "^4")
sys.Respond(404, "No match")

Regards,

Aaron
Last edited by Aaron on Wed Apr 30, 2008 12:25 pm, edited 1 time in total.

markcs
Posts: 42
Joined: Sat Feb 16, 2008 5:44 pm

Post by markcs » Sun Mar 30, 2008 6:55 pm

Aaron wrote: In the meantime I have created a workaround method that allows the use of regular expressions but only as a method call and not with the standard Ruby /../ or =~ syntax.

#Ruby
sys.Log("call received uri=#{req.URI.User}")
sys.Dial("blueface") if sys.RegexMatch(req.URI.User, "^3")
sys.Dial("voipstunt") if sys.RegexMatch(req.URI.User, "^4")
sys.Respond(404, "No match")
Hi Aaron,

Using this method is it possible to do somtehing link this?

Code: Select all

when /^0(2|3|4|5|6|7|8).*/
It seems to fail. Or do I have to use a seperate statement for each?

Thanks!

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Sun Mar 30, 2008 10:00 pm

Hi markcs,

I don't know enough about Ruby to be able to create a method that can duplicate the regular expressions in a case..when.. construct. Until IronRuby supports regular expressions it's likely to be sys.RegexMatch only.

Regards,

Aaron

jack9901
Posts: 277
Joined: Tue Jan 29, 2008 7:30 pm

Post by jack9901 » Mon Mar 31, 2008 7:48 am

Tried Ruby dial plan. Looks great to techies. Might be difficult for non-techies. Anyway, I am a novice to Ruby with two hours of stuffed quick reading of Ruby guide. Here is something I found out that might be userful to novice like me.

1. The good and old $(EXTEN} still works in sys.Dial, e.g. sys.Dial("${EXTEN:2}@provider"). This is useful if you are not yet ready to manipulate Strings in Ruby.

2. The new and super Ruby String methods does not work on req.URI.User, req.URI etc. because they are ClrString which has very limited methods. To gain the power of Ruby String methods, use the conversion method to_str. Some examples,

# strip prefix, prefix length=2, req.URI.User.to_str[0,2] is the prefix, req.URI.User.to_str[2,20] == ${EXTEN:2} assuming the length of req.URI.User is less than 22.
if req.URI.User.to_str[0,2] == "*1"
sys.Dial("#{req.URI.User.to_str[2,20]}@provider1")
elsif req.URI.User.to_str[0,2] == "*2"
sys.Dial("#{req.URI.User.to_str[2,20]}@provider2")
elsif req.URI.User.to_str[0,2] == "*3"
sys.Dial("00#{req.URI.User.to_str[2,20]}@provider3") #add prefix 00
end

I tried sys.RegexMatch but could not get it to work. Could be I missed something. You may find a one page reading here useful for you: http://www.techotopia.com/index.php/Rub ... Comparison

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Mon Mar 31, 2008 8:37 am

Hi jack9901,

Well spotted on the Ruby vs CLR string issue. It took me a while to sus that one out as well. In the same vein as IronRuby does not currently have support for regular expressions in the /../ form eventually it will fully support all the Ruby string methods as well.

The advantage of the current situation is that you can use all the .Net framework string methods within the dialplan script. The .Net String object has more functionality but not the same succinctness as the Ruby String.

http://msdn2.microsoft.com/en-us/librar ... S.85).aspx

Code: Select all

# The equivalent of ${EXTEN:2} using a .Net String is:
sys.Log("call uri=" + req.URI.User.Substring(2) + ".")
The RegexMatch method should be working correctly or at least there are no known issues with it. Can you post up the method call that was not working for you and I'll take a look.

Regards,

Aaron

markcs
Posts: 42
Joined: Sat Feb 16, 2008 5:44 pm

Post by markcs » Mon Mar 31, 2008 10:47 pm

Aaron wrote: The RegexMatch method should be working correctly or at least there are no known issues with it. Can you post up the method call that was not working for you and I'll take a look.
This doesn't seem to work for me:

if sys.RegexMatch(req.URI.User, "^*") then

These do

if sys.RegexMatch(req.URI.User, "^9") then

if (req.URI.User.to_str[0,1] == "*") then (thanks for the tip jack9901)

Aaron
Site Admin
Posts: 4652
Joined: Thu Jul 12, 2007 12:13 am

Post by Aaron » Mon Mar 31, 2008 10:56 pm

markcs wrote: This doesn't seem to work for me:

if sys.RegexMatch(req.URI.User, "^*") then
Hi markcs,

That's not a valid regular expression. Try:

if sys.RegexMatch(req.URI.User, "^.*") then

Although that's the same as saying:

if true then

Since a regular expression of ^.* will match anything.

Regards,

Aaron
Last edited by Aaron on Tue Apr 01, 2008 8:00 am, edited 1 time in total.

Locked