Enhancement #407

Refactor Session to use a serialization-based model

Added by Luci Stanescu over 4 years ago. Updated over 1 year ago.

Status:Closed Start date:
Priority:Low Due date:
Assignee:Adrian Georgescu % Done:

0%

Category:Middleware
Target version:3rd
Resolution:worksforme

Description

The current model of the Session requires the application to handle any race conditions that may appear as a result of events from the remote party. Refactoring it to handle both actions requested by the application and events received from the remote party in a serial fashion will simplify the way the Session is used and make it more robust.

The refactoring of the Session will also allow the refactoring of the interface between the Session and the media streams. The idea is to allow the streams to handle the SDP negotiation rather than PJSIP. This in turn will allow improvements such as allowing the audio codec to be changed within a stream.

History

Updated by Adrian Georgescu about 4 years ago

Redesign session and streams API #session

Currently if the session cannot perform an operation, it will simply give an error (or fail with a traceback). The only exception to this are the hold and unhold operations, which can be executed later, but their implementation is a hack (at best). This would require that the application contain logic for retrying requests, which would complicate the logic too much and is simply not feasible. A problem which has not (to my knowledge) been encountered but which exists and simply cannot be handled at the application level is the following race condition. Session between A (local endpoint) and B (remote endpoint) established; B sends re-INVITE to add a stream; application receives notification; user decides to reject the stream; application calls reject_proposal method; however, in the meanwhile, B CANCELs last reinvite and sends another one with a different stream; the call to reject_proposal will actually reject the second re-INVITE, rather than the first (much to the user's amazement). In short, the current design of the Session is not robust.
The API between the Session and the streams (described by IMediaStream) is the result of the combination of needs of the various stream types: for audio, the SDP is mostly managed by pjsip; for msrp based streams, the SDP is entirely managed within the middleware. pjsip managing the SDP results in a rather weird API (see get_local_media(for_offer), validate_update(..) and update(..) and how they're called), but also in some limitations: we currently have problems (some solved with hacks) with changing the codec, port and other parameters of the streams. The idea would be to eliminate the sdp negotiating role of pjsip and implement it ourselves, while changing the API between the Session and the streams to a more natural one (the streams should be in complete control of the part of SDP describing them).
Estimated time of development: 2-3 weeks.

Design notes

  • Serialize operations on the session
    • All operations (both locally and remotely initiated) should be serialized in one green thread running in a loop. Operations can be queued and this would mean that the application can push any number of operations, even while the Session is already handling others (even remotely initiated ones). This solves most of the problems but introduces new questions.
      • Since pjsip handles operations in the Engine's thread, the Session will be faced with the same problem when interfacing with the Invitation as is the application currently when interfacing with the Session; the Session needs to reschedule operations when it fails to process them.
      • Some operations need to be scheduled entirely (adding a stream), some need to be done in part immediately and in part scheduled (hold/unhold) and still others need to be done immediately (ending the session).
      • Some queued operations may have effect on other queued operations: adding a stream and then removing it may translate in either a NO-OP, a CANCEL sent to the re-INVITE (maybe?) or a new re-INVITE to remove the stream. All such possible interactions need to be considered carefully
    • Proposals such as adding streams should be encapsulated in an object (Proposal) and operations such as canceling, accepting, rejecting the proposal should be called on the Proposal object. The Session can associate such an object with the CSEQ of the re-INVITE in question. This fixes the race condition described above.
  • Remove the sdp negotiation role of pjsip and delegate it to the Invitation/Session and the streams.
    • Handle general SDP negotiation at Invitation/Session level and stream-specific parts at the streams level. This should provide a more flexible approach where each stream knows exactly what can and cannot be done (currently pjsip does some parts of the negotiation on behalf of the streams)
    • This may require patching pjsip

Updated by Adrian Georgescu almost 4 years ago

The implementation of the low level and high level SIP entities implemented so far in Pyrex and Python layers revealed weaknesses that need to be corrected before moving forward.

Inconsistencies

  • Invitation gets arguments in init, although they only apply for outgoing INVITEs
  • Invitation sometimes does not transition to disconnecting when local party initiated disconnect
  • Invitation timeouts are notified using a 408 code, even if a SIP response would not have been received (eg when ACK is not received)
  • An internal generated state change of Invitation is notified by sending a notification without headers and body; the reason is thus the error message, which is not clearly different from a reason received in a SIP response
  • The state machine of the Invitation is complicated by having the reinvited and reinviting states; these can be modelled as sub-states of the connected state
  • Methods for sending responses to INVITEs weren't symmetric -> 3 methods for responding to initial INVITE, 1 for responding to reINVITEs.
  • end method of Invitation accepted response_code, even though it wasn't always used
  • Credentials objects contains a SIPURI, although the Credentials should only contain a username and a password
  • SIPURI contains a display name, to represent From and To headers; a SIP URI does not contain one and different objects need to represent the headers
  • Headers are not represented as objects and are sent in notifications as tuples
  • No standard way to access underlying PJSIP structures of primitive objects (Route, Credentials etc. some have to_c, some don't. Some have _obj, some have other names.

Other problems

  • The primitive objects (SIPURI, Route etc.) are copied on every access; frozen counterparts to these would allow them to be readonly attributes
  • Invitation attribute call_id can not be accessed if Invitation transitioned to DISCONNECTED

Naming

  • Invitation: calling vs outgoing and incoming
  • Invitation: confirmed vs connected state
  • Invitation: callee_uri and caller_uri vs from_uri and to_uri
  • Invitation: is_outgoing vs direction

Others

  • Moved initialization of attributes in Invitation to cinit

Goal

The goal is a to have a consistent and generic SIP Session high level class and fix the underneath elements to behave in a better way.

Updated by Adrian Georgescu about 2 years ago

  • Priority changed from High to Low

Updated by Adrian Georgescu over 1 year ago

  • Status changed from New to Closed
  • Resolution set to worksforme

Low prio, to be revisited in the future after migration to pjsip 2.0, if time allows.

Also available in: Atom PDF