1 /*
2 * sipim.cxx
3 *
4 * Support for SIP session mode IM
5 *
6 * Open Phone Abstraction Library (OPAL)
7 *
8 * Copyright (c) 2008 Post Increment
9 *
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * The Original Code is Open Phone Abstraction Library.
21 *
22 * The Initial Developer of the Original Code is Post Increment
23 *
24 * Contributor(s): ______________________________________.
25 *
26 * $Revision: 26270 $
27 * $Author: rjongbloed $
28 * $Date: 2011-08-03 03:29:01 -0500 (Wed, 03 Aug 2011) $
29 */
30
31 #include <ptlib.h>
32 #include <opal/buildopts.h>
33
34 #ifdef __GNUC__
35 #pragma implementation "sipim.h"
36 #endif
37
38 #include <ptlib/socket.h>
39 #include <ptclib/random.h>
40 #include <ptclib/pxml.h>
41
42 #include <opal/transports.h>
43 #include <opal/mediatype.h>
44 #include <opal/mediafmt.h>
45 #include <opal/endpoint.h>
46
47 #include <im/im.h>
48 #include <im/sipim.h>
49 #include <sip/sipep.h>
50 #include <sip/sipcon.h>
51
52 #if OPAL_HAS_SIPIM
53
54 const char SIP_IM[] = "sip-im";
55
56 ////////////////////////////////////////////////////////////////////////////
57
OpalSIPIMMediaType()58 OpalSIPIMMediaType::OpalSIPIMMediaType()
59 : OpalIMMediaType(SIP_IM, "message|sip")
60 {
61 }
62
63
64 /////////////////////////////////////////////////////////
65 //
66 // SDP media description for the SIPIM type
67 //
68 // A new class is needed for "message" due to the following differences
69 //
70 // - the SDP type is "message"
71 // - the transport is "sip"
72 // - the format list is a SIP URL
73 //
74
75 class SDPSIPIMMediaDescription : public SDPMediaDescription
76 {
77 PCLASSINFO(SDPSIPIMMediaDescription, SDPMediaDescription);
78 public:
79 SDPSIPIMMediaDescription(const OpalTransportAddress & address);
80 SDPSIPIMMediaDescription(const OpalTransportAddress & address, const OpalTransportAddress & _transportAddr, const PString & fromURL);
81
GetSDPTransportType() const82 PCaselessString GetSDPTransportType() const
83 {
84 return "sip";
85 }
86
CreateEmpty() const87 virtual SDPMediaDescription * CreateEmpty() const
88 {
89 return new SDPSIPIMMediaDescription(OpalTransportAddress());
90 }
91
GetSDPMediaType() const92 virtual PString GetSDPMediaType() const
93 {
94 return "message";
95 }
96
97 virtual PString GetSDPPortList() const;
98
99 virtual void CreateSDPMediaFormats(const PStringArray &);
100 virtual bool PrintOn(ostream & str, const PString & connectString) const;
101 virtual void SetAttribute(const PString & attr, const PString & value);
102 virtual void ProcessMediaOptions(SDPMediaFormat & sdpFormat, const OpalMediaFormat & mediaFormat);
103 virtual void AddMediaFormat(const OpalMediaFormat & mediaFormat);
104
105 virtual OpalMediaFormatList GetMediaFormats() const;
106
107 // CreateSDPMediaFormat is used for processing format lists. MSRP always contains only "*"
CreateSDPMediaFormat(const PString &)108 virtual SDPMediaFormat * CreateSDPMediaFormat(const PString & ) { return NULL; }
109
110 // FindFormat is used only for rtpmap and fmtp, neither of which are used for MSRP
FindFormat(PString &) const111 virtual SDPMediaFormat * FindFormat(PString &) const { return NULL; }
112
113 protected:
114 OpalTransportAddress transportAddress;
115 PString fromURL;
116 };
117
118 ////////////////////////////////////////////////////////////////////////////////////////////
119
CreateSDPMediaDescription(const OpalTransportAddress & localAddress)120 SDPMediaDescription * OpalSIPIMMediaType::CreateSDPMediaDescription(const OpalTransportAddress & localAddress)
121 {
122 return new SDPSIPIMMediaDescription(localAddress);
123 }
124
125 ///////////////////////////////////////////////////////////////////////////////////////////
126
SDPSIPIMMediaDescription(const OpalTransportAddress & address)127 SDPSIPIMMediaDescription::SDPSIPIMMediaDescription(const OpalTransportAddress & address)
128 : SDPMediaDescription(address, SIP_IM)
129 {
130 SetDirection(SDPMediaDescription::SendRecv);
131 }
132
SDPSIPIMMediaDescription(const OpalTransportAddress & address,const OpalTransportAddress & _transportAddr,const PString & _fromURL)133 SDPSIPIMMediaDescription::SDPSIPIMMediaDescription(const OpalTransportAddress & address, const OpalTransportAddress & _transportAddr, const PString & _fromURL)
134 : SDPMediaDescription(address, SIP_IM)
135 , transportAddress(_transportAddr)
136 , fromURL(_fromURL)
137 {
138 SetDirection(SDPMediaDescription::SendRecv);
139 }
140
CreateSDPMediaFormats(const PStringArray &)141 void SDPSIPIMMediaDescription::CreateSDPMediaFormats(const PStringArray &)
142 {
143 formats.Append(new SDPMediaFormat(*this, OpalSIPIM));
144 }
145
146
PrintOn(ostream & str,const PString &) const147 bool SDPSIPIMMediaDescription::PrintOn(ostream & str, const PString & /*connectString*/) const
148 {
149 // call ancestor
150 if (!SDPMediaDescription::PrintOn(str, ""))
151 return false;
152
153 return true;
154 }
155
GetSDPPortList() const156 PString SDPSIPIMMediaDescription::GetSDPPortList() const
157 {
158 PIPSocket::Address addr; WORD port;
159 transportAddress.GetIpAndPort(addr, port);
160
161 PStringStream str;
162 str << ' ' << fromURL << '@' << addr << ':' << port;
163
164 return str;
165 }
166
167
SetAttribute(const PString &,const PString &)168 void SDPSIPIMMediaDescription::SetAttribute(const PString & /*attr*/, const PString & /*value*/)
169 {
170 }
171
ProcessMediaOptions(SDPMediaFormat &,const OpalMediaFormat &)172 void SDPSIPIMMediaDescription::ProcessMediaOptions(SDPMediaFormat & /*sdpFormat*/, const OpalMediaFormat & /*mediaFormat*/)
173 {
174 }
175
GetMediaFormats() const176 OpalMediaFormatList SDPSIPIMMediaDescription::GetMediaFormats() const
177 {
178 OpalMediaFormat sipim(OpalSIPIM);
179 sipim.SetOptionString("URL", fromURL);
180
181 PTRACE(4, "SIPIM\tNew format is " << setw(-1) << sipim);
182
183 OpalMediaFormatList fmts;
184 fmts += sipim;
185 return fmts;
186 }
187
AddMediaFormat(const OpalMediaFormat & mediaFormat)188 void SDPSIPIMMediaDescription::AddMediaFormat(const OpalMediaFormat & mediaFormat)
189 {
190 if (!mediaFormat.IsTransportable() || !mediaFormat.IsValidForProtocol("sip") || mediaFormat.GetMediaType() != SIP_IM) {
191 PTRACE(4, "SIPIM\tSDP not including " << mediaFormat << " as it is not a valid SIPIM format");
192 return;
193 }
194
195 SDPMediaFormat * sdpFormat = new SDPMediaFormat(*this, mediaFormat);
196 ProcessMediaOptions(*sdpFormat, mediaFormat);
197 AddSDPMediaFormat(sdpFormat);
198 }
199
200
201 ////////////////////////////////////////////////////////////////////////////////////////////
202
CreateMediaSession(OpalConnection & conn,unsigned sessionID) const203 OpalMediaSession * OpalSIPIMMediaType::CreateMediaSession(OpalConnection & conn, unsigned sessionID) const
204 {
205 // as this is called in the constructor of an OpalConnection descendant,
206 // we can't use a virtual function on OpalConnection
207
208 if (conn.GetPrefixName() *= "sip")
209 return new OpalSIPIMMediaSession(conn, sessionID);
210
211 return NULL;
212 }
213
214 ////////////////////////////////////////////////////////////////////////////////////////////
215
OpalSIPIMMediaSession(OpalConnection & _conn,unsigned _sessionId)216 OpalSIPIMMediaSession::OpalSIPIMMediaSession(OpalConnection & _conn, unsigned _sessionId)
217 : OpalMediaSession(_conn, SIP_IM, _sessionId)
218 {
219 transportAddress = connection.GetTransport().GetLocalAddress();
220 localURL = connection.GetLocalPartyURL();
221 remoteURL = connection.GetRemotePartyURL();
222 callId = connection.GetToken();
223 }
224
OpalSIPIMMediaSession(const OpalSIPIMMediaSession & obj)225 OpalSIPIMMediaSession::OpalSIPIMMediaSession(const OpalSIPIMMediaSession & obj)
226 : OpalMediaSession(obj)
227 {
228 transportAddress = obj.transportAddress;
229 localURL = obj.localURL;
230 remoteURL = obj.remoteURL;
231 callId = obj.callId;
232 }
233
GetLocalMediaAddress() const234 OpalTransportAddress OpalSIPIMMediaSession::GetLocalMediaAddress() const
235 {
236 return transportAddress;
237 }
238
239
CreateSDPMediaDescription(const OpalTransportAddress & sdpContactAddress)240 SDPMediaDescription * OpalSIPIMMediaSession::CreateSDPMediaDescription(const OpalTransportAddress & sdpContactAddress)
241 {
242 return new SDPSIPIMMediaDescription(sdpContactAddress, transportAddress, localURL);
243 }
244
245
CreateMediaStream(const OpalMediaFormat & mediaFormat,unsigned sessionID,PBoolean isSource)246 OpalMediaStream * OpalSIPIMMediaSession::CreateMediaStream(const OpalMediaFormat & mediaFormat,
247 unsigned sessionID,
248 PBoolean isSource)
249 {
250 PTRACE(2, "SIPIM\tCreated " << (isSource ? "source" : "sink") << " media stream in " << (connection.IsOriginating() ? "originator" : "receiver") << " with local " << localURL << " and remote " << remoteURL);
251 return new OpalIMMediaStream(connection, mediaFormat, sessionID, isSource);
252 }
253
254
SetRemoteMediaAddress(const OpalTransportAddress &,const OpalMediaFormatList &)255 void OpalSIPIMMediaSession::SetRemoteMediaAddress(const OpalTransportAddress &, const OpalMediaFormatList &)
256 {
257 }
258
259
260 ////////////////////////////////////////////////////////////////////////////////////////////
261
262 static PFactory<OpalIMContext>::Worker<OpalSIPIMContext> static_OpalSIPContext("sip");
263
OpalSIPIMContext()264 OpalSIPIMContext::OpalSIPIMContext()
265 {
266 m_attributes.Set("rx-composition-indication-state", "idle");
267 m_attributes.Set("tx-composition-indication-state", "idle");
268 m_attributes.Set("acceptable-content-types", "text/plain\ntext/html\napplication/im-iscomposing+xml");
269 m_rxCompositionTimeout.SetNotifier(PCREATE_NOTIFIER(OnRxCompositionTimerExpire));
270 m_txCompositionTimeout.SetNotifier(PCREATE_NOTIFIER(OnTxCompositionTimerExpire));
271 m_txIdleTimeout.SetNotifier(PCREATE_NOTIFIER(OnTxIdleTimerExpire));
272 }
273
PopulateParams(SIPMessage::Params & params,OpalIM & message)274 void OpalSIPIMContext::PopulateParams(SIPMessage::Params & params, OpalIM & message)
275 {
276 params.m_localAddress = message.m_from.AsString();
277 params.m_addressOfRecord = params.m_localAddress;
278 params.m_remoteAddress = message.m_to.AsString();
279 params.m_id = message.m_conversationId;
280 params.m_messageId = message.m_messageId;
281
282 switch (message.m_type) {
283 case OpalIM::CompositionIndication_Idle: // RFC 3994
284 case OpalIM::CompositionIndication_Active: // RFC 3994
285 {
286 bool toActive = message.m_type == OpalIM::CompositionIndication_Active;
287 params.m_contentType = "application/im-iscomposing+xml";
288 params.m_body = "<?xml version='1.0' encoding='UTF-8'?>\n"
289 "<isComposing xmlns='urn:ietf:params:xml:ns:im-iscomposing'\n"
290 " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\n";
291
292 params.m_body += PString(" <state>") + (toActive ? "active" : "idle") + "</state>\n";
293
294 // DO NOT ENABLE THIS BECAUSE XLite barfs on it
295 //params.m_body += PString(" <lastactive>") + PTime().AsString(PTime::RFC3339) + "</lastactive>\n";
296
297 params.m_body += " <refresh>60</refresh>\n"
298 "</isComposing>";
299 }
300 break;
301
302 //case OpalIM::Disposition: // RFC 5438
303 // break;
304
305 case OpalIM::Text:
306 default:
307 params.m_contentType = message.m_mimeType;
308 params.m_body = message.m_body;
309 break;
310 }
311 }
312
313
InternalSendOutsideCall(OpalIM * message)314 OpalIMContext::SentStatus OpalSIPIMContext::InternalSendOutsideCall(OpalIM * message)
315 {
316 ResetTimers(*message);
317
318 SIPEndPoint * ep = dynamic_cast<SIPEndPoint *>(m_manager->FindEndPoint("sip"));
319 if (ep == NULL) {
320 PTRACE(2, "OpalSIPIMContext\tAttempt to send SIP IM without SIP endpoint");
321 return SentNoTransport;
322 }
323
324 SIPMessage::Params params;
325 PopulateParams(params, *message);
326
327 return ep->SendMESSAGE(params) ? SentPending : SentNoTransport;
328 }
329
330
InternalSendInsideCall(OpalIM * message)331 OpalIMContext::SentStatus OpalSIPIMContext::InternalSendInsideCall(OpalIM * message)
332 {
333 ResetTimers(*message);
334
335 PSafePtr<SIPConnection> conn = PSafePtrCast<OpalConnection, SIPConnection>(m_connection);
336 if (conn == NULL) {
337 PTRACE(2, "OpalSIPIMContext\tAttempt to send SIP IM on non-SIP connection");
338 return SentFailedGeneric;
339 }
340
341 SIPMessage::Params params;
342 PopulateParams(params, *message);
343
344 PSafePtr<SIPTransaction> transaction = new SIPMessage(*conn, params);
345 return transaction->Start() ? SentOK : SentFailedGeneric;
346 }
347
348
ResetTimers(OpalIM & message)349 void OpalSIPIMContext::ResetTimers(OpalIM & message)
350 {
351 if (message.m_type == OpalIM::Text) {
352 m_txCompositionTimeout.Stop(true);
353 m_txIdleTimeout.Stop(true);
354 m_attributes.Set("tx-composition-indication-state", "idle");
355 }
356 }
357
358 #if P_EXPAT
359
360 static PXML::ValidationInfo const CompositionIndicationValidation[] = {
361 { PXML::SetDefaultNamespace, "urn:ietf:params:xml:ns:im-iscomposing" },
362 { PXML::ElementName, "isComposing", },
363
364 { PXML::OptionalElement, "state" },
365 { PXML::OptionalElement, "lastactive" },
366 { PXML::OptionalElement, "contenttype" },
367 { PXML::OptionalElementWithBodyMatchingEx, "refresh", { "[0-9]*" } },
368
369 { PXML::EndOfValidationList }
370 };
371
372 #endif
373
OnIncomingIM(OpalIM & message)374 OpalIMContext::SentStatus OpalSIPIMContext::OnIncomingIM(OpalIM & message)
375 {
376 if (message.m_mimeType == "application/im-iscomposing+xml") {
377 #if P_EXPAT
378 PXML xml;
379 PString error;
380 if (!xml.LoadAndValidate(message.m_body, CompositionIndicationValidation, error, PXML::WithNS)) {
381 PTRACE(2, "OpalSIPIMContext\tXML error: " << error);
382 return SentInvalidContent;
383 }
384 PString state = "idle";
385 int timeout = 15;
386
387 PXMLElement * element = xml.GetRootElement()->GetElement("state");
388 if ((element != NULL) && (element->GetData().Trim() == "active"))
389 state = "active";
390
391 element = xml.GetRootElement()->GetElement("refresh");
392 if (element != NULL)
393 timeout = element->GetData().Trim().AsInteger();
394
395 if (state == m_attributes.Get("rx-composition-indication-state")) {
396 PTRACE(2, "OpalSIPIMContext\tcomposition indication refreshed");
397 return SentOK;
398 }
399 m_attributes.Set("rx-composition-indication-state", state);
400
401 if (state == "active")
402 m_rxCompositionTimeout = timeout * 1000;
403 else
404 m_rxCompositionTimeout.Stop(true);
405
406 OnCompositionIndicationChanged(state);
407
408 return SentOK;
409 #else
410 PTRACE(2, "OpalSIPIMContext\tunsupported content type");
411 return SentInvalidContent;
412 #endif
413 }
414
415 // receipt of text always indicated idle
416 m_rxCompositionTimeout.Stop(true);
417 OnCompositionIndicationTimeout();
418
419 // forward the text
420 return OpalIMContext::OnIncomingIM(message);
421 }
422
OnRxCompositionTimerExpire(PTimer &,INT)423 void OpalSIPIMContext::OnRxCompositionTimerExpire(PTimer &, INT)
424 {
425 m_manager->GetIMManager().OnCompositionIndicationTimeout(GetID());
426 }
427
OnCompositionIndicationTimeout()428 void OpalSIPIMContext::OnCompositionIndicationTimeout()
429 {
430 if (m_attributes.Get("rx-composition-indication-state") != "idle") {
431 m_attributes.Set("rx-composition-indication-state", "idle");
432 OnCompositionIndicationChanged("idle");
433 }
434 }
435
436
437 /*
438 OpalIMContext::SentStatus OpalSIPIMContext::SendText(bool active)
439 {
440 m_txCompositionTimeout.Stop(true);
441 m_txIdleTimeout.Stop(true);
442 m_attributes.Set("tx-composition-indication-state", "idle");
443 }
444 */
445
SendCompositionIndication(bool active)446 OpalIMContext::SentStatus OpalSIPIMContext::SendCompositionIndication(bool active)
447 {
448 bool currentState = !(m_attributes.Get("tx-composition-indication-state", "idle") == "idle");
449
450 if (active == currentState)
451 return OpalIMContext::SentOK;
452
453 if (active) {
454 m_attributes.Set("tx-composition-indication-state", "active");
455 m_txCompositionTimeout = 1000 * 60;
456 m_txIdleTimeout = 1000 * 15;
457
458 }
459 else {
460 m_txCompositionTimeout.Stop(true);
461 m_txIdleTimeout.Stop(true);
462 }
463
464 return OpalIMContext::SendCompositionIndication(active);
465 }
466
467
OnTxCompositionTimerExpire(PTimer &,INT)468 void OpalSIPIMContext::OnTxCompositionTimerExpire(PTimer &, INT)
469 {
470 OpalIMContext::SendCompositionIndication(true);
471 m_txCompositionTimeout.SetMilliSeconds(1000 * 60);
472 }
473
OnTxIdleTimerExpire(PTimer &,INT)474 void OpalSIPIMContext::OnTxIdleTimerExpire(PTimer &, INT)
475 {
476 m_txCompositionTimeout.Stop(true);
477 OpalIMContext::SendCompositionIndication(false);
478 }
479
480 ////////////////////////////////////////////////////////////////////////////////////////////
481
482 #endif // OPAL_HAS_SIPIM
483
484