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);
AVal subscribepath;
AVal token;
bool authflag;
+ AMFObject extras;
double seekTime;
uint32_t length;
#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)
{
AVal flashVer = { 0, 0 };
AVal token = { 0, 0 };
char *sockshost = 0;
+ AMFObject extras = {0};
+ int edepth = 0;
#ifdef CRYPTO
unsigned char hash[HASHLEN];
{"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'},
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)
#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);
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;
&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;
int socket;
int state;
int streamID;
+ char *connect;
} STREAMING_SERVER;
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("<type %d>", 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",
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; i<cobj.o_num; i++)
{
pval.av_val = NULL;
pval.av_len = 0;
if (cobj.o_props[i].p_type == AMF_STRING)
- {
- pval = cobj.o_props[i].p_vu.p_aval;
- 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';
- }
- }
+ pval = cobj.o_props[i].p_vu.p_aval;
if (AVMATCH(&pname, &av_app))
{
r->Link.app = pval;
{
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);
}
}
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)
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);
obj.Dump(); */
- ServeInvoke(server, r, packet->m_body + 1, packet->m_nBodySize - 1);
+ ServeInvoke(server, r, packet, 1);
break;
}
case 0x12:
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;
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");
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);
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);
AVal flashVer;
AVal token;
AVal subscribepath;
+ AMFObject extras;
+ int edepth;
uint32_t swfSize;
uint32_t dStartOffset;
#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. */
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);
case 'u':
STR2AVAL(req->auth, arg);
break;
+ case 'C':
+ parseAMF(&req->extras, optarg, &req->edepth);
+ break;
case 'm':
req->timeout = atoi(arg);
break;
{"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'},
#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);