SipMSRPApi
Version 35 (Adrian Georgescu, 04/13/2010 09:48 am)
1 | 1 | Adrian Georgescu | = MSRP API = |
---|---|---|---|
2 | 1 | Adrian Georgescu | |
3 | 34 | Adrian Georgescu | [[TOC(SipMSRPApi, SipDeveloperGuide, depth=3)]] |
4 | 1 | Adrian Georgescu | |
5 | 31 | Adrian Georgescu | 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 | 1 | Adrian Georgescu | |
7 | 25 | Adrian Georgescu | * MSRP sessions are defined in [http://tools.ietf.org/html/rfc4975 RFC 4975] |
8 | 25 | Adrian Georgescu | * 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 | 26 | Adrian Georgescu | * Common Presence and Instant Messaging (CPIM): Message Format is defined in [http://tools.ietf.org/html/rfc3862 RFC 3862] |
10 | 1 | Adrian Georgescu | |
11 | 12 | Adrian Georgescu | == MSRP library == |
12 | 32 | Adrian Georgescu | |
13 | 32 | Adrian Georgescu | The MSRP protocol stack is implemented by [http://devel.ag-projects.com/cgi-bin/darcsweb.cgi?r=python-msrplib;a=summary msrplib] Python package. |
14 | 4 | Oliver Bril | |
15 | 33 | Adrian Georgescu | {{{msrplib}}} is based upon [http://twistedmatrix.com twisted] and [http://download.ag-projects.com/SipClient/ eventlet] and provides a set of classes for establishing and managing MSRP connections. |
16 | 1 | Adrian Georgescu | |
17 | 28 | Adrian Georgescu | The library consists of the following modules: |
18 | 1 | Adrian Georgescu | |
19 | 1 | Adrian Georgescu | '''msrplib.transport''':: |
20 | 27 | Adrian Georgescu | Defines {{{MSRPTransport}}} class, which provides low level control over MSRP connections. |
21 | 2 | Redmine Admin | |
22 | 1 | Adrian Georgescu | '''msrplib.connect''':: |
23 | 1 | Adrian Georgescu | Defines means to establish a connection, bind it, and provide an initialized {{{MSRPTransport}}} instance. |
24 | 1 | Adrian Georgescu | |
25 | 1 | Adrian Georgescu | '''msrplib.session''':: |
26 | 1 | Adrian Georgescu | Defines {{{MSRPSession}}} class, which provides high level control over a MSRP connection. |
27 | 1 | Adrian Georgescu | |
28 | 1 | Adrian Georgescu | '''msrplib.protocol''':: |
29 | 27 | Adrian Georgescu | Provides representation and parsing of MSRP entities - chunks and MSRP URIs. |
30 | 1 | Adrian Georgescu | |
31 | 1 | Adrian Georgescu | '''msrplib.trafficlog''':: |
32 | 1 | Adrian Georgescu | Defines {{{Logger}}} class that is used through out the library to log the connection state. |
33 | 1 | Adrian Georgescu | |
34 | 1 | Adrian Georgescu | === Usage === |
35 | 4 | Oliver Bril | |
36 | 1 | Adrian Georgescu | ==== Establish a connection ==== |
37 | 4 | Oliver Bril | |
38 | 3 | Oliver Bril | {{{msrplib.connect}}} provides a number of classes to establish a connection, so the first |
39 | 1 | Adrian Georgescu | thing to do is to select which one applies to your situation: |
40 | 1 | Adrian Georgescu | |
41 | 1 | Adrian Georgescu | 1. Calling endpoint, not using a relay ({{{ConnectorDirect}}}) |
42 | 1 | Adrian Georgescu | 2. Answering endpoint, not using a relay ({{{AcceptorDirect}}}) |
43 | 1 | Adrian Georgescu | 3. Calling endpoint, using a relay ({{{ConnectorRelay}}}) |
44 | 1 | Adrian Georgescu | 4. Answering endpoint, using a relay ({{{AcceptorRelay}}}) |
45 | 1 | Adrian Georgescu | |
46 | 27 | Adrian Georgescu | The answering endpoint may skip using the relay if sure that it's accessible directly, e.g is not behind a NAT. To be sure it works in any network topology a called end-point should always use a relay. |
47 | 1 | Adrian Georgescu | |
48 | 27 | Adrian Georgescu | The calling endpoint does not need a relay as the protocol mandates that it is establishing an outbound connection which always work from behind a NAT. |
49 | 27 | Adrian Georgescu | |
50 | 1 | Adrian Georgescu | Once you have an instance of the right class (use the convenience functions |
51 | 1 | Adrian Georgescu | {{{get_connector()}}} and {{{get_acceptor()}}} to get one), the procedure to establish the |
52 | 1 | Adrian Georgescu | connection is the same: |
53 | 1 | Adrian Georgescu | |
54 | 1 | Adrian Georgescu | {{{ |
55 | 1 | Adrian Georgescu | full_local_path = connector.prepare() |
56 | 1 | Adrian Georgescu | try: |
57 | 1 | Adrian Georgescu | ... put full_local_path in SDP 'a:path' attribute |
58 | 1 | Adrian Georgescu | ... get full_remote_path from remote's 'a:path: attribute |
59 | 1 | Adrian Georgescu | ... (the order of the above steps is reversed if you're the |
60 | 1 | Adrian Georgescu | ... answering party, but that does not affect connector's usage) |
61 | 1 | Adrian Georgescu | msrptransport = connector.complete(full_remote_path) |
62 | 1 | Adrian Georgescu | finally: |
63 | 1 | Adrian Georgescu | connector.cleanup() |
64 | 1 | Adrian Georgescu | }}} |
65 | 1 | Adrian Georgescu | |
66 | 27 | Adrian Georgescu | To customize connection's parameters, creates a new {{{protocol.URI}}} object and passes |
67 | 1 | Adrian Georgescu | it to prepare() function, e.g. |
68 | 1 | Adrian Georgescu | {{{ |
69 | 1 | Adrian Georgescu | local_uri = protocol.URI(use_tls=False, port=5000) |
70 | 1 | Adrian Georgescu | connector.prepare(local_uri) |
71 | 1 | Adrian Georgescu | }}} |
72 | 1 | Adrian Georgescu | |
73 | 1 | Adrian Georgescu | {{{prepare()}}} may update {{{local_uri}}} in place with the actual connection parameters |
74 | 1 | Adrian Georgescu | used (e.g. if you specified port=0). 'port' attribute of {{{local_uri}}} is currently |
75 | 1 | Adrian Georgescu | only respected by {{{AcceptorDirect}}}. |
76 | 1 | Adrian Georgescu | |
77 | 1 | Adrian Georgescu | Note that, acceptors and connectors are one-use only. Which means, that {{{AcceptorDirect}}} |
78 | 1 | Adrian Georgescu | will open a port just to handle one incoming connection and close it right after. |
79 | 1 | Adrian Georgescu | If your application behaves more like a server, i.e. opens a port and listens on it |
80 | 1 | Adrian Georgescu | constantly, use {{{MSRPServer}}} class. |
81 | 3 | Oliver Bril | |
82 | 1 | Adrian Georgescu | === Components === |
83 | 1 | Adrian Georgescu | |
84 | 4 | Oliver Bril | ==== a connector or acceptor ==== |
85 | 3 | Oliver Bril | |
86 | 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, |
87 | 8 | Oliver Bril | |
88 | 1 | Adrian Georgescu | '''prepare'''(''self'', ''local_uri''={{{None}}}):: |
89 | 35 | Adrian Georgescu | {{{prepare}}} returns a full local path - list of {{{protocol.URI}}} instances, suitable to be put in SDP {{{'a:path'}}} attribute. |
90 | 35 | Adrian Georgescu | |
91 | 8 | Oliver Bril | Depending on type of the connector, use local_uri to prepare the MSRP connection, which means: |
92 | 8 | Oliver Bril | * connecting and authenticating at the relay if a relay is used ({{{ConnectorRelay}}} and {{{AcceptorRelay}}}) |
93 | 27 | Adrian Georgescu | * starts listening on a local port for DirectAcceptor |
94 | 1 | Adrian Georgescu | |
95 | 8 | Oliver Bril | ''local_uri'' is used to specify the connection parameters, e.g. local port and local ip. |
96 | 1 | Adrian Georgescu | If not provided, suitable ''local_uri'' will be generated. |
97 | 8 | Oliver Bril | ''local_uri'' maybe updated in place by {{{prepare()}}} method if the real settings used are different from those specified. |
98 | 8 | Oliver Bril | |
99 | 1 | Adrian Georgescu | |
100 | 1 | Adrian Georgescu | '''complete'''(''self'', ''full_remote_path''):: |
101 | 35 | Adrian Georgescu | {{{complete}}} returns {{{transport.MSRPTransport}}} instance, ready to read and send chunks. |
102 | 35 | Adrian Georgescu | |
103 | 27 | Adrian Georgescu | Completes the establishment of the MSRP connection, which means: |
104 | 27 | Adrian Georgescu | * establishes the connection if it wasn't already established ({{{ConnectorDirect}}}) |
105 | 8 | Oliver Bril | * binds the connection, i.e. exchanges empty chunk to verify each other's From-Path and To-Path |
106 | 8 | Oliver Bril | |
107 | 8 | Oliver Bril | ''full_remote_path'' should be a list of {{{protocol.URI}}} instances, obtained by parsing {{{'a:path'}}} put in SDP by the remote party. |
108 | 9 | Oliver Bril | |
109 | 9 | Oliver Bril | |
110 | 27 | Adrian Georgescu | '''cleanup'''(''self''):: |
111 | 8 | Oliver Bril | Calls this method to cleanup after {{{initialize()}}} if it's impossible to call {{{complete()}}} |
112 | 1 | Adrian Georgescu | |
113 | 4 | Oliver Bril | ==== transport.MSRPTransport ==== |
114 | 1 | Adrian Georgescu | |
115 | 27 | Adrian Georgescu | Low level access to the MSRP connection. |
116 | 1 | Adrian Georgescu | |
117 | 1 | Adrian Georgescu | '''make_chunk'''(''self'', ''transaction_id''={{{None}}}, ''method''={{{'SEND'}}}, ''code''={{{None}}}, ''comment''={{{None}}}, ''data''={{{''}}}, ''contflag''={{{None}}}, ''start''={{{1}}}, ''end''={{{None}}}, ''length''={{{None}}}, ''message_id''={{{None}}}):: |
118 | 27 | Adrian Georgescu | Makes 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''. |
119 | 1 | Adrian Georgescu | [[BR]]''contflag'':[[BR]] |
120 | 1 | Adrian Georgescu | MSRP chunk's continuation flag ({{{'$'}}}, {{{'+'}}} or {{{'#'}}}). Default is {{{'$'}}}, unless you have a partial {{{SEND}}} chunk, in which case it is {{{'+'}}} |
121 | 1 | Adrian Georgescu | |
122 | 14 | Oliver Bril | '''write'''(''self'', ''bytes'', ''sync''={{{True}}}):: |
123 | 27 | Adrian Georgescu | Writes ''bytes'' to the socket. If ''sync'' is true, waits for an operation to complete. |
124 | 14 | Oliver Bril | |
125 | 14 | Oliver Bril | '''read_chunk'''(''self'', ''size''={{{None}}}):: |
126 | 27 | Adrian Georgescu | Waits for a new chunk and returns it. |
127 | 27 | Adrian Georgescu | If there was an error, closes the connection and raises {{{ChunkParseError}}}. |
128 | 14 | Oliver Bril | |
129 | 27 | Adrian Georgescu | In case of unintelligible input, loses the connection and returns {{{None}}}. |
130 | 27 | Adrian Georgescu | When the connection is closed, raises the reason of the closure (e.g. {{{ConnectionDone}}}). |
131 | 14 | Oliver Bril | |
132 | 27 | Adrian Georgescu | If the data already read exceeds ''size'', stops reading the data and returns |
133 | 14 | Oliver Bril | a "virtual" chunk, i.e. the one that does not actually correspond the the real |
134 | 14 | Oliver Bril | MSRP chunk. Such chunks have Byte-Range header changed to match the number of |
135 | 14 | Oliver Bril | bytes read and continuation that is {{{'+'}}}; they also possess {{{segment}}} attribute, |
136 | 14 | Oliver Bril | an integer, starting with 1 and increasing with every new segment of the chunk. |
137 | 14 | Oliver Bril | |
138 | 1 | Adrian Georgescu | Note, that ''size'' only hints when to interrupt the segment but does not affect |
139 | 1 | Adrian Georgescu | how the data is read from socket. You may have segments bigger than ''size'' and it's |
140 | 14 | Oliver Bril | legal to set ''size'' to zero (which would mean return a chunk as long as you get |
141 | 1 | Adrian Georgescu | some data, regardless how small). |
142 | 1 | Adrian Georgescu | |
143 | 15 | Oliver Bril | '''check_incoming_SEND_chunk'''(''self'', ''chunk''):: |
144 | 27 | Adrian Georgescu | Checks the 'To-Path' and 'From-Path' of the incoming SEND chunk. |
145 | 27 | Adrian Georgescu | Returns None is the paths are valid for this connection. |
146 | 15 | Oliver Bril | If an error is detected an MSRPError is created and returned. |
147 | 1 | Adrian Georgescu | |
148 | 4 | Oliver Bril | ==== session.MSRPSession ==== |
149 | 1 | Adrian Georgescu | |
150 | 15 | Oliver Bril | '''!__init!__'''(''self'', ''msrptransport'', ''accept_types''={{{['*']}}}, ''on_incoming_cb''={{{None}}}):: |
151 | 27 | Adrian Georgescu | Initializes MSRPSession instance. Reports the incoming chunks through ''on_incoming_cb'' callback. |
152 | 1 | Adrian Georgescu | |
153 | 15 | Oliver Bril | '''send_chunk'''(''self'', ''chunk'', ''response_cb''={{{None}}}):: |
154 | 27 | Adrian Georgescu | Sends ''chunk''. Report the result via ''response_cb''. |
155 | 15 | Oliver Bril | |
156 | 15 | Oliver Bril | When ''response_cb'' argument is present, it will be used to report |
157 | 15 | Oliver Bril | the transaction response to the caller. When a response is received or generated |
158 | 15 | Oliver Bril | locally, ''response_cb'' is called with one argument. The function must do something |
159 | 29 | Oliver Bril | quickly and must not block, because otherwise it would block the reader greenlet. |
160 | 15 | Oliver Bril | |
161 | 15 | Oliver Bril | If no response was received after {{{RESPONSE_TIMEOUT}}} seconds, |
162 | 15 | Oliver Bril | * 408 response is generated if Failure-Report was {{{'yes'}}} or absent |
163 | 1 | Adrian Georgescu | * 200 response is generated if Failure-Report was {{{'partial'}}} or {{{'no'}}} |
164 | 15 | Oliver Bril | |
165 | 1 | Adrian Georgescu | Note that it's rather wasteful to provide ''response_cb'' argument other than {{{None}}} |
166 | 15 | Oliver Bril | for chunks with Failure-Report='no' since it will always fire 30 seconds later |
167 | 15 | Oliver Bril | with 200 result (unless the other party is broken and ignores Failure-Report header) |
168 | 15 | Oliver Bril | |
169 | 16 | Oliver Bril | If sending is impossible raise {{{MSRPSessionError}}}. |
170 | 1 | Adrian Georgescu | |
171 | 16 | Oliver Bril | '''deliver_chunk'''(''self'', ''chunk'', ''event''={{{None}}}):: |
172 | 27 | Adrian Georgescu | Sends the chunk, waits for the transaction response (if Failure-Report header is not {{{'no'}}}). |
173 | 27 | Adrian Georgescu | Returns the transaction response if it's a success, raise {{{MSRPTransactionError}}} if it's not. |
174 | 16 | Oliver Bril | |
175 | 27 | Adrian Georgescu | If chunk's Failure-Report is {{{'no'}}}, returns {{{None}}} immediately. |
176 | 16 | Oliver Bril | |
177 | 16 | Oliver Bril | '''shutdown'''(''self'', ''sync''={{{True}}}):: |
178 | 27 | Adrian Georgescu | Sends the messages already in queue then close the connection. |
179 | 16 | Oliver Bril | |
180 | 16 | Oliver Bril | ==== session.GreenMSRPSession ==== |
181 | 16 | Oliver Bril | |
182 | 16 | Oliver Bril | A subclass of MSRPSession that delivers the incoming messages to the queue. |
183 | 16 | Oliver Bril | |
184 | 16 | Oliver Bril | '''!__init!__'''(''self'', ''msrptransport'', ''accept_types''={{{['*']}}}):: |
185 | 27 | Adrian Georgescu | Initializes GreenMSRPSession instance. The messages will be delivered to the queue (available as {{{incoming}}} attribute). |
186 | 16 | Oliver Bril | |
187 | 16 | Oliver Bril | '''receive_chunk'''(''self''):: |
188 | 27 | Adrian Georgescu | Returns a message from the queue. |
189 | 16 | Oliver Bril | |
190 | 1 | Adrian Georgescu | |
191 | 4 | Oliver Bril | ==== connect.MSRPServer ==== |
192 | 27 | Adrian Georgescu | Manages the listening sockets. Binds incoming requests. |
193 | 13 | Oliver Bril | |
194 | 13 | Oliver Bril | MSRPServer solves the problem with AcceptorDirect: concurrent using of 2 |
195 | 13 | Oliver Bril | or more AcceptorDirect instances on the same non-zero port is not possible. |
196 | 13 | Oliver Bril | If you initialize() those instances, one after another, one will listen on |
197 | 13 | Oliver Bril | the socket and another will get BindError. |
198 | 13 | Oliver Bril | |
199 | 13 | Oliver Bril | MSRPServer avoids the problem by sharing the listening socket between multiple connections. |
200 | 13 | Oliver Bril | It has a slightly different interface from AcceptorDirect, so it cannot be considered a drop-in |
201 | 13 | Oliver Bril | replacement. |
202 | 13 | Oliver Bril | |
203 | 13 | Oliver Bril | '''prepare'''(''self'', ''local_uri''={{{None}}}, ''logger''={{{None}}}):: |
204 | 27 | Adrian Georgescu | Starts a listening port specified by ''local_uri'' if there isn't one on that port/interface already. |
205 | 27 | Adrian Georgescu | Adds ''local_uri'' to the list of expected URIs, so that incoming connections featuring this URI won't be rejected. |
206 | 27 | Adrian Georgescu | If ''logger'' is provided, it uses it for this connection instead of the default one. |
207 | 13 | Oliver Bril | |
208 | 13 | Oliver Bril | '''complete'''(''self'', ''full_remote_path''):: |
209 | 27 | Adrian Georgescu | Waits until one of the incoming connections binds using provided ''full_remote_path''. |
210 | 27 | Adrian Georgescu | Returns connected and bounds the {{{MSRPTransport}}} instance. |
211 | 13 | Oliver Bril | |
212 | 13 | Oliver Bril | If no such binding was made within {{{MSRPBindSessionTimeout.seconds}}}, raise {{{MSRPBindSessionTimeout}}}. |
213 | 13 | Oliver Bril | ''full_remote_path'' should be a list of {{{protocol.URI}}} instances, obtained by parsing {{{'a:path'}}} put in SDP by the remote party. |
214 | 13 | Oliver Bril | |
215 | 13 | Oliver Bril | '''cleanup'''(''self'', ''local_uri''):: |
216 | 27 | Adrian Georgescu | Removes ''local_uri'' from the list of expected URIs. |