Skip to content

Instantly share code, notes, and snippets.

@loopingrage
Created May 27, 2011 03:58
Show Gist options
  • Save loopingrage/3343fd5fd3b14b861e1b to your computer and use it in GitHub Desktop.
Save loopingrage/3343fd5fd3b14b861e1b to your computer and use it in GitHub Desktop.

Calls

The Ozone protocol primarily deals with calls. Inbound calls originate from the PSTN or via SIP and are offered to Ozone clients via XMPP using a Jabber Identifier (JID). Each call is in turn represented by it's own unique JID allowing a two way conversation between the Ozone client and the server that's handling the call signaling and media.

JID Format

The JID follows a specific format. In XMPP the JID is constructed as

  <node>@<domain>/<resource>

For Ozone, the <node> portion of the JID always represents the call ID. The <resource>, when present, represents the affected command ID.

Incoming Calls

  <!-- Message comes from the Call's JID -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <offer xmlns='urn:xmpp:ozone:1'
        to='tel:+18003211212'
        from='tel:+13058881212'>
      <!-- Signaling (e.g. SIP) Headers -->
      <header name='Via' value='192.168.0.1' />
      <header name='Contact' value='192.168.0.1' />
    </offer>
  </iq>

An iq packet is acknowledged with an empty reply (note that 'to' and 'from' are now reversed):

  <iq type='result' from='16577@app.ozone.net/1' to='9f00061@call.ozone.net/1' id='1234'/>

Once the <offer/> is acknowledged, the Ozone client can control the call by using one of the following commands.

  <!-- Accept (e.g. SIP 180/Ringing). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <accept xmlns='urn:xmpp:ozone:1'>
      <!-- Sample Headers (optional) -->
      <header name="x-skill" value="agent" />
      <header name="x-customer-id" value="8877" />
    </accept>
  </iq>

  <!-- Answer (e.g. SIP 200/OK). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <answer xmlns='urn:xmpp:ozone:1'>    
      <!-- Sample Headers (optional) -->
      <header name="x-skill" value="agent" />
      <header name="x-customer-id" value="8877" />
    </answer>
  </iq>

  <!-- Redirect (e.g. SIP 302/Redirect). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <redirect to='tel:+14152226789' xmlns='urn:xmpp:ozone:1'>    
      <!-- Sample Headers (optional) -->
      <header name="x-skill" value="agent" />
      <header name="x-customer-id" value="8877" />
    </redirect>
  </iq>

A call can also be rejected. Rejections can include an optional rejection reason. Rejection reasons are one of <busy/>, <decline/> or <error/>. If not specified, <decline/> is used as the default reason.

  <!-- Decline  (.g. SIP 603/Decline). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <reject xmlns='urn:xmpp:ozone:1'>
      <decline />
      <!-- Sample Headers (optional) -->
      <header name="x-reason-internal" value="bad-skill" />
    </reject>
  </iq>

  <!-- Busy  (.g. SIP 486/Busy). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <reject xmlns='urn:xmpp:ozone:1'>
      <busy />
      <!-- Sample Headers (optional) -->
      <header name="x-busy-detail" value="out of licenses" />
    </reject>
  </iq>

  <!-- Error  (.g. SIP 500/Internal Server Error). Only applies to incoming calls. -->
  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <reject xmlns='urn:xmpp:ozone:1'>
      <error />
      <!-- Sample Headers (optional) -->
      <header name="x-error-detail" value="soem descriptive error message" />
    </reject>
  </iq>

Outbound Calls

Ozone clients can initiate outbound calls using the <dial /> command.

  <!-- Handled by the domain controller which picks a random Ozone Server -->
  <iq type='set' to='call.ozone.net' from='16577@app.ozone.net/1'>
     <dial to='tel:+13055195825' from='tel:+14152226789' xmlns='urn:xmpp:ozone:1' />
  </iq>
  
  <iq type='result' to='16577@app.ozone.net/1' from='call.ozone.net'>
     <!-- The Call's ID -->
     <ref id='9f00061' />
  </iq>

The client will then begin to receive progress events as the call makes it's way through the network.

  <!-- Far end has accepted the call and is ringing (e.g. 180/Ringing) -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <ringing xmlns='urn:xmpp:ozone:1' />
  </iq>
  
  <!-- The outgoing call has been answered (e.g. 200/OK) -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <answered xmlns='urn:xmpp:ozone:1' />
  </iq>

If for some reason the call is not accepted by the far end, the Ozone client will receive an <end/> event indicating the reason for the failure.

  <!-- Dial destination did not answer within the timeout period -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <end xmlns='urn:xmpp:ozone:1'>    
      <timeout />
    </end>
  </iq>
  
  <!-- Dial destination is busy and annot answer the call -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <end xmlns='urn:xmpp:ozone:1'>    
      <busy />
    </end>
  </iq>

  <!-- Dial destination rejected the call -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <end xmlns='urn:xmpp:ozone:1'>    
      <reject />
    </end>
  </iq>

  <!-- Ozone encountered a system error while dialing -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
    <end xmlns='urn:xmpp:ozone:1'>    
      <error>Lucy, you got some 'splainin to do</error>
    </end>
  </iq>

Note: A Ozone <end/> indicates that the call has been disconnected and that no more events are possible for this call. Therefore, the <end/> event is a perfect point for clients to clean up resources related to the controlling of the call.

Hanling caller hangup

If the caller hangs up the call Ozone will produce an <end/> event with a <hangup/> reason like so:

<iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/1' id='1234'>
  <end xmlns='urn:xmpp:ozone:1'>    
    <hangup/>
  </end>
</iq>

Note: A Ozone <end/> indicates that the call has been disconnected and that no more events are possible for this call. Therefore, the <end/> event is a perfect point for clients to clean up resources related to the controlling of the call.

Forcing a call to end

Ozone client can force a call to end by sending a <hangup/> command to the call's JID.

<iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
  <hangup xmlns='urn:xmpp:ozone:1'>    
    <!-- Sample Headers (optional) -->
    <header name="x-reason-internal" value="bad-skill" />
  </hangup>
</iq>

NOTE: The client will still receive an <end/> event indicating that that call has been disconnected and that no further events or commands are possible.

Components

Components extend the Ozone protocol by providing additional media and call control functionality.

Components are started by sending a specialized command to the Ozone server. This example shows the use of the <say xmlns='urn:xmpp:ozone:say:1'/> component. Don't worry about the specifics of the <say/> element for now. We'll discuss each component in detail in the folowing chapters. The key point here is that a component request is being sent to the call's JID.

NOTE: You can easily spot a component request because the namespace will be in the format urn:xmpp:ozone:COMPONENT_NAME:1

  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <say xmlns='urn:xmpp:ozone:say:1' 
      voice='allison'>
      <audio url='http://acme.com/greeting.mp3'>
          Thanks for calling ACME company
      </audio>
      <audio url='http://acme.com/package-shipped.mp3'>
          Your package was shipped on
      </audio>
      <say-as interpret-as='date'>12/01/2011</say-as>
    </say>
  </iq>

The Ozone server will validate the component request and attach a new instance of the component to the call. In a happy day scenario the client will immediately receive an IQ result containing the newly created component's ID. The component's ID is combined with the call's JID to control the component (e.g. pause, resume, stop, etc.) and to corelate events coming from the component as well.

A component's JID is calculated by combining the call's JID with the newly created component's ID like so: <call-id>@<ozone-domain>/<component-id>

  <!-- Server responds a unique ID -->
  <iq type='result' to='16577@app.ozone.net/1' to='9f00061@call.ozone.net/1'>
     <ref id='fgh4590' xmlns='urn:xmpp:ozone:1' />
  </iq>

NOTE: Remember that Ozone executes components asynchronously and in many cases more than one component can run at the same time. For example, you can have the <record xmlns='' /> component running throught the entire while you interact with the user using the "say" and "ask" components resulting in the entire call being recorded.

Component Commands

Components are controlled by sending command messages to their unique JID. The only command required by all components is the "stop" command.

  <iq type='set' to='9f00061@call.ozone.net/fgh4590' from='16577@app.ozone.net/1'>
    <stop xmlns='urn:xmpp:ozone:1' />
  </iq>

As you'll see in the following chapters, component developers can get very creative with the command they support allowing for some really interesting capabilities. For example, the ability to pause and resum audio playback as well as muting and unmuting the caller's microphone while in a conference.

Component Events

Events are specialized lifecycle messages that flow from a component instance to the Ozone client that's controlling the call. As you'll see in the following chapters, component events are very powerful and can provide great insight into a running application.

The only event required by all components is the <complete xmlns='urn:xmpp:ozone:comp:complete:1' />. This is an example complete event produced by the <say urn:xmpp:ozone:say:1/> component when audio playback has completed succesfully.

  <iq type='set' to='9f00061@call.ozone.net/fgh4590' from='16577@app.ozone.net/1'>
   <complete xmlns='urn:xmpp:ozone:comp:1'>
     <success xmlns='urn:xmpp:ozone:say:complete:1' />
   </complete>
  </iq>

Say Component

  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <say xmlns='urn:xmpp:ozone:say:1' 
        voice='allison'>
      <audio url='http://acme.com/greeting.mp3'>
          Thanks for calling ACME company
      </audio>
      <audio url='http://acme.com/package-shipped.mp3'>
          Your package was shipped on
      </audio>
      <say-as interpret-as='date'>12/01/2011</say-as>
    </say>
  </iq>

Commands

  <!-- Client pause the say -->
  <iq type='set' to='9f00061@call.ozone.net/fgh4590' from='16577@app.ozone.net/1'>
    <pause xmlns='urn:xmpp:ozone:say:1' />    
  </iq>

  <!-- Client resumes the say -->
  <iq type='set' to='9f00061@call.ozone.net/fgh4590' from='16577@app.ozone.net/1'>
    <resume xmlns='urn:xmpp:ozone:say:1' />    
  </iq>

Events

  <!-- Playback completed successfully -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <success xmlns='urn:xmpp:ozone:say:complete:1' />
    </complete>
  </iq>
  
  <!-- Component was stopped -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <stop xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>
  
  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <hangup xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <error xmlns='urn:xmpp:ozone:comp:complete:1'>
        Something really bad happened
      </error>
    </complete>
  </iq>
  

Ask Component

  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <ask xmlns='urn:xmpp:ozone:ask:1'
        bargein='true'
        min-confidence='0.3'
        mode='speech|dtmf|both'
        recognizer='en-US'
        terminator='#'
        timeout='12000'>
      <prompt voice='allison'>
        Please enter your four digit pin
      </prompt>
      <choices content-type='application/grammar+voxeo'>
        [4 DIGITS]
      </choices>
    </ask>
  </iq>

Events

  <!-- Successfull Input -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <success mode="speech" confidence="0.45" xmlns='urn:xmpp:ozone:ask:complete:1'>
        <interpretation>1234</interpretation>
        <utterance>one two three four</utterance>
      </success>
    </complete>
  </iq>
  
  <!-- Incorrect Input -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <nomatch xmlns='urn:xmpp:ozone:ask:complete:1' />
    </complete>
  </iq>  

  <!-- No Input Provided -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <noinput xmlns='urn:xmpp:ozone:ask:complete:1' />
    </complete>
  </iq>  

  <!-- Component was stopped -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <stop xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <hangup xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <error xmlns='urn:xmpp:ozone:comp:complete:1'>
        Something really bad happened
      </error>
    </complete>
  </iq>

Transfer Component

  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <transfer xmlns='urn:xmpp:ozone:transfer:1'
        from='tel:+14152226789'
        terminator='*'
        timeout='120000'
        answer-on-media='true'>
      <to>tel:+4159996565</to>
      <to>tel:+3059871234</to>
      <ring voice='allison'>
        <audio url='http://acme.com/transfering.mp3'>
            Please wait while your call is being transfered.
        </audio>
      </ring>
    </transfer>
  </iq>

Events

  <!-- Transfer Connected  -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <stop xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component was stopped -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <stop xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <hangup xmlns='urn:xmpp:ozone:comp:complete:1' />
    </complete>
  </iq>

  <!-- Component completed because the call was disconnected -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@call.ozone.net/fgh4590'>
    <complete xmlns='urn:xmpp:ozone:comp:1'>
      <error xmlns='urn:xmpp:ozone:comp:complete:1'>
        Something really bad happened
      </error>
    </complete>
  </iq>

Conference Component

  <iq type='set' to='9f00061@call.ozone.net/1' from='16577@app.ozone.net/1'>
    <conference xmlns='urn:xmpp:ozone:conference:1'
        name='1234'
        mute='false'
        terminator='*'
        tone-passthrough='true'
        beep='true'
        moderator='true'>
      <announcement>
        Jose de Castro has entered the conference
      </announcement>
      <music>
        The moderator how not yet joined.. Listen to this awesome music while you wait.
        <audio url='http://www.yanni.com/music/awesome.mp3' />
      </music>
      <meta>
        <data name='name'>Jose</entry>
        <data name='position'>Thing Doer</entry>
      </meta>
    </conference>
  </iq>

Commands

  <!-- Mute this participant -->
  <iq type='set' to='9f00061@ozone.net/d951cc41' from='16577@app.ozone.net/1'>
    <mute />
  </iq>

  <!-- Unmute this participant -->
  <iq type='set' to='9f00061@ozone.net/d951cc41' from='16577@app.ozone.net/1'>
    <unmute />
  </iq>

  <!-- Kick this participant, Do we need this? -->
  <iq type='set' to='9f00061@ozone.net/d951cc41' from='16577@app.ozone.net/1'>
    <kick>asshole</kick>
  </iq>

Events

  <!-- Indicates that this participant has been put on hold -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@ozone.net/d951cc41'>
    <on-hold />
  </iq>

  <!-- Indicates that this participant has been put back into the conference -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@ozone.net/d951cc41'>
    <off-hold />
  </iq>

  <!-- Final event raised when the this caller has left the conference -->
  <iq type='set' to='16577@app.ozone.net/1' from='9f00061@ozone.net/d951cc41'>
    <complete reason='terminator|hangup|stopped|timeout|closed|kicked'>
      <kick>asshole</kick>
    </complete>
  </iq>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment