Merge "Fix the non-ANW video decode path."
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index d172eef..a86ec7f 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -412,6 +412,12 @@
             LOGI("interrupting the connection process");
             mConnectingDataSource->disconnect();
         }
+
+        if (mFlags & PREPARING_CONNECTED) {
+            // We are basically done preparing, we're just buffering
+            // enough data to start playback, we can safely interrupt that.
+            finishAsyncPrepare_l();
+        }
     }
 
     while (mFlags & PREPARING) {
@@ -1625,7 +1631,7 @@
     }
 
     mPrepareResult = err;
-    mFlags &= ~(PREPARING|PREPARE_CANCELLED);
+    mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
     mAsyncPrepareEvent = NULL;
     mPreparedCondition.broadcast();
 }
@@ -1673,6 +1679,8 @@
         }
     }
 
+    mFlags |= PREPARING_CONNECTED;
+
     if (mCachedSource != NULL || mRTSPController != NULL) {
         postBufferingEvent_l();
     } else {
@@ -1692,7 +1700,7 @@
     }
 
     mPrepareResult = OK;
-    mFlags &= ~(PREPARING|PREPARE_CANCELLED);
+    mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
     mFlags |= PREPARED;
     mAsyncPrepareEvent = NULL;
     mPreparedCondition.broadcast();
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index e53b0a0..6c8287c 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -2194,6 +2194,9 @@
                         CHECK(mCodecSpecificData);
                         CHECK(mCodecSpecificDataSize > 0);
 
+                        // Make sure all sizes encode to a single byte.
+                        CHECK(mCodecSpecificDataSize + 23 < 128);
+
                         mOwner->writeInt32(0);     // version=0, flags=0
                         mOwner->writeInt8(0x03);   // ES_DescrTag
                         mOwner->writeInt8(23 + mCodecSpecificDataSize);
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 0837be8..f4a2024 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -111,6 +111,11 @@
         AUDIO_AT_EOS        = 256,
         VIDEO_AT_EOS        = 512,
         AUTO_LOOPING        = 1024,
+
+        // We are basically done preparing but are currently buffering
+        // sufficient data to begin playback and finish the preparation phase
+        // for good.
+        PREPARING_CONNECTED = 2048,
     };
 
     mutable Mutex mLock;
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index f928c06..824fb65 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 @@
 
 ARTSPConnection::ARTSPConnection()
     : mState(DISCONNECTED),
+      mAuthType(NONE),
       mSocket(-1),
       mConnectionID(0),
       mNextCSeq(0),
@@ -114,10 +117,13 @@
 
 // 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,6 +139,24 @@
         path->setTo(slashPos);
     }
 
+    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) {
@@ -187,7 +211,12 @@
 
     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 @@
         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 @@
     reply->setInt32("result", OK);
     mState = DISCONNECTED;
 
+    mUser.clear();
+    mPass.clear();
+    mAuthType = NONE;
+    mNonce.clear();
+
     reply->post();
 }
 
@@ -335,6 +373,12 @@
     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 @@
 
     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 @@
         }
     }
 
+    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 @@
     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,150 @@
     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 {
+        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;
+}
+
+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);
+    }
+}
+
+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;
+    }
+
+    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);
+}
+
 }  // namespace android
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
index 96e0d5b..19be2a6 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.h
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -42,6 +42,10 @@
 
     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 @@
         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 @@
     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 612caff..880aa85 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -57,12 +57,20 @@
 
     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;
@@ -141,7 +149,7 @@
             }
         }
 
-        i = eolPos + 2;
+        i = eolPos + 1;
     }
 
     return true;
@@ -245,7 +253,7 @@
         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 081ae32..0bbadc1 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -23,6 +23,7 @@
 	$(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 6943608..9bb8c46 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -96,6 +96,7 @@
           mNetLooper(new ALooper),
           mConn(new ARTSPConnection),
           mRTPConn(new ARTPConnection),
+          mOriginalSessionURL(url),
           mSessionURL(url),
           mSetupTracksSuccessful(false),
           mSeekPending(false),
@@ -113,6 +114,23 @@
         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 @@
         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 @@
                 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 @@
         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 @@
     sp<ARTSPConnection> mConn;
     sp<ARTPConnection> mRTPConn;
     sp<ASessionDescription> mSessionDesc;
+    AString mOriginalSessionURL;  // This one still has user:pass@
     AString mSessionURL;
     AString mBaseURL;
     AString mSessionID;