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