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