1 // Red5 server side support for the echo_test via HTTP
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 
20 #include <string>
21 #include <log.h>
22 #include <iostream>
23 #include <string>
24 
25 #include "amf.h"
26 #include "arg_parser.h"
27 #include "buffer.h"
28 #include "network.h"
29 #include "element.h"
30 
31 #include "gateway.h"
32 #include "diskstream.h"
33 
34 using namespace amf;
35 using namespace gnash;
36 using namespace std;
37 using namespace cygnal;
38 
39 static void usage (void);
40 
41 LogFile& dbglogfile = LogFile::getDefaultInstance();
42 
43 // Toggles very verbose debugging info from the network Network class
44 static bool netdebug = false;
45 
46 static GatewayTest gateway;
47 
48 extern "C" {
49 
50     // the standard API
51     std::shared_ptr<Handler::cygnal_init_t>
gateway_init_func(std::shared_ptr<gnash::RTMPMsg> & msg)52     gateway_init_func(std::shared_ptr<gnash::RTMPMsg> &msg)
53     {
54 	GNASH_REPORT_FUNCTION;
55         std::shared_ptr<Handler::cygnal_init_t> init(new Handler::cygnal_init_t);
56 
57         init->version = "Gateway Test 0.1 (Gnash)";
58         init->description = "gateway RTMPT test for Cygnal.\n"
59             "\tThis supplies the server side functionality equired for\n"
60             "\tCygnal to handle the Red5 Gateway test";
61         return init;
62     }
63 
gateway_read_func()64     std::shared_ptr<amf::Buffer> gateway_read_func()
65     {
66 // 	GNASH_REPORT_FUNCTION;
67 
68 //      GNASH_REPORT_RETURN;
69     }
70 
gateway_write_func(std::uint8_t * data,size_t size)71     size_t gateway_write_func(std::uint8_t *data, size_t size)
72     {
73 // 	GNASH_REPORT_FUNCTION;
74 
75 //      GNASH_REPORT_RETURN;
76     }
77 
78 } // end of extern C
79 
80 int
main(int argc,char * argv[])81 main(int argc, char *argv[])
82 {
83     int port = CGIBIN_PORT;
84     bool done = false;
85     bool gdb = false;
86 
87     dbglogfile.setLogFilename("gateway-test.log");
88 //    dbglogfile.setWriteDisk(true);
89 
90     const Arg_parser::Option opts[] =
91         {
92             { 'h', "help",          Arg_parser::no  },
93             { 'v', "verbose",       Arg_parser::no  },
94             { 'n', "netdebug",      Arg_parser::no  },
95             { 'p', "port",          Arg_parser::yes  },
96             { 'g', "gdb",           Arg_parser::no  },
97             { 'd', "dump",          Arg_parser::no  },
98         };
99 
100     Arg_parser parser(argc, argv, opts);
101     if( ! parser.error().empty() ) {
102         log_error("%s", parser.error());
103         exit(EXIT_FAILURE);
104     }
105 
106     string infile;
107 
108     for( int i = 0; i < parser.arguments(); ++i ) {
109         const int code = parser.code(i);
110         try {
111             switch( code ) {
112               case 'h':
113                   usage ();
114                   exit(EXIT_SUCCESS);
115               case 'v':
116                     dbglogfile.setVerbosity();
117                     // This happens once per 'v' flag
118                     log_debug(_("Verbose output turned on"));
119                     break;
120               case 'n':
121                   netdebug = true;
122                   break;
123               case 'g':
124                   gdb = true;
125                   break;
126               case 'p':
127                   port = parser.argument<int>(i);
128                   break;
129               case 0:
130                   infile = parser.argument(i);
131                   break;
132               default:
133                   break;
134 	    }
135         }
136 
137         catch (Arg_parser::ArgParserException &e) {
138             log_error(_("Error parsing command line options: %s"), e.what());
139         }
140     }
141 
142     // if set, wait for us to connectGDB for debugging
143     while (gdb) {
144         cout << "Waiting for GDB " << getpid() << endl;
145         sleep(5);
146     }
147 
148 
149     GatewayTest net;
150 
151     if (netdebug) {
152 	net.toggleDebug(true);
153     }
154 
155     int fd = 0;
156     int netfd = 0;
157     if (infile.empty()) {
158         fd = net.createServer(port);
159         if (fd <= 0) {
160             exit(EXIT_FAILURE);
161         }
162         // Only wait for a limited time.
163         net.setTimeout(10);
164 //        netfd = net.newConnection(false, fd);
165     }
166 
167     // Wait for data, and when we get it, process it.
168     std::shared_ptr<amf::Buffer> content;
169     vector<std::shared_ptr<amf::Element> > headers;
170     net.setTimeout(10);
171     do {
172         netfd = net.newConnection(false, fd);
173         if (netfd <= 0) {
174             done = true;
175             break;
176         }
177         // See if we have any messages waiting
178         if (infile.empty()) {
179             std::shared_ptr<amf::Buffer> content = net.readNet();
180             if (!content) {
181                 done = true;
182                 break;
183             }
184 //            content->dump();
185             headers = net.parseEchoRequest(*content);
186         } else {
187             DiskStream filestream;
188             filestream.open(infile);
189             filestream.loadToMem(0);
190             headers = net.parseEchoRequest(filestream.get(), filestream.getPagesize());
191             filestream.close();
192             done = true;
193             break;
194         }
195 
196   	//std::shared_ptr<amf::Element> &el0 = headers[0];
197 
198         if (!done) {
199             if (headers.size() >= 4) {
200                 if (headers[3]) {
201                     amf::Buffer reply;
202                     if (headers[1]->getNameSize()) {
203                         reply = net.formatEchoResponse(headers[1]->getName(), *headers[3]);
204                     } else {
205                         reply = net.formatEchoResponse("no name", *headers[3]);
206                     }
207 
208                     if (infile.empty()) {
209                         int ret = net.writeNet(netfd, reply);
210                         if (ret <= 0) {
211 //                        reply.dump();
212                             // For now exit after only one packet
213                             done = true;
214                         }
215                     } else {
216                         cerr << hexify(reply.reference(), reply.allocated(), true) << endl;
217                     }
218                 }
219             }
220         }
221     } while(done != true);
222 
223     net.closeNet();
224 }
225 
GatewayTest()226 GatewayTest::GatewayTest()
227 {
228 //    GNASH_REPORT_FUNCTION;
229 }
230 
~GatewayTest()231 GatewayTest::~GatewayTest()
232 {
233 //    GNASH_REPORT_FUNCTION;
234 }
235 
236 // Parse an Echo Request message coming from the Red5 echo_test. This
237 // method should only be used for testing purposes.
238 vector<std::shared_ptr<amf::Element > >
parseEchoRequest(std::uint8_t * data,size_t size)239 GatewayTest::parseEchoRequest(std::uint8_t *data, size_t size)
240 {
241 //    GNASH_REPORT_FUNCTION;
242 
243     vector<std::shared_ptr<amf::Element > > headers;
244 
245     // skip past the header bytes, we don't care about them.
246     std::uint8_t *tmpptr = data + 6;
247 
248     std::uint16_t length;
249     length = ntohs((*(std::uint16_t *)tmpptr) & 0xffff);
250     tmpptr += sizeof(std::uint16_t);
251 
252     // Get the first name, which is a raw string, and not preceded by
253     // a type byte.
254     std::shared_ptr<amf::Element > el1(new amf::Element);
255 
256     // If the length of the name field is corrupted, then we get out of
257     // range quick, and corrupt memory. This is a bit of a hack, but
258     // reduces memory errors caused by some of the corrupted tes cases.
259     std::uint8_t *endstr = std::find(tmpptr, tmpptr+length, '\0');
260     if (endstr != tmpptr+length) {
261 	log_debug("Caught corrupted string! length was %d, null at %d",
262 		  length,  endstr-tmpptr);
263 	length = endstr-tmpptr;
264     }
265     el1->setName(tmpptr, length);
266     tmpptr += length;
267     headers.push_back(el1);
268 
269     // Get the second name, which is a raw string, and not preceded by
270     // a type byte.
271     length = ntohs((*(std::uint16_t *)tmpptr) & 0xffff);
272     tmpptr += sizeof(std::uint16_t);
273     std::shared_ptr<amf::Element > el2(new amf::Element);
274 
275 //     std::string name2(reinterpret_cast<const char *>(tmpptr), length);
276 //     el2->setName(name2.c_str(), name2.size());
277     // If the length of the name field is corrupted, then we get out of
278     // range quick, and corrupt memory. This is a bit of a hack, but
279     // reduces memory errors caused by some of the corrupted tes cases.
280     endstr = std::find(tmpptr, tmpptr+length, '\0');
281     if (endstr != tmpptr+length) {
282 	log_debug("Caught corrupted string! length was %d, null at %d",
283 		  length,  endstr-tmpptr);
284 	length = endstr-tmpptr;
285     }
286     el2->setName(tmpptr, length);
287     headers.push_back(el2);
288     tmpptr += length;
289 
290     // Get the last two pieces of data, which are both AMF encoded
291     // with a type byte.
292     amf::AMF amf;
293     std::shared_ptr<amf::Element> el3 = amf.extractAMF(tmpptr, tmpptr + size);
294     headers.push_back(el3);
295     tmpptr += amf.totalsize();
296 
297     std::shared_ptr<amf::Element> el4 = amf.extractAMF(tmpptr, tmpptr + size);
298     headers.push_back(el4);
299 
300      return headers;
301 }
302 
303 // format a response to the 'echo' test used for testing Gnash. This
304 // is only used for testing by developers. The format appears to be
305 // two strings, followed by a double, followed by the "onResult".
306 amf::Buffer &
formatEchoResponse(const std::string & num,amf::Element & el)307 GatewayTest::formatEchoResponse(const std::string &num, amf::Element &el)
308 {
309 //    GNASH_REPORT_FUNCTION;
310     std::shared_ptr<amf::Buffer> data;
311 
312     amf::Element nel;
313     if (el.getType() == amf::Element::TYPED_OBJECT_AMF0) {
314 	nel.makeTypedObject();
315 	string name = el.getName();
316 	nel.setName(name);
317 	if (el.propertySize()) {
318 	    // FIXME: see about using std::reverse() instead.
319 	    for (int i=el.propertySize()-1; i>=0; i--) {
320 // 	    for (int i=0 ; i<el.propertySize(); i++) {
321 		std::shared_ptr<amf::Element> child = el.getProperty(i);
322 		nel.addProperty(child);
323 	    }
324 	    data = nel.encode();
325 	} else {
326 	    data = el.encode();
327 	}
328     } else {
329 	data = el.encode();
330     }
331 
332     return formatEchoResponse(num, data->reference(), data->allocated());
333 }
334 
335 amf::Buffer &
formatEchoResponse(const std::string & num,amf::Buffer & data)336 GatewayTest::formatEchoResponse(const std::string &num, amf::Buffer &data)
337 {
338 //    GNASH_REPORT_FUNCTION;
339     return formatEchoResponse(num, data.reference(), data.allocated());
340 }
341 
342 amf::Buffer &
formatEchoResponse(const std::string & num,std::uint8_t * data,size_t size)343 GatewayTest::formatEchoResponse(const std::string &num, std::uint8_t *data, size_t size)
344 {
345 //    GNASH_REPORT_FUNCTION;
346 
347     //std::uint8_t *tmpptr  = data;
348 
349     // FIXME: temporary hacks while debugging
350     amf::Buffer fixme("00 00 00 00 00 01");
351     amf::Buffer fixme2("ff ff ff ff");
352 
353     _buffer = "HTTP/1.1 200 OK\r\n";
354     formatContentType(DiskStream::FILETYPE_AMF);
355 //    formatContentLength(size);
356     // FIXME: this is a hack ! Calculate a real size!
357     formatContentLength(size+29);
358 
359     // Don't pretend to be the Red5 server
360     formatServer("Cygnal(0.8.6)");
361 
362     // All HTTP messages are followed by a blank line.
363     terminateHeader();
364 
365     // Add the binary blob for the header
366     _buffer += fixme;
367 
368     // Make the result response, which is the 2nd data item passed in
369     // the request, a slash followed by a number like "/2".
370     string result = num;
371     result += "/onResult";
372     std::shared_ptr<amf::Buffer> res = amf::AMF::encodeString(result);
373     _buffer.append(res->begin()+1, res->size()-1);
374 
375     // Add the null data item
376     std::shared_ptr<amf::Buffer> null = amf::AMF::encodeString("null");
377     _buffer.append(null->begin()+1, null->size()-1);
378 
379     // Add the other binary blob
380     _buffer += fixme2;
381 
382     amf::Element::amf0_type_e type = static_cast<amf::Element::amf0_type_e>(*data);
383     if ((type == amf::Element::UNSUPPORTED_AMF0)
384 	|| (type == amf::Element::NULL_AMF0)) {
385 	_buffer += type;
386 	// Red5 returns a NULL object when it's recieved an undefined one in the echo_test
387     } else if (type == amf::Element::UNDEFINED_AMF0) {
388 	_buffer += amf::Element::NULL_AMF0;
389     } else {
390 	// Add the AMF data we're echoing back
391 	if (size) {
392 	    _buffer.append(data, size);
393 	}
394     }
395 
396     return _buffer;
397 }
398 
399 static void
usage(void)400 usage (void)
401 {
402     cerr << "This program tests AMF support in the AMF library." << endl
403          << endl
404          << _("Usage: test_amf [options...]") << endl
405          << _("  -h,  --help          Print this help and exit") << endl
406          << _("  -v,  --verbose       Output verbose debug info") << endl
407 	 << _("  -n,  --netdebug      Turn on net debugging messages") << endl
408          << endl;
409 }
410 
411 
412