diff options
| author | 2011-01-11 09:51:34 -0800 | |
|---|---|---|
| committer | 2011-01-11 09:53:30 -0800 | |
| commit | 549f12ad04c491a2f25f599794868a4e21e9f1eb (patch) | |
| tree | d73542bd124ce4b5c80e868484f9ecd7d2d7914b | |
| parent | 65ba2c421c19fde04e6b294087a5a9c507964575 (diff) | |
DO NOT MERGE: Fix parsing of ntp= PLAY response.
related-to-bug: 3340186
Squashed commit of the following:
commit b61c36b7228aec9f5360883b1e1c1e0530488974
Author: Andreas Huber <andih@google.com>
Date: Wed Oct 27 13:59:59 2010 -0700
Better support for MP4A-LATM RTP disassembly. This used to fail if mNumSubFrames > 1 and the sub frames did not align with RTP packet boundaries.
commit b10f322c07e5bebcaf032e8624cb4a5d733dfc15
Author: Andreas Huber <andih@google.com>
Date: Mon Oct 25 09:40:52 2010 -0700
We don't have access to the md5 implementation on the simulator, let's disable digest authentication in rtsp for simulator targets.
commit 0aa83cf9e4637adf9501708fcdf7d0d6d4dc4fe1
Author: Andreas Huber <andih@google.com>
Date: Wed Oct 20 15:00:34 2010 -0700
Support for BASIC and DIGEST authentication schemes in RTSP. Support for malformed packet descriptions that end lines in LF only, instead of CRLF.
related-to-bug: 3084183
Change-Id: I6e512cb73cc8d5624a83f7154aa5699f7fef7534
| -rw-r--r-- | media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp | 384 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/AMPEG4AudioAssembler.h | 13 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/ARTPSource.cpp | 2 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/ARTSPConnection.cpp | 265 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/ARTSPConnection.h | 20 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/ASessionDescription.cpp | 19 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/Android.mk | 1 | ||||
| -rw-r--r-- | media/libstagefright/rtsp/MyHandler.h | 25 |
8 files changed, 690 insertions, 39 deletions
diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp index b0d2c64e3f40..bbde516bb3f1 100644 --- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp +++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.cpp @@ -18,18 +18,381 @@ #include "ARTPSource.h" +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABitReader.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> + +#include <ctype.h> namespace android { -AMPEG4AudioAssembler::AMPEG4AudioAssembler(const sp<AMessage> ¬ify) +static bool GetAttribute(const char *s, const char *key, AString *value) { + value->clear(); + + size_t keyLen = strlen(key); + + for (;;) { + while (isspace(*s)) { + ++s; + } + + const char *colonPos = strchr(s, ';'); + + size_t len = + (colonPos == NULL) ? strlen(s) : colonPos - s; + + if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) { + value->setTo(&s[keyLen + 1], len - keyLen - 1); + return true; + } + + if (colonPos == NULL) { + return false; + } + + s = colonPos + 1; + } +} + +static sp<ABuffer> decodeHex(const AString &s) { + if ((s.size() % 2) != 0) { + return NULL; + } + + size_t outLen = s.size() / 2; + sp<ABuffer> buffer = new ABuffer(outLen); + uint8_t *out = buffer->data(); + + uint8_t accum = 0; + for (size_t i = 0; i < s.size(); ++i) { + char c = s.c_str()[i]; + unsigned value; + if (c >= '0' && c <= '9') { + value = c - '0'; + } else if (c >= 'a' && c <= 'f') { + value = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + value = c - 'A' + 10; + } else { + return NULL; + } + + accum = (accum << 4) | value; + + if (i & 1) { + *out++ = accum; + + accum = 0; + } + } + + return buffer; +} + +static status_t parseAudioObjectType( + ABitReader *bits, unsigned *audioObjectType) { + *audioObjectType = bits->getBits(5); + if ((*audioObjectType) == 31) { + *audioObjectType = 32 + bits->getBits(6); + } + + return OK; +} + +static status_t parseGASpecificConfig( + ABitReader *bits, + unsigned audioObjectType, unsigned channelConfiguration) { + unsigned frameLengthFlag = bits->getBits(1); + unsigned dependsOnCoreCoder = bits->getBits(1); + if (dependsOnCoreCoder) { + /* unsigned coreCoderDelay = */bits->getBits(1); + } + unsigned extensionFlag = bits->getBits(1); + + if (!channelConfiguration) { + // program_config_element + return ERROR_UNSUPPORTED; // XXX to be implemented + } + + if (audioObjectType == 6 || audioObjectType == 20) { + /* unsigned layerNr = */bits->getBits(3); + } + + if (extensionFlag) { + if (audioObjectType == 22) { + /* unsigned numOfSubFrame = */bits->getBits(5); + /* unsigned layerLength = */bits->getBits(11); + } else if (audioObjectType == 17 || audioObjectType == 19 + || audioObjectType == 20 || audioObjectType == 23) { + /* unsigned aacSectionDataResilienceFlag = */bits->getBits(1); + /* unsigned aacScalefactorDataResilienceFlag = */bits->getBits(1); + /* unsigned aacSpectralDataResilienceFlag = */bits->getBits(1); + } + + unsigned extensionFlag3 = bits->getBits(1); + CHECK_EQ(extensionFlag3, 0u); // TBD in version 3 + } + + return OK; +} + +static status_t parseAudioSpecificConfig(ABitReader *bits) { + unsigned audioObjectType; + CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK); + + unsigned samplingFreqIndex = bits->getBits(4); + if (samplingFreqIndex == 0x0f) { + /* unsigned samplingFrequency = */bits->getBits(24); + } + + unsigned channelConfiguration = bits->getBits(4); + + unsigned extensionAudioObjectType = 0; + unsigned sbrPresent = 0; + + if (audioObjectType == 5) { + extensionAudioObjectType = audioObjectType; + sbrPresent = 1; + unsigned extensionSamplingFreqIndex = bits->getBits(4); + if (extensionSamplingFreqIndex == 0x0f) { + /* unsigned extensionSamplingFrequency = */bits->getBits(24); + } + CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK); + } + + CHECK((audioObjectType >= 1 && audioObjectType <= 4) + || (audioObjectType >= 6 && audioObjectType <= 7) + || audioObjectType == 17 + || (audioObjectType >= 19 && audioObjectType <= 23)); + + CHECK_EQ(parseGASpecificConfig( + bits, audioObjectType, channelConfiguration), (status_t)OK); + + if (audioObjectType == 17 + || (audioObjectType >= 19 && audioObjectType <= 27)) { + unsigned epConfig = bits->getBits(2); + if (epConfig == 2 || epConfig == 3) { + // ErrorProtectionSpecificConfig + return ERROR_UNSUPPORTED; // XXX to be implemented + + if (epConfig == 3) { + unsigned directMapping = bits->getBits(1); + CHECK_EQ(directMapping, 1u); + } + } + } + +#if 0 + // This is not supported here as the upper layers did not explicitly + // signal the length of AudioSpecificConfig. + + if (extensionAudioObjectType != 5 && bits->numBitsLeft() >= 16) { + unsigned syncExtensionType = bits->getBits(11); + if (syncExtensionType == 0x2b7) { + CHECK_EQ(parseAudioObjectType(bits, &extensionAudioObjectType), + (status_t)OK); + + sbrPresent = bits->getBits(1); + + if (sbrPresent == 1) { + unsigned extensionSamplingFreqIndex = bits->getBits(4); + if (extensionSamplingFreqIndex == 0x0f) { + /* unsigned extensionSamplingFrequency = */bits->getBits(24); + } + } + } + } +#endif + + return OK; +} + +static status_t parseStreamMuxConfig( + ABitReader *bits, + unsigned *numSubFrames, + unsigned *frameLengthType, + bool *otherDataPresent, + unsigned *otherDataLenBits) { + unsigned audioMuxVersion = bits->getBits(1); + + unsigned audioMuxVersionA = 0; + if (audioMuxVersion == 1) { + audioMuxVersionA = bits->getBits(1); + } + + CHECK_EQ(audioMuxVersionA, 0u); // otherwise future spec + + if (audioMuxVersion != 0) { + return ERROR_UNSUPPORTED; // XXX to be implemented; + } + CHECK_EQ(audioMuxVersion, 0u); // XXX to be implemented + + unsigned allStreamsSameTimeFraming = bits->getBits(1); + CHECK_EQ(allStreamsSameTimeFraming, 1u); // There's only one stream. + + *numSubFrames = bits->getBits(6); + unsigned numProgram = bits->getBits(4); + CHECK_EQ(numProgram, 0u); // disabled in RTP LATM + + unsigned numLayer = bits->getBits(3); + CHECK_EQ(numLayer, 0u); // disabled in RTP LATM + + if (audioMuxVersion == 0) { + // AudioSpecificConfig + CHECK_EQ(parseAudioSpecificConfig(bits), (status_t)OK); + } else { + TRESPASS(); // XXX to be implemented + } + + *frameLengthType = bits->getBits(3); + switch (*frameLengthType) { + case 0: + { + /* unsigned bufferFullness = */bits->getBits(8); + + // The "coreFrameOffset" does not apply since there's only + // a single layer. + break; + } + + case 1: + { + /* unsigned frameLength = */bits->getBits(9); + break; + } + + case 3: + case 4: + case 5: + { + /* unsigned CELPframeLengthTableIndex = */bits->getBits(6); + break; + } + + case 6: + case 7: + { + /* unsigned HVXCframeLengthTableIndex = */bits->getBits(1); + break; + } + + default: + break; + } + + *otherDataPresent = bits->getBits(1); + *otherDataLenBits = 0; + if (*otherDataPresent) { + if (audioMuxVersion == 1) { + TRESPASS(); // XXX to be implemented + } else { + *otherDataLenBits = 0; + + unsigned otherDataLenEsc; + do { + (*otherDataLenBits) <<= 8; + otherDataLenEsc = bits->getBits(1); + unsigned otherDataLenTmp = bits->getBits(8); + (*otherDataLenBits) += otherDataLenTmp; + } while (otherDataLenEsc); + } + } + + unsigned crcCheckPresent = bits->getBits(1); + if (crcCheckPresent) { + /* unsigned crcCheckSum = */bits->getBits(8); + } + + return OK; +} + +sp<ABuffer> AMPEG4AudioAssembler::removeLATMFraming(const sp<ABuffer> &buffer) { + CHECK(!mMuxConfigPresent); // XXX to be implemented + + sp<ABuffer> out = new ABuffer(buffer->size()); + out->setRange(0, 0); + + size_t offset = 0; + uint8_t *ptr = buffer->data(); + + for (size_t i = 0; i <= mNumSubFrames; ++i) { + // parse PayloadLengthInfo + + unsigned payloadLength = 0; + + switch (mFrameLengthType) { + case 0: + { + unsigned muxSlotLengthBytes = 0; + unsigned tmp; + do { + CHECK_LT(offset, buffer->size()); + tmp = ptr[offset++]; + muxSlotLengthBytes += tmp; + } while (tmp == 0xff); + + payloadLength = muxSlotLengthBytes; + break; + } + + default: + TRESPASS(); // XXX to be implemented + break; + } + + CHECK_LE(offset + payloadLength, buffer->size()); + + memcpy(out->data() + out->size(), &ptr[offset], payloadLength); + out->setRange(0, out->size() + payloadLength); + + offset += payloadLength; + + if (mOtherDataPresent) { + // We want to stay byte-aligned. + + CHECK((mOtherDataLenBits % 8) == 0); + CHECK_LE(offset + (mOtherDataLenBits / 8), buffer->size()); + offset += mOtherDataLenBits / 8; + } + } + + CHECK_EQ(offset, buffer->size()); + + return out; +} + +AMPEG4AudioAssembler::AMPEG4AudioAssembler( + const sp<AMessage> ¬ify, const AString ¶ms) : mNotifyMsg(notify), + mMuxConfigPresent(false), mAccessUnitRTPTime(0), mNextExpectedSeqNoValid(false), mNextExpectedSeqNo(0), mAccessUnitDamaged(false) { + AString val; + if (!GetAttribute(params.c_str(), "cpresent", &val)) { + mMuxConfigPresent = true; + } else if (val == "0") { + mMuxConfigPresent = false; + } else { + CHECK(val == "1"); + mMuxConfigPresent = true; + } + + CHECK(GetAttribute(params.c_str(), "config", &val)); + + sp<ABuffer> config = decodeHex(val); + CHECK(config != NULL); + + ABitReader bits(config->data(), config->size()); + status_t err = parseStreamMuxConfig( + &bits, &mNumSubFrames, &mFrameLengthType, + &mOtherDataPresent, &mOtherDataLenBits); + + CHECK_EQ(err, (status_t)NO_ERROR); } AMPEG4AudioAssembler::~AMPEG4AudioAssembler() { @@ -108,13 +471,7 @@ void AMPEG4AudioAssembler::submitAccessUnit() { while (it != mPackets.end()) { const sp<ABuffer> &unit = *it; - size_t n = 0; - while (unit->data()[n] == 0xff) { - ++n; - } - ++n; - - totalSize += unit->size() - n; + totalSize += unit->size(); ++it; } @@ -124,20 +481,13 @@ void AMPEG4AudioAssembler::submitAccessUnit() { while (it != mPackets.end()) { const sp<ABuffer> &unit = *it; - size_t n = 0; - while (unit->data()[n] == 0xff) { - ++n; - } - ++n; - memcpy((uint8_t *)accessUnit->data() + offset, - unit->data() + n, unit->size() - n); - - offset += unit->size() - n; + unit->data(), unit->size()); ++it; } + accessUnit = removeLATMFraming(accessUnit); CopyTimes(accessUnit, *mPackets.begin()); #if 0 diff --git a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h index bf9f2049160e..9cef94c6591c 100644 --- a/media/libstagefright/rtsp/AMPEG4AudioAssembler.h +++ b/media/libstagefright/rtsp/AMPEG4AudioAssembler.h @@ -27,9 +27,11 @@ namespace android { struct AMessage; +struct AString; struct AMPEG4AudioAssembler : public ARTPAssembler { - AMPEG4AudioAssembler(const sp<AMessage> ¬ify); + AMPEG4AudioAssembler( + const sp<AMessage> ¬ify, const AString ¶ms); protected: virtual ~AMPEG4AudioAssembler(); @@ -40,6 +42,13 @@ protected: private: sp<AMessage> mNotifyMsg; + + bool mMuxConfigPresent; + unsigned mNumSubFrames; + unsigned mFrameLengthType; + bool mOtherDataPresent; + unsigned mOtherDataLenBits; + uint32_t mAccessUnitRTPTime; bool mNextExpectedSeqNoValid; uint32_t mNextExpectedSeqNo; @@ -49,6 +58,8 @@ private: AssemblyStatus addPacket(const sp<ARTPSource> &source); void submitAccessUnit(); + sp<ABuffer> removeLATMFraming(const sp<ABuffer> &buffer); + DISALLOW_EVIL_CONSTRUCTORS(AMPEG4AudioAssembler); }; diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp index 251826481207..5aae4e7d779a 100644 --- a/media/libstagefright/rtsp/ARTPSource.cpp +++ b/media/libstagefright/rtsp/ARTPSource.cpp @@ -57,7 +57,7 @@ ARTPSource::ARTPSource( mAssembler = new AAVCAssembler(notify); mIssueFIRRequests = true; } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) { - mAssembler = new AMPEG4AudioAssembler(notify); + mAssembler = new AMPEG4AudioAssembler(notify, params); } else if (!strncmp(desc.c_str(), "H263-1998/", 10) || !strncmp(desc.c_str(), "H263-2000/", 10)) { mAssembler = new AH263Assembler(notify); diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp index 5ec03b2e09ab..e93692377c97 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.cpp +++ b/media/libstagefright/rtsp/ARTSPConnection.cpp @@ -23,11 +23,13 @@ #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/base64.h> #include <media/stagefright/MediaErrors.h> #include <arpa/inet.h> #include <fcntl.h> #include <netdb.h> +#include <openssl/md5.h> #include <sys/socket.h> namespace android { @@ -37,6 +39,7 @@ const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll; ARTSPConnection::ARTSPConnection() : mState(DISCONNECTED), + mAuthType(NONE), mSocket(-1), mConnectionID(0), mNextCSeq(0), @@ -114,10 +117,13 @@ void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) { // static bool ARTSPConnection::ParseURL( - const char *url, AString *host, unsigned *port, AString *path) { + const char *url, AString *host, unsigned *port, AString *path, + AString *user, AString *pass) { host->clear(); *port = 0; path->clear(); + user->clear(); + pass->clear(); if (strncasecmp("rtsp://", url, 7)) { return false; @@ -133,7 +139,25 @@ bool ARTSPConnection::ParseURL( path->setTo(slashPos); } - char *colonPos = strchr(host->c_str(), ':'); + ssize_t atPos = host->find("@"); + + if (atPos >= 0) { + // Split of user:pass@ from hostname. + + AString userPass(*host, 0, atPos); + host->erase(0, atPos + 1); + + ssize_t colonPos = userPass.find(":"); + + if (colonPos < 0) { + *user = userPass; + } else { + user->setTo(userPass, 0, colonPos); + pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1); + } + } + + const char *colonPos = strchr(host->c_str(), ':'); if (colonPos != NULL) { unsigned long x; @@ -187,7 +211,12 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) { AString host, path; unsigned port; - if (!ParseURL(url.c_str(), &host, &port, &path)) { + if (!ParseURL(url.c_str(), &host, &port, &path, &mUser, &mPass) + || (mUser.size() > 0 && mPass.size() == 0)) { + // If we have a user name but no password we have to give up + // right here, since we currently have no way of asking the user + // for this information. + LOGE("Malformed rtsp url %s", url.c_str()); reply->setInt32("result", ERROR_MALFORMED); @@ -197,6 +226,10 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) { return; } + if (mUser.size() > 0) { + LOGV("user = '%s', pass = '%s'", mUser.c_str(), mPass.c_str()); + } + struct hostent *ent = gethostbyname(host.c_str()); if (ent == NULL) { LOGE("Unknown host %s", host.c_str()); @@ -262,6 +295,11 @@ void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) { reply->setInt32("result", OK); mState = DISCONNECTED; + mUser.clear(); + mPass.clear(); + mAuthType = NONE; + mNonce.clear(); + reply->post(); } @@ -335,6 +373,12 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) { AString request; CHECK(msg->findString("request", &request)); + // Just in case we need to re-issue the request with proper authentication + // later, stash it away. + reply->setString("original-request", request.c_str(), request.size()); + + addAuthentication(&request); + // Find the boundary between headers and the body. ssize_t i = request.find("\r\n\r\n"); CHECK_GE(i, 0); @@ -347,7 +391,7 @@ void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) { request.insert(cseqHeader, i + 2); - LOGV("%s", request.c_str()); + LOGV("request: '%s'", request.c_str()); size_t numBytesSent = 0; while (numBytesSent < request.size()) { @@ -612,6 +656,30 @@ bool ARTSPConnection::receiveRTSPReponse() { } } + if (response->mStatusCode == 401) { + if (mAuthType == NONE && mUser.size() > 0 + && parseAuthMethod(response)) { + ssize_t i; + CHECK_EQ((status_t)OK, findPendingRequest(response, &i)); + CHECK_GE(i, 0); + + sp<AMessage> reply = mPendingRequests.valueAt(i); + mPendingRequests.removeItemsAt(i); + + AString request; + CHECK(reply->findString("original-request", &request)); + + sp<AMessage> msg = new AMessage(kWhatSendRequest, id()); + msg->setMessage("reply", reply); + msg->setString("request", request.c_str(), request.size()); + + LOGI("re-sending request with authentication headers..."); + onSendRequest(msg); + + return true; + } + } + return notifyResponseListener(response); } @@ -628,26 +696,47 @@ bool ARTSPConnection::ParseSingleUnsignedLong( return true; } -bool ARTSPConnection::notifyResponseListener( - const sp<ARTSPResponse> &response) { +status_t ARTSPConnection::findPendingRequest( + const sp<ARTSPResponse> &response, ssize_t *index) const { + *index = 0; + ssize_t i = response->mHeaders.indexOfKey("cseq"); if (i < 0) { - return true; + // This is an unsolicited server->client message. + return OK; } AString value = response->mHeaders.valueAt(i); unsigned long cseq; if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) { - return false; + return ERROR_MALFORMED; } i = mPendingRequests.indexOfKey(cseq); if (i < 0) { - // Unsolicited response? - TRESPASS(); + return -ENOENT; + } + + *index = i; + + return OK; +} + +bool ARTSPConnection::notifyResponseListener( + const sp<ARTSPResponse> &response) { + ssize_t i; + status_t err = findPendingRequest(response, &i); + + if (err == OK && i < 0) { + // An unsolicited server response is not a problem. + return true; + } + + if (err != OK) { + return false; } sp<AMessage> reply = mPendingRequests.valueAt(i); @@ -660,4 +749,160 @@ bool ARTSPConnection::notifyResponseListener( return true; } +bool ARTSPConnection::parseAuthMethod(const sp<ARTSPResponse> &response) { + ssize_t i = response->mHeaders.indexOfKey("www-authenticate"); + + if (i < 0) { + return false; + } + + AString value = response->mHeaders.valueAt(i); + + if (!strncmp(value.c_str(), "Basic", 5)) { + mAuthType = BASIC; + } else { +#if !defined(HAVE_ANDROID_OS) + // We don't have access to the MD5 implementation on the simulator, + // so we won't support digest authentication. + return false; +#endif + + CHECK(!strncmp(value.c_str(), "Digest", 6)); + mAuthType = DIGEST; + + i = value.find("nonce="); + CHECK_GE(i, 0); + CHECK_EQ(value.c_str()[i + 6], '\"'); + ssize_t j = value.find("\"", i + 7); + CHECK_GE(j, 0); + + mNonce.setTo(value, i + 7, j - i - 7); + } + + return true; +} + +#if defined(HAVE_ANDROID_OS) +static void H(const AString &s, AString *out) { + out->clear(); + + MD5_CTX m; + MD5_Init(&m); + MD5_Update(&m, s.c_str(), s.size()); + + uint8_t key[16]; + MD5_Final(key, &m); + + for (size_t i = 0; i < 16; ++i) { + char nibble = key[i] >> 4; + if (nibble <= 9) { + nibble += '0'; + } else { + nibble += 'a' - 10; + } + out->append(&nibble, 1); + + nibble = key[i] & 0x0f; + if (nibble <= 9) { + nibble += '0'; + } else { + nibble += 'a' - 10; + } + out->append(&nibble, 1); + } +} +#endif + +static void GetMethodAndURL( + const AString &request, AString *method, AString *url) { + ssize_t space1 = request.find(" "); + CHECK_GE(space1, 0); + + ssize_t space2 = request.find(" ", space1 + 1); + CHECK_GE(space2, 0); + + method->setTo(request, 0, space1); + url->setTo(request, space1 + 1, space2 - space1); +} + +void ARTSPConnection::addAuthentication(AString *request) { + if (mAuthType == NONE) { + return; + } + + // Find the boundary between headers and the body. + ssize_t i = request->find("\r\n\r\n"); + CHECK_GE(i, 0); + + if (mAuthType == BASIC) { + AString tmp; + tmp.append(mUser); + tmp.append(":"); + tmp.append(mPass); + + AString out; + encodeBase64(tmp.c_str(), tmp.size(), &out); + + AString fragment; + fragment.append("Authorization: Basic "); + fragment.append(out); + fragment.append("\r\n"); + + request->insert(fragment, i + 2); + + return; + } + +#if defined(HAVE_ANDROID_OS) + CHECK_EQ((int)mAuthType, (int)DIGEST); + + AString method, url; + GetMethodAndURL(*request, &method, &url); + + AString A1; + A1.append(mUser); + A1.append(":"); + A1.append("Streaming Server"); + A1.append(":"); + A1.append(mPass); + + AString A2; + A2.append(method); + A2.append(":"); + A2.append(url); + + AString HA1, HA2; + H(A1, &HA1); + H(A2, &HA2); + + AString tmp; + tmp.append(HA1); + tmp.append(":"); + tmp.append(mNonce); + tmp.append(":"); + tmp.append(HA2); + + AString digest; + H(tmp, &digest); + + AString fragment; + fragment.append("Authorization: Digest "); + fragment.append("nonce=\""); + fragment.append(mNonce); + fragment.append("\", "); + fragment.append("username=\""); + fragment.append(mUser); + fragment.append("\", "); + fragment.append("uri=\""); + fragment.append(url); + fragment.append("\", "); + fragment.append("response=\""); + fragment.append(digest); + fragment.append("\""); + fragment.append("\r\n"); + + request->insert(fragment, i + 2); +#endif +} + } // namespace android diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h index 96e0d5bfd6d4..19be2a6c7791 100644 --- a/media/libstagefright/rtsp/ARTSPConnection.h +++ b/media/libstagefright/rtsp/ARTSPConnection.h @@ -42,6 +42,10 @@ struct ARTSPConnection : public AHandler { void observeBinaryData(const sp<AMessage> &reply); + static bool ParseURL( + const char *url, AString *host, unsigned *port, AString *path, + AString *user, AString *pass); + protected: virtual ~ARTSPConnection(); virtual void onMessageReceived(const sp<AMessage> &msg); @@ -62,9 +66,18 @@ private: kWhatObserveBinaryData = 'obin', }; + enum AuthType { + NONE, + BASIC, + DIGEST + }; + static const int64_t kSelectTimeoutUs; State mState; + AString mUser, mPass; + AuthType mAuthType; + AString mNonce; int mSocket; int32_t mConnectionID; int32_t mNextCSeq; @@ -90,8 +103,11 @@ private: sp<ABuffer> receiveBinaryData(); bool notifyResponseListener(const sp<ARTSPResponse> &response); - static bool ParseURL( - const char *url, AString *host, unsigned *port, AString *path); + bool parseAuthMethod(const sp<ARTSPResponse> &response); + void addAuthentication(AString *request); + + status_t findPendingRequest( + const sp<ARTSPResponse> &response, ssize_t *index) const; static bool ParseSingleUnsignedLong( const char *from, unsigned long *x); diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp index 0db3595a1465..5472bffbe8bf 100644 --- a/media/libstagefright/rtsp/ASessionDescription.cpp +++ b/media/libstagefright/rtsp/ASessionDescription.cpp @@ -53,21 +53,30 @@ bool ASessionDescription::parse(const void *data, size_t size) { mFormats.push(AString("[root]")); AString desc((const char *)data, size); - LOGI("%s", desc.c_str()); size_t i = 0; for (;;) { - ssize_t eolPos = desc.find("\r\n", i); + ssize_t eolPos = desc.find("\n", i); + if (eolPos < 0) { break; } - AString line(desc, i, eolPos - i); + AString line; + if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') { + // We accept both '\n' and '\r\n' line endings, if it's + // the latter, strip the '\r' as well. + line.setTo(desc, i, eolPos - i - 1); + } else { + line.setTo(desc, i, eolPos - i); + } if (line.size() < 2 || line.c_str()[1] != '=') { return false; } + LOGI("%s", line.c_str()); + switch (line.c_str()[0]) { case 'v': { @@ -141,7 +150,7 @@ bool ASessionDescription::parse(const void *data, size_t size) { } } - i = eolPos + 2; + i = eolPos + 1; } return true; @@ -245,7 +254,7 @@ bool ASessionDescription::getDurationUs(int64_t *durationUs) const { return false; } - if (value == "npt=now-") { + if (value == "npt=now-" || value == "npt=0-") { return false; } diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk index 081ae32a8f77..0bbadc10ab72 100644 --- a/media/libstagefright/rtsp/Android.mk +++ b/media/libstagefright/rtsp/Android.mk @@ -23,6 +23,7 @@ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ $(TOP)/frameworks/base/include/media/stagefright/openmax \ $(TOP)/frameworks/base/media/libstagefright/include \ + $(TOP)/external/openssl/include LOCAL_MODULE:= libstagefright_rtsp diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index 1bc99250733c..8f8e10a90bde 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -96,6 +96,7 @@ struct MyHandler : public AHandler { mNetLooper(new ALooper), mConn(new ARTSPConnection), mRTPConn(new ARTPConnection), + mOriginalSessionURL(url), mSessionURL(url), mSetupTracksSuccessful(false), mSeekPending(false), @@ -113,6 +114,23 @@ struct MyHandler : public AHandler { mNetLooper->start(false /* runOnCallingThread */, false /* canCallJava */, PRIORITY_HIGHEST); + + // Strip any authentication info from the session url, we don't + // want to transmit user/pass in cleartext. + AString host, path, user, pass; + unsigned port; + if (ARTSPConnection::ParseURL( + mSessionURL.c_str(), &host, &port, &path, &user, &pass) + && user.size() > 0) { + mSessionURL.clear(); + mSessionURL.append("rtsp://"); + mSessionURL.append(host); + mSessionURL.append(":"); + mSessionURL.append(StringPrintf("%u", port)); + mSessionURL.append(path); + + LOGI("rewritten session url: '%s'", mSessionURL.c_str()); + } } void connect(const sp<AMessage> &doneMsg) { @@ -126,7 +144,7 @@ struct MyHandler : public AHandler { mConn->observeBinaryData(notify); sp<AMessage> reply = new AMessage('conn', id()); - mConn->connect(mSessionURL.c_str(), reply); + mConn->connect(mOriginalSessionURL.c_str(), reply); } void disconnect(const sp<AMessage> &doneMsg) { @@ -312,7 +330,7 @@ struct MyHandler : public AHandler { int32_t reconnect; if (msg->findInt32("reconnect", &reconnect) && reconnect) { sp<AMessage> reply = new AMessage('conn', id()); - mConn->connect(mSessionURL.c_str(), reply); + mConn->connect(mOriginalSessionURL.c_str(), reply); } else { (new AMessage('quit', id()))->post(); } @@ -922,7 +940,7 @@ struct MyHandler : public AHandler { CHECK(GetAttribute(range.c_str(), "npt", &val)); float npt1, npt2; - if (val == "now-") { + if (val == "now-" || val == "0-") { // This is a live stream and therefore not seekable. return; } else { @@ -992,6 +1010,7 @@ private: sp<ARTSPConnection> mConn; sp<ARTPConnection> mRTPConn; sp<ASessionDescription> mSessionDesc; + AString mOriginalSessionURL; // This one still has user:pass@ AString mSessionURL; AString mBaseURL; AString mSessionID; |