SipMSRPApi » History » Version 17
Oliver Bril, 03/31/2009 04:03 PM
1 | 1 | Adrian Georgescu | = MSRP API = |
---|---|---|---|
2 | |||
3 | [[TOC(WikiStart, Sip*, depth=3)]] |
||
4 | |||
5 | Message Session Relay Protocol (MSRP) is a protocol for transmitting a series of related instant messages in the context of a session. Message sessions are treated like any other media stream when set up via a rendezvous or session creation protocol such as the Session Initiation Protocol (SIP). |
||
6 | |||
7 | * MSRP sessions are defined in [http://tools.ietf.org/html/rfc4975 RFC 4975]. |
||
8 | * MSRP relay extension used for NAT traversal of instant messaging and file transfer sessions is defined in [http://tools.ietf.org/html/rfc4976 RFC 4976]. |
||
9 | |||
10 | 5 | Oliver Bril | The MSRP protocol is implemented by [http://devel.ag-projects.com/cgi-bin/darcsweb.cgi?r=python-msrplib;a=summary msrplib] Python package. On top of it, {{{sipsimple}}} provides higher level classes integrated into middleware notification and configuration systems: |
11 | 1 | Adrian Georgescu | |
12 | 4 | Oliver Bril | * {{{sipsimple.msrp.MSRPChat}}} |
13 | * {{{sipsimple.msrp.MSRPFileTransfer}}} |
||
14 | 1 | Adrian Georgescu | * {{{sipsimple.msrp.MSRPDesktopSharing}}} |
15 | |||
16 | 11 | Adrian Georgescu | These classes are used internally by [wiki:SipMiddlewareApi#Session Session], which provides the necessary methods to access their features. The notifications posted by these classes are also handled internally by [wiki:SipMiddlewareApi#Session Session]. The notifications that are relevant to the user are then reposted by the Session instance. Refer to [wiki:SipMiddlewareApi#Session Session documentation] for details on the Session API. To communicate with the middleware, MSRP high level classes use the notification system provided by the [http://pypi.python.org/pypi/python-application python-application] package. |
17 | 10 | Adrian Georgescu | |
18 | 12 | Adrian Georgescu | == MSRPChat high level API == |
19 | 1 | Adrian Georgescu | |
20 | 10 | Adrian Georgescu | {{{sipsimple.msrp.MSRPChat}}} implements Instant Messaging over MSRP in the context of SIPSIMPLE middleware. This class |
21 | 1 | Adrian Georgescu | |
22 | 5 | Oliver Bril | * automatically wraps outgoing messages with Message/CPIM if that's necessary according to accept-types |
23 | 7 | Oliver Bril | * unwraps incoming Message/CPIM messages; for each incoming message, {{{MSRPChatGotMessage}}} is posted. |
24 | 5 | Oliver Bril | * plays notification sounds on received/sent message |
25 | |||
26 | 17 | Oliver Bril | === Methods === |
27 | 5 | Oliver Bril | |
28 | '''!__init!__'''(''self'', ''account'', ''remote_uri'', ''outgoing''):: |
||
29 | Initialize MSRPChat instance. |
||
30 | |||
31 | '''initialize'''(''self''):: |
||
32 | Initialize the MSRP connection; connect to the relay if necessary. When done, fire MSRPChatDidInitialize (with 'sdpmedia' attribute, containing the appropriate 'SDPMedia' instance) |
||
33 | |||
34 | '''start'''(''self'', ''remote_media''):: |
||
35 | Complete the MSRP connection establishment; this includes binding the MSRP session. [[BR]] |
||
36 | When done, fire MSRPChatDidStart. At this point each incoming message is posted as a {{{MSRPChatGotMessage}}} notification |
||
37 | |||
38 | '''end'''(''self''):: |
||
39 | Close the MSRP connection or cleanup after initialize(), whatever is necessary. [[BR]] |
||
40 | Before doing anything post {{{MSRPChatWillEnd}}}. |
||
41 | 7 | Oliver Bril | When done, post {{{MSRPChatDidEnd}}}. If there was an error, post {{{MSRPChatDidFail}}}. |
42 | 5 | Oliver Bril | {{{MSRPChatDidEnd}}} will be posted anyway. |
43 | |||
44 | 6 | Oliver Bril | '''send_message'''(''self'', ''content'', ''content_type''={{{'text/plain'}}}, ''to_uri''={{{None}}}, ''dt''={{{None}}}):: |
45 | Send IM message. Prefer Message/CPIM wrapper if it is supported. If called before the connection was established, the messages will be |
||
46 | queued until MSRPChatDidStart notification. |
||
47 | |||
48 | Return generated MSRP chunk (MSRPData instance); to get Message-ID use its 'message_id' attribute. |
||
49 | |||
50 | ''content'' str:[[BR]] |
||
51 | content of the message |
||
52 | |||
53 | ''to_uri'' SIPURI:[[BR]] |
||
54 | "To" header of CPIM wrapper; use to override the default supplied to {{{__init__}}}. |
||
55 | May only differ from the one supplied in __init__ if the remote party supports private messages. If it does not, {{{MSRPChatError}}} will be raised; |
||
56 | |||
57 | ''content_type'' str:[[BR]] |
||
58 | Content-Type of wrapped message if Message/CPIM is used (Content-Type of MSRP message is always Message/CPIM in that case); |
||
59 | otherwise, Content-Type of MSRP message. |
||
60 | |||
61 | These MSRP headers are used to enable end-to-end success reports and to disable hop-to-hop successful responses: |
||
62 | {{{ |
||
63 | Failure-Report: partial |
||
64 | Success-Report: yes |
||
65 | }}} |
||
66 | |||
67 | 5 | Oliver Bril | |
68 | 17 | Oliver Bril | === Notifications === |
69 | 1 | Adrian Georgescu | |
70 | 17 | Oliver Bril | '''MSRPChatDidInitialize''':: |
71 | Sent when the {{{MSRPChat}}} instance is initialized. |
||
72 | '''MSRPChatDidStart''':: |
||
73 | Sent when the {{{MSRPChat}}} instance is started. |
||
74 | '''MSRPChatWillEnd''':: |
||
75 | Sent when the {{{MSRPChat}}} is entering cleanup procedure initiated by calling end(). |
||
76 | '''MSRPChatDidEnd''':: |
||
77 | Sent when the {{{MSRPChat}}} has finished the cleanup procedure initiated by end(). |
||
78 | '''MSRPChatDidFail''':: |
||
79 | Sent whenever the {{{MSRPChat}}} instance fails. |
||
80 | [[BR]]''context'':[[BR]] |
||
81 | A text string identifying the context of the failure ('initialize', 'sdp_negotiation', 'start', 'reading'). |
||
82 | [[BR]]''reason'':[[BR]] |
||
83 | A text string describing the reason of failure. |
||
84 | [[BR]]''failure'':[[BR]] |
||
85 | A Failure instance providing detailed traceback information. |
||
86 | '''MSRPChatGotMessage''':: |
||
87 | Sent whenever a new incoming message is received, |
||
88 | [[BR]]''content'':[[BR]] |
||
89 | The string that the remote user has typed. |
||
90 | [[BR]]''content_type'':[[BR]] |
||
91 | Content-Type of the user message. |
||
92 | [[BR]]''cpim_headers'':[[BR]] |
||
93 | A dictionary of CPIM headers. (Empty dictionary if no CPIM wrapper was used). |
||
94 | [[BR]]''message'':[[BR]] |
||
95 | A {{{msrplib.protocol.MSRPData}}} instance providing all the MSRP information about the chunk. |
||
96 | '''MSRPChatDidDeliverMessage''':: |
||
97 | Sent when a successful report is received. |
||
98 | [[BR]]''message_id'':[[BR]] |
||
99 | Text identifier of the message. |
||
100 | [[BR]]''code'':[[BR]] |
||
101 | Integer result code. |
||
102 | [[BR]]''reason'':[[BR]] |
||
103 | Text comment. |
||
104 | [[BR]]''message'':[[BR]] |
||
105 | A {{{msrplib.protocol.MSRPData}}} instance providing all the MSRP information about the report. |
||
106 | '''MSRPChatDidNotDeliverMessage''':: |
||
107 | Sent when a failure report of failure transaction response is received. |
||
108 | [[BR]]''message_id'':[[BR]] |
||
109 | Text identifier of the message. |
||
110 | [[BR]]''code'':[[BR]] |
||
111 | Integer result code. |
||
112 | [[BR]]''reason'':[[BR]] |
||
113 | Text comment. |
||
114 | [[BR]]''message'':[[BR]] |
||
115 | A {{{msrplib.protocol.MSRPData}}} instance providing all the MSRP information about the report. |
||
116 | 5 | Oliver Bril | |
117 | 4 | Oliver Bril | == MSRPFileTransfer == |
118 | |||
119 | == MSRPDesktopSharing == |
||
120 | 1 | Adrian Georgescu | |
121 | 12 | Adrian Georgescu | ---------------------- |
122 | |||
123 | == msrplib low level API == |
||
124 | 4 | Oliver Bril | |
125 | 1 | Adrian Georgescu | {{{msrplib}}} is based upon [http://twistedmatrix.com twisted] and [http://devel.ag-projects.com/~denis/eventlet/ eventlet] and provides a set of |
126 | classes for establishing and managing MSRP connection. |
||
127 | |||
128 | The library consist of the following modules: |
||
129 | |||
130 | '''msrplib.transport''':: |
||
131 | Defines {{{MSRPTransport}}} class, which provides low level control over MSRP connection. |
||
132 | 2 | Redmine Admin | |
133 | 1 | Adrian Georgescu | '''msrplib.connect''':: |
134 | Defines means to establish a connection, bind it, and provide an initialized {{{MSRPTransport}}} instance. |
||
135 | |||
136 | '''msrplib.session''':: |
||
137 | Defines {{{MSRPSession}}} class, which provides high level control over a MSRP connection. |
||
138 | |||
139 | '''msrplib.protocol''':: |
||
140 | Provides representation and parsing of MSRP entities - chunks and URIs. |
||
141 | |||
142 | '''msrplib.trafficlog''':: |
||
143 | Defines {{{Logger}}} class that is used through out the library to log the connection state. |
||
144 | |||
145 | 4 | Oliver Bril | === Usage === |
146 | 1 | Adrian Georgescu | |
147 | 4 | Oliver Bril | ==== Establish a connection ==== |
148 | 3 | Oliver Bril | |
149 | 1 | Adrian Georgescu | {{{msrplib.connect}}} provides a number of classes to establish a connection, so the first |
150 | thing to do is to select which one applies to your situation: |
||
151 | |||
152 | 1. Calling endpoint, not using a relay ({{{ConnectorDirect}}}) |
||
153 | 2. Answering endpoint, not using a relay ({{{AcceptorDirect}}}) |
||
154 | 3. Calling endpoint, using a relay ({{{ConnectorRelay}}}) |
||
155 | 4. Answering endpoint, using a relay ({{{AcceptorRelay}}}) |
||
156 | |||
157 | The answering endpoint may skip using the relay if sure that it's accessible |
||
158 | directly. The calling endpoint is unlikely to need the relay. |
||
159 | |||
160 | Once you have an instance of the right class (use the convenience functions |
||
161 | {{{get_connector()}}} and {{{get_acceptor()}}} to get one), the procedure to establish the |
||
162 | connection is the same: |
||
163 | |||
164 | {{{ |
||
165 | full_local_path = connector.prepare() |
||
166 | try: |
||
167 | ... put full_local_path in SDP 'a:path' attribute |
||
168 | ... get full_remote_path from remote's 'a:path: attribute |
||
169 | ... (the order of the above steps is reversed if you're the |
||
170 | ... answering party, but that does not affect connector's usage) |
||
171 | msrptransport = connector.complete(full_remote_path) |
||
172 | finally: |
||
173 | connector.cleanup() |
||
174 | }}} |
||
175 | |||
176 | To customize connection's parameters, create a new {{{protocol.URI}}} object and pass |
||
177 | it to prepare() function, e.g. |
||
178 | {{{ |
||
179 | local_uri = protocol.URI(use_tls=False, port=5000) |
||
180 | connector.prepare(local_uri) |
||
181 | }}} |
||
182 | |||
183 | {{{prepare()}}} may update {{{local_uri}}} in place with the actual connection parameters |
||
184 | used (e.g. if you specified port=0). 'port' attribute of {{{local_uri}}} is currently |
||
185 | only respected by {{{AcceptorDirect}}}. |
||
186 | |||
187 | Note that, acceptors and connectors are one-use only. Which means, that {{{AcceptorDirect}}} |
||
188 | will open a port just to handle one incoming connection and close it right after. |
||
189 | If your application behaves more like a server, i.e. opens a port and listens on it |
||
190 | constantly, use {{{MSRPServer}}} class. |
||
191 | 3 | Oliver Bril | |
192 | 4 | Oliver Bril | === Components === |
193 | 1 | Adrian Georgescu | |
194 | 4 | Oliver Bril | ==== a connector or acceptor ==== |
195 | 3 | Oliver Bril | |
196 | 8 | Oliver Bril | {{{msrplib.connect}}} provides 2 connectors (with and without relay) and 2 acceptors (likewise, with or without relay). All of them have the exact same interface, |
197 | |||
198 | '''prepare'''(''self'', ''local_uri''={{{None}}}):: |
||
199 | Depending on type of the connector, use local_uri to prepare the MSRP connection, which means: |
||
200 | * connecting and authenticating at the relay if a relay is used ({{{ConnectorRelay}}} and {{{AcceptorRelay}}}) |
||
201 | * start listening on a local port for DirectAcceptor |
||
202 | |||
203 | ''local_uri'' is used to specify the connection parameters, e.g. local port and local ip. |
||
204 | If not provided, suitable ''local_uri'' will be generated. |
||
205 | ''local_uri'' maybe updated in place by {{{prepare()}}} method if the real settings used are different from those specified. |
||
206 | |||
207 | {{{prepare}}} returns a full local path - list of {{{protocol.URI}}} instances, suitable to be put in SDP {{{'a:path'}}} attribute. |
||
208 | |||
209 | '''complete'''(''self'', ''full_remote_path''):: |
||
210 | Complete establishing the MSRP connection, which means |
||
211 | * establishing the connection if it wasn't already established ({{{ConnectorDirect}}}) |
||
212 | * bind the connection, i.e. exchange empty chunk to verify each other's From-Path and To-Path |
||
213 | |||
214 | ''full_remote_path'' should be a list of {{{protocol.URI}}} instances, obtained by parsing {{{'a:path'}}} put in SDP by the remote party. |
||
215 | |||
216 | {{{complete}}} returns {{{transport.MSRPTransport}}} instance, ready to read and send chunks. |
||
217 | 9 | Oliver Bril | |
218 | '''cleanup'''(''self''):: |
||
219 | Call this method to cleanup after {{{initialize()}}} if it's impossible to call {{{complete()}}} |
||
220 | 8 | Oliver Bril | |
221 | 1 | Adrian Georgescu | |
222 | 4 | Oliver Bril | ==== transport.MSRPTransport ==== |
223 | 1 | Adrian Georgescu | |
224 | Low level access to MSRP connection. |
||
225 | |||
226 | '''make_chunk'''(''self'', ''transaction_id''={{{None}}}, ''method''={{{'SEND'}}}, ''code''={{{None}}}, ''comment''={{{None}}}, ''data''={{{''}}}, ''contflag''={{{None}}}, ''start''={{{1}}}, ''end''={{{None}}}, ''length''={{{None}}}, ''message_id''={{{None}}}):: |
||
227 | Make a new chunk ({{{protocol.MSRPData}}} instance) with proper {{{From-Path}}}, {{{To-Path}}}, {{{Byte-Range}}} and {{{Message-ID}}} headers set up based on MSRPTransport's state and the parameters provided. Use ''data'' for payload, and ''start''/''end''/''length'' to generate {{{Byte-Range}}} header. Generate new random strings for default values of ''transaction_id'' and ''message_id''. |
||
228 | [[BR]]''contflag'':[[BR]] |
||
229 | MSRP chunk's continuation flag ({{{'$'}}}, {{{'+'}}} or {{{'#'}}}). Default is {{{'$'}}}, unless you have a partial {{{SEND}}} chunk, in which case it is {{{'+'}}} |
||
230 | 14 | Oliver Bril | |
231 | '''write'''(''self'', ''bytes'', ''sync''={{{True}}}):: |
||
232 | Write ''bytes'' to the socket. If ''sync'' is true, wait for an operation to complete. |
||
233 | |||
234 | '''read_chunk'''(''self'', ''size''={{{None}}}):: |
||
235 | Wait for a new chunk and return it. |
||
236 | If there was an error, close the connection and raise {{{ChunkParseError}}}. |
||
237 | |||
238 | In case of unintelligible input, lose the connection and return {{{None}}}. |
||
239 | When the connection is closed, raise the reason of the closure (e.g. {{{ConnectionDone}}}). |
||
240 | |||
241 | If the data already read exceeds ''size'', stop reading the data and return |
||
242 | a "virtual" chunk, i.e. the one that does not actually correspond the the real |
||
243 | MSRP chunk. Such chunks have Byte-Range header changed to match the number of |
||
244 | bytes read and continuation that is {{{'+'}}}; they also possess {{{segment}}} attribute, |
||
245 | an integer, starting with 1 and increasing with every new segment of the chunk. |
||
246 | |||
247 | Note, that ''size'' only hints when to interrupt the segment but does not affect |
||
248 | how the data is read from socket. You may have segments bigger than ''size'' and it's |
||
249 | legal to set ''size'' to zero (which would mean return a chunk as long as you get |
||
250 | some data, regardless how small). |
||
251 | 1 | Adrian Georgescu | |
252 | 15 | Oliver Bril | '''check_incoming_SEND_chunk'''(''self'', ''chunk''):: |
253 | Check the 'To-Path' and 'From-Path' of the incoming SEND chunk. |
||
254 | Return None is the paths are valid for this connection. |
||
255 | If an error is detected an MSRPError is created and returned. |
||
256 | |||
257 | 4 | Oliver Bril | ==== session.MSRPSession ==== |
258 | 15 | Oliver Bril | |
259 | '''!__init!__'''(''self'', ''msrptransport'', ''accept_types''={{{['*']}}}, ''on_incoming_cb''={{{None}}}):: |
||
260 | Initialize MSRPSession instance. Report the incoming chunks through ''on_incoming_cb'' callback. |
||
261 | |||
262 | '''send_chunk'''(''self'', ''chunk'', ''response_cb''={{{None}}}):: |
||
263 | Send ''chunk''. Report the result via ''response_cb''. |
||
264 | |||
265 | When ''response_cb'' argument is present, it will be used to report |
||
266 | the transaction response to the caller. When a response is received or generated |
||
267 | locally, ''response_cb'' is called with one argument. The function must do something |
||
268 | quickly and must not block, because otherwise it would the reader greenlet. |
||
269 | |||
270 | If no response was received after {{{RESPONSE_TIMEOUT}}} seconds, |
||
271 | * 408 response is generated if Failure-Report was {{{'yes'}}} or absent |
||
272 | * 200 response is generated if Failure-Report was {{{'partial'}}} or {{{'no'}}} |
||
273 | |||
274 | Note that it's rather wasteful to provide ''response_cb'' argument other than {{{None}}} |
||
275 | for chunks with Failure-Report='no' since it will always fire 30 seconds later |
||
276 | with 200 result (unless the other party is broken and ignores Failure-Report header) |
||
277 | |||
278 | 16 | Oliver Bril | If sending is impossible raise {{{MSRPSessionError}}}. |
279 | 1 | Adrian Georgescu | |
280 | 16 | Oliver Bril | '''deliver_chunk'''(''self'', ''chunk'', ''event''={{{None}}}):: |
281 | Send chunk, wait for the transaction response (if Failure-Report header is not {{{'no'}}}). |
||
282 | Return the transaction response if it's a success, raise {{{MSRPTransactionError}}} if it's not. |
||
283 | |||
284 | If chunk's Failure-Report is {{{'no'}}}, return {{{None}}} immediately. |
||
285 | |||
286 | '''shutdown'''(''self'', ''sync''={{{True}}}):: |
||
287 | Send the messages already in queue then close the connection. |
||
288 | |||
289 | ==== session.GreenMSRPSession ==== |
||
290 | |||
291 | A subclass of MSRPSession that delivers the incoming messages to the queue. |
||
292 | |||
293 | '''!__init!__'''(''self'', ''msrptransport'', ''accept_types''={{{['*']}}}):: |
||
294 | Initialize GreenMSRPSession instance. The messages will be delivered to the queue (available as {{{incoming}}} attribute). |
||
295 | |||
296 | '''receive_chunk'''(''self''):: |
||
297 | Return a message from the queue. |
||
298 | |||
299 | 1 | Adrian Georgescu | |
300 | 4 | Oliver Bril | ==== connect.MSRPServer ==== |
301 | 13 | Oliver Bril | Manage listening sockets. Bind incoming requests. |
302 | |||
303 | MSRPServer solves the problem with AcceptorDirect: concurrent using of 2 |
||
304 | or more AcceptorDirect instances on the same non-zero port is not possible. |
||
305 | If you initialize() those instances, one after another, one will listen on |
||
306 | the socket and another will get BindError. |
||
307 | |||
308 | MSRPServer avoids the problem by sharing the listening socket between multiple connections. |
||
309 | It has a slightly different interface from AcceptorDirect, so it cannot be considered a drop-in |
||
310 | replacement. |
||
311 | |||
312 | '''prepare'''(''self'', ''local_uri''={{{None}}}, ''logger''={{{None}}}):: |
||
313 | Start a listening port specified by ''local_uri'' if there isn't one on that port/interface already. |
||
314 | Add ''local_uri'' to the list of expected URIs, so that incoming connections featuring this URI won't be rejected. |
||
315 | If ''logger'' is provided use it for this connection instead of the default one. |
||
316 | |||
317 | '''complete'''(''self'', ''full_remote_path''):: |
||
318 | Wait until one of the incoming connections binds using provided ''full_remote_path''. |
||
319 | Return connected and bound {{{MSRPTransport}}} instance. |
||
320 | |||
321 | If no such binding was made within {{{MSRPBindSessionTimeout.seconds}}}, raise {{{MSRPBindSessionTimeout}}}. |
||
322 | ''full_remote_path'' should be a list of {{{protocol.URI}}} instances, obtained by parsing {{{'a:path'}}} put in SDP by the remote party. |
||
323 | |||
324 | '''cleanup'''(''self'', ''local_uri''):: |
||
325 | Remove ''local_uri'' from the list of expected URIs. |