1 // Red5 server side support for the oflaDemo_test via RTMP
2 //
3 // Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 #ifdef HAVE_CONFIG_H
20 #include "gnashconfig.h"
21 #endif
22
23 #include <string>
24 #include <sstream>
25 #include <iostream>
26 #include <string>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <ctime>
30 #include <memory>
31
32 #if !defined(_MSC_VER)
33 # include <unistd.h>
34 # include <sys/stat.h>
35 # include <sys/types.h>
36 # include <dirent.h>
37 # include <cerrno>
38 #else
39 #include <io.h>
40 #define dup _dup
41 #endif
42
43 // Gnash headers
44
45 #include "GnashFileUtilities.h"
46 #include "amf.h"
47 #include "arg_parser.h"
48 #include "buffer.h"
49 #include "network.h"
50 #include "element.h"
51 #include "URL.h"
52 #include "log.h"
53
54 // cygnal headers
55 #include "crc.h"
56 #include "oflaDemo.h"
57 #include "cygnal.h"
58 #include "handler.h"
59 #include "rtmp_server.h"
60
61 #if defined(WIN32) || defined(_WIN32)
62 int lt_dlsetsearchpath (const char *search_path);
63 int lt_dlinit (void);
64 void * lt_dlsym (lt_dlhandle handle, const char *name);
65 const char *lt_dlerror (void);
66 int lt_dlclose (lt_dlhandle handle);
67 int lt_dlmakeresident (lt_dlhandle handle);
68 lt_dlhandle lt_dlopenext (const char *filename);
69 #endif
70
71 #if HAVE_DIRENT_H || WIN32==1 // win32 hack
72 # include <dirent.h>
73 # define NAMLEN(dirent) std::strlen((dirent)->d_name)
74 #else
75 # define dirent direct
76 # define NAMLEN(dirent) (dirent)->d_namlen
77 # if HAVE_SYS_NDIR_H
78 # include <sys/ndir.h>
79 # endif
80 # if HAVE_SYS_DIR_H
81 # include <sys/dir.h>
82 # endif
83 # if HAVE_NDIR_H
84 # include <ndir.h>
85 # endif
86 #endif
87
88 using namespace gnash;
89 using namespace std;
90 using namespace cygnal;
91
92 static void usage (void);
93
94 // The user config for Cygnal is loaded and parsed here:
95 static CRcInitFile& crcfile = CRcInitFile::getDefaultInstance();
96
97 LogFile& dbglogfile = LogFile::getDefaultInstance();
98
99 // Toggles very verbose debugging info from the network Network class
100 static bool netdebug = false;
101
102 static OflaDemoTest oflaDemo;
103
104 extern "C" {
105
106 // the standard API
107 std::shared_ptr<Handler::cygnal_init_t>
oflaDemo_init_func(std::shared_ptr<gnash::RTMPMsg> & msg)108 oflaDemo_init_func(std::shared_ptr<gnash::RTMPMsg> &msg)
109 {
110 GNASH_REPORT_FUNCTION;
111
112 std::shared_ptr<Handler::cygnal_init_t> init(new Handler::cygnal_init_t);
113 if (msg) {
114 oflaDemo.setNetConnection(msg);
115 } else {
116 log_error("No NetConnection message supplied to oflaDemo!");
117 }
118
119 init->version = "OflaDemo 0.1 (Gnash)";
120 init->description = "streaming Video test for Cygnal.\n"
121 "\tThis supplies the server side functionality required for\n"
122 "\tCygnal to handle the Red5 OflaDemo test";
123 return init;
124 }
125
oflaDemo_read_func()126 std::shared_ptr<cygnal::Buffer> oflaDemo_read_func()
127 {
128 // GNASH_REPORT_FUNCTION;
129
130 std::shared_ptr<cygnal::Buffer> buf = oflaDemo.getResponse();
131 // log_network("%s", hexify(data, safe, true));
132
133 return buf;
134
135 // GNASH_REPORT_RETURN;
136 }
137
oflaDemo_write_func(std::uint8_t * data,size_t size)138 size_t oflaDemo_write_func(std::uint8_t *data, size_t size)
139 {
140 // GNASH_REPORT_FUNCTION;
141
142 std::shared_ptr<cygnal::Buffer> buf = oflaDemo.getResponse();
143
144 vector<std::shared_ptr<cygnal::Element> > request =
145 oflaDemo.parseOflaDemoRequest(data, size);
146
147 if (request.empty()) {
148 // Send the packet to notify the client that the
149 // NetConnection::connect() was sucessful. After the client
150 // receives this, the handhsake is completed.
151 std::shared_ptr<cygnal::Buffer> error =
152 oflaDemo.encodeResult(RTMPMsg::NC_CALL_FAILED);
153 // This builds the full header,which is required as the first part
154 // of the packet.
155 std::shared_ptr<cygnal::Buffer> head = oflaDemo.encodeHeader(0x3,
156 RTMP::HEADER_12, error->allocated(),
157 RTMP::INVOKE, RTMPMsg::FROM_SERVER);
158 std::unique_ptr<cygnal::Buffer> response(new cygnal::Buffer(
159 error->allocated() + head->allocated()));
160 *response = head;
161 *response += error;
162 log_error("Couldn't send response to client!");
163
164 return - 1;
165 }
166
167 // log_network("%s", hexify(buf->reference(), buf->allocated(), true));
168
169 if (buf) {
170 return buf->allocated();
171 } else {
172 return 0;
173 }
174 // GNASH_REPORT_RETURN;
175 }
176
177 } // end of extern C
178
179 int
main(int argc,char * argv[])180 main(int argc, char *argv[])
181 {
182 int port = CGIBIN_PORT;
183 bool done = false;
184
185 dbglogfile.setLogFilename("oflaDemo-test.log");
186 // dbglogfile.setWriteDisk(true);
187
188 const Arg_parser::Option opts[] =
189 {
190 { 'h', "help", Arg_parser::no },
191 { 'v', "verbose", Arg_parser::no },
192 { 'd', "dump", Arg_parser::no },
193 { 'n', "netdebug", Arg_parser::no },
194 { 'p', "port", Arg_parser::yes },
195 };
196
197 Arg_parser parser(argc, argv, opts);
198 if( ! parser.error().empty() ) {
199 log_error("%s", parser.error());
200 exit(EXIT_FAILURE);
201 }
202
203 string infile;
204
205 for( int i = 0; i < parser.arguments(); ++i ) {
206 const int code = parser.code(i);
207 try {
208 switch( code ) {
209 case 'h':
210 usage ();
211 exit(EXIT_SUCCESS);
212 case 'v':
213 dbglogfile.setVerbosity();
214 // This happens once per 'v' flag
215 log_debug(_("Verbose output turned on"));
216 break;
217 case 'n':
218 netdebug = true;
219 break;
220 case 'p':
221 port = parser.argument<int>(i);
222 break;
223 case 0:
224 infile = parser.argument(i);
225 break;
226 default:
227 break;
228 }
229 }
230
231 catch (Arg_parser::ArgParserException &e) {
232 log_error(_("Error parsing command line options: %s"), e.what());
233 }
234 }
235
236 OflaDemoTest net;
237 int netfd = 0;
238
239 if (infile.empty()) {
240 if (netdebug) {
241 net.toggleDebug(true);
242 }
243 int fd = net.createServer(port);
244 // Only wait for a limited time.
245 net.setTimeout(10);
246 netfd = net.newConnection(false, fd);
247 }
248
249 // This is the main message processing loop for rtmp. All message received require
250 // a response.
251 do {
252 std::shared_ptr<cygnal::Buffer> bufptr(new cygnal::Buffer);
253 if (infile.empty()) {
254 net.readNet(netfd, *bufptr);
255 } else {
256 DiskStream filestream(infile);
257 filestream.loadToMem(0);
258 int ret = net.writeNet(netfd, filestream.get(), filestream.getPagesize());
259 if (ret <= 0) {
260 break;
261 }
262 }
263
264 vector<std::shared_ptr<cygnal::Element> > request = net.parseOflaDemoRequest(
265 bufptr->reference(), bufptr->allocated());
266 if (request[3]) {
267 std::shared_ptr<cygnal::Buffer> result = net.formatOflaDemoResponse(request[1]->to_number(), *request[3]);
268 if (net.writeNet(netfd, *result)) {
269 log_debug("Sent oflaDemo test response response to client.");
270 }
271 } else {
272 log_error("Couldn't send oflaDemo test response to client!");
273 done = true;
274 }
275 } while (!done);
276 }
277
demoService()278 demoService::demoService()
279 {
280 // GNASH_REPORT_FUNCTION;
281 }
282
~demoService()283 demoService::~demoService()
284 {
285 // GNASH_REPORT_FUNCTION;
286 }
287
288 std::vector<std::shared_ptr<demoService::filestats_t> > &
getListOfAvailableFiles(const std::string & path)289 demoService::getListOfAvailableFiles(const std::string &path)
290 {
291 // GNASH_REPORT_FUNCTION;
292 return getListOfAvailableFiles(path, ".flv");
293 }
294
295 std::vector<std::shared_ptr<demoService::filestats_t> > &
getListOfAvailableFiles(const std::string & path,const std::string & type)296 demoService::getListOfAvailableFiles(const std::string &path,
297 const std::string &type)
298 {
299 GNASH_REPORT_FUNCTION;
300 struct dirent **namelist;
301
302 _path = path; // store for later
303
304 // If we don't have any files yet, look for some.
305 if (_stats.empty()) {
306 struct dirent *entry;
307 #ifndef HAVE_SCANDIR
308 log_debug(_("Scanning directory \"%s\" for %s files"), path, type);
309 DIR *libdir = opendir(path.c_str());
310
311 if (!libdir) {
312 log_error(_("Can't open directory %s"), path);
313 return _stats;
314 }
315 while ((entry = readdir(libdir)) != NULL) {
316 #else
317 // The Adobe media server and Red5 sort the directories
318 // alphabetically, so we do too.
319 int ret = scandir(path.c_str(), &namelist, nullptr, alphasort);
320 for (int i=0; i<ret; ++i) {
321 entry = namelist[i];
322 #endif
323 // We only want media files that end with the suffix.
324 std::string name(entry->d_name);
325
326 // We don't want to see hidden files either.
327 if (name.at(0) == '.') {
328 continue;
329 }
330 const std::string::size_type pos = name.find_last_of('.');
331 if (pos == std::string::npos) {
332 continue;
333 }
334
335 const std::string suffix = name.substr(pos);
336
337 // We only want this type of file.
338 if (suffix == type) {
339 log_debug(_("Gnash media file name: %s"), name);
340 string filespec = path + "/";
341 filespec += name;
342 struct stat st;
343 if (stat(filespec.c_str(), &st) == 0) {
344 std::shared_ptr<demoService::filestats_t> stats(new filestats_t);
345 stats->name = name;
346 stringstream ss;
347 ss << st.st_size;
348 stats->size = ss.str();
349 // This originally used gmtime(), but then
350 // packet sniffing showed the Adobe player
351 // uses localtime.
352 struct tm *modified = localtime(&st.st_mtime);
353 // we know the max the date string will be is 24.
354 char modstr[24];
355 if (strftime(modstr, 24, "%d/%m/%y %H:%M:%S", modified)) {
356 stats->last = modstr;
357 }
358 _stats.push_back(stats);
359 }
360 } else {
361 continue;
362 }
363 }
364
365 #ifndef HAVE_SCANDIR
366 if (closedir(libdir) != 0) {
367 return _stats;
368 }
369 #endif
370 }
371
372 return _stats;
373 }
374
375 OflaDemoTest::OflaDemoTest()
376 {
377 // GNASH_REPORT_FUNCTION;
378 }
379
380 OflaDemoTest::~OflaDemoTest()
381 {
382 // GNASH_REPORT_FUNCTION;
383 }
384
385 // Parse an OflaDemo Request message coming from the Red5 oflaDemo_test. This
386 // method should only be used for testing purposes.
387 vector<std::shared_ptr<cygnal::Element > >
388 OflaDemoTest::parseOflaDemoRequest(std::uint8_t *ptr, size_t size)
389 {
390 GNASH_REPORT_FUNCTION;
391
392 demoService demo;
393 cygnal::AMF amf;
394 vector<std::shared_ptr<cygnal::Element > > headers;
395
396 std::shared_ptr<cygnal::Element> el1 = amf.extractAMF(ptr, ptr+size);
397 if (!el1) {
398 log_error("No AMF data in message!");
399 return headers;
400 }
401
402 string method = el1->to_string();
403 ptr += amf.totalsize();
404 headers.push_back(el1);
405
406 // The second element is a number
407 std::shared_ptr<cygnal::Element> el2 = amf.extractAMF(ptr, ptr+size);
408 if (!el2) {
409 log_error("No AMF data in message!");
410 return headers;
411 }
412 ptr += amf.totalsize();
413 headers.push_back(el2);
414
415 if (method == "demoService.getListOfAvailableFLVs") {
416 // Get the path from the NetConnection object we recieved from the
417 // client at the end of the handshake process.
418 std::shared_ptr<cygnal::Element> version;
419 std::shared_ptr<cygnal::Element> tcurl;
420 std::shared_ptr<cygnal::Element> swfurl;
421
422 std::shared_ptr<gnash::RTMPMsg> msg = getNetConnection();
423 if (msg) {
424 version = msg->findProperty("flashVer");
425 if (version) {
426 log_network("Flash player client version is: %s",
427 version->to_string());
428 }
429 tcurl = msg->findProperty("tcUrl");
430 swfurl = msg->findProperty("swfUrl");
431 } else {
432 log_error("No NetConnection message!");
433 return headers;
434 }
435 if (tcurl) {
436 string docroot;
437 URL url(tcurl->to_string());
438 if (crcfile.getDocumentRoot().size() > 0) {
439 docroot = crcfile.getDocumentRoot();
440 log_debug (_("Document Root for media files is: %s"),
441 docroot);
442 } else {
443 docroot = "/var/www/html";
444 }
445 std::string key = docroot + "/";
446 key += url.hostname() + url.path();
447 demo.getListOfAvailableFiles(key);
448 std::vector<std::shared_ptr<demoService::filestats_t> > &mediafiles = demo.getFileStats();
449 // std::vector<demoService::filestats_t> mediafiles = demo.getListOfAvailableFiles(key);
450 std::vector<std::shared_ptr<demoService::filestats_t> >::iterator it;
451 // Make the top level object
452 Element toparr;
453 toparr.makeECMAArray();
454
455 size_t total_size = 0;
456 vector<std::shared_ptr<cygnal::Buffer> > buffers;
457 for (it=mediafiles.begin(); it<mediafiles.end(); ++it) {
458 vector<std::shared_ptr<cygnal::Element> > data;
459
460 std::shared_ptr<demoService::filestats_t> file = *it;
461 std::shared_ptr<cygnal::Element> obj(new cygnal::Element);
462 obj->makeECMAArray();
463 obj->setName(file->name);
464
465 std::shared_ptr<cygnal::Element> modified(new cygnal::Element);
466 modified->makeString("lastModified", file->last);
467 obj->addProperty(modified);
468
469 std::shared_ptr<cygnal::Element> name(new cygnal::Element);
470 name->makeString("name", file->name);
471 obj->addProperty(name);
472
473 std::shared_ptr<cygnal::Element> size(new cygnal::Element);
474 size->makeString("size", file->size);
475 obj->addProperty(size);
476
477 data.push_back(obj);
478 toparr.addProperty(obj);
479 }
480
481 std::shared_ptr<cygnal::Buffer> topenc = toparr.encode();
482 total_size += topenc->allocated();
483
484 // Start with the method name for the INVOKE
485 cygnal::Element method;
486 method.makeString("_result");
487 std::shared_ptr<cygnal::Buffer> methodenc = method.encode();
488 total_size += methodenc->allocated();
489
490 // Add the stream ID
491 cygnal::Element sid;
492 sid.makeNumber(2); // FIXME: needs a real value!
493 std::shared_ptr<cygnal::Buffer> sidenc = sid.encode();
494 total_size += sidenc->allocated();
495
496 // There there is always a NULL object to start the data
497 Element null;
498 null.makeNull();
499 std::shared_ptr<cygnal::Buffer> encnull = null.encode();
500 total_size += encnull->allocated();
501
502 std::shared_ptr<cygnal::Buffer> result(new cygnal::Buffer(total_size+cygnal::AMF_HEADER_SIZE+RTMP_MAX_HEADER_SIZE+10));
503 _response.reset(new cygnal::Buffer(total_size+cygnal::AMF_HEADER_SIZE+RTMP_MAX_HEADER_SIZE+10));
504 #if 0
505 std::shared_ptr<cygnal::Buffer> head = encodeHeader(0x3,
506 RTMP::HEADER_8, total_size,
507 RTMP::INVOKE, RTMPMsg::FROM_SERVER);
508 *result = head;
509 #endif
510 *_response += methodenc;
511 *_response += sidenc;
512 *_response += encnull;
513 *_response += topenc;
514
515 #if 0
516 // Followed by all the encoded objects and properties
517 vector<std::shared_ptr<cygnal::Buffer> >::iterator rit;
518 for (rit=buffers.begin(); rit<buffers.end(); ++rit) {
519 std::shared_ptr<cygnal::Buffer> buf = *rit;
520 *_response += buf;
521 std::vector<std::shared_ptr<cygnal::Element> > data1;
522 }
523 #endif
524 }
525 } else {
526 log_error("Unknown oflaDemo method \"%s\" to INVOKE!", el1->getName());
527 }
528
529 return headers;
530 }
531
532 // format a response to the 'oflaDemo' test used for testing Gnash. This
533 // is only used for testing by developers. The format appears to be
534 // a string '_result', followed by the number of the test, and then two
535 // NULL objects.
536 std::shared_ptr<cygnal::Buffer>
537 OflaDemoTest::formatOflaDemoResponse(double num, cygnal::Element &el)
538 {
539 // GNASH_REPORT_FUNCTION;
540 std::shared_ptr<cygnal::Buffer> data = cygnal::AMF::encodeElement(el);
541 if (data) {
542 return formatOflaDemoResponse(num, data->reference(), data->allocated());
543 } else {
544 log_error("Couldn't encode element: %s", el.getName());
545 el.dump();
546 }
547
548 return data;
549 }
550
551 std::shared_ptr<cygnal::Buffer>
552 OflaDemoTest::formatOflaDemoResponse(double num, cygnal::Buffer &data)
553 {
554 // GNASH_REPORT_FUNCTION;
555 return formatOflaDemoResponse(num, data.reference(), data.allocated());
556 }
557
558 std::shared_ptr<cygnal::Buffer>
559 OflaDemoTest::formatOflaDemoResponse(double num, std::uint8_t *data, size_t size)
560 {
561 // GNASH_REPORT_FUNCTION;
562
563 string result = "_result";
564 Element oflaDemo;
565 oflaDemo.makeString(result);
566
567 Element index;
568 index.makeNumber(num);
569
570 Element null;
571 null.makeNull();
572
573 std::shared_ptr<cygnal::Buffer> encoflaDemo = oflaDemo.encode();
574 std::shared_ptr<cygnal::Buffer> encidx = index.encode();
575 std::shared_ptr<cygnal::Buffer> encnull = null.encode();
576
577 std::shared_ptr<cygnal::Buffer> buf(new cygnal::Buffer(encoflaDemo->size()
578 + encidx->size()
579 + encnull->size() + size));
580
581 *buf = encoflaDemo;
582 *buf += encidx;
583 *buf += encnull;
584 buf->append(data, size);
585
586 return buf;
587 }
588
589 static void
590 usage (void)
591 {
592 cerr << "This program tests AMF support in the AMF library." << endl
593 << endl
594 << _("Usage: test_amf [options...]") << endl
595 << _(" -h, --help Print this help and exit") << endl
596 << _(" -v, --verbose Output verbose debug info") << endl
597 << _(" -n, --netdebug Turn on net debugging messages") << endl
598 << _(" -p, --netdebug port for network") << endl
599 << endl;
600 }
601
602 // local Variables:
603 // mode: C++
604 // indent-tabs-mode: t
605 // End:
606