1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// A generic RTSP client
19// Implementation
20
21#include "RTSPClient.hh"
22#include "RTSPCommon.hh"
23#include "Base64.hh"
24#include "Locale.hh"
25#include <GroupsockHelper.hh>
26#include "ourMD5.hh"
27
28RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
29 int verbosityLevel,
30 char const* applicationName,
31 portNumBits tunnelOverHTTPPortNum,
32 int socketNumToServer) {
33 return new RTSPClient(env, rtspURL,
34 verbosityLevel, applicationName, tunnelOverHTTPPortNum, socketNumToServer);
35}
36
37unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
38 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
39 return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
40}
41
42unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) {
43 if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
44 return sendRequest(new RequestRecord(++fCSeq, "OPTIONS", responseHandler));
45}
46
47unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
48 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
49 return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
50}
51
52unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
53 Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified,
54 Authenticator* authenticator) {
55 if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition)
56 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
57
58 u_int32_t booleanFlags = 0;
59 if (streamUsingTCP) booleanFlags |= 0x1;
60 if (streamOutgoing) booleanFlags |= 0x2;
61 if (forceMulticastOnUnspecified) booleanFlags |= 0x4;
62 return sendRequest(new RequestRecord(++fCSeq, "SETUP", responseHandler, NULL, &subsession, booleanFlags));
63}
64
65unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
66 double start, double end, float scale,
67 Authenticator* authenticator) {
68 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
69 sendDummyUDPPackets(session); // hack to improve NAT traversal
70 return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
71}
72
73unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
74 double start, double end, float scale,
75 Authenticator* authenticator) {
76 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
77 sendDummyUDPPackets(subsession); // hack to improve NAT traversal
78 return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
79}
80
81unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
82 char const* absStartTime, char const* absEndTime, float scale,
83 Authenticator* authenticator) {
84 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
85 sendDummyUDPPackets(session); // hack to improve NAT traversal
86 return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, &session, NULL));
87}
88
89unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
90 char const* absStartTime, char const* absEndTime, float scale,
91 Authenticator* authenticator) {
92 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
93 sendDummyUDPPackets(subsession); // hack to improve NAT traversal
94 return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, NULL, &subsession));
95}
96
97unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
98 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
99 return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
100}
101
102unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
103 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
104 return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
105}
106
107unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
108 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
109 return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
110}
111
112unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
113 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
114 return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
115}
116
117unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
118 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
119 return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
120}
121
122unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
123 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
124 return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
125}
126
127unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
128 char const* parameterName, char const* parameterValue,
129 Authenticator* authenticator) {
130 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
131 char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10];
132 sprintf(paramString, "%s: %s\r\n", parameterName, parameterValue);
133 unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
134 delete[] paramString;
135 return result;
136}
137
138unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
139 Authenticator* authenticator) {
140 if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
141
142 // We assume that:
143 // parameterName is NULL or "" means: Send no body in the request.
144 // parameterName is non-empty means: Send "<parameterName>\r\n" as the request body.
145 unsigned parameterNameLen = parameterName == NULL ? 0 : strlen(parameterName);
146 char* paramString = new char[parameterNameLen + 3]; // the 3 is for \r\n + the '\0' byte
147 if (parameterName == NULL || parameterName[0] == '\0') {
148 paramString[0] = '\0';
149 } else {
150 sprintf(paramString, "%s\r\n", parameterName);
151 }
152 unsigned result = sendRequest(new RequestRecord(++fCSeq, "GET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
153 delete[] paramString;
154 return result;
155}
156
157void RTSPClient::sendDummyUDPPackets(MediaSession& session, unsigned numDummyPackets) {
158 MediaSubsessionIterator iter(session);
159 MediaSubsession* subsession;
160
161 while ((subsession = iter.next()) != NULL) {
162 sendDummyUDPPackets(*subsession, numDummyPackets);
163 }
164}
165
166void RTSPClient::sendDummyUDPPackets(MediaSubsession& subsession, unsigned numDummyPackets) {
167 // Hack: To increase the likelihood of UDP packets from the server reaching us,
168 // if we're behind a NAT, send a few 'dummy' UDP packets to the server now.
169 // (We do this on both our RTP port and our RTCP port.)
170 Groupsock* gs1 = NULL; Groupsock* gs2 = NULL;
171 if (subsession.rtpSource() != NULL) gs1 = subsession.rtpSource()->RTPgs();
172 if (subsession.rtcpInstance() != NULL) gs2 = subsession.rtcpInstance()->RTCPgs();
173 u_int32_t const dummy = 0xFEEDFACE;
174 for (unsigned i = 0; i < numDummyPackets; ++i) {
175 if (gs1 != NULL) gs1->output(envir(), (unsigned char*)&dummy, sizeof dummy);
176 if (gs2 != NULL) gs2->output(envir(), (unsigned char*)&dummy, sizeof dummy);
177 }
178}
179
180void RTSPClient::setSpeed(MediaSession& session, float speed) {
181 // Optionally set download speed for session to be used later on PLAY command:
182 // The user should call this function after the MediaSession is instantiated, but before the
183 // first "sendPlayCommand()" is called.
184 session.speed() = speed;
185 MediaSubsessionIterator iter(session);
186 MediaSubsession* subsession;
187
188 while ((subsession = iter.next()) != NULL) {
189 subsession->speed() = speed;
190 }
191}
192
193Boolean RTSPClient::changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler) {
194 // Look for the matching request record in each of our 'pending requests' queues:
195 RequestRecord* request;
196 if ((request = fRequestsAwaitingConnection.findByCSeq(cseq)) != NULL
197 || (request = fRequestsAwaitingHTTPTunneling.findByCSeq(cseq)) != NULL
198 || (request = fRequestsAwaitingResponse.findByCSeq(cseq)) != NULL) {
199 request->handler() = newResponseHandler;
200 return True;
201 }
202
203 return False;
204}
205
206Boolean RTSPClient::lookupByName(UsageEnvironment& env,
207 char const* instanceName,
208 RTSPClient*& resultClient) {
209 resultClient = NULL; // unless we succeed
210
211 Medium* medium;
212 if (!Medium::lookupByName(env, instanceName, medium)) return False;
213
214 if (!medium->isRTSPClient()) {
215 env.setResultMsg(instanceName, " is not a RTSP client");
216 return False;
217 }
218
219 resultClient = (RTSPClient*)medium;
220 return True;
221}
222
223static void copyUsernameOrPasswordStringFromURL(char* dest, char const* src, unsigned len) {
224 // Normally, we just copy from the source to the destination. However, if the source contains
225 // %-encoded characters, then we decode them while doing the copy:
226 while (len > 0) {
227 int nBefore = 0;
228 int nAfter = 0;
229
230 if (*src == '%' && len >= 3 && sscanf(src+1, "%n%2hhx%n", &nBefore, dest, &nAfter) == 1) {
231 unsigned codeSize = nAfter - nBefore; // should be 1 or 2
232
233 ++dest;
234 src += (1 + codeSize);
235 len -= (1 + codeSize);
236 } else {
237 *dest++ = *src++;
238 --len;
239 }
240 }
241 *dest = '\0';
242}
243
244Boolean RTSPClient::parseRTSPURL(char const* url,
245 char*& username, char*& password,
246 NetAddress& address,
247 portNumBits& portNum,
248 char const** urlSuffix) {
249 do {
250 // Parse the URL as "rtsp://[<username>[:<password>]@]<server-address-or-name>[:<port>][/<stream-name>]" (or "rtsps://...")
251 char const* prefix1 = "rtsp://";
252 unsigned const prefix1Length = 7;
253 char const* prefix2 = "rtsps://";
254 unsigned const prefix2Length = 8;
255
256 portNumBits defaultPortNumber;
257 char const* from;
258 if (_strncasecmp(url, prefix1, prefix1Length) == 0) {
259 defaultPortNumber = 554;
260 from = &url[prefix1Length];
261 } else if (_strncasecmp(url, prefix2, prefix2Length) == 0) {
262 useTLS();
263 defaultPortNumber = 322;
264 from = &url[prefix2Length];
265 } else {
266 envir().setResultMsg("URL does not begin with \"rtsp://\" or \"rtsps://\"");
267 break;
268 }
269
270 unsigned const parseBufferSize = 100;
271 char parseBuffer[parseBufferSize];
272
273 // Check whether "<username>[:<password>]@" occurs next.
274 // We do this by checking whether '@' appears before the end of the URL, or before the first '/'.
275 username = password = NULL; // default return values
276 char const* colonPasswordStart = NULL;
277 char const* lastAtPtr = NULL;
278 for (char const* p = from; *p != '\0' && *p != '/'; ++p) {
279 if (*p == ':' && colonPasswordStart == NULL) {
280 colonPasswordStart = p;
281 } else if (*p == '@') {
282 lastAtPtr = p;
283 }
284 }
285 if (lastAtPtr != NULL) {
286 // We found <username> (and perhaps <password>). Copy them into newly-allocated result strings:
287 if (colonPasswordStart == NULL || colonPasswordStart > lastAtPtr) colonPasswordStart = lastAtPtr;
288
289 char const* usernameStart = from;
290 unsigned usernameLen = colonPasswordStart - usernameStart;
291 username = new char[usernameLen + 1] ; // allow for the trailing '\0'
292 copyUsernameOrPasswordStringFromURL(username, usernameStart, usernameLen);
293
294 char const* passwordStart = colonPasswordStart;
295 if (passwordStart < lastAtPtr) ++passwordStart; // skip over the ':'
296 unsigned passwordLen = lastAtPtr - passwordStart;
297 password = new char[passwordLen + 1]; // allow for the trailing '\0'
298 copyUsernameOrPasswordStringFromURL(password, passwordStart, passwordLen);
299
300 from = lastAtPtr + 1; // skip over the '@'
301 }
302
303 // Next, parse <server-address-or-name>
304 char* to = &parseBuffer[0];
305 unsigned i;
306 for (i = 0; i < parseBufferSize; ++i) {
307 if (*from == '\0' || *from == ':' || *from == '/') {
308 // We've completed parsing the address
309 *to = '\0';
310 break;
311 }
312 *to++ = *from++;
313 }
314 if (i == parseBufferSize) {
315 envir().setResultMsg("URL is too long");
316 break;
317 }
318
319 NetAddressList addresses(parseBuffer);
320 if (addresses.numAddresses() == 0) {
321 envir().setResultMsg("Failed to find network address for \"",
322 parseBuffer, "\"");
323 break;
324 }
325 address = *(addresses.firstAddress());
326
327 portNum = defaultPortNumber; // unless it's specified explicitly in the URL
328 char nextChar = *from;
329 if (nextChar == ':') {
330 int portNumInt;
331 if (sscanf(++from, "%d", &portNumInt) != 1) {
332 envir().setResultMsg("No port number follows ':'");
333 break;
334 }
335 if (portNumInt < 1 || portNumInt > 65535) {
336 envir().setResultMsg("Bad port number");
337 break;
338 }
339 portNum = (portNumBits)portNumInt;
340 while (*from >= '0' && *from <= '9') ++from; // skip over port number
341 }
342
343 // The remainder of the URL is the suffix:
344 if (urlSuffix != NULL) *urlSuffix = from;
345
346 return True;
347 } while (0);
348
349 // An error occurred in the parsing:
350 return False;
351}
352
353void RTSPClient::setUserAgentString(char const* userAgentName) {
354 if (userAgentName == NULL) return;
355
356 // Change the existing user agent header string:
357 char const* const formatStr = "User-Agent: %s\r\n";
358 unsigned const headerSize = strlen(formatStr) + strlen(userAgentName);
359 delete[] fUserAgentHeaderStr;
360 fUserAgentHeaderStr = new char[headerSize];
361 sprintf(fUserAgentHeaderStr, formatStr, userAgentName);
362 fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr);
363}
364
365unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reassign this in your application if you need to
366
367RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
368 int verbosityLevel, char const* applicationName,
369 portNumBits tunnelOverHTTPPortNum, int socketNumToServer)
370 : Medium(env),
371 desiredMaxIncomingPacketSize(0), fVerbosityLevel(verbosityLevel), fCSeq(1),
372 fAllowBasicAuthentication(True), fServerAddress(0),
373 fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
374 fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
375 fInputSocketNum(-1), fOutputSocketNum(-1), fBaseURL(NULL), fTCPStreamIdCount(0),
376 fLastSessionId(NULL), fSessionTimeoutParameter(0), fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False),
377 fTLS(*this) {
378 setBaseURL(rtspURL);
379
380 fResponseBuffer = new char[responseBufferSize+1];
381 resetResponseBuffer();
382
383 if (socketNumToServer >= 0) {
384 // This socket number is (assumed to be) already connected to the server.
385 // Use it, and arrange to handle responses to requests sent on it:
386 fInputSocketNum = fOutputSocketNum = socketNumToServer;
387 env.taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
388 (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
389 }
390
391 // Set the "User-Agent:" header to use in each request:
392 char const* const libName = "LIVE555 Streaming Media v";
393 char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
394 char const* libPrefix; char const* libSuffix;
395 if (applicationName == NULL || applicationName[0] == '\0') {
396 applicationName = libPrefix = libSuffix = "";
397 } else {
398 libPrefix = " (";
399 libSuffix = ")";
400 }
401 unsigned userAgentNameSize
402 = strlen(applicationName) + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1;
403 char* userAgentName = new char[userAgentNameSize];
404 sprintf(userAgentName, "%s%s%s%s%s", applicationName, libPrefix, libName, libVersionStr, libSuffix);
405 setUserAgentString(userAgentName);
406 delete[] userAgentName;
407}
408
409RTSPClient::~RTSPClient() {
410 reset();
411
412 delete[] fResponseBuffer;
413 delete[] fUserAgentHeaderStr;
414}
415
416void RTSPClient::reset() {
417 resetTCPSockets();
418 resetResponseBuffer();
419 fRequestsAwaitingConnection.reset();
420 fRequestsAwaitingHTTPTunneling.reset();
421 fRequestsAwaitingResponse.reset();
422 fServerAddress = 0;
423
424 setBaseURL(NULL);
425
426 fCurrentAuthenticator.reset();
427
428 delete[] fLastSessionId; fLastSessionId = NULL;
429}
430
431void RTSPClient::setBaseURL(char const* url) {
432 delete[] fBaseURL; fBaseURL = strDup(url);
433}
434
435int RTSPClient::grabSocket() {
436 int inputSocket = fInputSocketNum;
437 RTPInterface::clearServerRequestAlternativeByteHandler(envir(), fInputSocketNum); // in case we were receiving RTP-over-TCP
438 fInputSocketNum = -1;
439
440 return inputSocket;
441}
442
443unsigned RTSPClient::sendRequest(RequestRecord* request) {
444 char* cmd = NULL;
445 do {
446 Boolean connectionIsPending = False;
447 if (!fRequestsAwaitingConnection.isEmpty()) {
448 // A connection is currently pending (with at least one enqueued request). Enqueue this request also:
449 connectionIsPending = True;
450 } else if (fInputSocketNum < 0) { // we need to open a connection
451 int connectResult = openConnection();
452 if (connectResult < 0) break; // an error occurred
453 else if (connectResult == 0) {
454 // A connection is pending
455 connectionIsPending = True;
456 } // else the connection succeeded. Continue sending the command.
457 }
458 if (connectionIsPending) {
459 fRequestsAwaitingConnection.enqueue(request);
460 return request->cseq();
461 }
462
463 // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP:
464 if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) {
465 if (!setupHTTPTunneling1()) break;
466 fRequestsAwaitingHTTPTunneling.enqueue(request);
467 return request->cseq();
468 }
469
470 // Construct and send the command:
471
472 // First, construct command-specific headers that we need:
473
474 char* cmdURL = fBaseURL; // by default
475 Boolean cmdURLWasAllocated = False;
476
477 char const* protocolStr = "RTSP/1.0"; // by default
478
479 char* extraHeaders = (char*)""; // by default
480 Boolean extraHeadersWereAllocated = False;
481
482 char* contentLengthHeader = (char*)""; // by default
483 Boolean contentLengthHeaderWasAllocated = False;
484
485 if (!setRequestFields(request,
486 cmdURL, cmdURLWasAllocated,
487 protocolStr,
488 extraHeaders, extraHeadersWereAllocated)) {
489 break;
490 }
491
492 char const* contentStr = request->contentStr(); // by default
493 if (contentStr == NULL) contentStr = "";
494 unsigned contentStrLen = strlen(contentStr);
495 if (contentStrLen > 0) {
496 char const* contentLengthHeaderFmt =
497 "Content-Length: %d\r\n";
498 unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt)
499 + 20 /* max int len */;
500 contentLengthHeader = new char[contentLengthHeaderSize];
501 sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen);
502 contentLengthHeaderWasAllocated = True;
503 }
504
505 char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL);
506
507 char const* const cmdFmt =
508 "%s %s %s\r\n"
509 "CSeq: %d\r\n"
510 "%s"
511 "%s"
512 "%s"
513 "%s"
514 "\r\n"
515 "%s";
516 unsigned cmdSize = strlen(cmdFmt)
517 + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr)
518 + 20 /* max int len */
519 + strlen(authenticatorStr)
520 + fUserAgentHeaderStrLen
521 + strlen(extraHeaders)
522 + strlen(contentLengthHeader)
523 + contentStrLen;
524 cmd = new char[cmdSize];
525 sprintf(cmd, cmdFmt,
526 request->commandName(), cmdURL, protocolStr,
527 request->cseq(),
528 authenticatorStr,
529 fUserAgentHeaderStr,
530 extraHeaders,
531 contentLengthHeader,
532 contentStr);
533 delete[] authenticatorStr;
534 if (cmdURLWasAllocated) delete[] cmdURL;
535 if (extraHeadersWereAllocated) delete[] extraHeaders;
536 if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader;
537
538 if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n";
539
540 if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) {
541 // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it.
542 // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.)
543 char* origCmd = cmd;
544 cmd = base64Encode(origCmd, strlen(cmd));
545 if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n";
546 delete[] origCmd;
547 }
548
549 if (write(cmd, strlen(cmd)) < 0) {
550 char const* errFmt = "%s write() failed: ";
551 unsigned const errLength = strlen(errFmt) + strlen(request->commandName());
552 char* err = new char[errLength];
553 sprintf(err, errFmt, request->commandName());
554 envir().setResultErrMsg(err);
555 delete[] err;
556 break;
557 }
558
559 // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled.
560 // However, note that we do not expect a response to a POST command with RTSP-over-HTTP, so don't enqueue that.
561 int cseq = request->cseq();
562
563 if (fTunnelOverHTTPPortNum == 0 || strcmp(request->commandName(), "POST") != 0) {
564 fRequestsAwaitingResponse.enqueue(request);
565 } else {
566 delete request;
567 }
568
569 delete[] cmd;
570 return cseq;
571 } while (0);
572
573 // An error occurred, so call the response handler immediately (indicating the error):
574 delete[] cmd;
575 handleRequestError(request);
576 delete request;
577 return 0;
578}
579
580static char* createSessionString(char const* sessionId) {
581 char* sessionStr;
582 if (sessionId != NULL) {
583 sessionStr = new char[20+strlen(sessionId)];
584 sprintf(sessionStr, "Session: %s\r\n", sessionId);
585 } else {
586 sessionStr = strDup("");
587 }
588 return sessionStr;
589}
590
591// Add support for faster download thru "speed:" option on PLAY
592static char* createSpeedString(float speed) {
593 char buf[100];
594 if (speed == 1.0f ) {
595 // This is the default value; we don't need a "Speed:" header:
596 buf[0] = '\0';
597 } else {
598 sprintf(buf, "Speed: %.3f\r\n",speed);
599 }
600
601 return strDup(buf);
602}
603
604static char* createScaleString(float scale, float currentScale) {
605 char buf[100];
606 if (scale == 1.0f && currentScale == 1.0f) {
607 // This is the default value; we don't need a "Scale:" header:
608 buf[0] = '\0';
609 } else {
610 Locale l("C", Numeric);
611 sprintf(buf, "Scale: %f\r\n", scale);
612 }
613
614 return strDup(buf);
615}
616
617static char* createRangeString(double start, double end, char const* absStartTime, char const* absEndTime) {
618 char buf[100];
619
620 if (absStartTime != NULL) {
621 // Create a "Range:" header that specifies 'absolute' time values:
622
623 if (absEndTime == NULL) {
624 // There's no end time:
625 snprintf(buf, sizeof buf, "Range: clock=%s-\r\n", absStartTime);
626 } else {
627 // There's both a start and an end time; include them both in the "Range:" hdr
628 snprintf(buf, sizeof buf, "Range: clock=%s-%s\r\n", absStartTime, absEndTime);
629 }
630 } else {
631 // Create a "Range:" header that specifies relative (i.e., NPT) time values:
632
633 if (start < 0) {
634 // We're resuming from a PAUSE; there's no "Range:" header at all
635 buf[0] = '\0';
636 } else if (end < 0) {
637 // There's no end time:
638 Locale l("C", Numeric);
639 sprintf(buf, "Range: npt=%.3f-\r\n", start);
640 } else {
641 // There's both a start and an end time; include them both in the "Range:" hdr
642 Locale l("C", Numeric);
643 sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
644 }
645 }
646
647 return strDup(buf);
648}
649
650Boolean RTSPClient::setRequestFields(RequestRecord* request,
651 char*& cmdURL, Boolean& cmdURLWasAllocated,
652 char const*& protocolStr,
653 char*& extraHeaders, Boolean& extraHeadersWereAllocated
654 ) {
655 // Set various fields that will appear in our outgoing request, depending upon the particular command that we are sending.
656
657 if (strcmp(request->commandName(), "DESCRIBE") == 0) {
658 extraHeaders = (char*)"Accept: application/sdp\r\n";
659 } else if (strcmp(request->commandName(), "OPTIONS") == 0) {
660 // If we're currently part of a session, create a "Session:" header (in case the server wants this to indicate
661 // client 'liveness); this makes up our 'extra headers':
662 extraHeaders = createSessionString(fLastSessionId);
663 extraHeadersWereAllocated = True;
664 } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) {
665 extraHeaders = (char*)"Content-Type: application/sdp\r\n";
666 } else if (strcmp(request->commandName(), "SETUP") == 0) {
667 MediaSubsession& subsession = *request->subsession();
668 Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0;
669 Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0;
670 Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0;
671
672 char const *prefix, *separator, *suffix;
673 constructSubsessionURL(subsession, prefix, separator, suffix);
674
675 char const* transportFmt;
676 if (strcmp(subsession.protocolName(), "UDP") == 0) {
677 suffix = "";
678 transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
679 } else {
680 transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
681 }
682
683 cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
684 cmdURLWasAllocated = True;
685 sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
686
687 // Construct a "Transport:" header.
688 char const* transportTypeStr;
689 char const* modeStr = streamOutgoing ? ";mode=receive" : "";
690 // Note: I think the above is nonstandard, but DSS wants it this way
691 char const* portTypeStr;
692 portNumBits rtpNumber, rtcpNumber;
693 if (streamUsingTCP) { // streaming over the RTSP connection
694 transportTypeStr = "/TCP;unicast";
695 portTypeStr = ";interleaved";
696 rtpNumber = fTCPStreamIdCount++;
697 rtcpNumber = fTCPStreamIdCount++;
698 } else { // normal RTP streaming
699 unsigned connectionAddress = subsession.connectionEndpointAddress();
700 Boolean requestMulticastStreaming
701 = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);
702 transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
703 portTypeStr = requestMulticastStreaming ? ";port" : ";client_port";
704 rtpNumber = subsession.clientPortNum();
705 if (rtpNumber == 0) {
706 envir().setResultMsg("Client port number unknown\n");
707 delete[] cmdURL;
708 return False;
709 }
710 rtcpNumber = subsession.rtcpIsMuxed() ? rtpNumber : rtpNumber + 1;
711 }
712 unsigned transportSize = strlen(transportFmt)
713 + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
714 char* transportStr = new char[transportSize];
715 sprintf(transportStr, transportFmt,
716 transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber);
717
718 // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands:
719 char* sessionStr = createSessionString(fLastSessionId);
720
721 // Optionally include a "Blocksize:" string:
722 char* blocksizeStr = createBlocksizeString(streamUsingTCP);
723
724 // The "Transport:" and "Session:" (if present) and "Blocksize:" (if present) headers
725 // make up the 'extra headers':
726 extraHeaders = new char[transportSize + strlen(sessionStr) + strlen(blocksizeStr)];
727 extraHeadersWereAllocated = True;
728 sprintf(extraHeaders, "%s%s%s", transportStr, sessionStr, blocksizeStr);
729 delete[] transportStr; delete[] sessionStr; delete[] blocksizeStr;
730 } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) {
731 // We will be sending a HTTP (not a RTSP) request.
732 // Begin by re-parsing our RTSP URL, to get the stream name (which we'll use as our 'cmdURL'
733 // in the subsequent request), and the server address (which we'll use in a "Host:" header):
734 char* username;
735 char* password;
736 NetAddress destAddress;
737 portNumBits urlPortNum;
738 if (!parseRTSPURL(fBaseURL, username, password, destAddress, urlPortNum, (char const**)&cmdURL)) return False;
739 if (cmdURL[0] == '\0') cmdURL = (char*)"/";
740 delete[] username;
741 delete[] password;
742 netAddressBits serverAddress = *(netAddressBits*)(destAddress.data());
743 AddressString serverAddressString(serverAddress);
744
745 protocolStr = "HTTP/1.1";
746
747 if (strcmp(request->commandName(), "GET") == 0) {
748 // Create a 'session cookie' string, using MD5:
749 struct {
750 struct timeval timestamp;
751 unsigned counter;
752 } seedData;
753 gettimeofday(&seedData.timestamp, NULL);
754 seedData.counter = ++fSessionCookieCounter;
755 our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie);
756 // DSS seems to require that the 'session cookie' string be 22 bytes long:
757 fSessionCookie[23] = '\0';
758
759 char const* const extraHeadersFmt =
760 "Host: %s\r\n"
761 "x-sessioncookie: %s\r\n"
762 "Accept: application/x-rtsp-tunnelled\r\n"
763 "Pragma: no-cache\r\n"
764 "Cache-Control: no-cache\r\n";
765 unsigned extraHeadersSize = strlen(extraHeadersFmt)
766 + strlen(serverAddressString.val())
767 + strlen(fSessionCookie);
768 extraHeaders = new char[extraHeadersSize];
769 extraHeadersWereAllocated = True;
770 sprintf(extraHeaders, extraHeadersFmt,
771 serverAddressString.val(),
772 fSessionCookie);
773 } else { // "POST"
774 char const* const extraHeadersFmt =
775 "Host: %s\r\n"
776 "x-sessioncookie: %s\r\n"
777 "Content-Type: application/x-rtsp-tunnelled\r\n"
778 "Pragma: no-cache\r\n"
779 "Cache-Control: no-cache\r\n"
780 "Content-Length: 32767\r\n"
781 "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";
782 unsigned extraHeadersSize = strlen(extraHeadersFmt)
783 + strlen(serverAddressString.val())
784 + strlen(fSessionCookie);
785 extraHeaders = new char[extraHeadersSize];
786 extraHeadersWereAllocated = True;
787 sprintf(extraHeaders, extraHeadersFmt,
788 serverAddressString.val(),
789 fSessionCookie);
790 }
791 } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER"
792 // First, make sure that we have a RTSP session in progress
793 if (fLastSessionId == NULL) {
794 envir().setResultMsg("No RTSP session is currently in progress\n");
795 return False;
796 }
797
798 char const* sessionId;
799 float originalScale;
800 if (request->session() != NULL) {
801 // Session-level operation
802 cmdURL = (char*)sessionURL(*request->session());
803
804 sessionId = fLastSessionId;
805 originalScale = request->session()->scale();
806 } else {
807 // Media-level operation
808 char const *prefix, *separator, *suffix;
809 constructSubsessionURL(*request->subsession(), prefix, separator, suffix);
810 cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
811 cmdURLWasAllocated = True;
812 sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
813
814 sessionId = request->subsession()->sessionId();
815 originalScale = request->subsession()->scale();
816 }
817
818 if (strcmp(request->commandName(), "PLAY") == 0) {
819 // Create possible "Session:", "Scale:", "Speed:", and "Range:" headers;
820 // these make up the 'extra headers':
821 char* sessionStr = createSessionString(sessionId);
822 char* scaleStr = createScaleString(request->scale(), originalScale);
823 float speed = request->session() != NULL ? request->session()->speed() : request->subsession()->speed();
824 char* speedStr = createSpeedString(speed);
825 char* rangeStr = createRangeString(request->start(), request->end(), request->absStartTime(), request->absEndTime());
826 extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(speedStr) + strlen(rangeStr) + 1];
827 extraHeadersWereAllocated = True;
828 sprintf(extraHeaders, "%s%s%s%s", sessionStr, scaleStr, speedStr, rangeStr);
829 delete[] sessionStr; delete[] scaleStr; delete[] speedStr; delete[] rangeStr;
830 } else {
831 // Create a "Session:" header; this makes up our 'extra headers':
832 extraHeaders = createSessionString(sessionId);
833 extraHeadersWereAllocated = True;
834 }
835 }
836
837 return True;
838}
839
840Boolean RTSPClient::isRTSPClient() const {
841 return True;
842}
843
844void RTSPClient::resetTCPSockets() {
845 if (fInputSocketNum >= 0) {
846 RTPInterface::clearServerRequestAlternativeByteHandler(envir(), fInputSocketNum); // in case we were receiving RTP-over-TCP
847 envir().taskScheduler().disableBackgroundHandling(fInputSocketNum);
848 ::closeSocket(fInputSocketNum);
849 if (fOutputSocketNum != fInputSocketNum) {
850 envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
851 ::closeSocket(fOutputSocketNum);
852 }
853 }
854 fInputSocketNum = fOutputSocketNum = -1;
855}
856
857void RTSPClient::resetResponseBuffer() {
858 fResponseBytesAlreadySeen = 0;
859 fResponseBufferBytesLeft = responseBufferSize;
860}
861
862int RTSPClient::openConnection() {
863 do {
864 // Set up a connection to the server. Begin by parsing the URL:
865
866 char* username;
867 char* password;
868 NetAddress destAddress;
869 portNumBits urlPortNum;
870 char const* urlSuffix;
871 if (!parseRTSPURL(fBaseURL, username, password, destAddress, urlPortNum, &urlSuffix)) break;
872 portNumBits destPortNum = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
873 if (destPortNum == 322) useTLS(); // port 322 is a special case: "rtsps"
874
875 if (username != NULL || password != NULL) {
876 fCurrentAuthenticator.setUsernameAndPassword(username, password);
877 delete[] username;
878 delete[] password;
879 }
880
881 // We don't yet have a TCP socket (or we used to have one, but it got closed). Set it up now.
882 fInputSocketNum = setupStreamSocket(envir(), 0);
883 if (fInputSocketNum < 0) break;
884 ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that get killed don't also kill us
885 if (fOutputSocketNum < 0) fOutputSocketNum = fInputSocketNum;
886 envir() << "Created new TCP socket " << fInputSocketNum << " for connection\n";
887
888 // Connect to the remote endpoint:
889 fServerAddress = *(netAddressBits*)(destAddress.data());
890 int connectResult = connectToServer(fInputSocketNum, destPortNum);
891 if (connectResult < 0) break;
892 else if (connectResult > 0) {
893 if (fTLS.isNeeded) {
894 // We need to complete an additional TLS connection:
895 connectResult = fTLS.connect(fInputSocketNum);
896 if (connectResult < 0) break;
897 if (connectResult > 0 && fVerbosityLevel >= 1) envir() << "...TLS connection completed\n";
898 }
899
900 if (connectResult > 0 && fVerbosityLevel >= 1) envir() << "...local connection opened\n";
901 }
902
903 return connectResult;
904 } while (0);
905
906 resetTCPSockets();
907 return -1;
908}
909
910int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) {
911 MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum));
912 if (fVerbosityLevel >= 1) {
913 envir() << "Connecting to " << AddressString(remoteName).val() << ", port " << remotePortNum << " on socket " << socketNum << "...\n";
914 }
915 if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
916 int const err = envir().getErrno();
917 if (err == EINPROGRESS || err == EWOULDBLOCK) {
918 // The connection is pending; we'll need to handle it later. Wait for our socket to be 'writable', or have an exception.
919 envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION,
920 (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this);
921 return 0;
922 }
923 envir().setResultErrMsg("connect() failed: ");
924 if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
925 return -1;
926 }
927
928 // The connection succeeded. Arrange to handle responses to requests sent on it:
929 envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
930 (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
931
932 return 1;
933}
934
935char* RTSPClient::createAuthenticatorString(char const* cmd, char const* url) {
936 Authenticator& auth = fCurrentAuthenticator; // alias, for brevity
937 if (auth.realm() != NULL && auth.username() != NULL && auth.password() != NULL) {
938 // We have a filled-in authenticator, so use it:
939 char* authenticatorStr;
940 if (auth.nonce() != NULL) { // Digest authentication
941 char const* const authFmt =
942 "Authorization: Digest username=\"%s\", realm=\"%s\", "
943 "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
944 char const* response = auth.computeDigestResponse(cmd, url);
945 unsigned authBufSize = strlen(authFmt)
946 + strlen(auth.username()) + strlen(auth.realm())
947 + strlen(auth.nonce()) + strlen(url) + strlen(response);
948 authenticatorStr = new char[authBufSize];
949 sprintf(authenticatorStr, authFmt,
950 auth.username(), auth.realm(),
951 auth.nonce(), url, response);
952 auth.reclaimDigestResponse(response);
953 } else { // Basic authentication
954 char const* const authFmt = "Authorization: Basic %s\r\n";
955
956 unsigned usernamePasswordLength = strlen(auth.username()) + 1 + strlen(auth.password());
957 char* usernamePassword = new char[usernamePasswordLength+1];
958 sprintf(usernamePassword, "%s:%s", auth.username(), auth.password());
959
960 char* response = base64Encode(usernamePassword, usernamePasswordLength);
961 unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
962 authenticatorStr = new char[authBufSize];
963 sprintf(authenticatorStr, authFmt, response);
964 delete[] response; delete[] usernamePassword;
965 }
966
967 return authenticatorStr;
968 }
969
970 // We don't have a (filled-in) authenticator.
971 return strDup("");
972}
973
974char* RTSPClient::createBlocksizeString(Boolean streamUsingTCP) {
975 char* blocksizeStr;
976 u_int16_t maxPacketSize = desiredMaxIncomingPacketSize;
977
978 // Allow for the RTP header (if streaming over TCP)
979 // or the IP/UDP/RTP headers (if streaming over UDP):
980 u_int16_t const headerAllowance = streamUsingTCP ? 12 : 50/*conservative*/;
981 if (maxPacketSize < headerAllowance) {
982 maxPacketSize = 0;
983 } else {
984 maxPacketSize -= headerAllowance;
985 }
986
987 if (maxPacketSize > 0) {
988 blocksizeStr = new char[25]; // more than enough space
989 sprintf(blocksizeStr, "Blocksize: %u\r\n", maxPacketSize);
990 } else {
991 blocksizeStr = strDup("");
992 }
993 return blocksizeStr;
994}
995
996void RTSPClient::handleRequestError(RequestRecord* request) {
997 int resultCode = -envir().getErrno();
998 if (resultCode == 0) {
999 // Choose some generic error code instead:
1000#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
1001 resultCode = -WSAENOTCONN;
1002#else
1003 resultCode = -ENOTCONN;
1004#endif
1005 }
1006 if (request->handler() != NULL) (*request->handler())(this, resultCode, strDup(envir().getResultMsg()));
1007}
1008
1009Boolean RTSPClient
1010::parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString) {
1011 if (sscanf(line, "RTSP/%*s%u", &responseCode) != 1 &&
1012 sscanf(line, "HTTP/%*s%u", &responseCode) != 1) return False;
1013 // Note: We check for HTTP responses as well as RTSP responses, both in order to setup RTSP-over-HTTP tunneling,
1014 // and so that we get back a meaningful error if the client tried to mistakenly send a RTSP command to a HTTP-only server.
1015
1016 // Use everything after the RTSP/* (or HTTP/*) as the response string:
1017 responseString = line;
1018 while (responseString[0] != '\0' && responseString[0] != ' ' && responseString[0] != '\t') ++responseString;
1019 while (responseString[0] != '\0' && (responseString[0] == ' ' || responseString[0] == '\t')) ++responseString; // skip whitespace
1020
1021 return True;
1022}
1023
1024void RTSPClient::handleIncomingRequest() {
1025 // Parse the request string into command name and 'CSeq', then 'handle' the command (by responding that we don't support it):
1026 char cmdName[RTSP_PARAM_STRING_MAX];
1027 char urlPreSuffix[RTSP_PARAM_STRING_MAX];
1028 char urlSuffix[RTSP_PARAM_STRING_MAX];
1029 char cseq[RTSP_PARAM_STRING_MAX];
1030 char sessionId[RTSP_PARAM_STRING_MAX];
1031 unsigned contentLength;
1032 if (!parseRTSPRequestString(fResponseBuffer, fResponseBytesAlreadySeen,
1033 cmdName, sizeof cmdName,
1034 urlPreSuffix, sizeof urlPreSuffix,
1035 urlSuffix, sizeof urlSuffix,
1036 cseq, sizeof cseq,
1037 sessionId, sizeof sessionId,
1038 contentLength)) {
1039 return;
1040 } else {
1041 if (fVerbosityLevel >= 1) {
1042 envir() << "Received incoming RTSP request: " << fResponseBuffer << "\n";
1043 }
1044 char tmpBuf[2*RTSP_PARAM_STRING_MAX];
1045 snprintf(tmpBuf, sizeof tmpBuf,
1046 "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
1047 write(tmpBuf, strlen(tmpBuf));
1048 }
1049}
1050
1051Boolean RTSPClient::checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams) {
1052 if (_strncasecmp(line, headerName, headerNameLength) != 0) return False;
1053
1054 // The line begins with the desired header name. Trim off any whitespace, and return the header parameters:
1055 unsigned paramIndex = headerNameLength;
1056 while (line[paramIndex] != '\0' && (line[paramIndex] == ' ' || line[paramIndex] == '\t')) ++paramIndex;
1057 if (line[paramIndex] == '\0') return False; // the header is assumed to be bad if it has no parameters
1058
1059 headerParams = &line[paramIndex];
1060 return True;
1061}
1062
1063Boolean RTSPClient::parseTransportParams(char const* paramsStr,
1064 char*& serverAddressStr, portNumBits& serverPortNum,
1065 unsigned char& rtpChannelId, unsigned char& rtcpChannelId) {
1066 // Initialize the return parameters to 'not found' values:
1067 serverAddressStr = NULL;
1068 serverPortNum = 0;
1069 rtpChannelId = rtcpChannelId = 0xFF;
1070 if (paramsStr == NULL) return False;
1071
1072 char* foundServerAddressStr = NULL;
1073 Boolean foundServerPortNum = False;
1074 portNumBits clientPortNum = 0;
1075 Boolean foundClientPortNum = False;
1076 Boolean foundChannelIds = False;
1077 unsigned rtpCid, rtcpCid;
1078 Boolean isMulticast = True; // by default
1079 char* foundDestinationStr = NULL;
1080 portNumBits multicastPortNumRTP, multicastPortNumRTCP;
1081 Boolean foundMulticastPortNum = False;
1082
1083 // Run through each of the parameters, looking for ones that we handle:
1084 char const* fields = paramsStr;
1085 char* field = strDupSize(fields);
1086 while (sscanf(fields, "%[^;]", field) == 1) {
1087 if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
1088 foundServerPortNum = True;
1089 } else if (sscanf(field, "client_port=%hu", &clientPortNum) == 1) {
1090 foundClientPortNum = True;
1091 } else if (_strncasecmp(field, "source=", 7) == 0) {
1092 delete[] foundServerAddressStr;
1093 foundServerAddressStr = strDup(field+7);
1094 } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
1095 rtpChannelId = (unsigned char)rtpCid;
1096 rtcpChannelId = (unsigned char)rtcpCid;
1097 foundChannelIds = True;
1098 } else if (strcmp(field, "unicast") == 0) {
1099 isMulticast = False;
1100 } else if (_strncasecmp(field, "destination=", 12) == 0) {
1101 delete[] foundDestinationStr;
1102 foundDestinationStr = strDup(field+12);
1103 } else if (sscanf(field, "port=%hu-%hu", &multicastPortNumRTP, &multicastPortNumRTCP) == 2 ||
1104 sscanf(field, "port=%hu", &multicastPortNumRTP) == 1) {
1105 foundMulticastPortNum = True;
1106 }
1107
1108 fields += strlen(field);
1109 while (fields[0] == ';') ++fields; // skip over all leading ';' chars
1110 if (fields[0] == '\0') break;
1111 }
1112 delete[] field;
1113
1114 // If we're multicast, and have a "destination=" (multicast) address, then use this
1115 // as the 'server' address (because some weird servers don't specify the multicast
1116 // address earlier, in the "DESCRIBE" response's SDP:
1117 if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
1118 delete[] foundServerAddressStr;
1119 serverAddressStr = foundDestinationStr;
1120 serverPortNum = multicastPortNumRTP;
1121 return True;
1122 }
1123 delete[] foundDestinationStr;
1124
1125 // We have a valid "Transport:" header if any of the following are true:
1126 // - We saw a "interleaved=" field, indicating RTP/RTCP-over-TCP streaming, or
1127 // - We saw a "server_port=" field, or
1128 // - We saw a "client_port=" field.
1129 // If we didn't also see a "server_port=" field, then the server port is assumed to be the same as the client port.
1130 if (foundChannelIds || foundServerPortNum || foundClientPortNum) {
1131 if (foundClientPortNum && !foundServerPortNum) {
1132 serverPortNum = clientPortNum;
1133 }
1134 serverAddressStr = foundServerAddressStr;
1135 return True;
1136 }
1137
1138 delete[] foundServerAddressStr;
1139 return False;
1140}
1141
1142Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) {
1143 Locale l("C", Numeric);
1144 return sscanf(paramStr, "%f", &scale) == 1;
1145}
1146
1147Boolean RTSPClient::parseSpeedParam(char const* paramStr, float& speed) {
1148 Locale l("C", Numeric);
1149 return sscanf(paramStr, "%f", &speed) >= 1;
1150}
1151
1152Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) {
1153 if (paramsStr == NULL || paramsStr[0] == '\0') return False;
1154 while (paramsStr[0] == ',') ++paramsStr;
1155
1156 // "paramsStr" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
1157 char* field = strDupSize(paramsStr);
1158
1159 Boolean sawSeq = False, sawRtptime = False;
1160 while (sscanf(paramsStr, "%[^;,]", field) == 1) {
1161 if (sscanf(field, "seq=%hu", &seqNum) == 1) {
1162 sawSeq = True;
1163 } else if (sscanf(field, "rtptime=%u", &timestamp) == 1) {
1164 sawRtptime = True;
1165 }
1166
1167 paramsStr += strlen(field);
1168 if (paramsStr[0] == '\0' || paramsStr[0] == ',') break;
1169 // ASSERT: paramsStr[0] == ';'
1170 ++paramsStr; // skip over the ';'
1171 }
1172
1173 delete[] field;
1174 // For the "RTP-Info:" parameters to be useful to us, we need to have seen both the "seq=" and "rtptime=" parameters:
1175 return sawSeq && sawRtptime;
1176}
1177
1178Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
1179 Boolean streamUsingTCP) {
1180 char* sessionId = new char[responseBufferSize]; // ensures we have enough space
1181 Boolean success = False;
1182 do {
1183 // Check for a session id:
1184 if (sessionParamsStr == NULL || sscanf(sessionParamsStr, "%[^;]", sessionId) != 1) {
1185 envir().setResultMsg("Missing or bad \"Session:\" header");
1186 break;
1187 }
1188 subsession.setSessionId(sessionId);
1189 delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
1190
1191 // Also look for an optional "; timeout = " parameter following this:
1192 char const* afterSessionId = sessionParamsStr + strlen(sessionId);
1193 int timeoutVal;
1194 if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
1195 fSessionTimeoutParameter = timeoutVal;
1196 }
1197
1198 // Parse the "Transport:" header parameters:
1199 char* serverAddressStr;
1200 portNumBits serverPortNum;
1201 unsigned char rtpChannelId, rtcpChannelId;
1202 if (!parseTransportParams(transportParamsStr, serverAddressStr, serverPortNum, rtpChannelId, rtcpChannelId)) {
1203 envir().setResultMsg("Missing or bad \"Transport:\" header");
1204 break;
1205 }
1206 delete[] subsession.connectionEndpointName();
1207 subsession.connectionEndpointName() = serverAddressStr;
1208 subsession.serverPortNum = serverPortNum;
1209 subsession.rtpChannelId = rtpChannelId;
1210 subsession.rtcpChannelId = rtcpChannelId;
1211
1212 if (streamUsingTCP) {
1213 // Tell the subsession to receive RTP (and send/receive RTCP) over the RTSP stream:
1214 if (subsession.rtpSource() != NULL) {
1215 subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
1216 // So that we continue to receive & handle RTSP commands and responses from the server
1217 subsession.rtpSource()->enableRTCPReports() = False;
1218 // To avoid confusing the server (which won't start handling RTP/RTCP-over-TCP until "PLAY"), don't send RTCP "RR"s yet
1219 increaseReceiveBufferTo(envir(), fInputSocketNum, 50*1024);
1220 }
1221 if (subsession.rtcpInstance() != NULL) subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId);
1222 RTPInterface::setServerRequestAlternativeByteHandler(envir(), fInputSocketNum, handleAlternativeRequestByte, this);
1223 } else {
1224 // Normal case.
1225 // Set the RTP and RTCP sockets' destination address and port from the information in the SETUP response (if present):
1226 netAddressBits destAddress = subsession.connectionEndpointAddress();
1227 if (destAddress == 0) destAddress = fServerAddress;
1228 subsession.setDestinations(destAddress);
1229 }
1230
1231 success = True;
1232 } while (0);
1233
1234 delete[] sessionId;
1235 return success;
1236}
1237
1238Boolean RTSPClient::handlePLAYResponse(MediaSession* session, MediaSubsession* subsession,
1239 char const* scaleParamsStr, char const* speedParamsStr,
1240 char const* rangeParamsStr, char const* rtpInfoParamsStr) {
1241 Boolean scaleOK = False, rangeOK = False, speedOK = False;
1242 do {
1243 if (session != NULL) {
1244 // The command was on the whole session
1245 if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, session->scale())) break;
1246 scaleOK = True;
1247 if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, session->speed())) break;
1248 speedOK = True;
1249 Boolean startTimeIsNow;
1250 if (rangeParamsStr != NULL &&
1251 !parseRangeParam(rangeParamsStr,
1252 session->playStartTime(), session->playEndTime(),
1253 session->_absStartTime(), session->_absEndTime(),
1254 startTimeIsNow)) break;
1255 rangeOK = True;
1256
1257 MediaSubsessionIterator iter(*session);
1258 MediaSubsession* subsession;
1259 while ((subsession = iter.next()) != NULL) {
1260 u_int16_t seqNum; u_int32_t timestamp;
1261 subsession->rtpInfo.infoIsNew = False;
1262 if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) {
1263 subsession->rtpInfo.seqNum = seqNum;
1264 subsession->rtpInfo.timestamp = timestamp;
1265 subsession->rtpInfo.infoIsNew = True;
1266 }
1267
1268 if (subsession->rtpSource() != NULL) subsession->rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now
1269 }
1270 } else {
1271 // The command was on a subsession
1272 if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, subsession->scale())) break;
1273 scaleOK = True;
1274 if (speedParamsStr != NULL && !parseSpeedParam(speedParamsStr, subsession->speed())) break;
1275 speedOK = True;
1276 Boolean startTimeIsNow;
1277 if (rangeParamsStr != NULL &&
1278 !parseRangeParam(rangeParamsStr,
1279 subsession->_playStartTime(), subsession->_playEndTime(),
1280 subsession->_absStartTime(), subsession->_absEndTime(),
1281 startTimeIsNow)) break;
1282 rangeOK = True;
1283
1284 u_int16_t seqNum; u_int32_t timestamp;
1285 subsession->rtpInfo.infoIsNew = False;
1286 if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) {
1287 subsession->rtpInfo.seqNum = seqNum;
1288 subsession->rtpInfo.timestamp = timestamp;
1289 subsession->rtpInfo.infoIsNew = True;
1290 }
1291
1292 if (subsession->rtpSource() != NULL) subsession->rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now
1293 }
1294
1295 return True;
1296 } while (0);
1297
1298 // An error occurred:
1299 if (!scaleOK) {
1300 envir().setResultMsg("Bad \"Scale:\" header");
1301 } else if (!speedOK) {
1302 envir().setResultMsg("Bad \"Speed:\" header");
1303 } else if (!rangeOK) {
1304 envir().setResultMsg("Bad \"Range:\" header");
1305 } else {
1306 envir().setResultMsg("Bad \"RTP-Info:\" header");
1307 }
1308 return False;
1309}
1310
1311Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& /*session*/, MediaSubsession& /*subsession*/) {
1312 // Because we don't expect to always get a response to "TEARDOWN", we don't need to do anything if we do get one:
1313 return True;
1314}
1315
1316Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString, char* resultValueStringEnd) {
1317 do {
1318 // If "parameterName" is non-empty, it may be (possibly followed by ':' and whitespace) at the start of the result string:
1319 if (parameterName != NULL && parameterName[0] != '\0') {
1320 if (parameterName[1] == '\0') break; // sanity check; there should have been \r\n at the end of "parameterName"
1321
1322 unsigned parameterNameLen = strlen(parameterName);
1323 // ASSERT: parameterNameLen >= 2;
1324 parameterNameLen -= 2; // because of the trailing \r\n
1325 if (resultValueString + parameterNameLen > resultValueStringEnd) break; // not enough space
1326 if (parameterNameLen > 0 && _strncasecmp(resultValueString, parameterName, parameterNameLen) == 0) {
1327 resultValueString += parameterNameLen;
1328 // ASSERT: resultValueString <= resultValueStringEnd
1329 if (resultValueString == resultValueStringEnd) break;
1330
1331 if (resultValueString[0] == ':') ++resultValueString;
1332 while (resultValueString < resultValueStringEnd
1333 && (resultValueString[0] == ' ' || resultValueString[0] == '\t')) {
1334 ++resultValueString;
1335 }
1336 }
1337 }
1338
1339 // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end:
1340 char saved = *resultValueStringEnd;
1341 *resultValueStringEnd = '\0';
1342 unsigned resultLen = strlen(resultValueString);
1343 *resultValueStringEnd = saved;
1344
1345 while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen;
1346 resultValueString[resultLen] = '\0';
1347
1348 return True;
1349 } while (0);
1350
1351 // An error occurred:
1352 envir().setResultMsg("Bad \"GET_PARAMETER\" response");
1353 return False;
1354}
1355
1356Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) {
1357 if (paramsStr == NULL) return False; // There was no "WWW-Authenticate:" header; we can't proceed.
1358
1359 // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header:
1360 Boolean realmHasChanged = False; // by default
1361 Boolean isStale = False; // by default
1362 char* realm = strDupSize(paramsStr);
1363 char* nonce = strDupSize(paramsStr);
1364 char* stale = strDupSize(paramsStr);
1365 Boolean success = True;
1366 if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\", stale=%[a-zA-Z]", realm, nonce, stale) == 3) {
1367 realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
1368 isStale = _strncasecmp(stale, "true", 4) == 0;
1369 fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
1370 } else if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
1371 realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
1372 fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
1373 } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1 && fAllowBasicAuthentication) {
1374 realmHasChanged = fCurrentAuthenticator.realm() == NULL || strcmp(fCurrentAuthenticator.realm(), realm) != 0;
1375 fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication
1376 } else {
1377 success = False; // bad "WWW-Authenticate:" header
1378 }
1379 delete[] realm; delete[] nonce; delete[] stale;
1380
1381 if (success) {
1382 if ((!realmHasChanged && !isStale) || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) {
1383 // We already tried with the same realm (and a non-stale nonce),
1384 // or don't have a username and/or password, so the new "WWW-Authenticate:" header
1385 // information won't help us. We remain unauthenticated.
1386 success = False;
1387 }
1388 }
1389
1390 return success;
1391}
1392
1393Boolean RTSPClient::resendCommand(RequestRecord* request) {
1394 if (fVerbosityLevel >= 1) envir() << "Resending...\n";
1395 if (request != NULL && strcmp(request->commandName(), "GET") != 0) request->cseq() = ++fCSeq;
1396 return sendRequest(request) != 0;
1397}
1398
1399char const* RTSPClient::sessionURL(MediaSession const& session) const {
1400 char const* url = session.controlPath();
1401 if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
1402
1403 return url;
1404}
1405
1406void RTSPClient::handleAlternativeRequestByte(void* rtspClient, u_int8_t requestByte) {
1407 ((RTSPClient*)rtspClient)->handleAlternativeRequestByte1(requestByte);
1408}
1409
1410void RTSPClient::handleAlternativeRequestByte1(u_int8_t requestByte) {
1411 if (requestByte == 0xFF) {
1412 // Hack: The new handler of the input TCP socket encountered an error reading it. Indicate this:
1413 handleResponseBytes(-1);
1414 } else if (requestByte == 0xFE) {
1415 // Another hack: The new handler of the input TCP socket no longer needs it, so take back control:
1416 envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
1417 (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
1418 } else {
1419 // Normal case:
1420 fResponseBuffer[fResponseBytesAlreadySeen] = requestByte;
1421 handleResponseBytes(1);
1422 }
1423}
1424
1425static Boolean isAbsoluteURL(char const* url) {
1426 // Assumption: "url" is absolute if it contains a ':', before any
1427 // occurrence of '/'
1428 while (*url != '\0' && *url != '/') {
1429 if (*url == ':') return True;
1430 ++url;
1431 }
1432
1433 return False;
1434}
1435
1436void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
1437 char const*& prefix,
1438 char const*& separator,
1439 char const*& suffix) {
1440 // Figure out what the URL describing "subsession" will look like.
1441 // The URL is returned in three parts: prefix; separator; suffix
1442 //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
1443 // doesn't end with a "/", and "subsession.controlPath()" is relative.
1444 // The right thing would have been to truncate "sessionURL()" back to the
1445 // rightmost "/", and then add "subsession.controlPath()".
1446 // In practice, though, each "DESCRIBE" response typically contains
1447 // a "Content-Base:" header that consists of "sessionURL()" followed by
1448 // a "/", in which case this code ends up giving the correct result.
1449 // However, we should really fix this code to do the right thing, and
1450 // also check for and use the "Content-Base:" header appropriately. #####
1451 prefix = sessionURL(subsession.parentSession());
1452 if (prefix == NULL) prefix = "";
1453
1454 suffix = subsession.controlPath();
1455 if (suffix == NULL) suffix = "";
1456
1457 if (isAbsoluteURL(suffix)) {
1458 prefix = separator = "";
1459 } else {
1460 unsigned prefixLen = strlen(prefix);
1461 separator = (prefixLen == 0 || prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
1462 }
1463}
1464
1465Boolean RTSPClient::setupHTTPTunneling1() {
1466 // Set up RTSP-over-HTTP tunneling, as described in
1467 // http://mirror.informatimago.com/next/developer.apple.com/quicktime/icefloe/dispatch028.html
1468 // and http://images.apple.com/br/quicktime/pdf/QTSS_Modules.pdf
1469 if (fVerbosityLevel >= 1) {
1470 envir() << "Requesting RTSP-over-HTTP tunneling (on port " << fTunnelOverHTTPPortNum << ")\n\n";
1471 }
1472
1473 // Begin by sending a HTTP "GET", to set up the server->client link. Continue when we handle the response:
1474 return sendRequest(new RequestRecord(1, "GET", responseHandlerForHTTP_GET)) != 0;
1475}
1476
1477void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString) {
1478 if (rtspClient != NULL) rtspClient->responseHandlerForHTTP_GET1(responseCode, responseString);
1479}
1480
1481void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) {
1482 RequestRecord* request;
1483 do {
1484 delete[] responseString; // we don't need it (but are responsible for deleting it)
1485 if (responseCode != 0) break; // The HTTP "GET" failed.
1486
1487 // Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection
1488 // (to the same server & port as before) for the client->server link. All future output will be to this new socket.
1489 fOutputSocketNum = setupStreamSocket(envir(), 0);
1490 if (fOutputSocketNum < 0) break;
1491 ignoreSigPipeOnSocket(fOutputSocketNum); // so that servers on the same host that killed don't also kill us
1492
1493 fHTTPTunnelingConnectionIsPending = True;
1494 int connectResult = connectToServer(fOutputSocketNum, fTunnelOverHTTPPortNum);
1495 if (connectResult < 0) break; // an error occurred
1496 else if (connectResult == 0) {
1497 // A connection is pending. Continue setting up RTSP-over-HTTP when the connection completes.
1498 // First, move the pending requests to the 'awaiting connection' queue:
1499 while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
1500 fRequestsAwaitingConnection.enqueue(request);
1501 }
1502 return;
1503 }
1504
1505 // The connection succeeded. Continue setting up RTSP-over-HTTP:
1506 if (!setupHTTPTunneling2()) break;
1507
1508 // RTSP-over-HTTP tunneling succeeded. Resume the pending request(s):
1509 while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
1510 sendRequest(request);
1511 }
1512 return;
1513 } while (0);
1514
1515 // An error occurred. Dequeue the pending request(s), and tell them about the error:
1516 fHTTPTunnelingConnectionIsPending = False;
1517 resetTCPSockets(); // do this now, in case an error handler deletes "this"
1518 RequestQueue requestQueue(fRequestsAwaitingHTTPTunneling);
1519 while ((request = requestQueue.dequeue()) != NULL) {
1520 handleRequestError(request);
1521 delete request;
1522 }
1523}
1524
1525Boolean RTSPClient::setupHTTPTunneling2() {
1526 fHTTPTunnelingConnectionIsPending = False;
1527
1528 // Send a HTTP "POST", to set up the client->server link. (Note that we won't see a reply to the "POST".)
1529 return sendRequest(new RequestRecord(1, "POST", NULL)) != 0;
1530}
1531
1532void RTSPClient::connectionHandler(void* instance, int /*mask*/) {
1533 RTSPClient* client = (RTSPClient*)instance;
1534 client->connectionHandler1();
1535}
1536
1537void RTSPClient::connectionHandler1() {
1538 // Restore normal handling on our sockets:
1539 envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
1540 envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
1541 (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
1542
1543 // Move all requests awaiting connection into a new, temporary queue, to clear "fRequestsAwaitingConnection"
1544 // (so that "sendRequest()" doesn't get confused by "fRequestsAwaitingConnection" being nonempty, and enqueue them all over again).
1545 RequestQueue tmpRequestQueue(fRequestsAwaitingConnection);
1546 RequestRecord* request;
1547
1548 // Find out whether the connection succeeded or failed:
1549 do {
1550 int err = 0;
1551 SOCKLEN_T len = sizeof err;
1552 if (getsockopt(fInputSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) {
1553 envir().setResultErrMsg("Connection to server failed: ", err);
1554 if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
1555 break;
1556 }
1557
1558 // The connection succeeded. If the connection came about from an attempt to set up RTSP-over-HTTP, finish this now:
1559 if (fHTTPTunnelingConnectionIsPending && !setupHTTPTunneling2()) break;
1560
1561 if (fTLS.isNeeded) {
1562 // We need to complete an additional TLS connection:
1563 int tlsConnectResult = fTLS.connect(fInputSocketNum);
1564 if (tlsConnectResult < 0) break; // error in TLS connection
1565 if (tlsConnectResult > 0 && fVerbosityLevel >= 1) envir() << "...TLS connection completed\n";
1566 if (tlsConnectResult == 0) {
1567 // The connection is still pending. Continue deferring...
1568 while ((request = tmpRequestQueue.dequeue()) != NULL) {
1569 fRequestsAwaitingConnection.enqueue(request);
1570 }
1571 return;
1572 }
1573 }
1574
1575 // The connection is complete. Resume sending all pending requests:
1576 if (fVerbosityLevel >= 1) envir() << "...remote connection opened\n";
1577 while ((request = tmpRequestQueue.dequeue()) != NULL) {
1578 sendRequest(request);
1579 }
1580 return;
1581 } while (0);
1582
1583 // An error occurred. Tell all pending requests about the error:
1584 resetTCPSockets(); // do this now, in case an error handler deletes "this"
1585 while ((request = tmpRequestQueue.dequeue()) != NULL) {
1586 handleRequestError(request);
1587 delete request;
1588 }
1589}
1590
1591void RTSPClient::incomingDataHandler(void* instance, int /*mask*/) {
1592 RTSPClient* client = (RTSPClient*)instance;
1593 client->incomingDataHandler1();
1594}
1595
1596void RTSPClient::incomingDataHandler1() {
1597 int bytesRead = read((u_int8_t*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft);
1598 handleResponseBytes(bytesRead);
1599}
1600
1601static char* getLine(char* startOfLine) {
1602 // returns the start of the next line, or NULL if none. Note that this modifies the input string to add '\0' characters.
1603 for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
1604 // Check for the end of line: \r\n (but also accept \r or \n by itself):
1605 if (*ptr == '\r' || *ptr == '\n') {
1606 // We found the end of the line
1607 if (*ptr == '\r') {
1608 *ptr++ = '\0';
1609 if (*ptr == '\n') ++ptr;
1610 } else {
1611 *ptr++ = '\0';
1612 }
1613 return ptr;
1614 }
1615 }
1616
1617 return NULL;
1618}
1619
1620void RTSPClient::handleResponseBytes(int newBytesRead) {
1621 do {
1622 if (newBytesRead >= 0 && (unsigned)newBytesRead < fResponseBufferBytesLeft) break; // data was read OK; process it below
1623
1624 if (newBytesRead >= (int)fResponseBufferBytesLeft) {
1625 // We filled up our response buffer. Treat this as an error (for the first response handler):
1626 envir().setResultMsg("RTSP response was truncated. Increase \"RTSPClient::responseBufferSize\"");
1627 }
1628
1629 // An error occurred while reading our TCP socket. Call all pending response handlers, indicating this error.
1630 // (However, the "RTSP response was truncated" error is applied to the first response handler only.)
1631 resetResponseBuffer();
1632 RequestRecord* request;
1633 if (newBytesRead > 0) { // The "RTSP response was truncated" error
1634 if ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
1635 handleRequestError(request);
1636 delete request;
1637 }
1638 } else {
1639 RequestQueue requestQueue(fRequestsAwaitingResponse);
1640 resetTCPSockets(); // do this now, in case an error handler deletes "this"
1641
1642 while ((request = requestQueue.dequeue()) != NULL) {
1643 handleRequestError(request);
1644 delete request;
1645 }
1646 }
1647 return;
1648 } while (0);
1649
1650 fResponseBufferBytesLeft -= newBytesRead;
1651 fResponseBytesAlreadySeen += newBytesRead;
1652 fResponseBuffer[fResponseBytesAlreadySeen] = '\0';
1653 if (fVerbosityLevel >= 1 && newBytesRead > 1) envir() << "Received " << newBytesRead << " new bytes of response data.\n";
1654
1655 unsigned numExtraBytesAfterResponse = 0;
1656 Boolean responseSuccess = False; // by default
1657 do {
1658 // Data was read OK. Look through the data that we've read so far, to see if it contains <CR><LF><CR><LF>.
1659 // (If not, wait for more data to arrive.)
1660 Boolean endOfHeaders = False;
1661 char const* ptr = fResponseBuffer;
1662 if (fResponseBytesAlreadySeen > 3) {
1663 char const* const ptrEnd = &fResponseBuffer[fResponseBytesAlreadySeen-3];
1664 while (ptr < ptrEnd) {
1665 if (*ptr++ == '\r' && *ptr++ == '\n' && *ptr++ == '\r' && *ptr++ == '\n') {
1666 // This is it
1667 endOfHeaders = True;
1668 break;
1669 }
1670 }
1671 }
1672
1673 if (!endOfHeaders) return; // subsequent reads will be needed to get the complete response
1674
1675 // Now that we have the complete response headers (ending with <CR><LF><CR><LF>), parse them to get the response code, CSeq,
1676 // and various other header parameters. To do this, we first make a copy of the received header data, because we'll be
1677 // modifying it by adding '\0' bytes.
1678 char* headerDataCopy;
1679 unsigned responseCode = 200;
1680 char const* responseStr = NULL;
1681 RequestRecord* foundRequest = NULL;
1682 char const* sessionParamsStr = NULL;
1683 char const* transportParamsStr = NULL;
1684 char const* scaleParamsStr = NULL;
1685 char const* speedParamsStr = NULL;
1686 char const* rangeParamsStr = NULL;
1687 char const* rtpInfoParamsStr = NULL;
1688 char const* wwwAuthenticateParamsStr = NULL;
1689 char const* publicParamsStr = NULL;
1690 char* bodyStart = NULL;
1691 unsigned numBodyBytes = 0;
1692 responseSuccess = False;
1693 do {
1694 headerDataCopy = new char[responseBufferSize];
1695 strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen);
1696 headerDataCopy[fResponseBytesAlreadySeen] = '\0';
1697
1698 char* lineStart;
1699 char* nextLineStart = headerDataCopy;
1700 do {
1701 lineStart = nextLineStart;
1702 nextLineStart = getLine(lineStart);
1703 } while (lineStart[0] == '\0' && nextLineStart != NULL); // skip over any blank lines at the start
1704 if (!parseResponseCode(lineStart, responseCode, responseStr)) {
1705 // This does not appear to be a RTSP response; perhaps it's a RTSP request instead?
1706 handleIncomingRequest();
1707 break; // we're done with this data
1708 }
1709
1710 // Scan through the headers, handling the ones that we're interested in:
1711 Boolean reachedEndOfHeaders;
1712 unsigned cseq = 0;
1713 unsigned contentLength = 0;
1714
1715 while (1) {
1716 reachedEndOfHeaders = True; // by default; may get changed below
1717 lineStart = nextLineStart;
1718 if (lineStart == NULL) break;
1719
1720 nextLineStart = getLine(lineStart);
1721 if (lineStart[0] == '\0') break; // this is a blank line
1722 reachedEndOfHeaders = False;
1723
1724 char const* headerParamsStr;
1725 if (checkForHeader(lineStart, "CSeq:", 5, headerParamsStr)) {
1726 if (sscanf(headerParamsStr, "%u", &cseq) != 1 || cseq <= 0) {
1727 envir().setResultMsg("Bad \"CSeq:\" header: \"", lineStart, "\"");
1728 break;
1729 }
1730 // Find the handler function for "cseq":
1731 RequestRecord* request;
1732 while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
1733 if (request->cseq() < cseq) { // assumes that the CSeq counter will never wrap around
1734 // We never received (and will never receive) a response for this handler, so delete it:
1735 if (fVerbosityLevel >= 1 && strcmp(request->commandName(), "POST") != 0) {
1736 envir() << "WARNING: The server did not respond to our \"" << request->commandName() << "\" request (CSeq: "
1737 << request->cseq() << "). The server appears to be buggy (perhaps not handling pipelined requests properly).\n";
1738 }
1739 delete request;
1740 } else if (request->cseq() == cseq) {
1741 // This is the handler that we want. Remove its record, but remember it, so that we can later call its handler:
1742 foundRequest = request;
1743 break;
1744 } else { // request->cseq() > cseq
1745 // No handler was registered for this response, so ignore it.
1746 break;
1747 }
1748 }
1749 } else if (checkForHeader(lineStart, "Content-Length:", 15, headerParamsStr)) {
1750 if (sscanf(headerParamsStr, "%u", &contentLength) != 1) {
1751 envir().setResultMsg("Bad \"Content-Length:\" header: \"", lineStart, "\"");
1752 break;
1753 }
1754 } else if (checkForHeader(lineStart, "Content-Base:", 13, headerParamsStr)) {
1755 setBaseURL(headerParamsStr);
1756 } else if (checkForHeader(lineStart, "Session:", 8, sessionParamsStr)) {
1757 } else if (checkForHeader(lineStart, "Transport:", 10, transportParamsStr)) {
1758 } else if (checkForHeader(lineStart, "Scale:", 6, scaleParamsStr)) {
1759 } else if (checkForHeader(lineStart, "Speed:",
1760// NOTE: Should you feel the need to modify this code,
1761 6,
1762// please first email the "live-devel" mailing list
1763 speedParamsStr
1764// (see http://live555.com/liveMedia/faq.html#mailing-list-address for details),
1765 )) {
1766// to check whether your proposed modification is appropriate/correct,
1767 } else if (checkForHeader(lineStart, "Range:",
1768// and, if so, whether instead it could be included in
1769 6,
1770// a future release of the "LIVE555 Streaming Media" software,
1771 rangeParamsStr
1772// so that other projects that use the code could benefit (not just your own project).
1773 )) {
1774 } else if (checkForHeader(lineStart, "RTP-Info:", 9, rtpInfoParamsStr)) {
1775 } else if (checkForHeader(lineStart, "WWW-Authenticate:", 17, headerParamsStr)) {
1776 // If we've already seen a "WWW-Authenticate:" header, then we replace it with this new one only if
1777 // the new one specifies "Digest" authentication:
1778 if (wwwAuthenticateParamsStr == NULL || _strncasecmp(headerParamsStr, "Digest", 6) == 0) {
1779 wwwAuthenticateParamsStr = headerParamsStr;
1780 }
1781 } else if (checkForHeader(lineStart, "Public:", 7, publicParamsStr)) {
1782 } else if (checkForHeader(lineStart, "Allow:", 6, publicParamsStr)) {
1783 // Note: we accept "Allow:" instead of "Public:", so that "OPTIONS" requests made to HTTP servers will work.
1784 } else if (checkForHeader(lineStart, "Location:", 9, headerParamsStr)) {
1785 setBaseURL(headerParamsStr);
1786 } else if (checkForHeader(lineStart, "com.ses.streamID:", 17, headerParamsStr)) {
1787 // Replace the tail of the 'base URL' with the value of this header parameter:
1788 char* oldBaseURLTail = strrchr(fBaseURL, '/');
1789 if (oldBaseURLTail != NULL) {
1790 unsigned newBaseURLLen
1791 = (oldBaseURLTail - fBaseURL) + 8/* for "/stream=" */ + strlen(headerParamsStr);
1792 char* newBaseURL = new char[newBaseURLLen + 1];
1793 // Note: We couldn't use "asprintf()", because some compilers don't support it
1794 sprintf(newBaseURL, "%.*s/stream=%s",
1795 (int)(oldBaseURLTail - fBaseURL), fBaseURL, headerParamsStr);
1796 setBaseURL(newBaseURL);
1797 delete[] newBaseURL;
1798 }
1799 } else if (checkForHeader(lineStart, "Connection:", 11, headerParamsStr)) {
1800 if (fTunnelOverHTTPPortNum == 0 && _strncasecmp(headerParamsStr, "Close", 5) == 0) {
1801 resetTCPSockets();
1802 }
1803 }
1804 }
1805 if (!reachedEndOfHeaders) break; // an error occurred
1806
1807 if (foundRequest == NULL) {
1808 // Hack: The response didn't have a "CSeq:" header; assume it's for our most recent request:
1809 foundRequest = fRequestsAwaitingResponse.dequeue();
1810 }
1811
1812 // If we saw a "Content-Length:" header, then make sure that we have the amount of data that it specified:
1813 unsigned bodyOffset = nextLineStart == NULL ? fResponseBytesAlreadySeen : nextLineStart - headerDataCopy;
1814 bodyStart = &fResponseBuffer[bodyOffset];
1815 numBodyBytes = fResponseBytesAlreadySeen - bodyOffset;
1816 if (contentLength > numBodyBytes) {
1817 // We need to read more data. First, make sure we have enough space for it:
1818 unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
1819 unsigned remainingBufferSize = responseBufferSize - fResponseBytesAlreadySeen;
1820 if (numExtraBytesNeeded > remainingBufferSize) {
1821 char tmpBuf[200];
1822 sprintf(tmpBuf, "Response buffer size (%d) is too small for \"Content-Length:\" %d (need a buffer size of >= %d bytes\n",
1823 responseBufferSize, contentLength, fResponseBytesAlreadySeen + numExtraBytesNeeded);
1824 envir().setResultMsg(tmpBuf);
1825 break;
1826 }
1827
1828 if (fVerbosityLevel >= 1) {
1829 envir() << "Have received " << fResponseBytesAlreadySeen << " total bytes of a "
1830 << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
1831 << " RTSP response; awaiting " << numExtraBytesNeeded << " bytes more.\n";
1832 }
1833 delete[] headerDataCopy;
1834 if (foundRequest != NULL) fRequestsAwaitingResponse.putAtHead(foundRequest);// put our request record back; we need it again
1835 return; // We need to read more data
1836 }
1837
1838 // We now have a complete response (including all bytes specified by the "Content-Length:" header, if any).
1839 char* responseEnd = bodyStart + contentLength;
1840 numExtraBytesAfterResponse = &fResponseBuffer[fResponseBytesAlreadySeen] - responseEnd;
1841
1842 if (fVerbosityLevel >= 1) {
1843 char saved = *responseEnd;
1844 *responseEnd = '\0';
1845 envir() << "Received a complete "
1846 << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
1847 << " response:\n" << fResponseBuffer << "\n";
1848 if (numExtraBytesAfterResponse > 0) envir() << "\t(plus " << numExtraBytesAfterResponse << " additional bytes)\n";
1849 *responseEnd = saved;
1850 }
1851
1852 if (foundRequest != NULL) {
1853 Boolean needToResendCommand = False; // by default...
1854 if (responseCode == 200) {
1855 // Do special-case response handling for some commands:
1856 if (strcmp(foundRequest->commandName(), "SETUP") == 0) {
1857 if (!handleSETUPResponse(*foundRequest->subsession(), sessionParamsStr, transportParamsStr, foundRequest->booleanFlags()&0x1)) break;
1858 } else if (strcmp(foundRequest->commandName(), "PLAY") == 0) {
1859 if (!handlePLAYResponse(foundRequest->session(), foundRequest->subsession(), scaleParamsStr, speedParamsStr, rangeParamsStr, rtpInfoParamsStr)) break;
1860 } else if (strcmp(foundRequest->commandName(), "TEARDOWN") == 0) {
1861 if (!handleTEARDOWNResponse(*foundRequest->session(), *foundRequest->subsession())) break;
1862 } else if (strcmp(foundRequest->commandName(), "GET_PARAMETER") == 0) {
1863 if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart, responseEnd)) break;
1864 }
1865 } else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) {
1866 // We need to resend the command, with an "Authorization:" header:
1867 needToResendCommand = True;
1868
1869 if (strcmp(foundRequest->commandName(), "GET") == 0) {
1870 // Note: If a HTTP "GET" command (for RTSP-over-HTTP tunneling) returns "401 Unauthorized", then we resend it
1871 // (with an "Authorization:" header), just as we would for a RTSP command. However, we do so using a new TCP connection,
1872 // because some servers close the original connection after returning the "401 Unauthorized".
1873 resetTCPSockets(); // forces the opening of a new connection for the resent command
1874 }
1875 } else if (responseCode == 301 || responseCode == 302) { // redirection
1876 resetTCPSockets(); // because we need to connect somewhere else next
1877 needToResendCommand = True;
1878 }
1879
1880 if (needToResendCommand) {
1881 resetResponseBuffer();
1882 (void)resendCommand(foundRequest);
1883 delete[] headerDataCopy;
1884 return; // without calling our response handler; the response to the resent command will do that
1885 }
1886 }
1887
1888 responseSuccess = True;
1889 } while (0);
1890
1891 // If we have a handler function for this response, call it.
1892 // But first, reset our response buffer, in case the handler goes to the event loop, and we end up getting called recursively:
1893 if (numExtraBytesAfterResponse > 0) {
1894 // An unusual case; usually due to having received pipelined responses. Move the extra bytes to the front of the buffer:
1895 char* responseEnd = &fResponseBuffer[fResponseBytesAlreadySeen - numExtraBytesAfterResponse];
1896
1897 // But first: A hack to save a copy of the response 'body', in case it's needed below for "resultString":
1898 numBodyBytes -= numExtraBytesAfterResponse;
1899 if (numBodyBytes > 0) {
1900 char saved = *responseEnd;
1901 *responseEnd = '\0';
1902 bodyStart = strDup(bodyStart);
1903 *responseEnd = saved;
1904 }
1905
1906 memmove(fResponseBuffer, responseEnd, numExtraBytesAfterResponse);
1907 fResponseBytesAlreadySeen = numExtraBytesAfterResponse;
1908 fResponseBufferBytesLeft = responseBufferSize - numExtraBytesAfterResponse;
1909 fResponseBuffer[numExtraBytesAfterResponse] = '\0';
1910 } else {
1911 resetResponseBuffer();
1912 }
1913 if (foundRequest != NULL && foundRequest->handler() != NULL) {
1914 int resultCode;
1915 char* resultString;
1916 if (responseSuccess) {
1917 if (responseCode == 200) {
1918 resultCode = 0;
1919 resultString = numBodyBytes > 0 ? strDup(bodyStart) : strDup(publicParamsStr);
1920 // Note: The "strDup(bodyStart)" call assumes that the body is encoded without interior '\0' bytes
1921 } else {
1922 resultCode = responseCode;
1923 resultString = strDup(responseStr);
1924 envir().setResultMsg(responseStr);
1925 }
1926 (*foundRequest->handler())(this, resultCode, resultString);
1927 } else {
1928 // An error occurred parsing the response, so call the handler, indicating an error:
1929 handleRequestError(foundRequest);
1930 }
1931 }
1932 delete foundRequest;
1933 delete[] headerDataCopy;
1934 if (numExtraBytesAfterResponse > 0 && numBodyBytes > 0) delete[] bodyStart;
1935 } while (numExtraBytesAfterResponse > 0 && responseSuccess);
1936}
1937
1938int RTSPClient::write(const char* data, unsigned count) {
1939 if (fTLS.isNeeded) {
1940 return fTLS.write(data, count);
1941 } else {
1942 return send(fOutputSocketNum, (const u_int8_t*)data, count, 0);
1943 }
1944}
1945
1946int RTSPClient::read(u_int8_t* buffer, unsigned bufferSize) {
1947 if (fTLS.isNeeded) {
1948 return fTLS.read(buffer, bufferSize);
1949 } else {
1950 struct sockaddr_in dummy; // 'from' address - not used
1951 return readSocket(envir(), fInputSocketNum, buffer, bufferSize, dummy);
1952 }
1953}
1954
1955
1956////////// RTSPClient::RequestRecord implementation //////////
1957
1958RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
1959 MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags,
1960 double start, double end, float scale, char const* contentStr)
1961 : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags),
1962 fStart(start), fEnd(end), fAbsStartTime(NULL), fAbsEndTime(NULL), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) {
1963}
1964
1965RTSPClient::RequestRecord::RequestRecord(unsigned cseq, responseHandler* handler,
1966 char const* absStartTime, char const* absEndTime, float scale,
1967 MediaSession* session, MediaSubsession* subsession)
1968 : fNext(NULL), fCSeq(cseq), fCommandName("PLAY"), fSession(session), fSubsession(subsession), fBooleanFlags(0),
1969 fStart(0.0f), fEnd(-1.0f), fAbsStartTime(strDup(absStartTime)), fAbsEndTime(strDup(absEndTime)), fScale(scale),
1970 fContentStr(NULL), fHandler(handler) {
1971}
1972
1973RTSPClient::RequestRecord::~RequestRecord() {
1974 // Delete the rest of the list first:
1975 delete fNext;
1976
1977 delete[] fAbsStartTime; delete[] fAbsEndTime;
1978 delete[] fContentStr;
1979}
1980
1981
1982////////// RTSPClient::RequestQueue implementation //////////
1983
1984RTSPClient::RequestQueue::RequestQueue()
1985 : fHead(NULL), fTail(NULL) {
1986}
1987
1988RTSPClient::RequestQueue::RequestQueue(RequestQueue& origQueue)
1989 : fHead(NULL), fTail(NULL) {
1990 RequestRecord* request;
1991 while ((request = origQueue.dequeue()) != NULL) {
1992 enqueue(request);
1993 }
1994}
1995
1996RTSPClient::RequestQueue::~RequestQueue() {
1997 reset();
1998}
1999
2000void RTSPClient::RequestQueue::enqueue(RequestRecord* request) {
2001 if (fTail == NULL) {
2002 fHead = request;
2003 } else {
2004 fTail->next() = request;
2005 }
2006 fTail = request;
2007}
2008
2009RTSPClient::RequestRecord* RTSPClient::RequestQueue::dequeue() {
2010 RequestRecord* request = fHead;
2011 if (fHead == fTail) {
2012 fHead = NULL;
2013 fTail = NULL;
2014 } else {
2015 fHead = fHead->next();
2016 }
2017 if (request != NULL) request->next() = NULL;
2018 return request;
2019}
2020
2021void RTSPClient::RequestQueue::putAtHead(RequestRecord* request) {
2022 request->next() = fHead;
2023 fHead = request;
2024 if (fTail == NULL) {
2025 fTail = request;
2026 }
2027}
2028
2029RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) {
2030 RequestRecord* request;
2031 for (request = fHead; request != NULL; request = request->next()) {
2032 if (request->cseq() == cseq) return request;
2033 }
2034 return NULL;
2035}
2036
2037void RTSPClient::RequestQueue::reset() {
2038 delete fHead;
2039 fHead = fTail = NULL;
2040}
2041
2042
2043#ifndef OMIT_REGISTER_HANDLING
2044////////// HandlerServerForREGISTERCommand implementation /////////
2045
2046HandlerServerForREGISTERCommand* HandlerServerForREGISTERCommand
2047::createNew(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, Port ourPort,
2048 UserAuthenticationDatabase* authDatabase, int verbosityLevel, char const* applicationName) {
2049 int ourSocket = setUpOurSocket(env, ourPort);
2050 if (ourSocket == -1) return NULL;
2051
2052 return new HandlerServerForREGISTERCommand(env, creationFunc, ourSocket, ourPort, authDatabase, verbosityLevel, applicationName);
2053}
2054
2055HandlerServerForREGISTERCommand
2056::HandlerServerForREGISTERCommand(UsageEnvironment& env, onRTSPClientCreationFunc* creationFunc, int ourSocket, Port ourPort,
2057 UserAuthenticationDatabase* authDatabase, int verbosityLevel, char const* applicationName)
2058 : RTSPServer(env, ourSocket, ourPort, authDatabase, 30/*small reclamationTestSeconds*/),
2059 fCreationFunc(creationFunc), fVerbosityLevel(verbosityLevel), fApplicationName(strDup(applicationName)) {
2060}
2061
2062HandlerServerForREGISTERCommand::~HandlerServerForREGISTERCommand() {
2063 delete[] fApplicationName;
2064}
2065
2066RTSPClient* HandlerServerForREGISTERCommand
2067::createNewRTSPClient(char const* rtspURL, int verbosityLevel, char const* applicationName, int socketNumToServer) {
2068 // Default implementation: create a basic "RTSPClient":
2069 return RTSPClient::createNew(envir(), rtspURL, verbosityLevel, applicationName, 0, socketNumToServer);
2070}
2071
2072char const* HandlerServerForREGISTERCommand::allowedCommandNames() {
2073 return "OPTIONS, REGISTER";
2074}
2075
2076Boolean HandlerServerForREGISTERCommand
2077::weImplementREGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/,
2078 char const* /*proxyURLSuffix*/, char*& responseStr) {
2079 responseStr = NULL;
2080 // By default, we implement only "REGISTER"; not "DEREGISTER". Subclass to implement "DEREGISTER"
2081 return strcmp(cmd, "REGISTER") == 0;
2082}
2083
2084void HandlerServerForREGISTERCommand
2085::implementCmd_REGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/,
2086 char const* url, char const* urlSuffix, int socketToRemoteServer,
2087 Boolean deliverViaTCP, char const* /*proxyURLSuffix*/) {
2088 if (strcmp(cmd, "REGISTER") == 0) { // By default, we don't implement "DEREGISTER"
2089 // Create a new "RTSPClient" object, and call our 'creation function' with it:
2090 RTSPClient* newRTSPClient = createNewRTSPClient(url, fVerbosityLevel, fApplicationName, socketToRemoteServer);
2091
2092 if (fCreationFunc != NULL) (*fCreationFunc)(newRTSPClient, deliverViaTCP);
2093 }
2094}
2095#endif
2096