1 /*
2  * msrp.cxx
3  *
4  * Support for RFC 4975 Message Session Relay Protocol (MSRP)
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: 27149 $
27  * $Author: rjongbloed $
28  * $Date: 2012-03-07 18:32:36 -0600 (Wed, 07 Mar 2012) $
29  */
30 
31 #include <ptlib.h>
32 #include <opal/buildopts.h>
33 
34 #ifdef __GNUC__
35 #pragma implementation "msrp.h"
36 #endif
37 
38 #include <ptlib/socket.h>
39 #include <ptclib/random.h>
40 #include <ptclib/pdns.h>
41 #include <ptclib/mime.h>
42 #include <ptclib/http.h>
43 
44 #include <opal/transports.h>
45 #include <opal/mediatype.h>
46 #include <opal/mediafmt.h>
47 #include <opal/endpoint.h>
48 
49 #include <im/im.h>
50 #include <im/msrp.h>
51 
52 #if OPAL_HAS_MSRP
53 
54 #define CRLF "\r\n"
55 
56 const char MSRP[] = "msrp";
57 
OpalMSRPMediaType()58 OpalMSRPMediaType::OpalMSRPMediaType()
59   : OpalIMMediaType(MSRP, "message|tcp/msrp")
60 {
61 }
62 
63 #if OPAL_SIP
64 
65 /////////////////////////////////////////////////////////
66 //
67 //  SDP media description for the MSRP type
68 //
69 //  A new class is needed for "message" due to the following differences
70 //
71 //  - the SDP type is "message"
72 //  - the transport is "tcp/msrp"
73 //  - the format list is always "*". The actual supported formats are defined by the a=accept-types attribute
74 //  - the OpalMediaFormats for the IM types have no RTP encoding names
75 //
76 
77 class SDPMSRPMediaDescription : public SDPMediaDescription
78 {
79   PCLASSINFO(SDPMSRPMediaDescription, SDPMediaDescription);
80   public:
81     SDPMSRPMediaDescription(const OpalTransportAddress & address);
82     SDPMSRPMediaDescription(const OpalTransportAddress & address, const PString & url);
83 
GetSDPTransportType() const84     PCaselessString GetSDPTransportType() const
85     {
86       return "tcp/msrp";
87     }
88 
CreateEmpty() const89     virtual SDPMediaDescription * CreateEmpty() const
90     {
91       return new SDPMSRPMediaDescription(OpalTransportAddress());
92     }
93 
GetSDPMediaType() const94     virtual PString GetSDPMediaType() const
95     {
96       return "message";
97     }
98 
GetSDPPortList() const99     virtual PString GetSDPPortList() const
100     {
101       return " *";
102     }
103 
104     virtual void CreateSDPMediaFormats(const PStringArray &);
105     virtual bool PrintOn(ostream & str, const PString & connectString) const;
106     virtual void SetAttribute(const PString & attr, const PString & value);
107     virtual void ProcessMediaOptions(SDPMediaFormat & sdpFormat, const OpalMediaFormat & mediaFormat);
108     virtual void AddMediaFormat(const OpalMediaFormat & mediaFormat);
109 
110     virtual OpalMediaFormatList GetMediaFormats() const;
111 
112     // CreateSDPMediaFormat is used for processing format lists. MSRP always contains only "*"
CreateSDPMediaFormat(const PString &)113     virtual SDPMediaFormat * CreateSDPMediaFormat(const PString & ) { return NULL; }
114 
115     // FindFormat is used only for rtpmap and fmtp, neither of which are used for MSRP
FindFormat(PString &) const116     virtual SDPMediaFormat * FindFormat(PString &) const { return NULL; }
117 
118   protected:
119     PString path;
120     PString types;
121 };
122 
123 ////////////////////////////////////////////////////////////////////////////////////////////
124 
CreateSDPMediaDescription(const OpalTransportAddress & localAddress)125 SDPMediaDescription * OpalMSRPMediaType::CreateSDPMediaDescription(const OpalTransportAddress & localAddress)
126 {
127   return new SDPMSRPMediaDescription(localAddress);
128 }
129 
130 ///////////////////////////////////////////////////////////////////////////////////////////
131 
SDPMSRPMediaDescription(const OpalTransportAddress & address)132 SDPMSRPMediaDescription::SDPMSRPMediaDescription(const OpalTransportAddress & address)
133   : SDPMediaDescription(address, MSRP)
134 {
135   SetDirection(SDPMediaDescription::SendRecv);
136 }
137 
SDPMSRPMediaDescription(const OpalTransportAddress & address,const PString & _path)138 SDPMSRPMediaDescription::SDPMSRPMediaDescription(const OpalTransportAddress & address, const PString & _path)
139   : SDPMediaDescription(address, MSRP)
140   , path(_path)
141 {
142   SetDirection(SDPMediaDescription::SendRecv);
143 }
144 
CreateSDPMediaFormats(const PStringArray &)145 void SDPMSRPMediaDescription::CreateSDPMediaFormats(const PStringArray &)
146 {
147   formats.Append(new SDPMediaFormat(*this, RTP_DataFrame::MaxPayloadType, OpalMSRP));
148 }
149 
150 
PrintOn(ostream & str,const PString &) const151 bool SDPMSRPMediaDescription::PrintOn(ostream & str, const PString & /*connectString*/) const
152 {
153   // call ancestor. Never output the connect string, as the listening TCP sockets
154   // for the MSRP manager will always give an address of 0.0.0.0
155   if (!SDPMediaDescription::PrintOn(str, ""))
156     return false;
157 
158   str << "a=accept-types:" << types << "\r\n";
159   str << "a=path:" << path << "\r\n";
160 
161   return true;
162 }
163 
SetAttribute(const PString & attr,const PString & value)164 void SDPMSRPMediaDescription::SetAttribute(const PString & attr, const PString & value)
165 {
166   if (attr *= "path")
167     path = value;
168   else if (attr *= "accept-types")
169     types = value.Trim();
170 }
171 
ProcessMediaOptions(SDPMediaFormat &,const OpalMediaFormat & mediaFormat)172 void SDPMSRPMediaDescription::ProcessMediaOptions(SDPMediaFormat & /*sdpFormat*/, const OpalMediaFormat & mediaFormat)
173 {
174   if (mediaFormat.GetMediaType() == MSRP)
175     types = mediaFormat.GetOptionString("Accept Types").Trim();
176 }
177 
GetMediaFormats() const178 OpalMediaFormatList SDPMSRPMediaDescription::GetMediaFormats() const
179 {
180   OpalMediaFormat msrp(OpalMSRP);
181   msrp.SetOptionString("Accept Types", types);
182   msrp.SetOptionString("Path",         path);
183 
184   PTRACE(4, "MSRP\tNew format is\n" << setw(-1) << msrp);
185 
186   OpalMediaFormatList fmts;
187   fmts += msrp;
188   return fmts;
189 }
190 
AddMediaFormat(const OpalMediaFormat & mediaFormat)191 void SDPMSRPMediaDescription::AddMediaFormat(const OpalMediaFormat & mediaFormat)
192 {
193   if (!mediaFormat.IsTransportable() || !mediaFormat.IsValidForProtocol("sip") || mediaFormat.GetMediaType() != MSRP) {
194     PTRACE(4, "MSRP\tSDP not including " << mediaFormat << " as it is not a valid MSRP format");
195     return;
196   }
197 
198   SDPMediaFormat * sdpFormat = new SDPMediaFormat(*this, mediaFormat);
199   ProcessMediaOptions(*sdpFormat, mediaFormat);
200   AddSDPMediaFormat(sdpFormat);
201 }
202 
203 
204 #endif // OPAL_SIP
205 
206 ////////////////////////////////////////////////////////////////////////////////////////////
207 
208 class MSRPInitialiser : public PProcessStartup
209 {
210   PCLASSINFO(MSRPInitialiser, PProcessStartup)
211   public:
OnShutdown()212     virtual void OnShutdown()
213     {
214       PWaitAndSignal m(mutex);
215       delete manager;
216       manager = NULL;
217     }
218 
KickStart(OpalManager & opalManager)219     static OpalMSRPManager & KickStart(OpalManager & opalManager)
220     {
221       PWaitAndSignal m(mutex);
222       if (manager == NULL)
223         manager = new OpalMSRPManager(opalManager, OpalMSRPManager::DefaultPort);
224 
225       return * manager;
226     }
227 
228 protected:
229     static PMutex mutex;
230     static OpalMSRPManager * manager;
231 };
232 
233 PMutex MSRPInitialiser::mutex;
234 OpalMSRPManager * MSRPInitialiser::manager = NULL;
235 
236 PFACTORY_CREATE_SINGLETON(PProcessStartupFactory, MSRPInitialiser);
237 
238 
239 ////////////////////////////////////////////////////////////////////////////////////////////
240 
OpalMSRPMediaSession(OpalConnection & _conn,unsigned _sessionId)241 OpalMSRPMediaSession::OpalMSRPMediaSession(OpalConnection & _conn, unsigned _sessionId)
242   : OpalMediaSession(_conn, MSRP, _sessionId)
243   , m_manager(MSRPInitialiser::KickStart(_conn.GetEndPoint().GetManager()))
244   , m_isOriginating(_conn.IsOriginating())
245   , m_localMSRPSessionId(m_manager.CreateSessionID())
246   , m_localUrl(m_manager.SessionIDToURL(connection.GetTransport().GetLocalAddress(), m_localMSRPSessionId))
247 {
248   // set the local URL
249 }
250 
OpalMSRPMediaSession(const OpalMSRPMediaSession & _obj)251 OpalMSRPMediaSession::OpalMSRPMediaSession(const OpalMSRPMediaSession & _obj)
252   : OpalMediaSession(_obj)
253   , m_manager(_obj.m_manager)
254   , m_isOriginating(_obj.m_isOriginating)
255   , m_localMSRPSessionId(_obj.m_localMSRPSessionId)
256   , m_localUrl(_obj.m_localUrl)
257   , m_remoteUrl(_obj.m_remoteUrl)
258   , m_connectionPtr(_obj.m_connectionPtr)
259   , m_remoteAddress(_obj.m_remoteAddress)
260 {
261 }
262 
~OpalMSRPMediaSession()263 OpalMSRPMediaSession::~OpalMSRPMediaSession()
264 {
265   CloseMSRP();
266 }
267 
Close()268 void OpalMSRPMediaSession::Close()
269 {
270   CloseMSRP();
271 }
272 
GetLocalMediaAddress() const273 OpalTransportAddress OpalMSRPMediaSession::GetLocalMediaAddress() const
274 {
275   return OpalTransportAddress(m_localUrl.GetHostName(), m_localUrl.GetPort());
276 }
277 
278 #if OPAL_SIP
279 
CreateSDPMediaDescription(const OpalTransportAddress & sdpContactAddress)280 SDPMediaDescription * OpalMSRPMediaSession::CreateSDPMediaDescription(const OpalTransportAddress & sdpContactAddress)
281 {
282   return new SDPMSRPMediaDescription(sdpContactAddress, m_localUrl.AsString());
283 }
284 
285 #endif
286 
CreateMediaStream(const OpalMediaFormat & mediaFormat,unsigned sessionID,PBoolean isSource)287 OpalMediaStream * OpalMSRPMediaSession::CreateMediaStream(const OpalMediaFormat & mediaFormat,
288                                                                          unsigned sessionID,
289                                                                          PBoolean isSource)
290 {
291   PTRACE(2, "MSRP\tCreated " << (isSource ? "source" : "sink") << " media stream in " << (connection.IsOriginating() ? "originator" : "receiver") << " with " << m_localUrl);
292   return new OpalMSRPMediaStream(connection, mediaFormat, sessionID, isSource, *this);
293 }
294 
SetRemoteMediaAddress(const OpalTransportAddress & transportAddress,const OpalMediaFormatList &)295 void OpalMSRPMediaSession::SetRemoteMediaAddress(const OpalTransportAddress & transportAddress, const OpalMediaFormatList & )
296 {
297   PTRACE(2, "MSRP\tSetting remote media address to " << transportAddress);
298   m_remoteAddress = transportAddress;
299 }
300 
WritePacket(RTP_DataFrame & frame)301 bool OpalMSRPMediaSession::WritePacket(RTP_DataFrame & frame)
302 {
303   if (m_connectionPtr == NULL) {
304     PTRACE(2, "MSRP\tCannot send MSRP message as no connection has been established");
305   }
306   else {
307     RTP_IMFrame * imFrame = dynamic_cast<RTP_IMFrame *>(&frame);
308     if (imFrame != NULL) {
309       PString messageId;
310       T140String content;
311       PString str;
312       if (imFrame->GetContent(content) && content.AsString(str))
313         m_connectionPtr->m_protocol->SendSEND(m_localUrl, m_remoteUrl, str, imFrame->GetContentType(), messageId);
314       else {
315         PTRACE(1, "MSRP\tCannot convert IM message to string");
316       }
317     }
318   }
319   return true;
320 }
321 
OpenMSRP(const PURL & remoteUrl)322 bool OpalMSRPMediaSession::OpenMSRP(const PURL & remoteUrl)
323 {
324   if (m_connectionPtr != NULL)
325     return true;
326 
327   if (remoteUrl.IsEmpty())
328     return false;
329 
330   m_remoteUrl = remoteUrl;
331 
332   // only create connections when originating the call
333   if (!m_isOriginating)
334     return true;
335 
336   // create connection to remote
337   m_connectionPtr = m_manager.OpenConnection(m_localUrl, m_remoteUrl);
338   if (m_connectionPtr == NULL) {
339     PTRACE(3, "MSRP\tCannot create connection to remote URL '" << m_remoteUrl << "'");
340     return false;
341   }
342 
343   m_connectionPtr.SetSafetyMode(PSafeReference);
344 
345   return true;
346 }
347 
CloseMSRP()348 void OpalMSRPMediaSession::CloseMSRP()
349 {
350   if (m_connectionPtr != NULL)
351     m_manager.CloseConnection(m_connectionPtr);
352 }
353 
SetConnection(PSafePtr<OpalMSRPManager::Connection> & conn)354 void OpalMSRPMediaSession::SetConnection(PSafePtr<OpalMSRPManager::Connection> & conn)
355 {
356   if (m_connectionPtr == NULL) {
357     m_connectionPtr = conn;
358     m_connectionPtr.SetSafetyMode(PSafeReference);
359   }
360 }
361 
362 ////////////////////////////////////////////////////////
363 
CreateMediaSession(OpalConnection & conn,unsigned sessionID) const364 OpalMediaSession * OpalMSRPMediaType::CreateMediaSession(OpalConnection & conn, unsigned sessionID) const
365 {
366   PTRACE(2, "MSRP\tCreating MSRP media session for SIP connection");
367   return new OpalMSRPMediaSession(conn, sessionID);
368 }
369 
370 ////////////////////////////////////////////////////////
371 
OpalMSRPMediaStream(OpalConnection & conn,const OpalMediaFormat & mediaFormat,unsigned sessionID,bool isSource,OpalMSRPMediaSession & msrpSession)372 OpalMSRPMediaStream::OpalMSRPMediaStream(
373       OpalConnection & conn,
374       const OpalMediaFormat & mediaFormat, ///<  Media format for stream
375       unsigned sessionID,                  ///<  Session number for stream
376       bool isSource,                       ///<  Is a source stream
377       OpalMSRPMediaSession & msrpSession
378 )
379   : OpalIMMediaStream(conn, mediaFormat, sessionID, isSource)
380   , m_msrpSession(msrpSession)
381   , m_remoteParty(mediaFormat.GetOptionString("Path"))
382   , m_rfc4103Context(mediaFormat)
383 {
384   PTRACE(3, "MSRP\tOpening MSRP connection from " << m_msrpSession.GetLocalURL() << " to " << m_remoteParty);
385   if (isSource)
386     m_msrpSession.GetManager().SetNotifier(m_msrpSession.GetLocalURL(), m_remoteParty,
387                                            PCREATE_NOTIFIER2(OnReceiveMSRP, OpalMSRPManager::IncomingMSRP &));
388 }
389 
~OpalMSRPMediaStream()390 OpalMSRPMediaStream::~OpalMSRPMediaStream()
391 {
392   m_msrpSession.GetManager().RemoveNotifier(m_msrpSession.GetLocalURL(), m_remoteParty);
393 }
394 
Open()395 bool OpalMSRPMediaStream::Open()
396 {
397   return m_msrpSession.OpenMSRP(m_remoteParty) && OpalMediaStream::Open();
398 }
399 
ReadPacket(RTP_DataFrame &)400 PBoolean OpalMSRPMediaStream::ReadPacket(RTP_DataFrame &)
401 {
402   PAssertAlways("Cannot ReadData from OpalMSRPMediaStream");
403   return false;
404 }
405 
WritePacket(RTP_DataFrame & frame)406 PBoolean OpalMSRPMediaStream::WritePacket(RTP_DataFrame & frame)
407 {
408   if (!IsOpen())
409     return false;
410 
411   return m_msrpSession.WritePacket(frame);
412 }
413 
414 
OnReceiveMSRP(OpalMSRPManager &,OpalMSRPManager::IncomingMSRP & incomingMSRP)415 void OpalMSRPMediaStream::OnReceiveMSRP(OpalMSRPManager &, OpalMSRPManager::IncomingMSRP & incomingMSRP)
416 {
417   m_msrpSession.SetConnection(incomingMSRP.m_connection);
418 
419   if (connection.GetPhase() != OpalConnection::EstablishedPhase) {
420     PTRACE(3, "MSRP\tMediaStream " << *this << " receiving MSRP message in non-Established phase");
421   }
422   else if (incomingMSRP.m_command == MSRPProtocol::SEND) {
423     PTRACE(3, "MSRP\tMediaStream " << *this << " received SEND");
424     T140String t140(incomingMSRP.m_body);
425     RTP_DataFrameList frames = m_rfc4103Context.ConvertToFrames(incomingMSRP.m_mime.GetString(PHTTP::ContentTypeTag, PMIMEInfo::TextPlain()), t140);
426     OpalMediaFormat fmt(m_rfc4103Context.m_mediaFormat);
427     for (PINDEX i = 0; i < frames.GetSize(); ++i) {
428       //connection.OnReceiveExternalIM(m_rfc4103Context.m_mediaFormat, (RTP_IMFrame &)frames[i]);
429     }
430   }
431   else {
432     PTRACE(3, "MSRP\tMediaStream " << *this << " receiving unknown MSRP message");
433   }
434 }
435 
436 
437 ////////////////////////////////////////////////////////////////////////////////////////////
438 
OpalMSRPManager(OpalManager & _opalManager,WORD _port)439 OpalMSRPManager::OpalMSRPManager(OpalManager & _opalManager, WORD _port)
440   : opalManager(_opalManager), m_listenerPort(_port), m_listenerThread(NULL)
441 {
442   if (m_listenerSocket.Listen(5, m_listenerPort, PSocket::CanReuseAddress))
443     m_listenerThread = new PThreadObj<OpalMSRPManager>(*this, &OpalMSRPManager::ListenerThread);
444   else {
445     PTRACE(2, "MSRP\tCannot start MSRP listener on port " << m_listenerPort);
446   }
447 }
448 
~OpalMSRPManager()449 OpalMSRPManager::~OpalMSRPManager()
450 {
451   PWaitAndSignal m(mutex);
452 
453   if (m_listenerThread != NULL) {
454     m_listenerSocket.Close();
455     m_listenerThread->WaitForTermination();
456     delete m_listenerThread;
457   }
458 }
459 
460 
CreateSessionID()461 std::string OpalMSRPManager::CreateSessionID()
462 {
463   PString str = PGloballyUniqueID().AsString();
464   return (const char *)str;
465 }
466 
467 
SessionIDToURL(const OpalTransportAddress & taddr,const std::string & id)468 PURL OpalMSRPManager::SessionIDToURL(const OpalTransportAddress & taddr, const std::string & id)
469 {
470   PIPSocket::Address addr;
471   taddr.GetIpAddress(addr);
472 
473   PStringStream str;
474   str << "msrp://"
475       << addr.AsString()
476       << ":"
477       << m_listenerPort
478       << "/"
479       << id
480       << ";tcp";
481 
482   return PURL(str);
483 }
484 
GetLocalPort(WORD & port)485 bool OpalMSRPManager::GetLocalPort(WORD & port)
486 {
487   port = m_listenerPort;
488   return true;
489 }
490 
ListenerThread()491 void OpalMSRPManager::ListenerThread()
492 {
493   PTRACE(2, "MSRP\tListener thread started");
494 
495   for (;;) {
496     MSRPProtocol * protocol = new MSRPProtocol;
497     if (!protocol->Accept(m_listenerSocket)) {
498       PTRACE(2, "MSRP\tListener accept failed");
499       delete protocol;
500       break;
501     }
502 
503     PIPSocket * socket = protocol->GetSocket();
504     PIPSocketAddressAndPort remoteAddr;
505     socket->GetPeerAddress(remoteAddr);
506 
507     PTRACE(2, "MSRP\tListener accepted new incoming connection");
508     PSafePtr<Connection> connection = new Connection(*this, remoteAddr.AsString(), protocol);
509     {
510       PWaitAndSignal m(m_connectionInfoMapAddMutex);
511       connection.SetSafetyMode(PSafeReference);
512       m_connectionInfoMap.insert(ConnectionInfoMapType::value_type((const char *)remoteAddr.AsString(), connection));
513       connection.SetSafetyMode(PSafeReadWrite);
514     }
515     connection->StartHandler();
516   }
517   PTRACE(2, "MSRP\tListener thread ended");
518 }
519 
520 
OpenConnection(const PURL & localURL,const PURL & remoteURL)521 PSafePtr<OpalMSRPManager::Connection> OpalMSRPManager::OpenConnection(const PURL & localURL, const PURL & remoteURL)
522 {
523   // get hostname of remote
524   PIPSocket::Address ip = remoteURL.GetHostName();
525   WORD port             = remoteURL.GetPort();
526   if (!ip.IsValid()) {
527     if (remoteURL.GetPortSupplied()) {
528       if (!PIPSocket::GetHostAddress(remoteURL.GetHostName(), ip)) {
529         PTRACE(2, "MSRP\tUnable to resolve MSRP URL '" << remoteURL << "' with explicit port");
530         return NULL;
531       }
532     }
533     else {
534 #if P_DNS
535       PIPSocketAddressAndPortVector addresses;
536       if (PDNS::LookupSRV(remoteURL.GetHostName(), "_im._msrp", remoteURL.GetPort(), addresses) && !addresses.empty()) {
537         ip   = addresses[0].GetAddress(); // Only use first entry
538         port = addresses[0].GetPort();
539       }
540       else if (!PIPSocket::GetHostAddress(remoteURL.GetHostName(), ip)) {
541         PTRACE(2, "MSRP\tUnable to resolve MSRP URL hostname '" << remoteURL << "' ");
542         return NULL;
543       }
544 #else
545       return NULL;
546 #endif
547     }
548   }
549 
550   PString connectionKey(ip.AsString() + ":" + PString(PString::Unsigned, port));
551 
552   PSafePtr<Connection> connectionPtr = NULL;
553 
554   // see if we already have a connection to that remote host
555   // if not, create one and add to the connection map
556   {
557     PWaitAndSignal m(m_connectionInfoMapAddMutex);
558     ConnectionInfoMapType::iterator r = m_connectionInfoMap.find(connectionKey);
559     if (r != m_connectionInfoMap.end()) {
560       PTRACE(2, "MSRP\tReusing existing connection to " << ip << ":" << port);
561       connectionPtr = r->second;
562       connectionPtr.SetSafetyMode(PSafeReadWrite);
563       ++connectionPtr->m_refCount;
564       return connectionPtr;
565     }
566 
567     connectionPtr = PSafePtr<Connection>(new Connection(*this, connectionKey));
568     m_connectionInfoMap.insert(ConnectionInfoMapType::value_type(connectionKey, connectionPtr));
569   }
570 
571   connectionPtr.SetSafetyMode(PSafeReadWrite);
572 
573   // create a connection to the remote
574   // if cannot, remove it from connection map
575   connectionPtr->m_protocol->SetReadTimeout(2000);
576   if (!connectionPtr->m_protocol->Connect(ip, port)) {
577     PTRACE(2, "MSRP\tUnable to make new connection to " << ip << ":" << port);
578     PWaitAndSignal m(m_connectionInfoMapAddMutex);
579     m_connectionInfoMap.erase(connectionKey);
580     connectionPtr.SetNULL();
581     return NULL;
582   }
583 
584   PTRACE(2, "MSRP\tConnection established to to " << ip << ":" << port);
585 
586 
587   PString uid;
588   connectionPtr->m_protocol->SendSEND(localURL, remoteURL, "", "", uid);
589   connectionPtr->StartHandler();
590 
591   return connectionPtr;
592 }
593 
594 
CloseConnection(PSafePtr<OpalMSRPManager::Connection> & connection)595 bool OpalMSRPManager::CloseConnection(PSafePtr<OpalMSRPManager::Connection> & connection)
596 {
597   PWaitAndSignal m(m_connectionInfoMapAddMutex);
598   if (--connection->m_refCount == 0) {
599     m_connectionInfoMap.erase(connection->m_key);
600     connection.SetNULL();
601   }
602   return true;
603 }
604 
SetNotifier(const PURL & localUrl,const PURL & remoteUrl,const CallBack & notifier)605 void OpalMSRPManager::SetNotifier(const PURL & localUrl,
606                                   const PURL & remoteUrl,
607                                   const CallBack & notifier)
608 {
609   PString key(localUrl.AsString() + '\t' + remoteUrl.AsString());
610   PTRACE(2, "MSRP\tRegistering callback for incoming MSRP messages with '" << key << "'");
611   PWaitAndSignal m(m_callBacksMutex);
612   m_callBacks.insert(CallBackMap::value_type(key, CallBack(notifier)));
613 }
614 
615 
RemoveNotifier(const PURL & localUrl,const PURL & remoteUrl)616 void OpalMSRPManager::RemoveNotifier(const PURL & localUrl, const PURL & remoteUrl)
617 {
618   PString key(localUrl.AsString() + '\t' + remoteUrl.AsString());
619   PWaitAndSignal m(m_callBacksMutex);
620   m_callBacks.erase(key);
621 }
622 
DispatchMessage(IncomingMSRP & incomingMsg)623 void OpalMSRPManager::DispatchMessage(IncomingMSRP & incomingMsg)
624 {
625   PString fromUrl(incomingMsg.m_mime("From-Path"));
626   PString toUrl  (incomingMsg.m_mime("To-Path"));
627 
628   if (!toUrl.IsEmpty() && !fromUrl.IsEmpty()) {
629     PString key(toUrl + '\t' + fromUrl);
630 
631     PWaitAndSignal m(m_callBacksMutex);
632     CallBackMap::iterator r = m_callBacks.find(key);
633     if (r == m_callBacks.end()) {
634       PTRACE(2, "MSRP\tNo registered callbacks with '" << key << "'");
635     } else {
636       PTRACE(2, "MSRP\tCalling registered callbacks for '" << key << "'");
637       r->second(*this, incomingMsg);
638     }
639   }
640 }
641 
642 ////////////////////////////////////////////////////////
643 
Connection(OpalMSRPManager & manager,const std::string & key,MSRPProtocol * protocol)644 OpalMSRPManager::Connection::Connection(OpalMSRPManager & manager, const std::string & key, MSRPProtocol * protocol)
645   : m_manager(manager)
646   , m_key(key)
647   , m_protocol(protocol)
648   , m_running(true)
649   , m_handlerThread(NULL)
650 
651 {
652   PTRACE(3, "MSRP\tCreating connection");
653   if (m_protocol == NULL)
654     m_protocol = new MSRPProtocol();
655   m_refCount.SetValue(1);
656 }
657 
StartHandler()658 void OpalMSRPManager::Connection::StartHandler()
659 {
660   m_handlerThread = new PThreadObj<OpalMSRPManager::Connection>(*this, &OpalMSRPManager::Connection::HandlerThread);
661 }
662 
~Connection()663 OpalMSRPManager::Connection::~Connection()
664 {
665   if (m_handlerThread != NULL) {
666     m_running = false;
667     m_handlerThread->WaitForTermination();
668     delete m_handlerThread;
669     m_handlerThread = NULL;
670   }
671   delete m_protocol;
672   m_protocol = NULL;
673   PTRACE(3, "MSRP\tDestroying connection");
674 }
675 
676 
HandlerThread()677 void OpalMSRPManager::Connection::HandlerThread()
678 {
679   PTRACE(2, "MSRP\tMSRP connection thread started");
680 
681   m_protocol->SetReadTimeout(1000);
682 
683   while (m_running) {
684 
685     PIPSocket::SelectList sockets;
686     sockets += *m_protocol->GetSocket();
687 
688     if (PIPSocket::Select(sockets, 1000) != PChannel::NoError)
689       break;
690 
691     if (sockets.GetSize() != 0) {
692 
693       PTRACE(3, "MSRP\tMSRP message received");
694 
695       OpalMSRPManager::IncomingMSRP incomingMsg;
696       if (!m_protocol->ReadMessage(incomingMsg.m_command, incomingMsg.m_chunkId, incomingMsg.m_mime, incomingMsg.m_body))
697         break;
698 
699       PString fromUrl(incomingMsg.m_mime("From-Path"));
700       PString toUrl  (incomingMsg.m_mime("To-Path"));
701 
702       if (incomingMsg.m_command == MSRPProtocol::SEND) {
703         m_protocol->SendResponse(incomingMsg.m_chunkId, 200, "OK", toUrl, fromUrl);
704         PTRACE(3, "MSRP\tMSRP SEND received from=" << fromUrl << ",to=" << toUrl);
705         if (incomingMsg.m_mime.Contains(PHTTP::ContentTypeTag)) {
706           incomingMsg.m_connection = PSafePtr<Connection>(this);
707           m_manager.DispatchMessage(incomingMsg);
708         }
709         if (incomingMsg.m_mime("Success-Report") *= "yes") {
710           PMIMEInfo mime;
711           PString fromUrl(incomingMsg.m_mime("From-Path"));
712           PString toUrl  (incomingMsg.m_mime("To-Path"));
713           mime.SetAt("Message-ID", incomingMsg.m_mime("Message-ID"));
714           mime.SetAt("Byte-Range", incomingMsg.m_mime("Byte-Range"));
715           mime.SetAt("Status",     "000 200 OK");
716           m_protocol->SendREPORT(incomingMsg.m_chunkId, toUrl, fromUrl, mime);
717         }
718       }
719     }
720   }
721 
722   PTRACE(2, "MSRP\tMSRP protocol thread finished");
723 }
724 
725 ////////////////////////////////////////////////////////
726 
727 static char const * const MSRPCommands[MSRPProtocol::NumCommands] = {
728   "SEND", "REPORT"
729 };
730 
MSRPProtocol()731 MSRPProtocol::MSRPProtocol()
732 : PInternetProtocol("msrp 2855", NumCommands, MSRPCommands)
733 { }
734 
SendSEND(const PURL & from,const PURL & to,const PString & text,const PString & contentType,PString & messageId)735 bool MSRPProtocol::SendSEND(const PURL & from,
736                             const PURL & to,
737                             const PString & text,
738                             const PString & contentType,
739                                   PString & messageId)
740 {
741   // create a message
742   Message message;
743   message.m_id          = messageId = PGloballyUniqueID().AsString();
744   message.m_fromURL     = from;
745   message.m_toURL       = to;
746   message.m_contentType = contentType;
747   message.m_length      = text.GetLength();
748 
749   // break the text into chunks
750   if (message.m_length == 0) {
751     Message::Chunk chunk(PGloballyUniqueID().AsString(), 0, 0);
752     message.m_chunks.push_back(chunk);
753   }
754   else {
755     unsigned offs = 0;
756     while ((message.m_length - offs) > MaximumMessageLength) {
757       Message::Chunk chunk(PGloballyUniqueID().AsString(), offs, MaximumMessageLength);
758       message.m_chunks.push_back(chunk);
759       offs += MaximumMessageLength;
760     }
761     Message::Chunk chunk(PGloballyUniqueID().AsString(), offs, message.m_length - offs);
762     message.m_chunks.push_back(chunk);
763   }
764 
765   // add message to the message map
766   //m_messageMap.insert(MessageMap::value_type(message.m_id, message));
767 
768   // send the chunks
769   for (Message::ChunkList::const_iterator r = message.m_chunks.begin(); r != message.m_chunks.end(); ++r) {
770     PMIMEInfo mime;
771     mime.SetAt("Message-ID",   message.m_id);
772     bool isLast = ((r+1) == message.m_chunks.end());
773 
774     PString body;
775     if (message.m_length != 0) {
776       mime.SetAt("Success-Report", "yes");
777       mime.SetAt("Byte-Range",   psprintf("%u-%u/%u", r->m_rangeFrom, r->m_rangeTo, message.m_length));
778       body = (PHTTP::ContentTypeTag() & message.m_contentType) + CRLF CRLF +
779              text.Mid(r->m_rangeFrom-1, r->m_rangeTo - r->m_rangeFrom + 1) + CRLF;
780     }
781 
782     body += PString("-------") + r->m_chunkId + (isLast ? '$' : '+') + CRLF;   // note that RFC 4975 mandates a CRLF before the terminator
783 
784     if (!SendChunk(r->m_chunkId,
785                    message.m_toURL.AsString(),
786                    message.m_fromURL.AsString(),
787                    mime,
788                    body))
789       return false;
790   }
791   return true;
792 }
793 
794 
SendChunk(const PString & chunkId,const PString toUrl,const PString fromUrl,const PMIMEInfo & mime,const PString & body)795 bool MSRPProtocol::SendChunk(const PString & chunkId,
796                              const PString toUrl,
797                              const PString fromUrl,
798                              const PMIMEInfo & mime,
799                              const PString & body)
800 {
801   // Note that RFC 4975 mandates the order and position of of To-Path and From-Path
802   *this << "MSRP " << chunkId << " " << MSRPCommands[SEND] << CRLF
803         << "To-Path: " << toUrl << CRLF
804         << "From-Path: "<< fromUrl << CRLF
805         << ::setfill('\r');
806   mime.PrintContents(*this);
807   *this << body;
808   flush();
809 
810   {
811     PStringStream str; str << ::setfill('\r');
812     mime.PrintContents(str);
813     PTRACE(4, "Sending MSRP chunk\n" << "MSRP " << chunkId << " " << MSRPCommands[SEND] << CRLF
814                                        << "To-Path: " << toUrl << CRLF
815                                        << "From-Path: "<< fromUrl << CRLF
816                                        << str << CRLF
817                                        << body);
818   }
819 
820   return true;
821 }
822 
SendREPORT(const PString & chunkId,const PString & toUrl,const PString & fromUrl,const PMIMEInfo & mime)823 bool MSRPProtocol::SendREPORT(const PString & chunkId,
824                               const PString & toUrl,
825                               const PString & fromUrl,
826                             const PMIMEInfo & mime)
827 {
828   // Note that RFC 4975 mandates the order and position of of To-Path and From-Path
829   *this << "MSRP " << chunkId << " " << MSRPCommands[REPORT] << CRLF
830         << "To-Path: " << toUrl << CRLF
831         << "From-Path: "<< fromUrl << CRLF
832         << ::setfill('\r');
833   mime.PrintContents(*this);
834   *this << "-------" << chunkId << "$" << CRLF;
835   flush();
836 
837   {
838     PStringStream str; str << ::setfill('\r');
839     mime.PrintContents(str);
840     PTRACE(4, "Sending MSRP REPORT\n" << "MSRP " << chunkId << " " << MSRPCommands[REPORT] << CRLF
841                                                  << "To-Path: " << toUrl << CRLF
842                                                  << "From-Path: "<< fromUrl << CRLF
843                                                  << str << CRLF
844                                                  << "-------" << chunkId << "$");
845   }
846 
847   return true;
848 }
849 
SendResponse(const PString & chunkId,unsigned response,const PString & text,const PString & toUrl,const PString & fromUrl)850 bool MSRPProtocol::SendResponse(const PString & chunkId,
851                                 unsigned response,
852                                 const PString & text,
853                                 const PString & toUrl,
854                                 const PString & fromUrl)
855 {
856   // Note that RFC 4975 mandates the order and position of of To-Path and From-Path
857   *this << "MSRP " << chunkId << " " << response << (text.IsEmpty() ? "" : " ") << text << CRLF
858         << "To-Path: " << toUrl << CRLF
859         << "From-Path: "<< fromUrl << CRLF
860         << "-------" << chunkId << "$" << CRLF;
861   flush();
862 
863   PTRACE(4, "Sending MSRP response\n" << "MSRP " << chunkId << " " << response << (text.IsEmpty() ? "" : " ") << CRLF
864                                                  << "To-Path: " << toUrl << CRLF
865                                                  << "From-Path: "<< fromUrl << CRLF
866                                                  << "-------" << chunkId << "$");
867 
868   return true;
869 }
870 
ReadMessage(int & command,PString & chunkId,PMIMEInfo & mime,PString & body)871 bool MSRPProtocol::ReadMessage(int & command,
872                            PString & chunkId,
873                          PMIMEInfo & mime,
874                            PString & body)
875 {
876   // get the MSRP start line
877   PString line;
878   do {
879     if (!ReadLine(line, false)) {
880       PTRACE(2, "MSRP\tError while reading MSRP command");
881       return PFalse;
882     }
883   } while (line.IsEmpty());
884 
885   // get tokens
886   PStringArray tokens = line.Tokenise(' ', false);
887   if (tokens.GetSize() < 3) {
888     PTRACE(2, "MSRP\tReceived malformed MSRP command line with " << tokens.GetSize() << " tokens");
889     return false;
890   }
891 
892   if (!(tokens[0] *= "MSRP")) {
893     PTRACE(2, "MSRP\tFirst token on MSRP command line is not MSRP");
894     return false;
895   }
896 
897   chunkId = tokens[1];
898   PString terminator = "-------" + chunkId;
899   body.MakeEmpty();
900 
901   // read MIME until empty line or terminator
902   bool terminated = false;
903   {
904     mime.RemoveAll();
905     PString line;
906     while (ReadLine(line, false)) {
907       if (line.IsEmpty())
908         break;
909       if (line.Find(terminator) == 0) {
910         terminated = true;
911         break;
912       }
913       mime.AddMIME(line);
914     }
915   }
916 
917   // determine what command was given
918   command = NumCommands;
919   for (PINDEX i = 0; i < NumCommands; ++i) {
920     if (tokens[2] *= MSRPCommands[i]) {
921       command = i;
922       break;
923     }
924   }
925   if (command == NumCommands) {
926     unsigned code = tokens[2].AsUnsigned();
927     if (code > NumCommands)
928       command = code;
929   }
930 
931   // handle SEND bodies
932   if ((command == SEND) && mime.Contains(PHTTP::ContentTypeTag)) {
933     for (;;) {
934       PString line;
935       if (!ReadLine(line)) {
936         PTRACE(2, "MSRP\tError while reading MSRP command body");
937         return false;
938       }
939       if (line.Find(terminator) == 0) {
940         break;
941       }
942       if ((body.GetSize() + line.GetLength()) > 10240) {
943         PTRACE(2, "MSRP\tMaximum body size exceeded");
944         return false;
945       }
946       body += line;
947     }
948   }
949 
950   {
951     PStringStream str; str << ::setfill('\r');
952     mime.PrintContents(str);
953     PTRACE(4, "Received MSRP message\n" << line << "\n" << str << body << terminator);
954   }
955 
956   return true;
957 }
958 
959 
960 
961 
962 ////////////////////////////////////////////////////////
963 
964 #endif //  OPAL_HAS_MSRP
965