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