cURL / Mailing Lists / curl-library / Single Mail

curl-library

Re: Serious problem with CURLOPT_WRITEDATA

From: <nights_at_unku.us>
Date: Mon, 24 Jun 2013 10:00:15 -0400

On Monday, June 24, 2013 09:49:41 AM Daniel Stenberg wrote:
> On Mon, 24 Jun 2013, nights_at_unku.us wrote:
>
> > The following code will work fine...
> >
> > my_curl_easy_setopt(TestApp.CurlInfo.curl, CURLOPT_WRITEFUNCTION, &TestWriteFunction);
> > my_curl_easy_setopt (TestApp.CurlInfo.curl, CURLOPT_WRITEDATA, &TestApp);
> > my_curl_easy_perform(TestApp->CurlInfo.curl);
> >
> > But if I repeat the perform command in order to collect the stream data,
> > "TestApp" will not be sent and in its place will be a NULL...
>
> You didn't say which libcurl version you use and you don't show your own code
> for this. It is not possible for us to tell you where the problem lies with
> any certainty.
>
> The logic to pass on the WRITEDATA pointer to the callback is a very
> fundamental piece of libcurl that virtually every single user of libcurl will
> take advantage from and that I don't think has ever not worked.
>
> I'm not excluding the possiblity that there's a bug in there, especially
> perhaps in code that is specific for RTSP which isn't one of the most
> frequently used protocols by libcurl users, but we need much more details to
> properly tell if this is truly the case.
>
> Experience tells us that almost every time someone points out a flaw like
> this, the source of it is within the application that uses libcurl. Do use
> valgrind to verify that your app is doing the right thing, and if it does and
> this case still fails, please write up a smallish example program that repeats
> the problem with a modern libcurl and post that example here!
>
> Alternatively, you can debug the case yourself by figuring out where in the
> chain that pointer is set to NULL.
>
>

The following is an entire program in C based on the RTSP sample code on the website. I threw it together to demonstrate the issue. It has been rearranged for easier debugging. Note that I purposefully omitted the cleanup code since this is just a demonstration program. Also note that I am using cURL V7.27.0

//-- begin code --------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <curl/curl.h>

#define b_TRUE 1
#define b_FALSE 0

/* error handling macros */
#define my_curl_easy_setopt(A, B, C) \
  if ((res = curl_easy_setopt((A), (B), (C))) != CURLE_OK) \
    fprintf(stderr, "curl_easy_setopt(%s, %s, %s) failed: %d\n", \
            #A, #B, #C, res);

#define my_curl_easy_perform(A) \
  if ((res = curl_easy_perform((A))) != CURLE_OK) \
    fprintf(stderr, "curl_easy_perform(%s) failed: %d\n", #A, res);

typedef struct {
    char *Range;
    char *Transport;
    char *URL;
    char *URI;
    char *SDP;
    char *Control;
    CURL *curl;
} iCurlInfo;

typedef struct {
    iCurlInfo CurlInfo;
} iTestApp;

/* send RTSP PLAY request */
static void Curl_Play(iTestApp *TestApp)
{
    CURLcode res = CURLE_OK;
    
    my_curl_easy_setopt (TestApp->CurlInfo.curl, CURLOPT_RTSP_STREAM_URI, TestApp->CurlInfo.URI);
    my_curl_easy_setopt (TestApp->CurlInfo.curl, CURLOPT_RANGE, NULL);
    my_curl_easy_setopt (TestApp->CurlInfo.curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY);
    my_curl_easy_setopt (TestApp->CurlInfo.curl, CURLOPT_WRITEDATA, (FILE *)&TestApp);
    
    my_curl_easy_perform(TestApp->CurlInfo.curl);
}

size_t TestWriteFunction( char *ptr, size_t size, size_t nmemb, void *userdata) {
  
    if (userdata == NULL)
        printf("userdata == NULL; Data Size = %i;\n", size * nmemb);
    else {
        printf("TestWriteFunction: %s", ptr);
    }
    
    return size * nmemb;
}

int CURL_Connect(iCurlInfo *CurlInfo) {
    
    CURLcode res = CURLE_OK; // Used in my_* functions
    FILE *sdp_fp;
    
    CurlInfo->Transport = "RTP/AVP/TCP;unicast;client_port=1234-1235"; /* TCP */
    CurlInfo->Range = "0.000-";
    CurlInfo->URI = malloc(strlen(CurlInfo->URL) + 32);
    CurlInfo->SDP = malloc(strlen(CurlInfo->URL) + 32);
    CurlInfo->Control = malloc(strlen(CurlInfo->URL) + 32);
    
    char *TempChar = strrchr(CurlInfo->URL, '/');
    strcpy(CurlInfo->SDP, "video.sdp");
    if (TempChar != NULL) {
        TempChar++;
        if (TempChar[0] != '\0')
            sprintf(CurlInfo->SDP, "%s.sdp", TempChar);
    }
    
    // initialize curl
    if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {\
        fprintf(stderr, "curl_global_init(%s) failed: %d\n", "CURL_GLOBAL_ALL", res);
        return b_FALSE;
    }
    
    /* initialize this curl session */
    CurlInfo->curl = curl_easy_init();
    if (CurlInfo->curl == NULL) {
        fprintf(stderr, "curl_easy_init() failed\n");
        curl_global_cleanup();
        return b_FALSE;
    }
    
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_VERBOSE, 0L);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_NOPROGRESS, 1L);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_WRITEHEADER, stdout);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_URL, CurlInfo->URL);
    
    /* request server options */
    sprintf(CurlInfo->URI, "%s", CurlInfo->URL);
    
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_RTSP_STREAM_URI, CurlInfo->URI);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS);
    my_curl_easy_perform(CurlInfo->curl);
    
    /* request session description and write response to sdp file */
    sdp_fp = fopen(CurlInfo->SDP, "wt");
    my_curl_easy_setopt (CurlInfo->curl, CURLOPT_WRITEDATA, sdp_fp);
    my_curl_easy_setopt (CurlInfo->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE);
    my_curl_easy_perform(CurlInfo->curl);
    my_curl_easy_setopt (CurlInfo->curl, CURLOPT_WRITEDATA, stdout);
    fclose(sdp_fp);
    
    /* get media control attribute from sdp file */
    int max_len = 256;
    TempChar = malloc(max_len);
    sdp_fp = fopen(CurlInfo->SDP, "rt");
    CurlInfo->Control[0] = '\0';
    if (sdp_fp != NULL) {
        while (fgets(TempChar, max_len - 2, sdp_fp) != NULL) {
            sscanf(TempChar, " a = control: %s", CurlInfo->Control);
        }
        fclose(sdp_fp);
    }
    free(TempChar);

    /* setup media stream */
    sprintf(CurlInfo->URI, "%s/%s", CurlInfo->URL, CurlInfo->Control);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_RTSP_STREAM_URI, CurlInfo->URI);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_RTSP_TRANSPORT, CurlInfo->Transport);
    my_curl_easy_setopt(CurlInfo->curl, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP);
    my_curl_easy_perform(CurlInfo->curl);
  
    /* start playing media stream */
    sprintf(CurlInfo->URI, "%s/", CurlInfo->URL);
    
    return b_TRUE;
}

/* main app */
int main(int argc, char * const argv[])
{
    CURLcode res = CURLE_OK; // Used in my_* functions
    
    // Version Info
    curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
    fprintf(stderr, " cURL V%s loaded\n", data->version);
    
    // Initialize CURL
    iTestApp TestApp;
    TestApp.CurlInfo.URL = argv[1];
    if (!CURL_Connect(&TestApp.CurlInfo))
        return 1;
    
    my_curl_easy_setopt(TestApp.CurlInfo.curl, CURLOPT_WRITEFUNCTION, &TestWriteFunction);
    
    Curl_Play(&TestApp);
    Curl_Play(&TestApp);
    Curl_Play(&TestApp);
    return 0;
}
//-- end code --------------------------------------------------------------------------------------------------------

Here is the output for this program...

-- begin output ------------------------------------------------------------------------------------------------------
   cURL V7.27.0 loaded
RTSP/1.0 200 OK
CSeq: 1
Public: DESCRIBE, GET_PARAMETER, PAUSE, PLAY, SETUP, SET_PARAMETER, TEARDOWN
Date: Tue, 21 Aug 2001 22:18:21 GMT

RTSP/1.0 200 OK
CSeq: 2
Content-Type: application/sdp
Content-Base: rtsp://192.168.1.xxx/axis-media/media.amp/
Date: Tue, 21 Aug 2001 22:18:21 GMT
Content-Length: 405

RTSP/1.0 200 OK
CSeq: 3
Session: 255CC4FE; timeout=60
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=517DB6C6;mode="PLAY"
Date: Tue, 21 Aug 2001 22:18:21 GMT

TestWriteFunction: RTSP/1.0 200 OK
TestWriteFunction: CSeq: 4
TestWriteFunction: Session: 255CC4FE
TestWriteFunction: Range: npt=0-
TestWriteFunction: RTP-Info: url=rtsp://192.168.1.xxx/axis-media/media.amp/trackID=1;seq=55741;rtptime=238935752
TestWriteFunction: Date: Tue, 21 Aug 2001 22:18:21 GMT
TestWriteFunction:
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 863;
userdata == NULL; Data Size = 502;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 879;
TestWriteFunction: RTSP/1.0 200 OK
TestWriteFunction: CSeq: 5
TestWriteFunction: Session: 255CC4FE
TestWriteFunction: Range: npt=0.133455-
TestWriteFunction: RTP-Info: url=rtsp://192.168.1.xxx/axis-media/media.amp/trackID=1;seq=55747;rtptime=238947763
TestWriteFunction: Date: Tue, 21 Aug 2001 22:18:21 GMT
TestWriteFunction:
userdata == NULL; Data Size = 486;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 1404;
userdata == NULL; Data Size = 890;
TestWriteFunction: RTSP/1.0 200 OK
TestWriteFunction: CSeq: 6
TestWriteFunction: Session: 255CC4FE
TestWriteFunction: Range: npt=0.266921-
TestWriteFunction: RTP-Info: url=rtsp://192.168.1.xxx/axis-media/media.amp/trackID=1;seq=55752;rtptime=238959775
TestWriteFunction: Date: Tue, 21 Aug 2001 22:18:21 GMT
TestWriteFunction:
-- end output ------------------------------------------------------------------------------------------------------

While I was writing this demonstration program I noticed that whenever userdata is not NULL text information would come back. As it turns out this text data is useless to me as I am far more interested in the binary stream itself. In fact, having this text data piping through the same callback function is bad design since there are no parameters available to hint to the callback function whether the data is text or binary. This would most certainly cause data corruption in the stream.

So if I had to guess, the problem here has to do with a bug in the way the callback function deals with RTSP streams, particularly relating to lack of parameters. If I was a developer and noticed there was no useful way to alert the callback function what kind of data is being sent back, I could see them deciding to use userdata NULL/not NULL as an indicator, but now the program is forced to be single threaded.

Anyways, I hope you could help me figure out what I could do to work around this issue.
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html
Received on 2013-06-24