From: hyc Date: Sat, 9 Jan 2010 00:23:22 +0000 (+0000) Subject: Add --conn|-C option to append arbitrary AMF data to connect request X-Git-Tag: v2.4~300 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d9c75f794160f228d06b4c8bc0940c4cb7bf7732;p=rtmpdump Add --conn|-C option to append arbitrary AMF data to connect request git-svn-id: svn://svn.mplayerhq.hu/rtmpdump/trunk@212 400ebc74-4327-4243-bc38-086b20814532 --- diff --git a/rtmp.c b/rtmp.c index 9174d75..2dfc717 100644 --- a/rtmp.c +++ b/rtmp.c @@ -956,6 +956,16 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp) if (!enc) return false; } + if (r->Link.extras.o_num) + { + int i; + for (i=0; i < r->Link.extras.o_num; i++) + { + enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend); + if (!enc) + return false; + } + } packet.m_nBodySize = enc - packet.m_body; return RTMP_SendPacket(r, &packet, true); diff --git a/rtmp.h b/rtmp.h index cd27ac2..aa20767 100644 --- a/rtmp.h +++ b/rtmp.h @@ -135,6 +135,7 @@ typedef struct RTMP_LNK AVal subscribepath; AVal token; bool authflag; + AMFObject extras; double seekTime; uint32_t length; diff --git a/rtmpdump.c b/rtmpdump.c index 3f6f7b0..751727d 100644 --- a/rtmpdump.c +++ b/rtmpdump.c @@ -1050,6 +1050,91 @@ Download(RTMP * rtmp, // connected RTMP object #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val) +int +parseAMF(AMFObject *obj, const char *arg, int *depth) +{ + AMFObjectProperty prop = {{0,0}}; + int i; + char *p; + + if (arg[1] == ':') + { + p = (char *)arg+2; + switch(arg[0]) + { + case 'B': + prop.p_type = AMF_BOOLEAN; + prop.p_vu.p_number = atoi(p); + break; + case 'S': + prop.p_type = AMF_STRING; + STR2AVAL(prop.p_vu.p_aval,p); + break; + case 'N': + prop.p_type = AMF_NUMBER; + prop.p_vu.p_number = strtod(p, NULL); + break; + case 'O': + i = atoi(p); + if (i) + { + prop.p_type = AMF_OBJECT; + } + else + { + (*depth)--; + return 0; + } + break; + default: + return -1; + } + } + else if (arg[2] == ':' && arg[0] == 'N') + { + p = strchr(arg+3, ':'); + if (!p || !*depth) + return -1; + prop.p_name.av_val = (char *)arg+3; + prop.p_name.av_len = p - (arg+3); + + p++; + switch(arg[1]) + { + case 'B': + prop.p_type = AMF_BOOLEAN; + prop.p_vu.p_number = atoi(p); + break; + case 'S': + prop.p_type = AMF_STRING; + STR2AVAL(prop.p_vu.p_aval,p); + break; + case 'N': + prop.p_type = AMF_NUMBER; + prop.p_vu.p_number = strtod(p, NULL); + break; + default: + return -1; + } + } + else + return -1; + + if (*depth) + { + AMFObject *o2; + for (i=0; i<*depth; i++) + { + o2 = &obj->o_props[obj->o_num-1].p_vu.p_object; + obj = o2; + } + } + AMF_AddProp(obj, &prop); + if (prop.p_type == AMF_OBJECT) + (*depth)++; + return 0; +} + int main(int argc, char **argv) { @@ -1102,6 +1187,8 @@ main(int argc, char **argv) AVal flashVer = { 0, 0 }; AVal token = { 0, 0 }; char *sockshost = 0; + AMFObject extras = {0}; + int edepth = 0; #ifdef CRYPTO unsigned char hash[HASHLEN]; @@ -1163,6 +1250,7 @@ main(int argc, char **argv) {"pageUrl", 1, NULL, 'p'}, {"app", 1, NULL, 'a'}, {"auth", 1, NULL, 'u'}, + {"conn", 1, NULL, 'C'}, #ifdef CRYPTO {"swfhash", 1, NULL, 'w'}, {"swfsize", 1, NULL, 'x'}, @@ -1188,7 +1276,7 @@ main(int argc, char **argv) while ((opt = getopt_long(argc, argv, - "hVveqzr:s:t:p:a:b:f:o:u:n:c:l:y:m:k:d:A:B:T:w:x:W:S:#", + "hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:m:k:d:A:B:T:w:x:W:S:#", longopts, NULL)) != -1) { switch (opt) @@ -1224,6 +1312,12 @@ main(int argc, char **argv) #endif LogPrintf ("--auth|-u string Authentication string to be appended to the connect string\n"); + LogPrintf + ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n"); + LogPrintf + (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n"); + LogPrintf + (" NB:name:boolean, NS:name:string, NN:name:number\n"); LogPrintf ("--flashVer|-f string Flash version string (default: \"%s\")\n", DEFAULT_FLASH_VER); @@ -1412,6 +1506,13 @@ main(int argc, char **argv) case 'u': STR2AVAL(auth, optarg); break; + case 'C': + if (parseAMF(&extras, optarg, &edepth)) + { + Log(LOGERROR, "Invalid AMF parameter: %s", optarg); + return RD_FAILED; + } + break; case 'm': timeout = atoi(optarg); break; @@ -1546,6 +1647,11 @@ main(int argc, char **argv) &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, dSeek, 0, bLiveStream, timeout); + /* backward compatibility, we always sent this as true before */ + if (auth.av_len) + rtmp.Link.authflag = true; + + rtmp.Link.extras = extras; rtmp.Link.token = token; off_t size = 0; diff --git a/rtmpsrv.c b/rtmpsrv.c index 70c8149..9a3f861 100644 --- a/rtmpsrv.c +++ b/rtmpsrv.c @@ -75,6 +75,7 @@ typedef struct int socket; int state; int streamID; + char *connect; } STREAMING_SERVER; @@ -240,11 +241,53 @@ SendResultNumber(RTMP *r, double txn, double ID) return RTMP_SendPacket(r, &packet, false); } +static void +dumpAMF(AMFObject *obj) +{ + int i; + const char opt[] = "NBSO"; + + for (i=0; i < obj->o_num; i++) + { + AMFObjectProperty *p = &obj->o_props[i]; + LogPrintf(" -C "); + if (p->p_name.av_val) + LogPrintf("N"); + LogPrintf("%c:", opt[p->p_type]); + if (p->p_name.av_val) + LogPrintf("%.*s:", p->p_name.av_len, p->p_name.av_val); + switch(p->p_type) + { + case AMF_BOOLEAN: + LogPrintf("%d", p->p_vu.p_number != 0); + break; + case AMF_STRING: + LogPrintf("%.*s", p->p_vu.p_aval.av_len, p->p_vu.p_aval.av_val); + break; + case AMF_NUMBER: + LogPrintf("%f", p->p_vu.p_number); + break; + case AMF_OBJECT: + LogPrintf("1"); + dumpAMF(&p->p_vu.p_object); + LogPrintf(" -C O:0"); + break; + default: + LogPrintf("", p->p_type); + } + } +} // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' int -ServeInvoke(STREAMING_SERVER *server, RTMP * r, const char *body, unsigned int nBodySize) +ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int offset) { + const char *body; + unsigned int nBodySize; int ret = 0, nRes; + + body = packet->m_body + offset; + nBodySize = packet->m_nBodySize - offset; + if (body[0] != 0x02) // make sure it is a string method name we start with { Log(LOGWARNING, "%s, Sanity failed. no string method in invoke packet", @@ -271,6 +314,10 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, const char *body, unsigned int n AMFObject cobj; AVal pname, pval; int i; + + server->connect = packet->m_body; + packet->m_body = NULL; + AMFProp_GetObject(AMF_GetProp(&obj, NULL, 2), &cobj); for (i=0; iLink.app = pval; @@ -325,25 +363,15 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, const char *body, unsigned int n { r->m_fEncoding = cobj.o_props[i].p_vu.p_number; } - /* Dup'd a sting we didn't recognize? */ - if (pval.av_val) - free(pval.av_val); } + /* Still have more parameters? Copy them */ if (obj.o_num > 3) { - r->Link.authflag = AMFProp_GetBoolean(&obj.o_props[3]); - if (obj.o_num > 4) - { - AMFProp_GetString(&obj.o_props[4], &pval); - if (pval.av_val) - { - char *p = malloc(pval.av_len+1); - memcpy(p, pval.av_val, pval.av_len); - pval.av_val = p; - p[pval.av_len] = '\0'; - r->Link.auth = pval; - } - } + int i = obj.o_num - 3; + r->Link.extras.o_num = i; + r->Link.extras.o_props = malloc(i*sizeof(AMFObjectProperty)); + memcpy(r->Link.extras.o_props, obj.o_props+3, i*sizeof(AMFObjectProperty)); + obj.o_num = 3; } SendConnectResult(r, txn); } @@ -357,6 +385,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, const char *body, unsigned int n } else if (AVMATCH(&method, &av_play)) { + RTMPPacket pc = {0}; AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &r->Link.playpath); r->Link.seekTime = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 4)); if (obj.o_num > 5) @@ -375,9 +404,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, const char *body, unsigned int n LogPrintf(" -p '%s'", r->Link.pageUrl.av_val); if (r->Link.auth.av_val) LogPrintf(" -u '%s'", r->Link.auth.av_val); + if (r->Link.extras.o_num) + { + dumpAMF(&r->Link.extras); + AMF_Reset(&r->Link.extras); + } LogPrintf(" -y '%.*s' -o output.flv\n\n", r->Link.playpath.av_len, r->Link.playpath.av_val); } + pc.m_body = server->connect; + server->connect = NULL; + RTMPPacket_Free(&pc); ret = 1; } AMF_Reset(&obj); @@ -450,7 +487,7 @@ ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet) obj.Dump(); */ - ServeInvoke(server, r, packet->m_body + 1, packet->m_nBodySize - 1); + ServeInvoke(server, r, packet, 1); break; } case 0x12: @@ -467,7 +504,7 @@ ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet) packet->m_nBodySize); //LogHex(packet.m_body, packet.m_nBodySize); - if (ServeInvoke(server, r, packet->m_body, packet->m_nBodySize)) + if (ServeInvoke(server, r, packet, 0)) RTMP_Close(r); break; @@ -553,17 +590,11 @@ cleanup: RTMP_Close(&rtmp); /* Should probably be done by RTMP_Close() ... */ rtmp.Link.playpath.av_val = NULL; - free(rtmp.Link.tcUrl.av_val); rtmp.Link.tcUrl.av_val = NULL; - free(rtmp.Link.swfUrl.av_val); rtmp.Link.swfUrl.av_val = NULL; - free(rtmp.Link.pageUrl.av_val); rtmp.Link.pageUrl.av_val = NULL; - free(rtmp.Link.app.av_val); rtmp.Link.app.av_val = NULL; - free(rtmp.Link.auth.av_val); rtmp.Link.auth.av_val = NULL; - free(rtmp.Link.flashVer.av_val); rtmp.Link.flashVer.av_val = NULL; LogPrintf("done!\n\n"); @@ -618,7 +649,7 @@ STREAMING_SERVER * startStreaming(const char *address, int port) { struct sockaddr_in addr; - int sockfd; + int sockfd, tmp; STREAMING_SERVER *server; sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); @@ -628,6 +659,10 @@ startStreaming(const char *address, int port) return 0; } + tmp = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (char *) &tmp, sizeof(tmp) ); + addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(address); //htonl(INADDR_ANY); addr.sin_port = htons(port); diff --git a/streams.c b/streams.c index 9120e78..303130f 100644 --- a/streams.c +++ b/streams.c @@ -96,6 +96,8 @@ typedef struct AVal flashVer; AVal token; AVal subscribepath; + AMFObject extras; + int edepth; uint32_t swfSize; uint32_t dStartOffset; @@ -105,6 +107,91 @@ typedef struct #define STR2AVAL(av,str) av.av_val = str; av.av_len = strlen(av.av_val) +int +parseAMF(AMFObject *obj, const char *arg, int *depth) +{ + AMFObjectProperty prop = {{0,0}}; + int i; + char *p; + + if (arg[1] == ':') + { + p = (char *)arg+2; + switch(arg[0]) + { + case 'B': + prop.p_type = AMF_BOOLEAN; + prop.p_vu.p_number = atoi(p); + break; + case 'S': + prop.p_type = AMF_STRING; + STR2AVAL(prop.p_vu.p_aval,p); + break; + case 'N': + prop.p_type = AMF_NUMBER; + prop.p_vu.p_number = strtod(p, NULL); + break; + case 'O': + i = atoi(p); + if (i) + { + prop.p_type = AMF_OBJECT; + } + else + { + (*depth)--; + return 0; + } + break; + default: + return -1; + } + } + else if (arg[2] == ':' && arg[0] == 'N') + { + p = strchr(arg+3, ':'); + if (!p || !*depth) + return -1; + prop.p_name.av_val = (char *)arg+3; + prop.p_name.av_len = p - (arg+3); + + p++; + switch(arg[1]) + { + case 'B': + prop.p_type = AMF_BOOLEAN; + prop.p_vu.p_number = atoi(p); + break; + case 'S': + prop.p_type = AMF_STRING; + STR2AVAL(prop.p_vu.p_aval,p); + break; + case 'N': + prop.p_type = AMF_NUMBER; + prop.p_vu.p_number = strtod(p, NULL); + break; + default: + return -1; + } + } + else + return -1; + + if (*depth) + { + AMFObject *o2; + for (i=0; i<*depth; i++) + { + o2 = &obj->o_props[obj->o_num-1].p_vu.p_object; + obj = o2; + } + } + AMF_AddProp(obj, &prop); + if (prop.p_type == AMF_OBJECT) + (*depth)++; + return 0; +} + /* this request is formed from the parameters and used to initialize a new request, * thus it is a default settings list. All settings can be overriden by specifying the * parameters in the GET request. */ @@ -636,6 +723,11 @@ void processTCPrequest(STREAMING_SERVER * server, // server socket and state (ou RTMP_SetupStream(&rtmp, req.protocol, req.hostname, req.rtmpport, NULL, // sockshost &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, dSeek, -1, // length req.bLiveStream, req.timeout); + /* backward compatibility, we always sent this as true before */ + if (req.auth.av_len) + rtmp.Link.authflag = true; + + rtmp.Link.extras = req.extras; rtmp.Link.token = req.token; LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app); @@ -1012,6 +1104,9 @@ ParseOption(char opt, char *arg, RTMP_REQUEST * req) case 'u': STR2AVAL(req->auth, arg); break; + case 'C': + parseAMF(&req->extras, optarg, &req->edepth); + break; case 'm': req->timeout = atoi(arg); break; @@ -1085,6 +1180,7 @@ main(int argc, char **argv) {"swfVfy", 1, NULL, 'W'}, #endif {"auth", 1, NULL, 'u'}, + {"conn", 1, NULL, 'C'}, {"flashVer", 1, NULL, 'f'}, {"live", 0, NULL, 'v'}, //{"flv", 1, NULL, 'o'}, @@ -1147,6 +1243,12 @@ main(int argc, char **argv) #endif LogPrintf ("--auth|-u string Authentication string to be appended to the connect string\n"); + LogPrintf + ("--conn|-C type:data Arbitrary AMF data to be appended to the connect string\n"); + LogPrintf + (" B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n"); + LogPrintf + (" NB:name:boolean, NS:name:string, NN:name:number\n"); LogPrintf ("--flashVer|-f string Flash version string (default: \"%s\")\n", DEFAULT_FLASH_VER);